D3.js Streamgraph with text
by Mike Rawling
HTML
<div id="chart">
<h2>Known issues:</h2>
<ul>
<li>WebKit, FF and the rest suffer from <a href="http://paulirish.com/2009/fighting-the-font-face-fout/">FOUT</a>; we cope with it by hiding and monitoring the SVG <code>.getBBox()</code> call return values as those are what's important to us: see the WARNING comments in the code.
<li>Mouseover any stream strip when you just updated and the update transition will halt, so that you're still stuck with the 'old' data. Click 'update' twice more, not moving the mouse outside the button, to recover. (Yes, all streamgraph examples in github / gist suffer from this AFAICT. Haven't investigated a fix, yet.)</ul>
<h2>Use / Play</h2>
<p>Hover over the stream strips, the legenda and see the fades; click on either of 'em to highlight one or more streams. Hit the
<button class="first last" onclick="transition(); return false;">Update</button>button to switch between the two datasets.</div>
CSS
#chart {
font: 12px/18px'Rock Salt', sans-serif;
}
button {
font: 14px, sans-serif;
background-color: #222;
background-image: -moz-linear-gradient(top, rgba(255, 255, 255, .25), rgba(255, 255, 255, .11));
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, rgba(255, 255, 255, .25)), color-stop(1, rgba(255, 255, 255, .11)));
background-image: -webkit-linear-gradient(rgba(255, 255, 255, .25), rgba(255, 255, 255, .11));
color: #fff;
text-rendering: optimizeLegibility;
text-shadow: 0 -1px 1px #222;
padding: 3px 10px 3px 10px;
border: 0;
border-radius: 0;
border-bottom: 1px solid #222;
margin: 0;
-moz-box-shadow: 0 1px 3px #999;
-webkit-box-shadow: 0 1px 3px #999;
display: inline-block;
}
button.first {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
button.last {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
button.active {
background-color: rgb(65, 102, 133);
}
button:hover {
background-color: steelblue;
}
/* SVG styles */
rect.wrapper {
fill: none;
}
.hover rect.wrapper {
fill: rgba(255, 102, 0, 0.3);
}
text {
/* font: 12px 'PT Sans Narrow',sans-serif; */
fill: black;
}
text.bbox_hack {
font: 24px'Rock Salt', sans-serif;
}
.tags_in_stream text {
fill: white;
/* fill: red; -- use this while debugging the graphic placement of the in-stream tags */
}
svg {
font: 14px'PT Sans Narrow', sans-serif;
}
JavaScript
/*
http://bl.ocks.org/GerHobbelt/3480186
*/
var n = 15, // number of layers
m = 10, // number of samples per layer
datagen = stream_layers,
raw_data0 = datagen(n, m),
raw_data1 = datagen(n, m),
data0 = d3.layout.stack().offset("expand")(raw_data0),
data1 = d3.layout.stack().offset("expand")(raw_data1),
tags,
color = d3.interpolateRgb("#aad", "#556"),
strip_toggled = [];
var w = 960,
h = 300,
top_height = 40,
/* WARNING: can't use the name 'top' here as that will break in Chrome20, which has that naem reserved for an internal, non-overridable function! So we name this one top_height */
mx = m - 1,
my = d3.max(data0.concat(data1), function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
}),
// convert values to pixels:
y = function (v) {
return v * (h - top_height) / my;
},
x = function (v) {
return v * w / mx;
};
var area = d3.svg.area()
.x(function (d) {
return x(d.x);
})
.y0(function (d) {
return h - y(d.y0);
})
.y1(function (d) {
return h - y(d.y + d.y0);
})
.interpolate('basis');
var graph = d3.select("#chart")
.append("svg")
.attr("width", w)
.attr("height", h);
var header = graph.append("g")
.attr("class", "topside")
.style("visibility", "hidden"); // flicker fix for the d3.timer+getBBox further below.
// print the tags at the top:
var topside_sel = header.selectAll("g")
.data(tags = names_gen(n)) // w and h are used by names_gen() but are not defined at the time tags var is declared above, so we init tags here!
.enter()
.append("g")
.attr("class", "tag_top")
.on('click', function (d, i) {
var c;
// undefined is treated as if it was 'false'
strip_toggled[i] = !strip_toggled[i];
// set/reset the color:
c = (strip_toggled[i] ? '#ff6600' : color(i / n));
// tweak the top_tag rect:
//d3.select(this).select("rect") -- needs...