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.
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <style> | |
| body { | |
| font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| margin: auto; | |
| position: relative; | |
| width: 960px; | |
| } | |
| text { | |
| font: 10px sans-serif; | |
| } | |
| .axis path, | |
| .axis line { | |
| fill: none; | |
| stroke: #000; | |
| shape-rendering: crispEdges; | |
| } | |
| form { | |
| position: absolute; | |
| right: 10px; | |
| top: 10px; | |
| } | |
| </style> | |
| <form> | |
| <label><input type="radio" name="mode" value="grouped"> Grouped</label> | |
| <label><input type="radio" name="mode" value="stacked" checked> Stacked</label> | |
| </form> | |
| <script src="//d3js.org/d3.v3.min.js"></script> | |
| <script> | |
| var n = 4, // number of layers | |
| m = 58, // number of samples per layer | |
| stack = d3.layout.stack(), | |
| layers = stack(d3.range(n).map(function() { return bumpLayer(m, .1); })), | |
| yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }), | |
| yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); }); | |
| var margin = {top: 40, right: 10, bottom: 20, left: 10}, | |
| width = 960 - margin.left - margin.right, | |
| height = 500 - margin.top - margin.bottom; | |
| var x = d3.scale.ordinal() | |
| .domain(d3.range(m)) | |
| .rangeRoundBands([0, width], .08); | |
| var y = d3.scale.linear() | |
| .domain([0, yStackMax]) | |
| .range([height, 0]); | |
| var color = d3.scale.linear() | |
| .domain([0, n - 1]) | |
| .range(["#aad", "#556"]); | |
| var xAxis = d3.svg.axis() | |
| .scale(x) | |
| .tickSize(0) | |
| .tickPadding(6) | |
| .orient("bottom"); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width + margin.left + margin.right) | |
| .attr("height", height + margin.top + margin.bottom) | |
| .append("g") | |
| .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); | |
| var layer = svg.selectAll(".layer") | |
| .data(layers) | |
| .enter().append("g") | |
| .attr("class", "layer") | |
| .style("fill", function(d, i) { return color(i); }); | |
| var rect = layer.selectAll("rect") | |
| .data(function(d) { return d; }) | |
| .enter().append("rect") | |
| .attr("x", function(d) { return x(d.x); }) | |
| .attr("y", height) | |
| .attr("width", x.rangeBand()) | |
| .attr("height", 0); | |
| rect.transition() | |
| .delay(function(d, i) { return i * 10; }) | |
| .attr("y", function(d) { return y(d.y0 + d.y); }) | |
| .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); }); | |
| svg.append("g") | |
| .attr("class", "x axis") | |
| .attr("transform", "translate(0," + height + ")") | |
| .call(xAxis); | |
| d3.selectAll("input").on("change", change); | |
| var timeout = setTimeout(function() { | |
| d3.select("input[value=\"grouped\"]").property("checked", true).each(change); | |
| }, 2000); | |
| function change() { | |
| clearTimeout(timeout); | |
| if (this.value === "grouped") transitionGrouped(); | |
| else transitionStacked(); | |
| } | |
| function transitionGrouped() { | |
| y.domain([0, yGroupMax]); | |
| rect.transition() | |
| .duration(500) | |
| .delay(function(d, i) { return i * 10; }) | |
| .attr("x", function(d, i, j) { return x(d.x) + x.rangeBand() / n * j; }) | |
| .attr("width", x.rangeBand() / n) | |
| .transition() | |
| .attr("y", function(d) { return y(d.y); }) | |
| .attr("height", function(d) { return height - y(d.y); }); | |
| } | |
| function transitionStacked() { | |
| y.domain([0, yStackMax]); | |
| rect.transition() | |
| .duration(500) | |
| .delay(function(d, i) { return i * 10; }) | |
| .attr("y", function(d) { return y(d.y0 + d.y); }) | |
| .attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); }) | |
| .transition() | |
| .attr("x", function(d) { return x(d.x); }) | |
| .attr("width", x.rangeBand()); | |
| } | |
| // Inspired by Lee Byron's test data generator. | |
| function bumpLayer(n, o) { | |
| function bump(a) { | |
| var x = 1 / (.1 + Math.random()), | |
| y = 2 * Math.random() - .5, | |
| z = 10 / (.1 + Math.random()); | |
| for (var i = 0; i < n; i++) { | |
| var w = (i / n - y) * z; | |
| a[i] += x * Math.exp(-w * w); | |
| } | |
| } | |
| var a = [], i; | |
| for (i = 0; i < n; ++i) a[i] = o + o * Math.random(); | |
| for (i = 0; i < 5; ++i) bump(a); | |
| return a.map(function(d, i) { return {x: i, y: Math.max(0, d)}; }); | |
| } | |
| </script> |
jaishivn
commented
Jul 24, 2014
igs52ru
commented
Jul 7, 2016
I noob in IS and D3 and don't migrate this example from version 3 to 4.0 (only stacked bar part).
v3 https://jsfiddle.net/9y2g65qc/
v4 https://jsfiddle.net/9y2g65qc/10/
Could you please help me? Thanks!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey Mike,
Sorry i am a beginner in D3 concepts. I am currently using your Stacked Bar chart to learn and understand the D3 Concepts. I am just playing with it and struck with a problem recently. I want to click a bar and display it's value when it is clicked. Since the data is grouped into layers i couldn't sort out how to retrieve the values of bars and display it. Could you please help me in sorting out this issue? Help appreciated. Thanks!