Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active December 6, 2016 11:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mbostock/582915 to your computer and use it in GitHub Desktop.
Save mbostock/582915 to your computer and use it in GitHub Desktop.
Streamgraph
license: gpl-3.0

Streamgraphs are a generalization of stacked area graphs where the baseline is free. By shifting the baseline, it is possible to minimize the change in slope (or “wiggle”) in individual series, thereby making it easier to perceive the thickness of any given layer across the data. Byron & Wattenberg describe several streamgraph algorithms in “Stacked Graphs—Geometry & Aesthetics”, several of which are implemented by pv.Layout.Stack. As additional examples, see stacked graphs of employment and unemployment statistics.

<html>
<head>
<title>Streamgraph</title>
<script type="text/javascript" src="http://vis.stanford.edu/protovis/protovis-r3.2.js"></script>
<script type="text/javascript" src="stream.js"></script>
<style type="text/css">
body {
margin: 0;
}
</style>
</head>
<body>
<script type="text/javascript+protovis">
var n = 20, // number of layers
m = 400, // number of samples per layer
data = layers(n, m);
var w = document.body.clientWidth,
h = document.body.clientHeight,
x = pv.Scale.linear(0, m - 1).range(0, w),
y = pv.Scale.linear(0, 2 * n).range(0, h);
var vis = new pv.Panel()
.width(w)
.height(h);
vis.add(pv.Layout.Stack)
.layers(data)
.order("inside-out")
.offset("wiggle")
.x(x.by(pv.index))
.y(y)
.layer.add(pv.Area)
.fillStyle(pv.ramp("#aad", "#556").by(Math.random))
.strokeStyle(function() this.fillStyle().alpha(.5));
vis.render();
</script>
</body>
</html>
/* Inspired by Lee Byron's test data generator. */
function layers(n, m) {
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 pv.range(n).map(function() {
var a = [], i;
for (i = 0; i < m; i++) a[i] = 0;
for (i = 0; i < 5; i++) bump(a);
return a;
});
}
/* Another layer generator using gamma distributions. */
function waves(n, m) {
return pv.range(n).map(function(i) {
return pv.range(m).map(function(j) {
var x = 20 * j / m - i / 3;
return x > 0 ? 2 * x * Math.exp(-.5 * x) : 0;
});
});
}
@bluePlayer
Copy link

bluePlayer commented Dec 6, 2016

I have done some hacking and found some flaws in the code. What is the waves() function used for?

Also all the code now is in one script file called stream.js

`/**
 * @see http://bl.ocks.org/mbostock/582915
 * @param {Object} window
 * @param {Object} pv
 */
(function(window, pv) {
    'use strict';

    var n = 20, // number of layers
        m = 400, // number of samples per layer
        w = window.innerWidth, //document.body.clientWidth,
        h = window.innerHeight, //document.body.clientHeight,
        x = pv.Scale.linear(0, m - 1).range(0, w),
        y = pv.Scale.linear(0, 2 * n).range(0, h),
        vis = new pv.Panel().width(w).height(h),

        layers = function (n, m) {
            var bump = function (a) {
                var i = 0,
                    w = 0,
                    x = 1 / (0.1 + Math.random()),
                    y = 2 * Math.random() - 0.5,
                    z = 10 / (0.1 + Math.random());

                for (i = 0; i < m; i += 1) {
                    w = (i / m - y) * z;
                    a[i] += x * Math.exp(-w * w);
                }
            };

            return pv.range(n).map(function () {
                var a = [], i;

                for (i = 0; i < m; i += 1) {
                    a[i] = 0;
                }

                for (i = 0; i < 5; i += 1) {
                    bump(a);
                }

                return a;
            });
        },
        data = layers(n, m);//,

        /*
         * What is this function used for?
         waves = function (n, m) {
            return pv.range(n).map(function(i) {
                return pv.range(m).map(function(j) {
                    var x = 20 * j / m - i / 3;
                    return x > 0 ? 2 * x * Math.exp(-0.5 * x) : 0;
                });
            });
        };*/

    vis.add(pv.Layout.Stack)
        .layers(data)
        .order("inside-out")
        .offset("wiggle")
        .x(x.by(pv.index))
        .y(y)
        .layer.add(pv.Area)
        .fillStyle(pv.ramp("#aad", "#556").by(Math.random))
        .strokeStyle(function () {this.fillStyle().alpha(0.5);});

    vis.render();

}(window, pv));`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment