I hope this sequel will help you to understand how a d3-transition works, specifically when it is applied on multiple elements.
The main objective of this sequel is to illustrate that:
This sequel also illustrates:
This sequel uses D3 v3.5.5.
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<title>D3.selectAll(...).transition() Explained</title>
<meta content="Explaining D3.selectAll(...).transition() behaviour" name="description">
</head>
<body>
<link rel="stylesheet" href="d3_selectAll_transition_explained.css">
<div class='top-container'>
<form action="">
<input type="radio" name="step" onclick="_goToStage(this.value)" value=0 checked><span class="comment">start</span><br>
<input type="radio" name="step" onclick="_goToStage(this.value)" value=1><span class="space-2">d3.selectAll("circle")</span><br>
<input type="radio" name="step" onclick="_goToStage(this.value)" value=2><span class="space-4">.transition()</span><br>
<input type="radio" name="step" style="visibility:hidden"><span class="space-6">.attr("delay", func(d,i){return 1000*i})</span><br>
<input type="radio" name="step" style="visibility:hidden"><span class="space-6">.attr("duration", func(d,i){return 1000*(i+1)})</span><br>
<input type="radio" name="step" style="visibility:hidden"><span class="space-6">.attr("cy", func(d,i){return 30*(i+1)})</span><br>
<input type="radio" name="step" onclick="_goToStage(this.value)" value=3><span class="comment">end</span>
</form>
<div class="explanation-area"> </div>
</div>
<hr/>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="d3_selectAll_transition_explained.js"></script>
</body>
.top-container {
position: relative;
width: 960px;
height: 160px;
}
input {
margin-right: 10px;
}
span.comment {
color: gray;
}
span.space-2 {
margin-left: 10px;
}
span.space-4 {
margin-left: 20px;
}
span.space-6 {
margin-left: 30px;
}
.explanation-area {
position: absolute;
left: 380px;
top: 0px;
}
svg{
position: absolute;
top: 0px;
}
line {
stroke-width: 1.5px;
stroke: #000;
}
circle {
fill-opacity: .2;
stroke-width: 1.5px;
fill: #000;
}
path {
stroke: #000;
stroke-width: 1.5px;
}
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: "<em>d3.selectAll(...)</em> selects several elements. In this example, it selects the " + circleCount + " circles."
},
{
stageIndex: 2,
explanation: "<b>selectAll(...).transition() schedules<sup>*</sup> SEVERAL transitions</b><br> As explained in <a href='http://bost.ocks.org/mike/transition/#per-element' target='_blank'>Transitions Are per-Element and Exclusive</a>, <em>selectAll(...).transition()</em> schedules 1 transition per selected element. This example schedules " + circleCount + " transitions, one per circle. Each transition has its own <em>delay</em>, <em>duration</em>, and end value of the <em>cy</em> attribute. <em>delay</em> and <em>duration</em> allows to derive the start time and the end time of a transition.<br><br><sup>*</sup>In the D3 world, <em>scheduling</em> a transition means <em>defining</em> a transition, ie. setting its properties."
},
{
stageIndex: 3,
explanation: "As explained in <a href='http://bost.ocks.org/mike/transition/#life-cycle' target='_blank'>The Life of a Transition</a>, when the scheduling of transitions is complete, each transition waits until it can start, then runs, and then stops.<br><br><b>Each transition runs independantly</b><br>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!<br> 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);
}
{
"name": "world",
"children": [
{
"name": "Asia",
"color": "#f58321",
"children": [
{"name": "China", "weight": 14.84, "code": "CN"},
{"name": "Japan", "weight": 5.91, "code": "JP"},
{"name": "India", "weight": 2.83, "code": "IN"},
{"name": "South Korea", "weight": 1.86, "code": "KR"},
{"name": "Russia", "weight": 1.8, "code": "RU"},
{"name": "Indonesia", "weight": 1.16, "code": "ID"},
{"name": "Turkey", "weight": 0.97, "code": "TR"},
{"name": "Saudi Arabia", "weight": 0.87, "code": "SA"},
{"name": "Iran", "weight": 0.57, "code": "IR"},
{"name": "Thaïland", "weight": 0.53, "code": "TH"},
{"name": "United Arab Emirates", "weight": 0.5, "code": "AE"},
{"name": "Hong Kong", "weight": 0.42, "code": "HK"},
{"name": "Israel", "weight": 0.4, "code": "IL"},
{"name": "Malasya", "weight": 0.4, "code": "MY"},
{"name": "Singapore", "weight": 0.39, "code": "SG"},
{"name": "Philippines", "weight": 0.39, "code": "PH"}
]
},
{
"name": "North America",
"color": "#ef1621",
"children": [
{"name": "United States", "weight": 24.32, "code": "US"},
{"name": "Canada", "weight": 2.09, "code": "CA"},
{"name": "Mexico", "weight": 1.54, "code": "MX"}
]
},
{
"name": "Europe",
"color": "#77bc45",
"children": [
{"name": "Germany", "weight": 4.54, "code": "DE"},
{"name": "United Kingdom", "weight": 3.85, "code": "UK"},
{"name": "France", "weight": 3.26, "code": "FR"},
{"name": "Italy", "weight": 2.46, "code": "IT"},
{"name": "Spain", "weight": 1.62, "code": "ES"},
{"name": "Netherlands", "weight": 1.01, "code": "NL"},
{"name": "Switzerland", "weight": 0.9, "code": "CH"},
{"name": "Sweden", "weight": 0.67, "code": "SE"},
{"name": "Poland", "weight": 0.64, "code": "PL"},
{"name": "Belgium", "weight": 0.61, "code": "BE"},
{"name": "Norway", "weight": 0.52, "code": "NO"},
{"name": "Austria", "weight": 0.51, "code": "AT"},
{"name": "Denmark", "weight": 0.4, "code": "DK"},
{"name": "Ireland", "weight": 0.38, "code": "IE"}
]
},
{
"name": "South America",
"color": "#4aaaea",
"children": [
{"name": "Brazil", "weight": 2.39, "code": "BR"},
{"name": "Argentina", "weight": 0.79, "code": "AR"},
{"name": "Venezuela", "weight": 0.5, "code": "VE"},
{"name": "Colombia", "weight": 0.39, "code": "CO"}
]
},
{
"name": "Australia",
"color": "#00acad",
"children": [
{"name": "Australia", "weight": 1.81, "code": "AU"}
]
},
{
"name": "Africa",
"color": "#f575a3",
"children": [
{"name": "Nigeria", "weight": 0.65, "code": "NG"},
{"name": "Egypt", "weight": 0.45, "code": "EG"},
{"name": "South Africa", "weight": 0.42, "code": "ZA"}
]
},
{"name": "Rest of the World",
"color": "#592c94",
"children": [
{"name": "Rest of the World", "weight": 9.41, "code": "RotW"}
]
}
]
}
continent,1950,1955,1960,1965,1970,1975,1980,1985,1990,1995,2000,2005,2010,2015,2020,2025,2030,2035,2040,2045,2050,2055,2060,2065,2070,2075,2080,2085,2090,2095,2100,color
Oceania,10,10,20,20,20,20,20,20,30,30,30,30,40,40,40,50,50,50,50,50,60,60,60,60,60,70,70,70,70,70,70,#a5a190
Northern America,170,190,200,220,230,240,250,270,280,300,320,330,350,360,370,380,400,410,420,430,430,440,450,460,470,470,480,490,490,500,500,#ea9439
Latin America & Caribbean,170,190,220,250,290,320,360,400,450,390,530,560,600,630,660,690,720,740,760,770,780,790,790,790,780,770,760,750,740,730,710,#d85a44
Europe,550,580,610,640,660,680,690,710,720,730,730,730,740,740,740,740,740,730,730,720,720,710,700,690,680,670,670,660,660,660,650,#3884a9
Africa,230,250,290,320,370,420,480,550,630,720,810,910,1030,1190,1350,1520,1700,1900,2100,2310,2530,2750,2960,3180,3390,3600,3800,3990,4160,4320,4470,#539344
Asia,1400,1540,1690,1880,2130,2390,2630,2910,3210,3480,3720,3940,4170,4420,4620,4800,4950,5060,5150,5220,5260,5270,5260,5230,5190,5130,5070,5000,4930,4860,4780,#fcd25b