转换条形图的宽度为负值

时间:2018-09-21 22:21:27

标签: javascript d3.js svg easing

我正在使用d3创建水平条形图。而且我正在使用动画在启动时“增长”图表。这是代码。

// Create the svg element
d3.select("#chart-area")
    .append("svg")
    .attr("height", 800)
    .attr("width", 800);

    .data(dataValues) // This data is previously prepared
    .enter().append("rect")
    .style("fill", "blue")

    .attr("x", function () { return xScale(0); }) // xScale is defined earlier
    .attr("y", function (d) { return yScale(d); }) // yScale is defined earlier

    .attr("height", yScale.bandwidth()) // yScale is defined earlier

    // Initial value of "width" (before animation)
    .attr("width", 0)


    // Start of animation transition
    .transition()
    .duration(5000) // 5 seconds

    .ease (d3.easeLinear); 

    // Final value of "width" (after animation)
    .attr("width", function(d) { return Math.abs(xScale(d) - xScale(0)); })

上面的代码可以正常工作,并且线条将在5秒钟内按预期的方式从0增长到任意宽度。

现在,如果我们将宽松线更改为以下内容

    // This line changed
    .ease (d3.easeElasticIn); 

然后,轻松度将尝试使宽度达到负值,然后再达到最终的正值。 As you can see hered3.easeElasticIn随着时间的流逝返回负值,然后返回正值,从而导致动画中某些点的宽度为负。因此,这些条不能正确渲染(因为SVG规范指出,如果width为负,则使用0)

我尝试了每种解决方案,以使金条负增长然后退出。但是找不到任何东西。我该如何解决这个问题?

谢谢。

1 个答案:

答案 0 :(得分:2)

您已经知道,在您的特定代码中使用d3.easeElasticIn将为矩形的宽度创建负值,这是不允许的。

此基本演示重现了该问题,在控制台(您的浏览器的控制台,而不是代码段的控制台)中填充了错误消息,如下所示:

  

错误:属性width =“-85.90933910798789”的无效负值

看看:

const svg = d3.select("svg");

const margin = 50;

const line = svg.append("line")
  .attr("x1", margin)
  .attr("x2", margin)
  .attr("y1", 0)
  .attr("y2", 150)
  .style("stroke", "black")

const data = d3.range(10).map(function(d) {
  return {
    y: "bar" + d,
    x: Math.random()
  }
});

const yScale = d3.scaleBand()
  .domain(data.map(function(d) {
    return d.y
  }))
  .range([0, 150])
  .padding(0.2);

const xScale = d3.scaleLinear()
  .range([margin, 300]);

const bars = svg.selectAll(null)
  .data(data)
  .enter()
  .append("rect")
  .attr("x", margin)
  .attr("width", 0)
  .style("fill", "steelblue")
  .attr("y", function(d) {
    return yScale(d.y)
  })
  .attr("height", yScale.bandwidth())
  .transition()
  .duration(2000)
  .ease(d3.easeElasticIn)
  .attr("width", function(d) {
    return xScale(d.x) - margin
  })
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

那么,解决方案是什么?

其中之一是在生成负值时捕获这些负值,然后将矩形向左移动(使用x属性并将这些负数转换为正数。

为此,我们必须在过渡选择中使用attrTween而不是attr

赞:

.attrTween("width", function(d) {
    return function(t){
        return Math.abs(xScale(d.x) * t);
    };
})
.attrTween("x", function(d) {
    return function(t){
        return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
    };
})

在上面的代码段中,margin只是我创建的一个空白,因此您可以看到轴左侧的条形图。

这是演示:

const svg = d3.select("svg");

const margin = 100;

const line = svg.append("line")
  .attr("x1", margin)
  .attr("x2", margin)
  .attr("y1", 0)
  .attr("y2", 150)
  .style("stroke", "black")

const data = d3.range(10).map(function(d) {
  return {
    y: "bar" + d,
    x: Math.random()
  }
});

const yScale = d3.scaleBand()
  .domain(data.map(function(d) {
    return d.y
  }))
  .range([0, 150])
  .padding(0.2);

const xScale = d3.scaleLinear()
  .range([0, 300 - margin]);

const bars = svg.selectAll(null)
  .data(data)
  .enter()
  .append("rect")
  .attr("x", margin)
  .attr("width", 0)
  .style("fill", "steelblue")
  .attr("y", function(d) {
    return yScale(d.y)
  })
  .attr("height", yScale.bandwidth())
  .transition()
  .duration(2000)
  .ease(d3.easeElasticIn)
  .attrTween("width", function(d) {
    return function(t) {
      return Math.abs(xScale(d.x) * t);
    };
  })
  .attrTween("x", function(d) {
    return function(t) {
      return xScale(d.x) * t < 0 ? margin + xScale(d.x) * t : margin;
    };
  })
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>