Skip to content

Instantly share code, notes, and snippets.

@yifancui
Created November 8, 2017 15:54
Show Gist options
  • Save yifancui/55b94c8b08a7d61fe3df2a0bbd81fa82 to your computer and use it in GitHub Desktop.
Save yifancui/55b94c8b08a7d61fe3df2a0bbd81fa82 to your computer and use it in GitHub Desktop.
Interactive Streamgraph D3
license: mit
# Editor backup files
*.bak
*~
<!DOCTYPE html>
<html>
<head>
<title>Streamgraph</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.v2.js?2.8.0"></script>
<script type="text/javascript" src="stream_layers.js"></script>
<script type="text/javascript" src="stream-chart.js"></script>
</head>
<body>
<div id="chart">
<button class="first last" onclick="transition()">
Update
</button><p>
</div>
<script>
var n = 20, // number of layers
m = 200; // number of samples per layer
var data1 = stream_layers(n, m);
var data0 = stream_layers(n, m);
var colors = d3.range(n).map(function() { return d3.interpolateRgb("#aad", "#556")(Math.random()); });
var streamgraph = streamgraphChart()
.margin({top: 10, right: 10, bottom: 10, left: 10})
.color(function(d, i) { return colors[i]; }) // use same colors for both data sets
.transitionDuration(1500)
.on("mouseover", fade(.2))
.on("mouseout", fade(1));
d3.select("#chart")
.datum(data0)
.call(streamgraph);
function transition() {
d3.select("#chart")
.datum(function() {
var d = data1;
data1 = data0;
return data0 = d;
})
.call(streamgraph);
}
function fade(opacity) {
return function(g, i) {
streamgraph.layers()
.filter(function(h, j) {
return j != i;
})
.transition(1000)
.style("opacity", opacity);
}
}
</script>
</body>
</html>
function streamgraphChart() {
var margin = {top: 0, right: 0, bottom: 0, left: 0},
width = 960,
height = 500,
transitionDuration = 1000,
color = function() { return d3.interpolateRgb("#aad", "#556")(Math.random()); },
eventHandlers = [],
layers = undefined;
var streamgraph = d3.layout.stack().offset("wiggle");
function chart(selection) {
selection.each(function(data) {
// Compute the streamgraph.
data = streamgraph(data);
var mx = data[0].length - 1, // assumes that all layers have same # of samples & that there is at least one layer
my = d3.max(data, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
});
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg").append("g");
// Update the outer dimensions.
svg .attr("width", width)
.attr("height", height);
// Update the inner dimensions.
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Update the streamgraph
var availableWidth = width - margin.left - margin.right,
availableHeight = height - margin.top - margin.bottom;
var area = d3.svg.area()
.x(function(d) { return d.x * availableWidth / mx; })
.y0(function(d) { return availableHeight - d.y0 * availableHeight / my; })
.y1(function(d) { return availableHeight - (d.y + d.y0) * availableHeight / my; });
layers = g.selectAll("path").data(data);
var enterPath = layers.enter().append("path");
eventHandlers.forEach(function(d){
enterPath.on(d.type, d.handler);
});
layers.exit().remove();
layers.style("fill", color).transition().duration(transitionDuration).attr("d", area);
});
}
chart.on = function(_) {
// TODO needs further work
eventHandlers.push({
"type": arguments[0],
"handler": arguments[1]
});
return chart;
}
chart.color = function(_) {
if (!arguments.length) return color;
color = _;
return chart;
};
chart.transitionDuration = function(_) {
if (!arguments.length) return transitionDuration;
transitionDuration = _;
return chart;
};
chart.layers = function() {
return layers;
}
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
return chart;
}
/* Inspired by Lee Byron's test data generator. */
function stream_layers(n, m, o) {
if (arguments.length < 3) o = 0;
function bump(a) {
var x = 1 / (.1 + Math.random()),
y = 2 * Math.random() - .5,
z = 10 / (.1 + Math.random());
for (var i = 0; i < m; i++) {
var w = (i / m - y) * z;
a[i] += x * Math.exp(-w * w);
}
}
return d3.range(n).map(function() {
var a = [], i;
for (i = 0; i < m; i++) a[i] = o + o * Math.random();
for (i = 0; i < 5; i++) bump(a);
return a.map(stream_index);
});
}
function stream_index(d, i) {
return {x: i, y: Math.max(0, d)};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment