The post shows you different ways that you can implement d3.js chained transitions.
Default transitions
d3.js offers great built-in transition()
API to allow simple animations, so we can animate positions, sizes very easily like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var data = [ [10, 50, 10, "red"], [70, 20, 15, "blue"], [80, 60, 20, "green"] ]; d3.select("#run").on("click", function() { d3.select(".container") .selectAll("circle") .data(data) .enter() .append('circle') // initial values .style("fill", function (d) { return d[3]; }) .attr("cx", 0) .attr("cy", 0) .attr("r", 0) .transition(200) // final values .attr("cx", function (d) { return d[0]; }) .attr("cy", function (d) { return d[1]; }) .attr("r", function (d) { return d[2]; }); }); |
Chained transitions, element by element
However, things get complicated when we want to chain animations, i.e. run animations in sequence, one after another.
d3.js offers built-in transition.each([type, ]listener) in API which allows us to chain transitions for individual elements, i.e triggering another transition after the previous element has finished transition:
1 |
mySelection.transition().each("end", myCallback); |
There are also examples (another) on how to use this type of d3.js chained transitions by Mike Bostock.
Chained transitions, transition by transition
However, what if I want to chain another transition after all the elements in the first transition has finished transition?
In the example below, we chain the transitions such that the transition of sizes only occur after all the positions have completed their transitions.
This effect can be achieved with the help of a small utility function endall:
1 2 3 4 5 6 7 |
function endall(transition, callback) { if (transition.size() === 0) { callback() } var n = 0; transition .each(function() { ++n; }) .each("end", function() { if (!--n) callback.apply(this, arguments); }); } |
With this function, we can trigger then the second transition only after all the queued animations in the first transition has ended:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
var data = [ [10, 50, 10, "red"], [70, 20, 15, "blue"], [80, 60, 20, "green"] ]; d3.select("#run").on("click", function() { d3.select(".container") .selectAll("circle") .data(data) .enter() .append('circle') // initial values .style("fill", function (d) { return d[3]; }) .attr("cx", 0) .attr("cy", 0) .attr("r", 5) .transition(200) // final values for positions .attr("cx", function (d) { return d[0]; }) .attr("cy", function (d) { return d[1]; }) .call(endall, function() { // previous transition has ended d3.selectAll("circle") .transition(200) .attr("r", function (d) { return d[2]; }); }); }); |
So, now we have three different ways to do a transition in d3.js:
- All transitions together simultaneously (default behavior)
- Transitions are chained one element after another
- using transition().each("end", myCallback);
- Transitions are chained one transition after another
- using endall utility function
Hope this post helps people experimenting with transitions and animations using d3.js!