Skip to content

Instantly share code, notes, and snippets.

@igorzilla
Created July 10, 2012 22:22
Show Gist options
  • Save igorzilla/3086583 to your computer and use it in GitHub Desktop.
Save igorzilla/3086583 to your computer and use it in GitHub Desktop.

Revisions

  1. @invalid-email-address Anonymous revised this gist Jul 10, 2012. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,9 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
    <title>Alluvial Diagram</title>
    <script type="text/javascript" src="../d3.v2.min.js"></script>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.5.0"></script>
    <style type="text/css">
    body {
    margin: 1em;
  2. @invalid-email-address Anonymous created this gist Jul 10, 2012.
    280 changes: 280 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,280 @@
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
    <title>Alluvial Diagram</title>
    <script type="text/javascript" src="../d3.v2.min.js"></script>
    <style type="text/css">
    body {
    margin: 1em;
    }

    .node {
    stroke: #fff;
    stroke-width: 2px;
    }

    .link {
    fill: none;
    stroke: #000;
    opacity: .3;
    }
    .link.on {
    stroke: #F00;
    opacity: .7;
    }

    .node {
    stroke: none;
    }

    </style>
    </head>
    <body>
    <script type="text/javascript">

    /* Make Fake Data */

    var data = (function() {
    var maxt = 5,
    maxn = 12,
    maxl = 4,
    maxv = 10,
    times = [],
    allLinks = [],
    counter = 0,
    addNodes = function() {
    var ncount = Math.random() * maxn + 1,
    nodes = d3.range(0, ncount).map(function(n) {
    return {
    id: counter++,
    nodeName: "Node " + n,
    nodeValue: 0,
    incoming: []
    }
    });
    times.push(nodes);
    return nodes;
    },
    addNext = function() {
    var current = times[times.length-1],
    nextt = addNodes();
    // make links
    current.forEach(function(n) {
    var linkCount = Math.min(~~(Math.random() * maxl + 1), nextt.length),
    breaks = d3.range(linkCount-1)
    .map(function() { return Math.random() * n.nodeValue })
    .sort(d3.ascending),
    links = {},
    target, link, x;
    for (x=0; x<linkCount; x++) {
    do {
    target = nextt[~~(Math.random() * nextt.length)];
    } while (target.id in links);
    // add link
    link = {
    source: n.id,
    target: target.id,
    value: (breaks[x] || n.nodeValue) - (breaks[x-1] || 0)
    };
    links[target.id] = link;
    allLinks.push(link);
    target.nodeValue += link.value;
    }
    });
    // prune next
    times[times.length-1] = nextt.filter(function(n) { return n.nodeValue });
    }
    // initial set
    addNodes().forEach(function(n) {
    n.nodeValue = Math.random() * maxv + 1;
    });
    // now add rest
    for (var t=0; t<maxt-1; t++) {
    addNext();
    }

    return {
    times: times,
    links: allLinks
    };
    })();

    /* Process Data */

    // make a node lookup map
    var nodeMap = (function() {
    var nm = {};
    data.times.forEach(function(nodes) {
    nodes.forEach(function(n) {
    nm[n.id] = n;
    // add links and assure node value
    n.links = [];
    n.incoming = [];
    n.nodeValue = n.nodeValue || 0;
    })
    });
    return nm;
    })();

    // attach links to nodes
    data.links.forEach(function(link) {
    nodeMap[link.source].links.push(link);
    nodeMap[link.target].incoming.push(link);
    });

    // sort by value and calculate offsets
    data.times.forEach(function(nodes) {
    var cumValue = 0;
    nodes.sort(function(a,b) {
    return d3.descending(a.nodeValue, b.nodeValue)
    });
    nodes.forEach(function(n, i) {
    n.order = i;
    n.offsetValue = cumValue;
    cumValue += n.nodeValue;
    // same for links
    var lCumValue;
    // outgoing
    if (n.links) {
    lCumValue = 0;
    n.links.sort(function(a,b) {
    return d3.descending(a.value, b.value)
    });
    n.links.forEach(function(l) {
    l.outOffset = lCumValue;
    lCumValue += l.value;
    });
    }
    // incoming
    if (n.incoming) {
    lCumValue = 0;
    n.incoming.sort(function(a,b) {
    return d3.descending(a.value, b.value)
    });
    n.incoming.forEach(function(l) {
    l.inOffset = lCumValue;
    lCumValue += l.value;
    });
    }
    })
    });
    data = data.times;

    // calculate maxes
    var maxn = d3.max(data, function(t) { return t.length }),
    maxv = d3.max(data, function(t) { return d3.sum(t, function(n) { return n.nodeValue }) });

    /* Make Vis */

    // settings and scales
    var w = 1400,
    h = 500,
    gapratio = .7,
    delay = 1500,
    padding = 15,
    x = d3.scale.ordinal()
    .domain(d3.range(data.length))
    .rangeBands([0, w + (w/(data.length-1))], gapratio),
    y = d3.scale.linear()
    .domain([0, maxv])
    .range([0, h - padding * maxn]),
    line = d3.svg.line()
    .interpolate('basis');

    // root
    var vis = d3.select("body")
    .append("svg:svg")
    .attr("width", w)
    .attr("height", h);


    var t = 0;
    function update(first) {
    // update data
    var currentData = data.slice(0, ++t);

    // time slots
    var times = vis.selectAll('g.time')
    .data(currentData)
    .enter().append('svg:g')
    .attr('class', 'time')
    .attr("transform", function(d, i) { return "translate(" + (x(i) - x(0)) + ",0)" });

    // node bars
    var nodes = times.selectAll('g.node')
    .data(function(d) { return d })
    .enter().append('svg:g')
    .attr('class', 'node');

    setTimeout(function() {
    nodes.append('svg:rect')
    .attr('fill', 'steelblue')
    .attr('y', function(n, i) {
    return y(n.offsetValue) + i * padding;
    })
    .attr('width', x.rangeBand())
    .attr('height', function(n) { return y(n.nodeValue) })
    .append('svg:title')
    .text(function(n) { return n.nodeName });
    }, (first ? 0 : delay));

    var linkLine = function(start) {
    return function(l) {
    var source = nodeMap[l.source],
    target = nodeMap[l.target],
    gapWidth = x(0),
    bandWidth = x.rangeBand() + gapWidth,
    startx = x.rangeBand() - bandWidth,
    sourcey = y(source.offsetValue) +
    source.order * padding +
    y(l.outOffset) +
    y(l.value)/2,
    targety = y(target.offsetValue) +
    target.order * padding +
    y(l.inOffset) +
    y(l.value)/2,
    points = start ?
    [
    [ startx, sourcey ], [ startx, sourcey ], [ startx, sourcey ], [ startx, sourcey ]
    ] :
    [
    [ startx, sourcey ],
    [ startx + gapWidth/2, sourcey ],
    [ startx + gapWidth/2, targety ],
    [ 0, targety ]
    ];
    return line(points);
    }
    }

    // links
    var links = nodes.selectAll('path.link')
    .data(function(n) { return n.incoming || [] })
    .enter().append('svg:path')
    .attr('class', 'link')
    .style('stroke-width', function(l) { return y(l.value) })
    .attr('d', linkLine(true))
    .on('mouseover', function() {
    d3.select(this).attr('class', 'link on')
    })
    .on('mouseout', function() {
    d3.select(this).attr('class', 'link')
    })
    .transition()
    .duration(delay)
    .attr('d', linkLine());

    }

    function updateNext() {
    if (t < data.length) {
    update();
    window.setTimeout(updateNext, delay)
    }
    }
    update(true);
    updateNext();

    </script>
    </body>
    </html>