block by GerHobbelt 1965462

Interactive Streamgraph D3

Full Screen

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Streamgraph</title>
    <script type="text/javascript" src="//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>

stream-chart.js

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;
}

stream_layers.js

/* 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)};
}