Skip to content

Instantly share code, notes, and snippets.

@zachmargolis
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zachmargolis/9315833 to your computer and use it in GitHub Desktop.
Save zachmargolis/9315833 to your computer and use it in GitHub Desktop.
Stack-to-Split Transition

Stack-Split Transition

Transitions between stacked and split (small multiples) area charts.

<!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