Transitions between stacked and split (small multiples) area charts.
Last active
August 29, 2015 13:56
-
-
Save zachmargolis/9315833 to your computer and use it in GitHub Desktop.
Stack-to-Split Transition
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<meta charset="utf-8" /> | |
<style> | |
body { | |
font: 10pt/12pt "Helvetica", sans-serif; | |
} | |
form { | |
position: absolute; | |
top: 1em; | |
left: 1em; | |
} | |
.x-axis path, .y-axis path { | |
fill: none; | |
stroke: #555; | |
shape-rendering: crispEdges; | |
} | |
.area { | |
fill: #ccc; | |
stroke: none; | |
} | |
</style> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<form> | |
<label for="stack"> | |
<input type="radio" id="stack" name="layout" value="stack" /> | |
Stack | |
</label> | |
<label for="stack"> | |
<input type="radio" id="split" name="layout" value="split" /> | |
Split | |
</label> | |
</form> | |
<script> | |
var width = 960, | |
height = 500, | |
margin = { top: 10, bottom: 25, left: 10, right: 50 }, // around the graph | |
spacing = { bottom: 15, right: 5 }, | |
n = 8, // number of layers | |
m = 50, // numbe of samples per layer, | |
stack = d3.layout.stack(), | |
data = stack(d3.range(n).map(function() { return bumpLayer(m, .1); })), | |
ySplitMax = d3.max(data, function(layer) { return d3.max(layer, function(d) { return d.y; }); }), | |
yStackMax = d3.max(data, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); }), | |
duration = 1000, | |
isTransition = false; | |
svg = d3.select('body').append('svg') | |
.attr('width', width) | |
.attr('height', height); | |
var layout = 'stack'; | |
function translate(x, y) { | |
return "translate(" + x + ", " + y + ")"; | |
} | |
function update(firstTime) { | |
function rowHeight() { | |
return Math.floor((height - margin.top - margin.bottom - (n - 1) * spacing.bottom) / n); | |
}; | |
var lastRow = n - 1; | |
var x = d3.scale.linear() | |
.range([margin.left, width - margin.right]) | |
.domain([0, m-1]); | |
var xAxis = d3.svg.axis() | |
.scale(x) | |
.orient('bottom'); | |
var y = d3.scale.linear() | |
if (layout == 'stack') { | |
y.range([height - margin.top - margin.bottom, 0]) | |
.domain([0, yStackMax]); | |
} else { | |
y.range([rowHeight(), 0]) | |
.domain([0, ySplitMax]) | |
} | |
yAxis = d3.svg.axis() | |
.scale(y) | |
.orient("right") | |
.ticks(layout == 'split' ? 3 : 10); | |
if (firstTime) { | |
svg.append('g') | |
.attr('class', 'x-axis') | |
.attr('transform', translate(0, height - margin.top - margin.bottom + 5)) | |
.call(xAxis) | |
} | |
var layers = svg.selectAll('g.layer') | |
.data(data, function(d) { return data.indexOf(d) }); | |
var enterLayers = layers.enter() | |
.append('g') | |
.attr('class', 'layer') | |
.attr('width', width) | |
.attr('transform', function(d, i) { | |
if (layout == 'split') { | |
return translate(0, (lastRow - i) * (rowHeight() + spacing.bottom)); | |
} | |
}); | |
layers.exit().remove() | |
layers.transition() | |
.duration(duration) | |
.attr('transform', function(d, i) { | |
if (layout == 'stack') { | |
return translate(0, 0); | |
} else { | |
return translate(0, (lastRow - i) * (rowHeight() + spacing.bottom)); | |
} | |
}) | |
.each('start', function() { isTransition = true }) | |
.each('end', function() { isTransition = false }); | |
var zeroArea = d3.svg.area() | |
.x(function(d) { return x(d.x) }) | |
.y0(y(0)) | |
.y1(y(0)); | |
var grayGradient = d3.interpolate('#666', '#ddd'); | |
function color(d, i) { | |
i = data.indexOf(d); | |
return grayGradient(i / n); | |
} | |
var areas = enterLayers.append('path') | |
.attr('class', 'area') | |
.attr('d', zeroArea) | |
.style('fill', color) | |
.style('stroke', color) | |
.style('stroke-width', 1) // fill gaps between layers | |
.on('mouseover', function() { | |
!isTransition && d3.select(this).transition() | |
.style('fill', '#d66').style('stroke', '#d66'); | |
}) | |
.on('mouseout', function() { | |
!isTransition && d3.select(this).transition() | |
.style('fill', color).style('stroke', color); | |
}); | |
var area = d3.svg.area() | |
.x(function(d) { return x(d.x) }); | |
if (layout == 'stack') { | |
area.y0(function(d) { return y(d.y0); }) | |
.y1(function(d) { return y(d.y0 + d.y); }); | |
} else { | |
area.y0(y(0)) | |
.y1(function(d) { return y(d.y)} ); | |
} | |
layers.selectAll('path.area').transition() | |
.duration(duration) | |
.attr('d', area); | |
enterLayers.append('g') | |
.attr('class', 'y-axis') | |
.attr('transform', translate(width - margin.right + spacing.right, 0)) | |
.attr('opacity', 0) | |
var yAxes = layers.selectAll('g.y-axis'); | |
yAxes.transition() | |
.duration(duration) | |
.attr('opacity', function(d, i) { | |
i = data.indexOf(d); | |
if (layout == 'stack') { | |
return (i == 0) ? 1 : 0; | |
} else { | |
return 1; | |
} | |
}) | |
.call(yAxis); | |
} | |
// Inspired by Lee Byron's test data generator. | |
// Borrowed from http://bl.ocks.org/mbostock/3943967 | |
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)}; }); | |
} | |
update(true) | |
setTimeout(function() { | |
d3.select('input#stack').attr('checked', 'checked'); | |
}, duration); | |
d3.selectAll('input').on('change', function() { | |
var e = d3.select(this); | |
if (e.attr('value') == 'stack' && e.attr('checked')) { | |
layout = 'stack'; | |
} else { | |
layout = 'split'; | |
} | |
update() | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment