Stacked Bars
Switch between stacked and grouped layouts using sequenced transitions! Animations preserve object constancy and allow the user to follow the data across views. Animation design by Heer and Robertson. Colors and data generation inspired by Byron and Wattenberg.
Source Code
1 var n = 4, // number of layers
2 m = 64, // number of samples per layer
3 data = d3.layout.stack()(stream_layers(n, m, .1)),
4 color = d3.interpolateRgb("#aad", "#556");
5
6 var margin = 20,
7 width = 960,
8 height = 500 - .5 - margin,
9 mx = m,
10 my = d3.max(data, function(d) {
11 return d3.max(d, function(d) {
12 return d.y0 + d.y;
13 });
14 }),
15 mz = d3.max(data, function(d) {
16 return d3.max(d, function(d) {
17 return d.y;
18 });
19 }),
20 x = function(d) { return d.x * width / mx; },
21 y0 = function(d) { return height - d.y0 * height / my; },
22 y1 = function(d) { return height - (d.y + d.y0) * height / my; },
23 y2 = function(d) { return d.y * height / mz; }; // or `my` to not rescale
24
25 var vis = d3.select("#chart")
26 .append("svg")
27 .attr("width", width)
28 .attr("height", height + margin);
29
30 var layers = vis.selectAll("g.layer")
31 .data(data)
32 .enter().append("g")
33 .style("fill", function(d, i) { return color(i / (n - 1)); })
34 .attr("class", "layer");
35
36 var bars = layers.selectAll("g.bar")
37 .data(function(d) { return d; })
38 .enter().append("g")
39 .attr("class", "bar")
40 .attr("transform", function(d) { return "translate(" + x(d) + ",0)"; });
41
42 bars.append("rect")
43 .attr("width", x({x: .9}))
44 .attr("x", 0)
45 .attr("y", height)
46 .attr("height", 0)
47 .transition()
48 .delay(function(d, i) { return i * 10; })
49 .attr("y", y1)
50 .attr("height", function(d) { return y0(d) - y1(d); });
51
52 var labels = vis.selectAll("text.label")
53 .data(data[0])
54 .enter().append("text")
55 .attr("class", "label")
56 .attr("x", x)
57 .attr("y", height + 6)
58 .attr("dx", x({x: .45}))
59 .attr("dy", ".71em")
60 .attr("text-anchor", "middle")
61 .text(function(d, i) { return i; });
62
63 vis.append("line")
64 .attr("x1", 0)
65 .attr("x2", width - x({x: .1}))
66 .attr("y1", height)
67 .attr("y2", height);
68
69 function transitionGroup() {
70 var group = d3.selectAll("#chart");
71
72 group.select("#group")
73 .attr("class", "first active");
74
75 group.select("#stack")
76 .attr("class", "last");
77
78 group.selectAll("g.layer rect")
79 .transition()
80 .duration(500)
81 .delay(function(d, i) { return (i % m) * 10; })
82 .attr("x", function(d, i) { return x({x: .9 * ~~(i / m) / n}); })
83 .attr("width", x({x: .9 / n}))
84 .each("end", transitionEnd);
85
86 function transitionEnd() {
87 d3.select(this)
88 .transition()
89 .duration(500)
90 .attr("y", function(d) { return height - y2(d); })
91 .attr("height", y2);
92 }
93 }
94
95 function transitionStack() {
96 var stack = d3.select("#chart");
97
98 stack.select("#group")
99 .attr("class", "first");
100
101 stack.select("#stack")
102 .attr("class", "last active");
103
104 stack.selectAll("g.layer rect")
105 .transition()
106 .duration(500)
107 .delay(function(d, i) { return (i % m) * 10; })
108 .attr("y", y1)
109 .attr("height", function(d) { return y0(d) - y1(d); })
110 .each("end", transitionEnd);
111
112 function transitionEnd() {
113 d3.select(this)
114 .transition()
115 .duration(500)
116 .attr("x", 0)
117 .attr("width", x({x: .9}));
118 }
119 }
1 /* Inspired by Lee Byron's test data generator. */
2 function stream_layers(n, m, o) {
3 if (arguments.length < 3) o = 0;
4 function bump(a) {
5 var x = 1 / (.1 + Math.random()),
6 y = 2 * Math.random() - .5,
7 z = 10 / (.1 + Math.random());
8 for (var i = 0; i < m; i++) {
9 var w = (i / m - y) * z;
10 a[i] += x * Math.exp(-w * w);
11 }
12 }
13 return d3.range(n).map(function() {
14 var a = [], i;
15 for (i = 0; i < m; i++) a[i] = o + o * Math.random();
16 for (i = 0; i < 5; i++) bump(a);
17 return a.map(stream_index);
18 });
19 }
20
21 /* Another layer generator using gamma distributions. */
22 function stream_waves(n, m) {
23 return d3.range(n).map(function(i) {
24 return d3.range(m).map(function(j) {
25 var x = 20 * j / m - i / 3;
26 return 2 * x * Math.exp(-.5 * x);
27 }).map(stream_index);
28 });
29 }
30
31 function stream_index(d, i) {
32 return {x: i, y: Math.max(0, d)};
33 }