Can the 'enter' selection for a line graph be animated?

这一生的挚爱 提交于 2019-12-11 03:51:38

问题


I have a line graph that updates very nicely. Until new data enters the question. At that point the 'update' selection animates down as it should but the new data doesn't.

It's hard to explain what's happening, but a gif is worth a thousand words (watch when it transitions down):

I've wrapped the graph in a class with an update and render method. Here's the render method I wrote.

render(){
    // Update both Axes
    this.xAxis.transition()
        .call( d3.axisBottom( this.xScale ) );
    this.yAxis.transition()
        .call( this.createYAxis );

    // Exit
    this.line.exit().remove();

    // Enter
    this.line
      .enter().append( 'path' )
        .attr( 'class', 'valueLine' )
        .transition()
        .attr( 'd', this.valueLine );

    // Update
    this.line.transition().attr( 'd', this.valueLine );

    // Allows method chaining
    return this;
}

Why doesn't the 'new' data animate in?


回答1:


Short answer

Yes, but you have to define the starting value for the d attribute.

Long answer

A transition goes from a starting value to a target value. Right now, in your "enter" selection, you have no starting value (that is, before transition()) for the d attribute:

// Enter
this.line
    .enter().append( 'path' )
    .attr( 'class', 'valueLine' )
    .transition()//no 'd' attribute before this line
    .attr( 'd', this.valueLine );

Therefore, the d attribute for that path is null (read more about this in this answer).

Let's see it:

var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
  .y(function(d) {
    return d
  })
  .x(function(d, i) {
    return i * 60
  });
var path = svg.selectAll(null)
  .data([data])
  .enter()
  .append("path");
  
console.log(path.attr("d"))
path {
  fill: none;
  stroke-width: 2px;
  stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>

Here the same code, setting the d attribute:

var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
  .y(function(d) {
    return d
  })
  .x(function(d, i) {
    return i * 60
  });
var path = svg.selectAll(null)
  .data([data])
  .enter()
  .append("path")
  .attr("d", lineGenerator);
path {
  fill: none;
  stroke-width: 2px;
  stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>

Back to your question:

You cannot transition the d attribute from null to your string. It's impossible. There are some possible workarounds, though.

One of them is creating a default d attribute before the transition. For instance:

var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
  .y(function(d) {
    return d
  })
  .x(function(d, i) {
    return i * 60
  });
var path = svg.selectAll(null)
  .data([data])
  .enter()
  .append("path")
  .attr("d", "M0,100 L600,100")
  .transition()
  .duration(2000)
  .attr("d", lineGenerator);
path {
  fill: none;
  stroke-width: 2px;
  stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>

But that's a horrible transition! The problem is that the initial d attribute has too few points. Because of that, the string interpolator cannot create the interpolation we wish.

So, we can use a custom function, like this one from Mike Bostock. Here is the demo:

var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
  .y(function(d) {
    return d
  })
  .x(function(d, i) {
    return i * 60
  });
var path = svg.selectAll(null)
  .data([data])
  .enter()
  .append("path")
  .attr("d", "M0,100 L600,100")
  .transition()
  .duration(2000)
  .attrTween("d", function(d) {
    return pathTween(lineGenerator(d), 4, this)()
  });

function pathTween(d1, precision, self) {
  return function() {
    var path0 = self,
      path1 = path0.cloneNode(),
      n0 = path0.getTotalLength(),
      n1 = (path1.setAttribute("d", d1), path1).getTotalLength();

    // Uniform sampling of distance based on specified precision.
    var distances = [0],
      i = 0,
      dt = precision / Math.max(n0, n1);
    while ((i += dt) < 1) distances.push(i);
    distances.push(1);

    // Compute point-interpolators at each distance.
    var points = distances.map(function(t) {
      var p0 = path0.getPointAtLength(t * n0),
        p1 = path1.getPointAtLength(t * n1);
      return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]);
    });

    return function(t) {
      return t < 1 ? "M" + points.map(function(p) {
        return p(t);
      }).join("L") : d1;
    };
  };
}
path {
  fill: none;
  stroke-width: 2px;
  stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>


来源:https://stackoverflow.com/questions/47363028/can-the-enter-selection-for-a-line-graph-be-animated

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!