-
Notifications
You must be signed in to change notification settings - Fork 35
/
10-d3update.html
191 lines (185 loc) · 12.3 KB
/
10-d3update.html
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<title>Software Carpentry: D3 - Transitions</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap.css" />
<link rel="stylesheet" type="text/css" href="css/bootstrap/bootstrap-theme.css" />
<link rel="stylesheet" type="text/css" href="css/swc.css" />
<link rel="alternate" type="application/rss+xml" title="Software Carpentry Blog" href="http://software-carpentry.org/feed.xml"/>
<meta charset="UTF-8" />
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body class="lesson">
<div class="container card">
<div class="banner">
<a href="http://software-carpentry.org" title="Software Carpentry">
<img alt="Software Carpentry banner" src="img/software-carpentry-banner.png" />
</a>
</div>
<div class="row">
<div class="col-md-10 col-md-offset-1">
<h1 class="title">D3 - Transitions</h1>
<h2 class="subtitle">Move it!</h2>
<div id="learning-objectives" class="objectives panel panel-warning">
<div class="panel-heading">
<h2><span class="glyphicon glyphicon-certificate"></span>Learning Objectives</h2>
</div>
<div class="panel-body">
<ul>
<li>Using a slider</li>
<li>Updating data points using d3.transition</li>
<li>Putting it all together by adding regional averages in a master challenge!</li>
</ul>
</div>
</div>
<p>At the moment, the year that we are looking at in the data is hardcoded. Naturally, we want the user to be able to see how the data changes over time.</p>
<p>Let's do this a slider. The first thing we need is add this slider to the user interface (our website). A slider element is actually an <code>input</code> element with the the type <code>range</code>. We give it a ID in order to be able to select is from our JavaScript script, a class to style it (if we choose to), and a minimum, maximum, and step size that depend on our data. <code>value</code> is what we read out in order to know the position of the slider. Let's initialise it somewhere in the middle (1979).</p>
<pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><input</span><span class="ot"> type=</span><span class="st">"range"</span><span class="ot"> name=</span><span class="st">"range"</span><span class="ot"> class=</span><span class="st">"slider"</span><span class="ot"> id=</span><span class="st">"year_slider"</span><span class="ot"> value=</span><span class="st">"1979"</span><span class="ot"> min=</span><span class="st">"1950"</span><span class="ot"> max=</span><span class="st">"2008"</span><span class="ot"> step=</span><span class="st">"1"</span> <span class="kw">><br></span></code></pre>
<p>In our script, we now want the year to be a variable, so we need to initialise it. Because the value is a string, we need to parse it to an integer using <code>parseInt()</code>. To get the index (rather than the actual year), we can simply subtract the first year 1950.</p>
<pre class="js"><code>var year_idx = parseInt(document.getElementById("year_slider").value)-1950;</code></pre>
<p>Updating the year becomes quite simple. All we need to do is add another event listener that changes the year the moment we touch the slider. The event we want to listen for is called <code>input</code>. We then execute the <code>update()</code> function we wrote earlier.</p>
<pre class="js"><code>d3.select("#year_slider").on("input", function () {
year_idx = parseInt(this.value) - 1950;
update();
});</code></pre>
<p>So far, the update function only knows how to handle new data (<code>.enter</code>) and removed data (<code>.exit</code>), but not what to do when we update data. In addition to <code>d3.enter()</code> and <code>d3.exit()</code>, D3 also offers <code>d3.transition</code> to handle updating data. First, we need to define how to transition between data points. We might want to interpolate between to values linearly over the duration of 200 ms, like this:</p>
<pre class="js"><code>dot.transition().ease("linear").duration(200);</code></pre>
<p>Now we know how it's gonna happen, but we need to tell the transition what the actual change is. We can simply move the part of our code that updates the circle attributes from our <code>enter</code> function to our <code>transition</code> function. Now, instead of using a hardcoded index for the year, we use the index <code>year_idx</code> that we updated in our event listener earlier.</p>
<pre class="js"><code>dot.enter().append("circle").attr("class","dot")
.style("fill", function(d) { return colorScale(d.region); });
dot.exit().remove();
dot.transition().ease("linear").duration(200)
.attr("cx", function(d) { return xScale(d.income[year_idx]); }) // this is how attr knows to work with the data
.attr("cy", function(d) { return yScale(d.lifeExpectancy[year_idx]); })
.attr("r", function(d) { return rScale(d.population[year_idx]); });</code></pre>
<div id="other-transition-functions-you-might-want" class="callout panel panel-info">
<div class="panel-heading">
<h2><span class="glyphicon glyphicon-pushpin"></span>Other transition functions you might want</h2>
</div>
<div class="panel-body">
<ul>
<li>sin - applies the trigonometric function sin.</li>
<li>exp - raises 2 to a power based on t.</li>
<li>bounce - simulates a bouncy collision.</li>
<li>elastic(a, p) - simulates an elastic band; may extend slightly beyond 0 and 1.</li>
<li><a href="https://github.com/mbostock/d3/wiki/Transitions#d3_ease">more here</a></li>
</ul>
</div>
</div>
<div id="play-time" class="challenge panel panel-success">
<div class="panel-heading">
<h1><span class="glyphicon glyphicon-pencil"></span>Play time</h1>
</div>
<div class="panel-body">
<p>D3 is incredible versatile. Try out different transitions and if you have time, maybe try drawing rectangles instead of circles.</p>
</div>
</div>
<p>Next, we might want to create a tooltip. Let's go have a look at what's already out there. The creator of D3 has put up some code for pretty much everything you can imagine. The example for a simple tooltip can be found <a href="http://bl.ocks.org/biovisualize/1016860">here</a>. We need to first create the variable tooltip:</p>
<pre class="js"><code>var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("visibility", "hidden");</code></pre>
<p>and then create event listeners for moving the mouse into a circle and out of one. Different from the example on the web page, we want to display the specific country we are looking at. When we move the mouse, we want the tool tip to move with it. And the moment we leave a circle, we want the tool tip to hide again.</p>
<pre class="js"><code>dot.enter().append("circle").attr("class","dot")
.style("fill", function(d) { return colorScale(d.region); })
.on("mouseover", function(d){return tooltip.style("visibility", "visible").text(d.name);})
.on("mousemove", function(){return tooltip.style("top", (d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});</code></pre>
<div id="we-have-used-some-special-objects-given-to-us-by-the-browser" class="callout panel panel-info">
<div class="panel-heading">
<h2><span class="glyphicon glyphicon-pushpin"></span>We have used some special objects given to us by the browser</h2>
</div>
<div class="panel-body">
<ul>
<li>document.x - selecting things within the page (getElementById)</li>
<li>console.x - interact with the browser's console (log)</li>
<li>event.x - only interesting in the scope of an event like "mouseover", "mousemove", "keydown". Returns information about the event (pageX - where on the page did this event occur?).</li>
</ul>
</div>
</div>
<p>Like any programming language, JavaScript can also be used to evaluate summary statistics of our data. As an example, let's compute the mean life expectancy and income for the different regions.</p>
<p>First, we need to loop through all the data and group them by the region they are in:</p>
<pre class="js"><code>// Calculate the averages for each region.
var region_names = ["Sub-Saharan Africa", "South Asia", "Middle East & North Africa", "America", "East Asia & Pacific", "Europe & Central Asia"];
var region_data = [];
for (var i in region_names) {
var filtered_nations_by_regions = nations.filter(function(nation){
return (nation.region == region_names[i]);
});
region_data[i] = calc_mean(filtered_nations_by_regions);
}
var filtered_reg_nations = region_data.map(function(region) { return region;});</code></pre>
<p>Next, we write a function that returns an array of objects region_data. We want it to contain the mean income and life for each year across all nations, weighted by population.</p>
<pre class="js"><code>function calc_mean(region_data) {
var mean_income = [];
var mean_lifeExpectancy = [];
for (var year_idx2 in region_data[0].years) {
var sum_income = 0;
var sum_lifeExpectancy = 0;
var sum_population = 0;
for (var k in region_data) {
var kpop = region_data[k].population[year_idx2];
var kincome = region_data[k].income[year_idx2];
var klife = region_data[k].lifeExpectancy[year_idx2];
sum_income += kpop*kincome;
sum_lifeExpectancy += kpop*klife;
sum_population += kpop;
}
mean_income[year_idx2] = sum_income/sum_population;
mean_lifeExpectancy[year_idx2] = sum_lifeExpectancy/sum_population;
}
averageData = {
region: region_data[0].region,
years: region_data[0].years,
mean_income: mean_income,
mean_lifeExpectancy: mean_lifeExpectancy
};
return averageData;
}</code></pre>
<div id="the-master-challenge" class="challenge panel panel-success">
<div class="panel-heading">
<h1><span class="glyphicon glyphicon-pencil"></span>The master challenge</h1>
</div>
<div class="panel-body">
<p>It's time to put together everything you've learned. Write code that displays (and updates) the mean values that we just computed as little crosses in the graph for the different regions.</p>
</div>
</div>
<div id="style" class="challenge panel panel-success">
<div class="panel-heading">
<h1><span class="glyphicon glyphicon-pencil"></span>...style!</h1>
</div>
<div class="panel-body">
<p>Add axis labels and make the fonts pretty.</p>
</div>
</div>
<div id="using-different-data-formats" class="challenge panel panel-success">
<div class="panel-heading">
<h1><span class="glyphicon glyphicon-pencil"></span>Using different data formats</h1>
</div>
<div class="panel-body">
<p>What if you don't have your data in JSON format? Change your code to load in nations.csv instead of nations.json and have it produce the same plot.</p>
</div>
</div>
<p>By the end of this lesson, your page should look something like this:</p>
<iframe src="code/index10.html" width="1000" height="600"></iframe>
</div>
</div>
<div class="footer">
<a class="label swc-blue-bg" href="http://software-carpentry.org">Software Carpentry</a>
<a class="label swc-blue-bg" href="https://github.com/swcarpentry/lesson-template">Source</a>
<a class="label swc-blue-bg" href="mailto:admin@software-carpentry.org">Contact</a>
<a class="label swc-blue-bg" href="LICENSE.html">License</a>
</div>
</div>
<!-- Javascript placed at the end of the document so the pages load faster -->
<script src="js/jquery.min.js"></script>
<script src="css/bootstrap/bootstrap-js/bootstrap.js"></script>
</body>
</html>