SVG path animations: animating both path interpolation and stroke-dasharray

759 views
Skip to first unread message

Simon Knight

unread,
Apr 10, 2013, 4:10:46 AM4/10/13
to d3...@googlegroups.com
Hi,

I am drawing a series of paths, which can change over time.
I'd like to do two things:
a) animate the length like in http://bl.ocks.org/duopixel/4063326
b) animate the path transitioning, so if it changes from [a, b, c, d] to [a, b, e, f, d] this animates the bundle

I can do a) with:
 var svg_line = d3.svg.line()
        .x(path_x)
        .y(path_y)
        .interpolate("cardinal")
        .tension(0.7)
        ;

    trace_path = g_traces.selectAll(".trace_path")
        .data(pathinfo, function(path) {
            return _.first(path) + "_" + _.last(path);;
        })
    
    var path_total_length = function(d) {
        return d.node().getTotalLength()
    }

    trace_path.enter().append("svg:path")
        .attr("d", svg_line)
        .attr("class", "trace_path")
        .style("stroke-width", 7)
        .style("stroke", "rgb(207,120,33)")
        .style("fill", "none")
        .attr("stroke-dasharray", function(d) {
            return path_total_length(d3.select(this)) + " " + path_total_length(d3.select(this))})
        .attr("stroke-dashoffset", function(d) {
            return path_total_length(d3.select(this))})

        trace_path
        .attr("d", svg_line)

        .transition()
        .style("stroke", "rgb(25,52,65)")
        .attr("d", svg_line)
        .attr("stroke-dasharray", function(d) {
            return path_total_length(d3.select(this)) + " " + path_total_length(d3.select(this))})
        .ease("linear")
        .attr("stroke-dashoffset", 0)
        .duration(1000)

and I can do b) by making the path only inside the transition:

        trace_path
        .transition()
        .style("stroke", "rgb(25,52,65)")
        .attr("d", svg_line)
        .attr("stroke-dasharray", function(d) {
            return path_total_length(d3.select(this)) + " " + path_total_length(d3.select(this))})
        .ease("linear")
        .attr("stroke-dashoffset", 0)
        .duration(1000)


instead of

   trace_path
        .attr("d", svg_line)

        .transition()
        .style("stroke", "rgb(25,52,65)")
        .attr("d", svg_line)
        .attr("stroke-dasharray", function(d) {
            return path_total_length(d3.select(this)) + " " + path_total_length(d3.select(this))})
        .ease("linear")
        .attr("stroke-dashoffset", 0)
        .duration(1000)
        .transition()
        .attr("marker-end", "url(#link_edge)")

The problem with approach b) is that if the path becomes longer, then the stroke is too short.
The problem looks to be in
            return path_total_length(d3.select(this)) + " " + path_total_length(d3.select(this))})

where the d.node().getTotalLength() function doesn't get the new length. It looks like having the line animation interpolated means that the d.node().getTotalLength() function lags behind by one update.

If I send a new batch of paths, then the getTotalLength function returns the right value.

So it appears that there is a race condition somewhere, possibly due to the transition().
Is there somewhere that I can get the getTotalLength of the path once it has transitioned? I tried chaining, by putting another transition after the first one:

        .transition()
        .style("stroke", "rgb(25,52,65)")
        .attr("d", svg_line)
        .attr("stroke-dasharray", function(d) {
            return path_total_length(d3.select(this)) + " " + path_total_length(d3.select(this))})
        .ease("linear")
        .attr("stroke-dashoffset", 0)
        .duration(1000)
        .transition()
        .attr("stroke-dasharray", function(d) {
            return path_total_length(d3.select(this)) + " " + path_total_length(d3.select(this))})
        .duration(100)

But it still only gets the old length.

Thanks
Simo



Simon Knight

unread,
Apr 10, 2013, 5:36:09 AM4/10/13
to d3...@googlegroups.com
Hi,
So I realised a solution would be to set the stroke-dasharray to be a solid line once the line has been drawn.

        trace_path
        .transition()
        .style("stroke", "rgb(25,52,65)")
        .attr("d", svg_line)
        .ease("linear")
        .attr("stroke-dashoffset", 0)
        .duration(1000)
        .transition()
        .attr("stroke-dasharray", "0")
        .attr("marker-end", "url(#link_edge)")
        .duration(1)


I'd still be curious if there is a way around the one-lag path length, but it's only a curiosity for now.

The finished product can be seen here: http://www.youtube.com/watch?v=k9q6otf3SCk
d3 is awesome!

Cheers
Simon





--
You received this message because you are subscribed to the Google Groups "d3-js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

marc fawzi

unread,
Apr 10, 2013, 6:32:18 AM4/10/13
to d3...@googlegroups.com
there is 

transition().duration(nnn).do_some_stuuf.each("end" function(d, i) { ...}) which waits for the nd of transition on each element

funny, I was doing path highlighting last night 
Reply all
Reply to author
Forward
0 new messages