forked from emeeks's block: Multi-Part Sankey
-
-
Save renecnielsen/c547fe16dd41854bd9f3 to your computer and use it in GitHub Desktop.
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="http://d3js.org/d3.v3.min.js" charset="utf-8" type="text/javascript"></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; | |
}; |