如何去掉 canvas 矩形之间的细线

How do I get rid of tiny lines between canvas rects

我是 D3 的新手,正如您在上图中看到的,每个矩形之间有微小的 lines/gaps,我想去掉,这是在 canvas 上绘制的每个矩形的元素从最后一个矩形开始,使用 D3.js 按照 this 教程几乎完全减去每个正方形之间的间隙。

我试过了

this.canvas.imageSmoothingQuality = 'low';
draw() {
  const canvas = d3
    .select(this.chartContainer.nativeElement)
    .append('canvas')
    .attr('width', this.width)
    .attr('height', this.height)
    .attr(
      'transform',
      'translate(' + this.margin.left + ',' + this.margin.top + ')'
    );

  this.canvas = canvas.node().getContext('2d');
  this.clearCanvas();
  this.canvas.imageSmoothingQuality = 'low';
  const elements = this.shadowContainer.selectAll('custom.rect');

  const _this = this;
  elements.each(function(d, i) {
    const node = d3.select(this);
    // Here you retrieve the colour from the individual in-memory node and set the fillStyle for the canvas paint
    _this.canvas.fillStyle = node.attr('color');
    // Here you retrieve the position of the node and apply it to the fillRect context function which will fill and paint the square.

    _this.canvas.fillRect(
      Number(node.attr('x')),
      Number(node.attr('y')),
      Number(node.attr('width')),
      Number(node.attr('height'))
    );
  });
}
private dataBind(value) {
  const customBase = document.createElement('custom');
  this.shadowContainer = d3.select(customBase);
  const {
    viewModes: {
      heatMap: {
        data,
        chartOptions: { engagementStatus, xAxis, yAxis }
      }
    }
  } = value;

  const x = this.d3
    .scaleBand()
    .range([0, this.width])
    .domain(xAxis.categories);

  this.shadowContainer
    .append('g')
    .style('font-size', 11)
    .attr('class', 'x-axis')
    .call(this.d3.axisTop(x).tickSize(0))
    .select('.domain')
    .remove();

  this.shadowContainer
    .selectAll('.x-axis text')
    .style('text-anchor', 'start')
    .attr('transform', function(d) {
      return `translate(8, -8)rotate(-90)`;
    });

  const y = this.d3
    .scaleBand()
    .domain(d3.reverse(yAxis.categories))
    .range([this.height, 0]);

  const color = this.d3
    .scaleLinear()
    .domain([-2, -1, 0, 1])
    // @ts-ignore
    .range(['#5b717d', '#ffb957', '#ee6b56', '#40a050']);

  const join = this.shadowContainer
    .selectAll('custom.rect')
    .data(data, function(d) {
      return `${d.Date}:${d.Member}`;
    });

  const enterSelection = join
    .enter()
    .append('custom')
    .attr('class', 'rect')
    .attr('x', d =>
      this.getCorrectDatePosition(
        d.Date,
        x,
        xAxis.categories[0].split('/').length
      )
    )
    .attr('y', function(d) {
      return y(d.Member);
    })
    .attr('width', 24)
    .attr('height', 24);

  join
    .merge(enterSelection)
    .attr('width', x.bandwidth())
    .attr('height', y.bandwidth())
    .attr('color', function(d) {
      return color(d.score);
    });

  const exitSelection = join
    .exit()
    .transition()
    .attr('width', 0)
    .attr('height', 0)
    .remove();
}

这可能是您的体重秤引起的问题。它可以与 SVG 或 canvas 一起出现,并且在处理需要在像素的分数处绘制的坐标时发生。

这是一个 SVG 演示:

var data = d3.range(20);

var x = d3.scaleBand()
  .range([10,250])
  .domain(data)
  
var svg = d3.select("body")
  .append("svg")
  .attr("width", 500);
  
var rect = svg.selectAll("rect")
 .data(data)
 .enter()
 .append("rect")
 .attr("x", d=>x(d) )
 .attr("y", 50)
 .attr("width", x.bandwidth())
 .attr("height",100)
 .attr("fill","crimson")

svg.transition()
  .attrTween("tween", function() {
    var i = d3.interpolate(250,480)
    return function(t) {
       x.range([50,i(t)])
       rect.attr("x",d=>x(d))
           .attr("width", x.bandwidth());
       
       return "";
    }
  })
  .duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

还有一个 Canvas:

var data = d3.range(20);

var x = d3.scaleBand()
  .range([10,250])
  .domain(data)
  
var canvas = d3.select("body")
  .append("canvas")
  .attr("width", 500);
  
var rect = d3.create("div").selectAll("rect")
 .data(data)
 .enter()
 .append("rect")
 .attr("x", d=>x(d) )
 .attr("y", 50)
 .attr("width", x.bandwidth())
 .attr("height",100)
 .attr("fill","crimson")

canvas.transition()
  .attrTween("tween", function() {
    var i = d3.interpolate(250,480)
    var context = canvas.node().getContext("2d");
    return function(t) {
       x.range([50,i(t)])
       context.fillStyle = "#fff";
       context.fillRect(0,0,550,300);
       rect.attr("x",d=>x(d))
           .attr("width", x.bandwidth())
           .each(function() {
              var node = d3.select(this);
              context.fillStyle = "crimson"
              context.fillRect(
                 +node.attr("x"),
                 +node.attr("y"),
                 +node.attr("width"),
                 +node.attr("height"))
           })
       
       return "";
    }
  })
  .duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

解决方案是更多地参与设置比例尺的域和范围。从所需的带宽开始,以像素为单位的整数,并设置范围,使最小值和最大值之间的差异等于域中值的数量 * 带宽。

所以代替:

 const x = this.d3
  .scaleBand()
  .range([0, this.width])
  .domain(xAxis.categories);

你会:

 const length = 10; // length of a box side
 const x = this.d3
  .scaleBand()
  .domain(xAxis.categories)
  .range([0,xAxis.categories * length])

您还可以动态计算上面的 length,例如使用:Math.floor(width/xAxis.categories)

使用上述方法和一个稍微人为设计的示例来适应转换,我们删除了 aliasing/moire 模式。 因为我们只使用完整像素,所以随着每个条的宽度同时增加一个完整像素,过渡会跳跃,因为 space 在以下范围内可用:

var data = d3.range(20);
var length = 30;

var x = d3.scaleBand()
  .range([10,data.length*length])
  .domain(data)
  
var canvas = d3.select("body")
  .append("canvas")
  .attr("width", 500);
  
var rect = d3.create("div").selectAll("rect")
 .data(data)
 .enter()
 .append("rect")
 .attr("x", d=>x(d) )
 .attr("y", 50)
 .attr("width", x.bandwidth())
 .attr("height",100)
 .attr("fill","crimson")

canvas.transition()
  .attrTween("tween", function() {
    var i = d3.interpolate(250,480)
    var context = canvas.node().getContext("2d");
    return function(t) {
       length = Math.floor(i(t)/data.length)
       x.range([10,length*data.length+10])
              
       context.fillStyle = "#fff";
       context.fillRect(0,0,550,300);
       rect.attr("x",d=>x(d))
           .attr("width", x.bandwidth())
           .each(function(d,i) {
              var node = d3.select(this);
              context.fillStyle = d3.schemeCategory10[i%10];
              context.fillRect(
                 +node.attr("x"),
                 +node.attr("y"),
                 +node.attr("width"),
                 +node.attr("height"))
           })
       
       return "";
    }
  })
  .duration(10000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>