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 }
Copyright © 2012 Mike Bostock
Fork me on GitHub