animationd3.js

d3-transition synchronization explanation


I am confused of the concept of syncing d3 transitions.

Consider this code:

const t = d3.transition()
        .duration(3000)
        .ease(d3.easeLinear);
    let width = 500;

    let mySVG1 = d3.select('body').append('svg')
        .attr('width', 500)
        .attr('height', 200)

    let mySVG2 = d3.select('body').append('svg')
        .attr('width', 500)
        .attr('height', 200)

    mySVG1.transition(t)
        .style('background-color', 'red')

mySVG2.transition(t)
            .style('background-color', 'blue')

As expected, mySVG1 goes red in 3 seconds and mySVG2 goes blue in 3 seconds where they both start at exactly the same time.

However, let's I change where mySVG2 is called:

setTimeout(() => {
        mySVG2.transition(t)
            .style('background-color', 'blue')
    }, 5000)

Now, you don't see blue - not even after 5 seconds. I thought they only run at the same time because they run in a synchronous and in a sequential manner. What I thought would happen is that the blue transition starts after 5 seconds and takes another 3 seconds to complete.

Any explanation would be greatly appreciated.


Solution

  • d3 transitions are scheduled to run on the next animation frame after they're created; they do not start running only when they're “used”. So the reason your blue SVG isn't turning blue is because the transition has simply stopped running after 3 seconds. If you used a timeout of at most 3000ms, you would catch the tail end of the transition and your blue SVG would abruptly go from transparent to partially blue before continuing to animate to fully blue.

    The proper way to use this transition is to either create a second transition with the same parameters as the first and set .delay(5000) on it (with no setTimeout anymore) or put the two SVGs in a selection and use the index or data to determine the delay. Or even better, use data to construct the SVGs and set the delay in one fell swoop.

    // method 1
    const t = d3.transition().duration(3000).ease(d3.easeLinear);
    const u = d3.transition().duration(3000).ease(d3.easeLinear).delay(5000);
    let width = 500;
    
    let mySVG1 = d3.select("body").append("svg").attr("width", 500).attr("height", 200);
    let mySVG2 = d3.select("body").append("svg").attr("width", 500).attr("height", 200);
    
    mySVG1.transition(t).style("background-color", "red");
    mySVG2.transition(u).style("background-color", "blue");
    
    // method 2
    // create SVGs as above
    d3.selectAll("svg")
        .transition()
        .duration(3000)
        .delay((_, i) => i * 5000)
        .style("background-color", (_, i) => (i === 0 ? "red" : "blue"));
    
    // method 3
    d3.select("body")
        .selectAll()
        .data([{ bg: "red" }, { bg: "blue" }])
        .join("svg")
        .attr("width", 500)
        .attr("height", 200)
        .transition()
        .duration(3000)
        .delay((_, i) => i * 5000)
        .style("background-color", d => d.bg);