score:4

Accepted answer

From the D3 documentation:

To apply a transition, select elements, call selection.transition, and then make the desired changes.

I found this in the code:

d3.selectAll("g").transition().duration(3000).ease(d3.easeLinear);

This won't animate anything, because there's no .attr() or .style() at the end—no "desired changes" are being made. It's a transition with no changes to make.

Now, let's look at this:

g.selectAll(".line")
  .transition()
  .duration(10000)
  .ease(d3.easeLinear)
  .attr("d", line);

This almost fulfills the requirements. It selects .line, creates the transition (and customizes it), and sets the d attribute. If you have d set elsewhere, then this would to transition the path from being empty to having all the data, only...

D3 doesn't transition strings that way. After first checking if the attribute is a number or color, D3 settles on using something called interpolateString. You'd think interpolateString would change characters from a to ab to abc, but actually, all it does is look for numbers within the string, and interpolate those, leaving the rest of the string constant. The upshot is, you just can't animate a string like d from empty to having data unless you do it yourself.

Here's how you can do that, using attrTween (note: not a good idea):

.attrTween("d", function() {
  return function(t) {
    const l = line(data);
    return l.substring(0, Math.ceil(l.length * t));
  };
})

This will actually transition between no text to the entire text of the d attribute. However, because of the way SVG paths work, this doesn't look very good.

There is another way, as demonstrated in the example you linked to (and also mentioned by Ryan Morton in a comment): transitioning the stroke-dashoffset. Here's how you would do that:

line_stuff.enter().append("path").classed("line", true)
  .merge(line_stuff)
  .attr('d', line)
  .attr("fill", "none")
  .attr("stroke", "black")
  .attr("stroke-dasharray", function(d) {
    return this.getTotalLength()
  })
  .attr("stroke-dashoffset", function(d) {
    return this.getTotalLength()
  });

g.selectAll(".line")
  .transition()
  .duration(10000)
  .ease(d3.easeLinear)
  .attr("stroke-dashoffset", 0);

Essentially, the first part tells D3 to:

  • create the line, make the fill invisible (so you can see the line)
  • make the stroke dashes equal to the total length of the line
  • offset the dashes, so that the line is completely hidden at the start

The next part sets up the transition and tells it to transition the offset to 0 (at which point the line will be completely visible because each dash is the same length as the line itself).

If you want to transition the fill, you could change .attr("fill", "none") to .attr("fill", "#fff"), and then do something like this:

g.selectAll(".line")
  .transition()
  .delay(10000)
  .duration(2000)
  .ease(d3.easeLinear)
  .attr('fill', '#000');

This would use .delay() to wait for the first transition to finish before changing the background from white to black. Note that opacity might be better to animate for performance.


Related Query