d3
Version:
A small, free JavaScript library for manipulating documents based on data.
406 lines (341 loc) • 39.1 kB
HTML
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title>d3.js ~ A Bar Chart, Part 2</title>
<script type="text/javascript" src="../d3.js?1.12.0"></script>
<style type="text/css">
@import url("../style.css?1.10.0");
@import url("../syntax.css?1.6.0");
</style>
</head>
<body>
<div class="body">
<div class="content">
<div class="topbar">
<a href="../">Overview</a>
<a href="../ex/">Examples</a>
<b><a href="../api/">Documentation</a></b>
<a href="http://github.com/mbostock/d3/archives/master">Download</a>
</div>
<div class="sidebar">
<h1>d3.js</h1>
</div>
<h1 id='a_bar_chart_part_2'>A Bar Chart, Part 2</h1>
<p>The <a href='bar-1.html'>previous part</a> of this tutorial covered the construction of a no-frills, static bar chart. This part will showcase some of the dynamic capabilities of D3, including <em>transitions</em> and <em>data joins</em>.</p>
<p>Say that, rather than a simple array of numbers, you want to visualize a <a href='http://en.wikipedia.org/wiki/Time_series'>time series</a>—a sequence of values sampled at regular time intervals. For example, say you run a website, and want to track how many visitors find your ideas intriguing? A bar chart could show the number of visitors that subscribe to your newsletter in realtime!</p>
<h2 id='dynamic_data'>Dynamic Data</h2>
<p>Now typically, the subscription data would be downloaded to the client via an HTTP request. You can poll the server to refresh the latest data every minute, or use <a href='http://www.w3.org/TR/websockets/'>web sockets</a> to stream data incrementally, minimizing latency. To simplify this tutorial and focus on the task of visualization, we’ll construct a synthetic (<em>i.e.</em>, fake) dataset by <a href='http://en.wikipedia.org/wiki/Random_walk'>random walk</a>:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'> 1</span> <span class='kd'>var</span> <span class='nx'>t</span> <span class='o'>=</span> <span class='mi'>1297110663</span><span class='p'>,</span> <span class='c1'>// start time (seconds since epoch)</span>
<span class='lineno'> 2</span> <span class='nx'>v</span> <span class='o'>=</span> <span class='mi'>70</span><span class='p'>,</span> <span class='c1'>// start value (subscribers)</span>
<span class='lineno'> 3</span> <span class='nx'>data</span> <span class='o'>=</span> <span class='nx'>d3</span><span class='p'>.</span><span class='nx'>range</span><span class='p'>(</span><span class='mi'>33</span><span class='p'>).</span><span class='nx'>map</span><span class='p'>(</span><span class='nx'>next</span><span class='p'>);</span> <span class='c1'>// starting dataset</span>
<span class='lineno'> 4</span>
<span class='lineno'> 5</span> <span class='kd'>function</span> <span class='nx'>next</span><span class='p'>()</span> <span class='p'>{</span>
<span class='lineno'> 6</span> <span class='k'>return</span> <span class='p'>{</span>
<span class='lineno'> 7</span> <span class='nx'>time</span><span class='o'>:</span> <span class='o'>++</span><span class='nx'>t</span><span class='p'>,</span>
<span class='lineno'> 8</span> <span class='nx'>value</span><span class='o'>:</span> <span class='nx'>v</span> <span class='o'>=</span> <span class='o'>~~</span><span class='nb'>Math</span><span class='p'>.</span><span class='nx'>max</span><span class='p'>(</span><span class='mi'>10</span><span class='p'>,</span> <span class='nb'>Math</span><span class='p'>.</span><span class='nx'>min</span><span class='p'>(</span><span class='mi'>90</span><span class='p'>,</span> <span class='nx'>v</span> <span class='o'>+</span> <span class='mi'>10</span> <span class='o'>*</span> <span class='p'>(</span><span class='nb'>Math</span><span class='p'>.</span><span class='nx'>random</span><span class='p'>()</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>)))</span>
<span class='lineno'> 9</span> <span class='p'>};</span>
<span class='lineno'>10</span> <span class='p'>}</span>
</code></pre>
</div>
<p>The exact mechanism of the random walk is unimportant, but you should understand the structure of the resulting data. Rather than a number, each data point is an object with <code>time</code> and <code>value</code> attributes:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'>1</span> <span class='p'>{</span><span class='s2'>"time"</span><span class='o'>:</span> <span class='mi'>1297110663</span><span class='p'>,</span> <span class='s2'>"value"</span><span class='o'>:</span> <span class='mi'>56</span><span class='p'>},</span>
<span class='lineno'>2</span> <span class='p'>{</span><span class='s2'>"time"</span><span class='o'>:</span> <span class='mi'>1297110664</span><span class='p'>,</span> <span class='s2'>"value"</span><span class='o'>:</span> <span class='mi'>53</span><span class='p'>},</span>
<span class='lineno'>3</span> <span class='p'>{</span><span class='s2'>"time"</span><span class='o'>:</span> <span class='mi'>1297110665</span><span class='p'>,</span> <span class='s2'>"value"</span><span class='o'>:</span> <span class='mi'>58</span><span class='p'>},</span>
<span class='lineno'>4</span> <span class='p'>{</span><span class='s2'>"time"</span><span class='o'>:</span> <span class='mi'>1297110666</span><span class='p'>,</span> <span class='s2'>"value"</span><span class='o'>:</span> <span class='mi'>58</span><span class='p'>},</span>
</code></pre>
</div>
<p>Note that the values in the dataset are constrained to the domain [10, 90], which is convenient because it allows a fixed <em>y</em>-scale. This simplifies the implementation, as the old bars will not resize as new data arrives. You <em>can</em> use a dynamic scale, but keep in mind that rescaling old values while introducing new ones makes it harder for the user to perceive changes accurately. Also, you’ll need reference lines! Cushioning your scales to avoid sudden changes, or applying <a href='http://en.wikipedia.org/wiki/Hysteresis'>hysteresis</a> to delay changes, is recommended.</p>
<p>If you stream data from the server, you can redraw the bar chart whenever new data becomes available. In this case, we’ll cycle the data every 1.5 seconds:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'>1</span> <span class='nx'>setInterval</span><span class='p'>(</span><span class='kd'>function</span><span class='p'>()</span> <span class='p'>{</span>
<span class='lineno'>2</span> <span class='nx'>data</span><span class='p'>.</span><span class='nx'>shift</span><span class='p'>();</span>
<span class='lineno'>3</span> <span class='nx'>data</span><span class='p'>.</span><span class='nx'>push</span><span class='p'>(</span><span class='nx'>next</span><span class='p'>());</span>
<span class='lineno'>4</span> <span class='nx'>redraw</span><span class='p'>();</span>
<span class='lineno'>5</span> <span class='p'>},</span> <span class='mi'>1500</span><span class='p'>);</span>
</code></pre>
</div>
<p>The <code>shift</code> operation removes the first (oldest) element in the array, while the <code>push</code> appends after the last (newest) element. If you have a lot of data, a <a href='http://en.wikipedia.org/wiki/Circular_buffer'>circular buffer</a> will improve performance; with smaller data, the inefficiency of the <code>shift</code> operation is negligible and can be ignored. The <code>redraw</code> method is a function that you will define; we’ll get to that shortly.</p>
<h2 id='dynamic_bars'>Dynamic Bars</h2>
<p>For now, the next step is to construct two scales, based on our knowledge of the dataset and the desired chart size. To fix the maximum bar size to 80×20, construct two linear scales:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'> 1</span> <span class='kd'>var</span> <span class='nx'>w</span> <span class='o'>=</span> <span class='mi'>20</span><span class='p'>,</span>
<span class='lineno'> 2</span> <span class='nx'>h</span> <span class='o'>=</span> <span class='mi'>80</span><span class='p'>;</span>
<span class='lineno'> 3</span>
<span class='lineno'> 4</span> <span class='kd'>var</span> <span class='nx'>x</span> <span class='o'>=</span> <span class='nx'>d3</span><span class='p'>.</span><span class='nx'>scale</span><span class='p'>.</span><span class='nx'>linear</span><span class='p'>()</span>
<span class='lineno'> 5</span> <span class='p'>.</span><span class='nx'>domain</span><span class='p'>([</span><span class='mi'>0</span><span class='p'>,</span> <span class='mi'>1</span><span class='p'>])</span>
<span class='lineno'> 6</span> <span class='p'>.</span><span class='nx'>range</span><span class='p'>([</span><span class='mi'>0</span><span class='p'>,</span> <span class='nx'>w</span><span class='p'>]);</span>
<span class='lineno'> 7</span>
<span class='lineno'> 8</span> <span class='kd'>var</span> <span class='nx'>y</span> <span class='o'>=</span> <span class='nx'>d3</span><span class='p'>.</span><span class='nx'>scale</span><span class='p'>.</span><span class='nx'>linear</span><span class='p'>()</span>
<span class='lineno'> 9</span> <span class='p'>.</span><span class='nx'>domain</span><span class='p'>([</span><span class='mi'>0</span><span class='p'>,</span> <span class='mi'>100</span><span class='p'>])</span>
<span class='lineno'>10</span> <span class='p'>.</span><span class='nx'>rangeRound</span><span class='p'>([</span><span class='mi'>0</span><span class='p'>,</span> <span class='nx'>h</span><span class='p'>]);</span>
</code></pre>
</div>
<p>The <em>x</em>-scale is a bit cheeky in that we’ve defined the domain as [0, 1], rather than the full time-domain of the dataset. That’s because we’ll assume (again, for simplicity) that the data is in chronological order and there are no missing data points. As such, we can use the index of the data to derive the <em>x</em>-position; <code>x(i)</code> is identical to <code>w * i</code>. A more robust implementation would update the domain from the <code>time</code> attributes of the dataset whenever the data changes.</p>
<p>The <em>y</em>-scale uses <code>rangeRound</code> rather than <code>range</code>; the only difference is that the output values of the scale are rounded to the nearest integer to avoid antialiasing artifacts. If you prefer, you can instead use SVG’s <a href='http://www.w3.org/TR/SVG/painting.html#ShapeRenderingProperty'>shape-rendering</a> property. However, antialiasing is nice for smooth intermediate values during transition.</p>
<p>With the scales ready, construct the SVG container for the chart:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'>1</span> <span class='kd'>var</span> <span class='nx'>chart</span> <span class='o'>=</span> <span class='nx'>d3</span><span class='p'>.</span><span class='nx'>select</span><span class='p'>(</span><span class='s2'>"body"</span><span class='p'>)</span>
<span class='lineno'>2</span> <span class='p'>.</span><span class='nx'>append</span><span class='p'>(</span><span class='s2'>"svg:svg"</span><span class='p'>)</span>
<span class='lineno'>3</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"class"</span><span class='p'>,</span> <span class='s2'>"chart"</span><span class='p'>)</span>
<span class='lineno'>4</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"width"</span><span class='p'>,</span> <span class='nx'>w</span> <span class='o'>*</span> <span class='nx'>data</span><span class='p'>.</span><span class='nx'>length</span> <span class='o'>-</span> <span class='mi'>1</span><span class='p'>)</span>
<span class='lineno'>5</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"height"</span><span class='p'>,</span> <span class='nx'>h</span><span class='p'>);</span>
</code></pre>
</div>
<p>Add the initial bars:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'>1</span> <span class='nx'>chart</span><span class='p'>.</span><span class='nx'>selectAll</span><span class='p'>(</span><span class='s2'>"rect"</span><span class='p'>)</span>
<span class='lineno'>2</span> <span class='p'>.</span><span class='nx'>data</span><span class='p'>(</span><span class='nx'>data</span><span class='p'>)</span>
<span class='lineno'>3</span> <span class='p'>.</span><span class='nx'>enter</span><span class='p'>().</span><span class='nx'>append</span><span class='p'>(</span><span class='s2'>"svg:rect"</span><span class='p'>)</span>
<span class='lineno'>4</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>,</span> <span class='nx'>i</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>x</span><span class='p'>(</span><span class='nx'>i</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'>5</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"y"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>h</span> <span class='o'>-</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'>6</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"width"</span><span class='p'>,</span> <span class='nx'>w</span><span class='p'>)</span>
<span class='lineno'>7</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"height"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>);</span> <span class='p'>});</span>
</code></pre>
</div>
<p>In SVG, rects are positioned relative to their top-left corner. For a vertical bar chart (also known as a column chart), the bars should be anchored by their bottom-left corner, so the “y” attribute flips the <em>y</em>-scale. Alternatively, you can use a transform to change the <a href='http://www.w3.org/TR/SVG/coords.html'>coordinate system</a>. The .5 offset is to avoid antialiasing; the 1-pixel white stroke is centered on the given location, so a half-pixel offset will fill the pixel exactly. If you are not the Martha Stewart type, and don’t care for crisp edges, you may omit this step.</p>
<p>Add the <em>y</em>-axis last, so that it appears on top of the bars:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'>1</span> <span class='nx'>chart</span><span class='p'>.</span><span class='nx'>append</span><span class='p'>(</span><span class='s2'>"svg:line"</span><span class='p'>)</span>
<span class='lineno'>2</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x1"</span><span class='p'>,</span> <span class='mi'>0</span><span class='p'>)</span>
<span class='lineno'>3</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x2"</span><span class='p'>,</span> <span class='nx'>w</span> <span class='o'>*</span> <span class='nx'>data</span><span class='p'>.</span><span class='nx'>length</span><span class='p'>)</span>
<span class='lineno'>4</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"y1"</span><span class='p'>,</span> <span class='nx'>h</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>)</span>
<span class='lineno'>5</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"y2"</span><span class='p'>,</span> <span class='nx'>h</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>)</span>
<span class='lineno'>6</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"stroke"</span><span class='p'>,</span> <span class='s2'>"#000"</span><span class='p'>);</span>
</code></pre>
</div>
<p>SVG draws shapes in the order they are specified, so to have the axis appear on top of the bars, the line must exist <em>after</em> the rects in the DOM. It is sometimes convenient to use <code>svg:g</code> elements to group shapes into the desired z-order.</p>
<p>A little bit of CSS will set the bar colors:</p>
<div class='highlight'><pre><code class='css'><span class='lineno'>1</span> <span class='nc'>.chart</span> <span class='nt'>rect</span> <span class='p'>{</span>
<span class='lineno'>2</span> <span class='n'>fill</span><span class='o'>:</span> <span class='nb'>steelblue</span><span class='p'>;</span>
<span class='lineno'>3</span> <span class='n'>stroke</span><span class='o'>:</span> <span class='nb'>white</span><span class='p'>;</span>
<span class='lineno'>4</span> <span class='p'>}</span>
</code></pre>
</div><style type='text/css'>
.chart {
margin-left: 42px;
}
.chart rect {
fill: steelblue;
stroke: white;
}
</style><script type='text/javascript'>
var t = 1297110663,
v = 70,
data = d3.range(33).map(next);
function next() {
return {
time: ++t,
value: v = ~~Math.max(10, Math.min(90, v + 10 * (Math.random() - .5)))
};
}
</script><script type='text/javascript'>
var w = 20,
h = 80;
var x = d3.scale.linear()
.domain([0, 1])
.range([0, w]);
var y = d3.scale.linear()
.domain([0, 100])
.rangeRound([0, h]);
</script>
<p>The code so far produces a static bar chart:</p>
<script type='text/javascript'>
var chart = d3.select(".content")
.append("svg:svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d, i) { return x(i) - .5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); });
chart.append("svg:line")
.attr("x1", 0)
.attr("x2", w * data.length)
.attr("y1", h - .5)
.attr("y2", h - .5)
.attr("stroke", "#000");
</script>
<p>Now, what about that <code>redraw</code> function?</p>
<div class='highlight'><pre><code class='js'><span class='lineno'> 1</span> <span class='kd'>function</span> <span class='nx'>redraw</span><span class='p'>()</span> <span class='p'>{</span>
<span class='lineno'> 2</span>
<span class='lineno'> 3</span> <span class='c1'>// Update…</span>
<span class='lineno'> 4</span> <span class='nx'>chart</span><span class='p'>.</span><span class='nx'>selectAll</span><span class='p'>(</span><span class='s2'>"rect"</span><span class='p'>)</span>
<span class='lineno'> 5</span> <span class='p'>.</span><span class='nx'>data</span><span class='p'>(</span><span class='nx'>data</span><span class='p'>)</span>
<span class='lineno'> 6</span> <span class='p'>.</span><span class='nx'>transition</span><span class='p'>()</span>
<span class='lineno'> 7</span> <span class='p'>.</span><span class='nx'>duration</span><span class='p'>(</span><span class='mi'>1000</span><span class='p'>)</span>
<span class='lineno'> 8</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"y"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>h</span> <span class='o'>-</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'> 9</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"height"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>);</span> <span class='p'>});</span>
<span class='lineno'>10</span>
<span class='lineno'>11</span> <span class='p'>}</span>
</code></pre>
</div>
<p>Observe how the bars dance happily in response to changing data:</p>
<script type='text/javascript'>
var chart1 = d3.select(".content")
.append("svg:svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart1.selectAll("rect")
.data(data)
.enter().append("svg:rect")
.attr("x", function(d, i) { return x(i) - .5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); });
chart1.append("svg:line")
.attr("x1", 0)
.attr("x2", w * data.length)
.attr("y1", h - .5)
.attr("y2", h - .5)
.attr("stroke", "#000");
redraw1();
function redraw1() {
chart1.selectAll("rect")
.data(data)
.transition()
.duration(1000)
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("height", function(d) { return y(d.value); });
}
</script>
<p>The redraw function is fairly trivial—reselect the <code>rect</code> elements, bind them to the new data, and then start a transition that updates the “y” and “height” attributes. No enter and exit selection is needed! Without a data join, the data are joined to nodes by index. As the length of the data array is fixed, the number of nodes never changes, and thus the enter and exit selections are always empty.</p>
<h2 id='object_constancy'>Object Constancy</h2>
<p>Yet, the above animation is poor because it lacks object constancy through the transition: it does not convey the changing data accurately. Rather than updating values in-place, the bars should slide to the left, so that each bar corresponds to the same point in time across the transition. Do this using a <em>data join</em>, to bind nodes to data by timestamp rather than index:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'> 1</span> <span class='kd'>function</span> <span class='nx'>redraw</span><span class='p'>()</span> <span class='p'>{</span>
<span class='lineno'> 2</span>
<span class='lineno'> 3</span> <span class='kd'>var</span> <span class='nx'>rect</span> <span class='o'>=</span> <span class='nx'>chart</span><span class='p'>.</span><span class='nx'>selectAll</span><span class='p'>(</span><span class='s2'>"rect"</span><span class='p'>)</span>
<span class='lineno'> 4</span> <span class='p'>.</span><span class='nx'>data</span><span class='p'>(</span><span class='nx'>data</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>d</span><span class='p'>.</span><span class='nx'>time</span><span class='p'>;</span> <span class='p'>});</span>
<span class='lineno'> 5</span>
<span class='lineno'> 6</span> <span class='c1'>// Enter…</span>
<span class='lineno'> 7</span> <span class='nx'>rect</span><span class='p'>.</span><span class='nx'>enter</span><span class='p'>().</span><span class='nx'>insert</span><span class='p'>(</span><span class='s2'>"svg:rect"</span><span class='p'>,</span> <span class='s2'>"line"</span><span class='p'>)</span>
<span class='lineno'> 8</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>,</span> <span class='nx'>i</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>x</span><span class='p'>(</span><span class='nx'>i</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'> 9</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"y"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>h</span> <span class='o'>-</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'>10</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"width"</span><span class='p'>,</span> <span class='nx'>w</span><span class='p'>)</span>
<span class='lineno'>11</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"height"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>);</span> <span class='p'>});</span>
<span class='lineno'>12</span>
<span class='lineno'>13</span> <span class='c1'>// Update…</span>
<span class='lineno'>14</span> <span class='nx'>rect</span><span class='p'>.</span><span class='nx'>transition</span><span class='p'>()</span>
<span class='lineno'>15</span> <span class='p'>.</span><span class='nx'>duration</span><span class='p'>(</span><span class='mi'>1000</span><span class='p'>)</span>
<span class='lineno'>16</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>,</span> <span class='nx'>i</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>x</span><span class='p'>(</span><span class='nx'>i</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>});</span>
<span class='lineno'>17</span>
<span class='lineno'>18</span> <span class='c1'>// Exit…</span>
<span class='lineno'>19</span> <span class='nx'>rect</span><span class='p'>.</span><span class='nx'>exit</span><span class='p'>()</span>
<span class='lineno'>20</span> <span class='p'>.</span><span class='nx'>remove</span><span class='p'>();</span>
<span class='lineno'>21</span>
<span class='lineno'>22</span> <span class='p'>}</span>
</code></pre>
</div>
<p>With the new data join, we can no longer assume that the enter and exit selections are empty; instead, each contains exactly one bar upon redraw, as a new data point arrives and an old data point leaves. (If using real data, don’t assume regularity; multiple bars could enter and exit with each redraw.) So, the update is split to handle enter and exit separately. However, the update transition is actually simplified: we only transition the “x” attribute, as the “y” and “height” attributes do not change!</p>
<p>Note that operations on the entering or exiting selection do <em>not</em> affect the updating selection. Thus, the transition defined on <code>rect</code> on L14 above includes only the updating bars, not any of the entering bars that are appended on L7.</p>
<p>The bar chart now slides as desired, but the enter and exit are a bit clunky:</p>
<script type='text/javascript'>
var chart2 = d3.select(".content")
.append("svg:svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart2.append("svg:line")
.attr("x1", 0)
.attr("x2", w * data.length)
.attr("y1", h - .5)
.attr("y2", h - .5)
.attr("stroke", "#000");
redraw2();
function redraw2() {
var rect = chart2.selectAll("rect")
.data(data, function(d) { return d.time; });
rect.enter().insert("svg:rect", "line")
.attr("x", function(d, i) { return x(i) - .5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); });
rect.transition()
.duration(1000)
.attr("x", function(d, i) { return x(i) - .5; });
rect.exit()
.remove();
}
</script>
<p>The above implementation enters new bars immediately, while old bars are removed immediately. A common alternative is to fade, but in this case the most intuitive transition is for new bars to enter from the right, and old bars to exit to the left. Enter and exit can have transitions, too, which you can use to offset the index <code>i</code> to the <em>x</em>-scale:</p>
<div class='highlight'><pre><code class='js'><span class='lineno'> 1</span> <span class='kd'>function</span> <span class='nx'>redraw</span><span class='p'>()</span> <span class='p'>{</span>
<span class='lineno'> 2</span>
<span class='lineno'> 3</span> <span class='kd'>var</span> <span class='nx'>rect</span> <span class='o'>=</span> <span class='nx'>chart</span><span class='p'>.</span><span class='nx'>selectAll</span><span class='p'>(</span><span class='s2'>"rect"</span><span class='p'>)</span>
<span class='lineno'> 4</span> <span class='p'>.</span><span class='nx'>data</span><span class='p'>(</span><span class='nx'>data</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>d</span><span class='p'>.</span><span class='nx'>time</span><span class='p'>;</span> <span class='p'>});</span>
<span class='lineno'> 5</span>
<span class='lineno'> 6</span> <span class='nx'>rect</span><span class='p'>.</span><span class='nx'>enter</span><span class='p'>().</span><span class='nx'>insert</span><span class='p'>(</span><span class='s2'>"svg:rect"</span><span class='p'>,</span> <span class='s2'>"line"</span><span class='p'>)</span>
<span class='lineno'> 7</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>,</span> <span class='nx'>i</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>x</span><span class='p'>(</span><span class='nx'>i</span> <span class='o'>+</span> <span class='mi'>1</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'> 8</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"y"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>h</span> <span class='o'>-</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'> 9</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"width"</span><span class='p'>,</span> <span class='nx'>w</span><span class='p'>)</span>
<span class='lineno'>10</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"height"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>y</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>.</span><span class='nx'>value</span><span class='p'>);</span> <span class='p'>})</span>
<span class='lineno'>11</span> <span class='p'>.</span><span class='nx'>transition</span><span class='p'>()</span>
<span class='lineno'>12</span> <span class='p'>.</span><span class='nx'>duration</span><span class='p'>(</span><span class='mi'>1000</span><span class='p'>)</span>
<span class='lineno'>13</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>,</span> <span class='nx'>i</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>x</span><span class='p'>(</span><span class='nx'>i</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>});</span>
<span class='lineno'>14</span>
<span class='lineno'>15</span> <span class='nx'>rect</span><span class='p'>.</span><span class='nx'>transition</span><span class='p'>()</span>
<span class='lineno'>16</span> <span class='p'>.</span><span class='nx'>duration</span><span class='p'>(</span><span class='mi'>1000</span><span class='p'>)</span>
<span class='lineno'>17</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>,</span> <span class='nx'>i</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>x</span><span class='p'>(</span><span class='nx'>i</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>});</span>
<span class='lineno'>18</span>
<span class='lineno'>19</span> <span class='nx'>rect</span><span class='p'>.</span><span class='nx'>exit</span><span class='p'>().</span><span class='nx'>transition</span><span class='p'>()</span>
<span class='lineno'>20</span> <span class='p'>.</span><span class='nx'>duration</span><span class='p'>(</span><span class='mi'>1000</span><span class='p'>)</span>
<span class='lineno'>21</span> <span class='p'>.</span><span class='nx'>attr</span><span class='p'>(</span><span class='s2'>"x"</span><span class='p'>,</span> <span class='kd'>function</span><span class='p'>(</span><span class='nx'>d</span><span class='p'>,</span> <span class='nx'>i</span><span class='p'>)</span> <span class='p'>{</span> <span class='k'>return</span> <span class='nx'>x</span><span class='p'>(</span><span class='nx'>i</span> <span class='o'>-</span> <span class='mi'>1</span><span class='p'>)</span> <span class='o'>-</span> <span class='p'>.</span><span class='mi'>5</span><span class='p'>;</span> <span class='p'>})</span>
<span class='lineno'>22</span> <span class='p'>.</span><span class='nx'>remove</span><span class='p'>();</span>
<span class='lineno'>23</span>
<span class='lineno'>24</span> <span class='p'>}</span>
</code></pre>
</div>
<p>Note that the enter transition is staged; we initialize the values, and then start the transition. This is not needed with the exit transition because we’ll transition from the current state of the bar, regardless of value.</p>
<p>Et voilà!</p>
<script type='text/javascript'>
var chart3 = d3.select(".content")
.append("svg:svg")
.attr("class", "chart")
.attr("width", w * data.length - 1)
.attr("height", h);
chart3.append("svg:line")
.attr("x1", 0)
.attr("x2", w * data.length)
.attr("y1", h - .5)
.attr("y2", h - .5)
.attr("stroke", "#000");
redraw3();
function redraw3() {
var rect = chart3.selectAll("rect")
.data(data, function(d) { return d.time; });
rect.enter().insert("svg:rect", "line")
.attr("x", function(d, i) { return x(i + 1) - .5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); })
.transition()
.duration(1000)
.attr("x", function(d, i) { return x(i) - .5; });
rect.transition()
.duration(1000)
.attr("x", function(d, i) { return x(i) - .5; });
rect.exit().transition()
.duration(1000)
.attr("x", function(d, i) { return x(i - 1) - .5; })
.remove();
}
</script>
<p>This tutorial covered several core concepts in D3, including transitions, enter and exit, and data joins. However, this only scratches the surface! Explore the <a href='../ex/'>examples gallery</a> to see more advanced techniques with D3.</p>
<script type='text/javascript'>
setInterval(function() {
data.shift();
data.push(next());
redraw1();
redraw2();
redraw3();
}, 1500);
</script>
</div>
<div class="foot">Copyright © 2011 <a href="http://bost.ocks.org/mike">Mike Bostock</a></div>
</div>
</div>
<a href="http://github.com/mbostock/d3"><img
style="position:absolute;top:0;right:0;border:0;"
width="149" height="149" src="../forkme.png" alt="Fork me on GitHub"
/></a>
</body>
</html>