15

Using the d3 graphics library, I can't seem to make paths draw slowly so they can be seen growing.

This site has a perfect example in the "Line Chart (Unrolling)" section, but no code is given for that section. Could someone please help me with the lines of D3 code that could make that happen?

When I try appending delay() or duration() such as in the following code snippet, the path still draws immediately, And all the SVG code after this segment fails to render.

    var mpath = svg.append ('path');
        mpath.attr ('d', 'M35 48 L22 48 L22 35 L22 22 L35 22 L35 35 L48 35 L48 48')
             .attr ('fill', 'none')
             .attr ('stroke', 'blue')
             .duration (1000);
Seanny123
  • 8,776
  • 13
  • 68
  • 124
tgoneil
  • 1,522
  • 3
  • 19
  • 30
  • 2
    Code for that example: http://blog.visual.ly/wp-content/uploads/2012/06/transitions.html#13 ... nevermind, when I run it through http://jsbeautifier.org/ I can see that the implementation is idiotic – nrabinowitz Nov 13 '12 at 00:41
  • Wow. Three different solutions, but can check only one. Thank you nrabinowitz, Duopixel, and btel! – tgoneil Nov 13 '12 at 05:07

3 Answers3

49

A common pattern when animating lines in svg is setting a stroke-dasharray of the length of the path and then animate stroke-dashoffset:

var totalLength = path.node().getTotalLength();

path
  .attr("stroke-dasharray", totalLength + " " + totalLength)
  .attr("stroke-dashoffset", totalLength)
  .transition()
    .duration(2000)
    .ease("linear")
    .attr("stroke-dashoffset", 0);

You can see a demo here: http://bl.ocks.org/4063326

methodofaction
  • 70,885
  • 21
  • 151
  • 164
  • 3
    Is this possible to do with d3.svg.area? So far I've been unsuccessful – mike Apr 29 '14 at 00:04
  • 1
    Advice: instead of `path.node.getTotalLength()` you should have `path.attr('stroke-dashoffset', function() { return this.getTotalLength(); })` Otherwise, if `path` is selected over multiple data points, `totalLength` will only be the length of the first `path` you are drawing. Subsequent `path`s will animate very strangely. – Jesse Dhillon Jul 20 '14 at 07:08
  • Neat technique. From my read of the code it's not possible to use it to grow from an arbitrary point on the path outwards in both direction, right? – George Mauer Nov 07 '14 at 15:13
  • @GeorgeMauer I think it would be possible if you animated both `stroke-dasharray` and `stroke-dashoffset` but I can't figure it out from the top of my head. I would suggest to open a new question. – methodofaction Nov 07 '14 at 17:06
18

I believe the "D3 way" to do this is with a custom tween function. You can see a working implementation here: http://jsfiddle.net/nrabinowitz/XytnD/

This assumes that you have a generator called line set up with d3.svg.line to calculate the path:

// add element and transition in
var path = svg.append('path')
    .attr('class', 'line')
    .attr('d', line(data[0]))
  .transition()
    .duration(1000)
    .attrTween('d', pathTween);

function pathTween() {
    var interpolate = d3.scale.quantile()
            .domain([0,1])
            .range(d3.range(1, data.length + 1));
    return function(t) {
        return line(data.slice(0, interpolate(t)));
    };
}​

The pathTween function here returns an interpolator that takes a given slice of the line, defined by how far we are through the transition, and updates the path accordingly.

It's worth noting, though, that I suspect you'd get better performance and a smoother animation by taking the easy route: put a white rectangle (if your background is simple) or a clipPath (if your background is complex) over the line, and transition it over to the right to reveal the line underneath.

nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • This certainly looks like the d3 way, but if you slow down the animation the paths are appended, not animated (at least in Chrome). – methodofaction Nov 13 '12 at 01:44
  • 3
    Yes, animating a rect to uncover the line will likely be better for performance. Simple clip-paths will also provide good performance in most cases, not clear if it would be better than tweaking the path (try and see, implementations will likely differ a bit on this). – Erik Dahlström Nov 13 '12 at 09:18
  • @Duopixel - yes, this is just appending the next segment on a tick-by-tick basis. But you could extend the tween function to add the next point as a copy of the previous, then use `interpolateObject` or `interpolateArray` to tween along the path segment (wouldn't work for other line interpolations though). – nrabinowitz Nov 13 '12 at 17:41
  • P.S. Thanks for the tip on JSBeautifer. I now can see that code in the transitions_010.html file, and you're right -- a messy cascade of nested "next" clauses. – tgoneil Nov 13 '12 at 18:24
1

Based on the post that you link to, I came up with the following example:

var i = 0,
    svg = d3.select("#main");

String.prototype.repeat = function(times) {
   return (new Array(times + 1)).join(this);
}

segments = [{x:35, y: 48}, {x: 22, y: 48}, {x: 22, y: 35}, {x: 34, y:35}, {x: 34, y:60}];
line = "M"+segments[0].x + " " + segments[0].y

new_line = line + (" L" + segments[0].x + " " + segments[0].y).repeat(segments.length);
 var mpath = svg.append ('path').attr ('d',new_line )
             .attr ('fill', 'none')
             .attr ('stroke', 'blue')

for (i=0; i<segments.length; i++)
    {
    new_segment = " " + "L"+segments[i].x + " " + segments[i].y
    new_line = line + new_segment.repeat(segments.length-i)
    mpath.transition().attr('d',new_line).duration(1000).delay(i*1000);
    line = line + new_segment

    }

It is a bit ugly, but works. You can see it on jsFiddle

btel
  • 5,563
  • 6
  • 37
  • 47