Transitions between stacked and split (small multiples) area charts.
forked from zachmargolis's block: Stack-to-Split Transition
forked from mashehu's block: Stack-to-Split Transition
license: mit |
Transitions between stacked and split (small multiples) area charts.
forked from zachmargolis's block: Stack-to-Split Transition
forked from mashehu's block: Stack-to-Split Transition
<!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 1; | |
} 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> |