Skip to content

Instantly share code, notes, and snippets.

@buremba
Created April 26, 2015 10:27
Show Gist options
  • Save buremba/ff651dc02a5f128eae7a to your computer and use it in GitHub Desktop.
Save buremba/ff651dc02a5f128eae7a to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html class="ocks-org do-not-copy">
<meta charset="utf-8">
<title>Sankey Diagram</title>
<style>
@import url(../style.css?aea6f0a);
#chart {
height: 500px;
}
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
</style>
<body>
<header>
<aside>May 22, 2012</aside>
<a href="../" rel="author">Mike Bostock</a>
</header>
<h1>Sankey Diagrams</h1>
<p id="chart">
<aside>Drag to rearrange nodes.</aside>
<p class="attribution">Source: <a href="http://www.decc.gov.uk/en/content/cms/tackling/2050/calculator_on/calculator_on.aspx">Department of Energy & Climate Change</a>, <a href="http://tamc.github.com/Sankey/">Tom Counsell</a>.
<aside>Sankey diagrams are closely related to <a href="http://en.wikipedia.org/wiki/Alluvial_diagram">alluvial diagrams</a>, which show how network structure changes over time.</aside>
<p><a href="http://en.wikipedia.org/wiki/Sankey_diagram">Sankey diagrams</a> visualize the magnitude of flow between nodes in a network. This intricate diagram shows a possible scenario for UK energy production and consumption in 2050: energy <b>supplies</b> are on the left, and <b>demands</b> are on the right. Intermediate nodes group related forms of production and show how energy is converted and transmitted before it is consumed (or lost!). The thickness of each link encodes the amount of flow from source to target.
<p>This example is built with <a href="http://d3js.org">D3</a>’s <a href="https://github.com/d3/d3-plugins/tree/master/sankey">Sankey plugin</a>. The plugin takes as input the nodes and weighted links, computing positions via <a href="http://en.wikipedia.org/wiki/Gauss–Seidel_method">iterative relaxation</a>. After fixing the horizontal position of each node, the algorithm starts from the sources on the left, positioning downstream nodes so as to minimize link distance. A reverse pass is then made from right-to-left, and then the entire process is repeated several times. Overlapping nodes are shifted to avoid collision.
<aside>The d3.sankey API is similar to D3’s <a href="http://mbostock.github.com/d3/ex/force.html">force-directed graph</a> layout, which is another type of network visualization.</aside>
<p>The fully automatic layout is convenient for rapid visualization—positioning nodes manually is tedious! However, the algorithm is not perfect; links are drawn with partial transparency to highlight crossings. To improve readability and further disambiguate links, this example also lets you reposition nodes interactively. The algorithm could be improved in the future, say to minimize link crossing or to support loopback in cyclical networks.
<p>Many thanks to Tom Counsell, whose <a href="http://tamc.github.com/Sankey/">Sankey library</a> provided inspiration for this example.
<footer>
<aside>May 22, 2012</aside>
<a href="../" rel="author">Mike Bostock</a>
</footer>
<script src="http://d3js.org/d3.v2.min.js?2.9.1"></script>
<script src="http://bost.ocks.org/mike/sankey/sankey.js"></script>
<script>
var margin = {top: 1, right: 1, bottom: 6, left: 1},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; },
color = d3.scale.category20();
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
d3.json("https://gist.githubusercontent.com/buremba/011552d41417df7fa127/raw/a5a0251a337b72fd74874dd2d5f23fcd64630f4d/gistfile1.txt", function(energy) {
var energy = {"nodes":[{"name":"DEACTIVATED PRODUCT Aylik Her Yone 120 Dk Service"},{"name":"PORT OUT"},{"name":"ACTIVATED PRODUCT Aylik Her Yone 120 Dk Service"},{"name":"MIGRATION FROM INDIVIDUAL POST TO PRE"},{"name":"MIGRATION FROM POST TO PRE"},{"name":"IVR MENU: s_info_loyaltyservices"},{"name":"CC SR FATURASIZA GECIS"},{"name":"CC SR AVEA URUN/SERVIS-DEGISIKLIK"},{"name":"ACCEPT CEP_CMPGN R_Loyalty"},{"name":"COMPLAINT CAGRI PROBLEMI"},{"name":"MOBILE APP MENU MUSTERI TARIFE ADI SORGULAMA"},{"name":"MOBILE APP MENU GECEBILECEK TARIFE LISTESI SORGULAMA"},{"name":"COMPLAINT CLOSED CAGRI PROBLEMI INFORMED"},{"name":"HANDSET CHANGED TO SAMSUNG GT-I8200Q GALAXY S III MINI VALUE EDITION"},{"name":"IVR MENU: m_gpo_postbillandkkpaymentmenu_pttcell"},{"name":"MADE 1 year COMMITMENT FOR FaturaliyaTransferInd12ayTaah"},{"name":"EXPIRED 2 year COMMITMENT"},{"name":"BECAME HOTLINE AS POSTPAID HIGH TRAFFIC"},{"name":"HANDSET CHANGED FROM SAMSUNG GT-I8200Q GALAXY S III MINI VALUE EDITION"},{"name":"COMPLAINT MT HATALI BILGI"},{"name":"COMPLAINT OSY"}],"links":[{"source":0,"target":1,"value":6103.0},{"source":2,"target":1,"value":6082.0},{"source":3,"target":1,"value":13292.0},{"source":4,"target":1,"value":13381.0},{"source":5,"target":1,"value":27105.0},{"source":6,"target":1,"value":8419.0},{"source":7,"target":1,"value":227384.0},{"source":8,"target":1,"value":90085.0},{"source":9,"target":1,"value":11612.0},{"source":10,"target":1,"value":12985.0},{"source":11,"target":1,"value":13521.0},{"source":12,"target":1,"value":6332.0},{"source":13,"target":1,"value":6348.0},{"source":14,"target":1,"value":7106.0},{"source":15,"target":1,"value":5262.0},{"source":16,"target":1,"value":11124.0},{"source":17,"target":10,"value":2010.0},{"source":18,"target":14,"value":6.0},{"source":18,"target":5,"value":51.0},{"source":19,"target":3,"value":600.0},{"source":20,"target":13,"value":6.0},{"source":21,"target":0,"value":10.0},{"source":21,"target":7,"value":12573.0},{"source":20,"target":14,"value":5.0},{"source":20,"target":16,"value":50.0},{"source":22,"target":5,"value":419.0},{"source":21,"target":11,"value":38.0},{"source":23,"target":3,"value":248.0},{"source":24,"target":15,"value":16.0},{"source":20,"target":12,"value":5.0},{"source":20,"target":7,"value":16998.0},{"source":17,"target":11,"value":2189.0},{"source":25,"target":7,"value":12789.0},{"source":22,"target":7,"value":94464.0},{"source":26,"target":13,"value":2361.0},{"source":27,"target":8,"value":3516.0},{"source":21,"target":9,"value":20.0},{"source":20,"target":6,"value":12.0},{"source":24,"target":3,"value":1059.0},{"source":22,"target":4,"value":168.0},{"source":23,"target":4,"value":255.0},{"source":28,"target":3,"value":157.0},{"source":29,"target":7,"value":71651.0},{"source":21,"target":10,"value":43.0},{"source":22,"target":8,"value":3053.0},{"source":21,"target":8,"value":1863.0},{"source":20,"target":15,"value":9.0},{"source":28,"target":16,"value":84.0},{"source":20,"target":5,"value":63.0},{"source":19,"target":4,"value":605.0},{"source":30,"target":4,"value":2627.0},{"source":22,"target":3,"value":166.0},{"source":21,"target":13,"value":32.0},{"source":20,"target":9,"value":17.0},{"source":28,"target":14,"value":42.0},{"source":31,"target":4,"value":195.0},{"source":32,"target":6,"value":990.0},{"source":28,"target":4,"value":159.0},{"source":18,"target":12,"value":8.0},{"source":31,"target":3,"value":194.0},{"source":32,"target":4,"value":2511.0},{"source":18,"target":9,"value":26.0},{"source":22,"target":11,"value":2083.0},{"source":21,"target":2,"value":10.0},{"source":18,"target":3,"value":20.0},{"source":30,"target":6,"value":1017.0},{"source":22,"target":10,"value":1914.0},{"source":17,"target":7,"value":87309.0},{"source":24,"target":4,"value":1077.0},{"source":18,"target":16,"value":50.0},{"source":21,"target":12,"value":6.0},{"source":18,"target":15,"value":6.0},{"source":32,"target":3,"value":2468.0},{"source":18,"target":4,"value":20.0},{"source":30,"target":3,"value":2583.0},{"source":33,"target":29,"value":36.0},{"source":34,"target":26,"value":2.0},{"source":35,"target":25,"value":45.0},{"source":34,"target":27,"value":2.0},{"source":36,"target":27,"value":13.0},{"source":33,"target":26,"value":8.0},{"source":35,"target":26,"value":6.0},{"source":37,"target":26,"value":4.0},{"source":35,"target":27,"value":5.0},{"source":34,"target":29,"value":74.0},{"source":35,"target":29,"value":75.0},{"source":34,"target":25,"value":6.0},{"source":38,"target":25,"value":8.0}]};
sankey
.nodes(energy.nodes)
.links(energy.links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var node = svg.append("g").selectAll(".node")
.data(energy.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
function dragmove(d) {
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
});
</script>
<script>
GoogleAnalyticsObject = "ga", ga = function() { ga.q.push(arguments); }, ga.q = [], ga.l = +new Date;
ga("create", "UA-48272912-3", "ocks.org");
ga("send", "pageview");
</script>
<script async src="//www.google-analytics.com/analytics.js"></script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment