Skip to content

Instantly share code, notes, and snippets.

@emeeks
Last active March 17, 2016 02:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save emeeks/1dd092dadc02a93cdebc to your computer and use it in GitHub Desktop.
Save emeeks/1dd092dadc02a93cdebc to your computer and use it in GitHub Desktop.
Multi-Part Sankey

This sankey diagram of energy generation and consumption shows the total energy flow from 2010 to 2050, split into 5-year segments. 2010 is in grey, 2050 is in dark green, and each 5-year step between is in order between those. You can see, for instance, the decline in oil reserves over the 40 year period, and corresponding increase in oil imports.

This relies on a modification of the original d3.sankey to draw adjusted links. This version of d3.sankey also differs in that it draws links as areas rather than relying on stroke-width.

The sankey.adjustedLink function works like the regular sankey.link function but takes an additional adjustment and offset value. The adjustment is the percent value of the overall link, and the offset is the y-pixel offset of the link. In this case, those both correspond to the percent and order of the segments, but don't have to.

The sankey diagram itself is laid out using the parent links as you would normally use d3.sankey (in this case the total energy flow over the forty year span) and then the adjusted links are drawn afterward as appropriate.

Source: https://www.gov.uk/government/publications/2050-pathways-calculator-with-costs

source target 2010 2015 2020 2025 2030 2035 2040 2045 2050 total
Coal reserves Coal 127.93 127.93 127.93 127.93 63.965 63.965 63.965 63.965 63.965 831.545
Coal imports Coal 349.7879708 296.3632186 211.2161187 77.82581145 35.20638477 19.10842823 22.86599313 26.79703903 31.37680448 1070.547769
Oil reserves Oil 802.5479528 646.8288435 501.7889501 388.2747242 300.4395801 232.47442 179.8842746 139.1910227 107.70336 3299.133128
Oil imports Oil 65.64315528 208.35818 357.8050143 457.5236318 528.0501593 614.9478991 678.4226006 733.685649 772.3784493 4416.814739
Gas reserves Natural Gas 645.7728959 495.8875831 383.1206459 296.4514526 229.3884829 177.4964354 137.3433582 106.2736724 82.23254189 2553.967068
Gas imports Natural Gas 355.6589677 584.2856578 819.597827 1092.709052 1345.782246 1550.934934 1723.772025 1892.529552 2034.326024 11399.59629
UK land based bioenergy Bio-conversion 3.027913952 4.692845238 6.402563082 8.158190817 9.960892754 11.81187653 13.71239565 15.66375217 17.66729961 91.09772981
Agricultural 'waste' Bio-conversion 9.282517755 14.61107771 30.99950457 31.97585802 32.98811297 34.0375862 35.12564273 36.2536977 37.42321811 262.6972158
Other waste Bio-conversion 28.71472764 28.44079879 27.91963931 30.12533828 32.73194046 34.35747868 36.16491136 38.1607047 40.34778662 296.9633259
Other waste Solid 7.120255333 7.788686663 8.479830435 9.738108471 11.083322 11.83909104 12.61845869 13.42142496 14.24798984 96.33716744
Biomass imports Solid 4.089432558 3.578253488 3.067074419 2.555895349 2.044716279 1.533537209 1.02235814 0.51117907 0 18.40244651
Coal Solid 477.7179708 424.2932186 339.1461187 205.7558115 99.17138477 83.07342823 86.83099313 90.76203903 95.34180448 1902.092769
Oil Liquid 868.1911081 855.1870236 859.5939643 845.798356 828.4897394 847.422319 858.3068752 872.8766716 880.0818093 7715.947867
Natural Gas Gas 1001.431864 1080.173241 1202.718473 1389.160505 1575.170729 1728.431369 1861.115383 1998.803225 2116.558565 13953.56335
Solar Solar PV 0.028059966 0.013604832 0 0 0 0 0 0 0 0.041664798
Solar PV Electricity grid 0.028059966 0.013604832 0 0 0 0 0 0 0 0.041664798
Bio-conversion Solid 15.69522779 16.8073649 17.95786943 21.31595717 23.85040188 25.35724667 26.91643317 28.52796138 30.1918313 206.6202937
Bio-conversion Liquid 1.069127005 1.681261069 2.309670538 3.528739363 4.329131458 5.150427938 5.993130385 6.857757484 7.74484597 38.66409121
Bio-conversion Gas 18.29875011 20.75020481 31.20578182 34.73401889 35.3876884 36.21199755 37.18458852 38.31187901 39.59732328 291.6822324
Bio-conversion Losses 5.962054444 8.505890961 13.84838517 10.6806717 12.11372446 13.48726925 14.90879767 16.38055669 17.90430379 113.7916541
Solid Thermal generation 434.145135 381.0784209 294.538574 160.8682199 52.95223532 33.60482625 33.60482625 33.20882816 32.82867 1456.829736
Liquid Thermal generation 8.534858112 0 0 0 0 0 0 0 0 8.534858112
Gas Thermal generation 343.3066404 391.9936816 491.9769445 640.9853217 783.690216 901.421775 993.9226489 1086.044761 1152.794637 6786.136626
Nuclear Thermal generation 160.71 134.9964 77.1408 25.7136 25.7136 0 0 0 0 424.2744
Thermal generation District heating 9.042140031 9.487279287 9.968747932 10.73757753 11.59832328 12.55911459 13.62952357 14.82061794 16.14504632 107.9883705
Thermal generation Electricity grid 366.4405941 361.9932623 364.3163394 376.1008348 410.1991759 453.1120284 498.3667387 543.4265396 576.0327661 3949.988279
Thermal generation Losses 571.2138994 536.5879609 489.3712311 440.7287293 440.5585522 469.3554583 515.5312129 561.0064317 593.4454949 4617.798971
Wind Electricity grid 14.4406701 29.3428701 45.35726512 57.69377964 48.16934532 32.30288532 15.20918532 0.08783532 0.08783532 242.6916716
Tidal Electricity grid 0.005003425 0.020013699 0.050034247 0.125085616 0.125085616 0 0 0 0 0.325222603
Wave Electricity grid 0 0.003002055 0.158441781 0.396104452 0.396104452 0 0 0 0 0.95365274
Hydro Electricity grid 5.329728 5.329728 5.329728 5.329728 5.329728 5.329728 5.329728 5.329728 5.329728 47.967552
Electricity grid Over generation / exports 1.13687E-13 0 0 0 0 0 0 0 0 1.13687E-13
Electricity grid Losses 26.94051694 27.66999195 28.96101727 30.66526914 32.37929876 34.22943122 36.19366126 38.28186775 40.55615154 295.8772058
District heating Industry 9.042140031 9.487279287 9.968747932 10.73757753 11.59832328 12.55911459 13.62952357 14.82061794 16.14504632 107.9883705
Electricity grid Heating and cooling - homes 28.7767749 23.94325074 28.18933662 32.84705757 37.9224739 42.61889891 47.89118557 53.84879587 60.65817298 356.6959471
Solid Heating and cooling - homes 13.14794248 10.7501536 9.93526176 8.879384012 7.579707236 5.910818211 4.105860803 2.144741614 0 62.45386972
Liquid Heating and cooling - homes 11.7924845 9.641890339 8.91100797 7.963983598 6.798294119 5.301455508 3.682576184 1.923634231 0 56.01532645
Gas Heating and cooling - homes 354.8435738 382.9695521 408.4682642 433.285271 457.2265205 470.0987089 484.2897767 500.170154 517.9434691 4009.29529
Electricity grid Heating and cooling - commercial 31.40903798 35.16946485 36.74416003 37.59493963 37.73848109 37.18693674 35.9477411 34.02338939 31.41118474 317.2253355
Liquid Heating and cooling - commercial 9.357802772 9.360191566 8.461869294 7.44926515 6.305083178 5.010234677 3.543657659 1.882119325 0 51.37022362
Gas Heating and cooling - commercial 80.65151402 85.39821393 91.44410327 98.05380686 105.2778409 113.1711026 121.793249 131.2091076 141.4891226 968.4880609
Electricity grid Lighting & appliances - homes 87.37770782 89.47851986 91.46434105 93.16411259 94.56743589 96.68001201 98.8234386 101.0623803 103.4015595 856.0195076
Gas Lighting & appliances - homes 8.015463096 8.015547174 8.015233023 8.014845996 8.014544549 8.031450005 8.032515957 8.03358205 8.034648285 72.20783013
Electricity grid Lighting & appliances - commercial 73.04774089 75.15818753 77.34780373 79.61979666 81.97751212 84.42444093 86.96422545 89.60066659 92.33773101 740.4781049
Gas Lighting & appliances - commercial 8.987057559 8.99526583 9.003481597 9.011704868 9.01993565 9.028173949 9.036419773 9.044673128 9.052934021 81.17964638
Electricity grid Industry 126.2492384 132.7476228 139.7553308 150.8393381 162.7076996 176.492717 191.8296813 208.8798113 227.8261992 1517.327639
Solid Industry 56.47800845 59.7818278 63.31457849 68.75029543 74.74457766 81.40888456 88.79327436 96.97920674 106.0575425 696.308196
Liquid Industry 137.4335097 139.6002686 142.2999648 148.4089907 155.8459842 164.6053674 174.7162779 186.2385821 199.2602412 1448.409187
Gas Industry 210.4929841 209.2079849 209.2640189 216.6542247 227.307703 241.1112516 257.9195694 277.7250409 300.5983185 2150.281096
Electricity grid Agriculture 4.259002504 4.285606784 4.312393687 4.33936525 4.366523528 4.393870604 4.421408582 4.449139588 4.477065773 39.3043763
Solid Agriculture 0.851800501 0.857121357 0.862478737 0.86787305 0.873304706 0.878774121 0.884281716 0.889827918 0.895413155 7.86087526
Liquid Agriculture 3.513677065 3.535625597 3.557724792 3.579976331 3.602381911 3.624943249 3.64766208 3.67054016 3.693579263 32.42611045
Gas Agriculture 2.023026189 2.035663222 2.048387002 2.061198494 2.074098676 2.087088537 2.100169076 2.113341304 2.126606242 18.66957874
Electricity grid Road transport 0 0.264318023 0.538634784 2.789636606 4.920208328 7.234926083 9.532878505 11.57811039 13.8422602 50.70097292
Liquid Road transport 470.2870297 444.8333119 423.8675334 389.6077924 351.198255 343.6312398 333.6424088 328.0204659 322.0183307 3407.106368
Electricity grid Rail transport 8.184036114 7.985518411 7.898790629 7.786017019 7.639806008 7.483408182 7.301431687 7.119941726 6.940004428 68.3389542
Liquid Rail transport 9.540451289 9.197930429 9.06515471 8.882304526 8.638272423 8.377036331 8.073455426 7.774544525 7.482590654 77.03174031
Liquid Domestic aviation 9.55109733 10.16371642 11.07874205 11.92797975 12.65784724 13.33107712 13.86025128 14.34440942 14.78544909 111.7005697
Liquid National navigation 26.57289571 25.38306456 24.58984379 23.99670496 23.68879172 23.38482946 23.0847675 22.78855577 22.49614487 215.9855983
Liquid International aviation 125.0236042 141.9277504 160.7246469 170.5797952 178.7278412 190.5888908 194.9306323 196.4187558 188.5816831 1547.5036
Liquid International shipping 57.28499215 62.90268135 69.07127281 76.70040745 85.17220349 94.57973548 105.0263583 116.6268428 129.5086365 796.8731304
Gas Losses 11.41035458 12.30753698 13.70382224 15.82815018 17.9475581 19.69381594 21.20562289 22.77444364 24.11615269 158.9874572
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Mutli-Part Sankey</title>
<meta charset="utf-8" />
</head>
<style>
body, html {
margin: 0;
padding: 0;
}
.viz {
position: fixed;
left:0;
top:0;
bottom:0;
right:0;
background:rgb(188,188,188);
}
path.domain {
fill: none;
}
</style>
<script>
expData = "";
nodes = [];
edges = [];
nodeHash = {};
edgeHash = {};
function makeVizSankey() {
d3.select(".viz")
.append("svg")
.attr("height", 1000)
.attr("width", 1000)
.style("background", "#FDFBF5")
d3.csv("energy.csv", createData);
}
function createData(data) {
//Build the network
data.forEach(function (datum) {
if (!nodeHash[datum.source]) {
nodeHash[datum.source] = {id: datum.source};
nodes.push(nodeHash[datum.source]);
}
if (!nodeHash[datum.target]) {
nodeHash[datum.target] = {id: datum.target};
nodes.push(nodeHash[datum.target]);
}
// Each link will have a segments array made up of the 5-year values
var newEdge = {source: nodeHash[datum.source], target: nodeHash[datum.target]};
edgeHash[datum.source+datum.target] = newEdge;
newEdge.weight = parseFloat(datum.total);
newEdge.value = parseFloat(datum.total);
newEdge.segments = [];
newEdge.segments.push(parseFloat(datum["2010"]));
newEdge.segments.push(parseFloat(datum["2015"]));
newEdge.segments.push(parseFloat(datum["2020"]));
newEdge.segments.push(parseFloat(datum["2025"]));
newEdge.segments.push(parseFloat(datum["2030"]));
newEdge.segments.push(parseFloat(datum["2035"]));
newEdge.segments.push(parseFloat(datum["2040"]));
newEdge.segments.push(parseFloat(datum["2045"]));
newEdge.segments.push(parseFloat(datum["2050"]));
edges.push((newEdge));
})
buildSankey();
}
function buildSankey() {
var sankey = d3.sankey()
.nodeWidth(20)
.nodePadding(15)
.size([660, 460]);
var path = sankey.link();
sankey
.nodes(nodes)
.links(edges)
.layout(200);
d3.select("svg").append("g").attr("transform", "translate(80,20)").attr("id", "sankeyG");
d3.select("#sankeyG").selectAll("g.link")
.data(edges)
.enter().append("g")
.attr("class", "link")
.append("path")
.attr("class", "link overall")
.attr("d", sankey.link())
.style("fill", "black")
.style("fill-opacity", .2)
d3.selectAll("g.link").each(function (link) {
var linkElement = this;
var offset = 0;
link.segments.forEach(function (segment, i) {
var adjustment = segment / link.value;
var adjustedPath = sankey.linkAdjusted();
d3.select(linkElement)
.append("path")
.attr("class", "link adjusted")
.style("fill", "green")
.style("fill-opacity", i / 10)
.attr("d", adjustedPath(link, adjustment, offset));
offset = offset + (link.dy * adjustment);
})
})
d3.select("#sankeyG").selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
d3.selectAll(".node").append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", 20)
.style("fill", "darkgreen")
.style("stroke", "none")
d3.selectAll(".node").append("text")
.attr("x", 0)
.attr("y", function(d) { return d.dy / 2; })
.attr("text-anchor", "middle")
.text(function(d) { return d.id; })
.style("opacity", .8)
}
</script>
<body onload="makeVizSankey()">
<div class="viz"></div>
<footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="sankey.js" charset="utf-8" type="text/javascript"></script>
</footer>
</body>
</html>
d3.sankey = function() {
var sankey = {},
nodeWidth = 24,
nodePadding = 8,
size = [1, 1],
nodes = [],
links = [];
sankey.nodeWidth = function(_) {
if (!arguments.length) return nodeWidth;
nodeWidth = +_;
return sankey;
};
sankey.nodePadding = function(_) {
if (!arguments.length) return nodePadding;
nodePadding = +_;
return sankey;
};
sankey.nodes = function(_) {
if (!arguments.length) return nodes;
nodes = _;
return sankey;
};
sankey.links = function(_) {
if (!arguments.length) return links;
links = _;
return sankey;
};
sankey.size = function(_) {
if (!arguments.length) return size;
size = _;
return sankey;
};
sankey.layout = function(iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeBreadths();
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
sankey.relayout = function() {
computeLinkDepths();
return sankey;
};
sankey.linkAdjusted = function() {
var curvature = .5;
function link(d, adjustment, segmentOffset) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy + segmentOffset,
y1 = d.target.y + d.ty + segmentOffset,
y2 = d.target.y + d.ty + (d.dy * adjustment) + segmentOffset,
y3 = d.source.y + d.sy + (d.dy * adjustment) + segmentOffset;
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + y1
+ " " + x1 + "," + y1
+ "L" + x1 + "," + y2
+ "C" + x3 + "," + y2
+ " " + x2 + "," + y3
+ " " + x0 + "," + y3
+ "Z";
}
link.curvature = function(_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
sankey.link = function() {
var curvature = .5;
function link(d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy,
y1 = d.target.y + d.ty,
y2 = d.target.y + d.ty + d.dy,
y3 = d.source.y + d.sy + d.dy;
if (y3 - y0 < 30000) {
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + y1
+ " " + x1 + "," + y1
+ "L" + x1 + "," + y2
+ "C" + x3 + "," + y2
+ " " + x2 + "," + y3
+ " " + x0 + "," + y3
+ "Z";
}
else {
var offset = (x1 - x0) /4;
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + (y1)
+ " " + (x1 - offset) + "," + (y1 + 0)
+ "L" + (x1 - 6) + "," + ((y2 + y1)/2)
+ "L" + (x1 - offset) + "," + (y2 + 0)
+ "C" + x3 + "," + (y2)
+ " " + x2 + "," + y3
+ " " + x0 + "," + y3
+ "Z";
}
}
link.curvature = function(_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach(function(link) {
var source = link.source,
target = link.target;
if (typeof source === "number") source = link.source = nodes[link.source];
if (typeof target === "number") target = link.target = nodes[link.target];
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach(function(node) {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
});
}
// Iteratively assign the breadth (x-position) for each node.
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
// nodes with no incoming links are assigned breadth zero, while
// nodes with no outgoing links are assigned the maximum breadth.
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
x = 0;
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
if (nextNodes.indexOf(link.target) < 0) {
nextNodes.push(link.target);
}
});
});
remainingNodes = nextNodes;
++x;
}
moveSinksRight(x);
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
}
function moveSourcesRight() {
nodes.forEach(function(node) {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
}
});
}
function moveSinksRight(x) {
nodes.forEach(function(node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
});
}
function scaleNodeBreadths(kx) {
nodes.forEach(function(node) {
node.x *= kx;
});
}
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) { return d.values; });
//
initializeNodeDepth();
resolveCollisions();
for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach(function(link) {
link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
nodesByBreadth.forEach(function(nodes, breadth) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedSource(link) {
return center(link.source) * link.value;
}
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedTarget(link) {
return center(link.target) * link.value;
}
}
function resolveCollisions() {
nodesByBreadth.forEach(function(nodes) {
var node,
dy,
y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y0 - node.y;
if (dy > 0) node.y += dy;
y0 = node.y + node.dy + nodePadding;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y0 - nodePadding - size[1];
if (dy > 0) {
y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y + node.dy + nodePadding - y0;
if (dy > 0) node.y -= dy;
y0 = node.y;
}
}
});
}
function ascendingDepth(a, b) {
return a.y - b.y;
}
}
function computeLinkDepths() {
nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach(function(node) {
var sy = 0, ty = 0;
node.sourceLinks.forEach(function(link) {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach(function(link) {
link.ty = ty;
ty += link.dy;
});
});
function ascendingSourceDepth(a, b) {
return a.source.y - b.source.y;
}
function ascendingTargetDepth(a, b) {
return a.target.y - b.target.y;
}
}
function center(node) {
return node.y + node.dy / 2;
}
function value(link) {
return link.value;
}
return sankey;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment