var width = 960, height = 500, delay = 1000, duration = 1000, circleInitialPosition = 200, circleCount = 7, circleSpacing = width/(circleCount+1), circleMovingIncrement = 30, data = d3.range(circleCount), explanations = _makeExplanations(), explanationArea = d3.select(".explanation-area"); var svg = d3.select("body").insert("svg", ".top-container") .attr("width", width) .attr("height", height); _goToStage(0); function _playStage0() { //prepare the field, add circles to svg svg.selectAll("circle") .data(data) .enter().append("circle") .attr("transform", function(d,i) { return "translate(" + circleSpacing*(i+1) + "," + circleInitialPosition + ")" ;}) .attr("r", 10) .style("stroke", '#000'); } function _playStage1() { //Highlight selected circles (bigger, green stroke) svg.selectAll("circle") .transition("playStage1") .attr('r', 15) .style('stroke', 'green') .ease('bounce') .duration(duration); } function _rewindStage1() { //interrupt stage1's transitions (running and scheduled) svg.selectAll('circle') .transition("playStage1") .duration(0) //rewind to stage1's initial state svg.selectAll("circle") .transition("rewindStage1") .attr('r', 10) .style("stroke", '#000'); } function _playStage2() { //Draw one scheduled transition per circle //Each transitiopn as a head, a body, a tail, a start time, and an end time var drawnTransitions = svg.selectAll(".drawn-transition") .data(data) .enter().append("g") .classed("drawn-transition", true) .attr("transform", function(d,i) { return "translate(" + circleSpacing*(i+1) + "," + circleInitialPosition + ")" ;}) var tails = drawnTransitions.append("path") .classed("tail", true) .attr("d", d3.svg.symbol().size(20)) .style("fill", 'grey') .style("stroke", 'grey'); var bodies = drawnTransitions.append("line") .classed("body", true) .attr({x1: 0, y1: 0, x2: 0, y2: 0}) .attr("stroke-dasharray", "5") .style("stroke", 'grey'); var heads = drawnTransitions.append("path") .classed("head", true) .attr("d", d3.svg.symbol().type("triangle-down").size(40)) .style("fill", 'grey') .style("stroke", 'grey'); var starts = drawnTransitions.append("text") .classed("start-time", true) .attr("transform", function (d, i) { return "translate(-10, " + (circleMovingIncrement*(i+1) + 15) + ")"; }) .text(function(d, i) {return "start: " + i + "s"}) .attr("text-anchor", "end") .attr("font-size", "13px") .style("fill", 'grey') .attr("fill-opacity", 0) .style("stroke", 'green') .attr("stroke-width", 2) .attr("stroke-opacity", 0); var ends = drawnTransitions.append("text") .classed("end-time", true) .attr("transform", function (d, i) { return "translate(-10, " + (circleMovingIncrement*(i+1) + 30) + ")"; }) .text(function(d, i) {return "end: " + (2*i+1) + "s"}) .attr("text-anchor", "end") .attr("font-size", "13px") .style("fill", 'grey') .attr("fill-opacity", 0) .style("stroke", 'red') .attr("stroke-width", 2) .attr("stroke-opacity", 0); //Animate each transition (longer bodies, adequate position of heads, show start-times and end-times) bodies.transition("playStage2") .attr("y2", function(d, i) { return circleMovingIncrement*(i+1); }) .duration(duration); heads.transition("playStage2") .attr("transform", function (d, i) { return "translate(0, " + circleMovingIncrement*(i+1) + ")"; }) .duration(duration); starts.transition("playStage2") .attr("fill-opacity", 1) .duration(duration); ends.transition("playStage2") .attr("fill-opacity", 1) .duration(duration); //Prepare animation for the next stage: add an 'executed-body' on each transition var executedBodies = drawnTransitions.append("line") .attr("class", "executed-body") .attr({x1: 0, y1: 0, x2: 0, y2: 0}) .style("stroke", 'grey'); } function _rewindStage2() { //interrupt stage2's transitions (running and scheduled) svg.selectAll('.drawn-transition') .transition("playStage2") .duration(0) //rewind to stage2's initial state svg.selectAll(".drawn-transition") .transition("rewindStage2") .style("fill-opacity", 0) .style("stroke-opacity", 0) .remove(); } function _playStage3() { //Run each transition, move circles down svg.selectAll('circle') .transition("playStage3") .attr('cy', function(d, i) { return circleMovingIncrement*(i+1); }) .delay(function(d, i) { return delay*(i); }) .duration(function(d, i) { return duration*(i+1); }) .ease('linear') //Create transitions for each drawn-transition. //Graphically speaking, those transitions does nothing //Those transitions allow synchronization between sub-transitions that applie on sub-elements of each drawn-transition var drawnTransitionAnimations = svg.selectAll(".drawn-transition") .transition("playStage3") .delay(function(d, i) { return delay*i; }) .duration(function(d, i) { return duration*(i+1); }) //Run each transition, make the body of each drawn-transition 'solid' (instead of being dashed) drawnTransitionAnimations.each(function (d, i) { d3.select(this).select(".executed-body") .transition("playStage3") .attr("y2", circleMovingIncrement*(i+1)) .ease('linear') }) drawnTransitionAnimations.each(function (d, i) { d3.select(this).select(".body") .transition("playStage3") .attr("y1", circleMovingIncrement*(i+1)) .ease('linear') }) //Run each transition, highlight each start-time drawnTransitionAnimations.each('start', function () { d3.select(this).select('.start-time') .attr("stroke-opacity", 1) .transition("playStage3") .attr("stroke-opacity", 0) }) //Run each transition, highlight each end-time drawnTransitionAnimations.each('end', function () { d3.select(this).select('.end-time') .attr("stroke-opacity", 1) .transition("playStage3") .attr("stroke-opacity", 0) }) } function _rewindStage3() { //interrupt stage3's transitions (running and scheduled) svg.selectAll('circle') .transition("playStage3") .duration(0) svg.selectAll('.drawn-transition') .transition("playStage3") .duration(0) svg.selectAll('.executed-body') .transition("playStage3") .duration(0) svg.selectAll('.body') .transition("playStage3") .duration(0) svg.selectAll('.start-time') .transition("playStage3") .duration(0) svg.selectAll('.end-time') .transition("playStage3") .duration(0) //rewind to stage3's initial state svg.selectAll('circle') .transition("rewindStage3") .attr('cy', 0); svg.selectAll(".executed-body") .transition("rewindStage3") .attr('y2', 0); svg.selectAll(".body") .transition("rewindStage3") .attr('y1', 0); svg.selectAll(".start-time") .transition("rewindStage3") .attr('stroke-opacity', 0); svg.selectAll(".end-time") .transition("rewindStage3") .attr('stroke-opacity', 0); } function _goToStage(n) { switch (parseInt(n)) { case 0: _rewindStage3(); _rewindStage2(); _rewindStage1(); _playStage0(); break; case 1: _rewindStage3(); _rewindStage2(); _playStage0(); _playStage1(); break; case 2: _rewindStage3(); _playStage0(); _playStage1(); _playStage2(); break; case 3: _playStage0(); _playStage1(); _playStage2(); _playStage3(); break; } _updateExplanations(n); } function _makeExplanations() { return [ { stageIndex: 0, explanation: "<== Choose a line of code for more explanations." }, { stageIndex: 1, explanation: "d3.selectAll(...) selects several elements. In this example, it selects the " + circleCount + " circles." }, { stageIndex: 2, explanation: "selectAll(...).transition() schedules* SEVERAL transitions
As explained in Transitions Are per-Element and Exclusive, selectAll(...).transition() schedules 1 transition per selected element. This example schedules " + circleCount + " transitions, one per circle. Each transition has its own delay, duration, and end value of the cy attribute. delay and duration allows to derive the start time and the end time of a transition.

*In the D3 world, scheduling a transition means defining a transition, ie. setting its properties." }, { stageIndex: 3, explanation: "As explained in The Life of a Transition, when the scheduling of transitions is complete, each transition waits until it can start, then runs, and then stops.

Each transition runs independantly
When a transition stops or is interupted, this has no side-effect on other transitions, even on sibling transitions (ie. defined by the same JavaScript lines of code). As a proof, note that when one of the transitions of this example stops, others transitions to the right still continue to run!
Synchronization between transitions comes with identical delays and/or durations." } ] } function _updateExplanations (index) { var explanation = explanations[index].explanation; //update explanations explanationArea.transition() .style("opacity", 0) .each('end', function() { explanationArea.html(explanation); }) .transition() .style("opacity", 1); }