Skip to content

Instantly share code, notes, and snippets.

@fogonwater
Last active April 22, 2018 05:12
Show Gist options
  • Save fogonwater/4b45f8af8d60c18603a88b2860cd8372 to your computer and use it in GitHub Desktop.
Save fogonwater/4b45f8af8d60c18603a88b2860cd8372 to your computer and use it in GitHub Desktop.
NZ Sankey Energy tests (new Sankey)
license: mit
<!DOCTYPE html>
<svg width="950" height="1000"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="sankey.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var formatNumber = d3.format(",.2f"),
format = function(d) { return formatNumber(d) + " PJ"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
var sankey = d3.sankey()
.nodeWidth(36)
.nodePadding(15)
.nodeAlign(d3.sankeyCenter)
.iterations(30)
.extent([[1, 1], [width - 1, height - 6]]);
var link = svg.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("path");
var node = svg.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");
d3.json("sankey.json", function(error, energy) {
if (error) throw error;
sankey(energy);
link = link
.data(energy.links)
.enter().append("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function(d) { return Math.max(1, d.width); })
.style("stroke", (d) => color(d.group))
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
node = node
.data(energy.nodes)
.enter().append("g");
node.append("rect")
.attr("x", function(d) { return d.x0; })
.attr("y", function(d) { return d.y0; })
.attr("height", function(d) { return d.y1 - d.y0; })
.attr("width", function(d) { return d.x1 - d.x0; })
.style("fill", (d) => color(d.group))
//.attr("stroke", "#000");
node.append("text")
.attr("x", function(d) { return d.x0 - 6; })
.attr("y", function(d) { return (d.y1 + d.y0) / 2; })
.attr("dy", "0.35em")
.attr("text-anchor", "end")
.text(function(d) { return d.name + ' ' + formatNumber(d.value); })
.filter(function(d) { return d.x0 < width / 2; })
.attr("x", function(d) { return d.x1 + 6; })
.attr("text-anchor", "start");
node.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
});
</script>
// https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2017 Mike Bostock.
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-shape')) :
typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-shape'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3));
}(this, (function (exports,d3Array,d3Collection,d3Shape) { 'use strict';
function targetDepth(d) {
return d.target.depth;
}
function left(node) {
return node.depth;
}
function right(node, n) {
return n - 1 - node.height;
}
function justify(node, n) {
return node.sourceLinks.length ? node.depth : n - 1;
}
function center(node) {
return node.targetLinks.length ? node.depth
: node.sourceLinks.length ? d3Array.min(node.sourceLinks, targetDepth) - 1
: 0;
}
function constant(x) {
return function() {
return x;
};
}
function ascendingSourceBreadth(a, b) {
return ascendingBreadth(a.source, b.source) || a.index - b.index;
}
function ascendingTargetBreadth(a, b) {
return ascendingBreadth(a.target, b.target) || a.index - b.index;
}
function ascendingBreadth(a, b) {
return a.y0 - b.y0;
}
function value(d) {
return d.value;
}
function nodeCenter(node) {
return (node.y0 + node.y1) / 2;
}
function weightedSource(link) {
return nodeCenter(link.source) * link.value;
}
function weightedTarget(link) {
return nodeCenter(link.target) * link.value;
}
function defaultId(d) {
return d.index;
}
function defaultNodes(graph) {
return graph.nodes;
}
function defaultLinks(graph) {
return graph.links;
}
function find(nodeById, id) {
var node = nodeById.get(id);
if (!node) throw new Error("missing: " + id);
return node;
}
var sankey = function() {
var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent
dx = 24, // nodeWidth
py = 8, // nodePadding
id = defaultId,
align = justify,
nodes = defaultNodes,
links = defaultLinks,
iterations = 32;
function sankey() {
var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)};
computeNodeLinks(graph);
computeNodeValues(graph);
computeNodeDepths(graph);
computeNodeBreadths(graph, iterations);
computeLinkBreadths(graph);
return graph;
}
sankey.update = function(graph) {
computeLinkBreadths(graph);
return graph;
};
sankey.nodeId = function(_) {
return arguments.length ? (id = typeof _ === "function" ? _ : constant(_), sankey) : id;
};
sankey.nodeAlign = function(_) {
return arguments.length ? (align = typeof _ === "function" ? _ : constant(_), sankey) : align;
};
sankey.nodeWidth = function(_) {
return arguments.length ? (dx = +_, sankey) : dx;
};
sankey.nodePadding = function(_) {
return arguments.length ? (py = +_, sankey) : py;
};
sankey.nodes = function(_) {
return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes;
};
sankey.links = function(_) {
return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links;
};
sankey.size = function(_) {
return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0];
};
sankey.extent = function(_) {
return arguments.length ? (x0 = +_[0][0], x1 = +_[1][0], y0 = +_[0][1], y1 = +_[1][1], sankey) : [[x0, y0], [x1, y1]];
};
sankey.iterations = function(_) {
return arguments.length ? (iterations = +_, sankey) : iterations;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks(graph) {
graph.nodes.forEach(function(node, i) {
node.index = i;
node.sourceLinks = [];
node.targetLinks = [];
});
var nodeById = d3Collection.map(graph.nodes, id);
graph.links.forEach(function(link, i) {
link.index = i;
var source = link.source, target = link.target;
if (typeof source !== "object") source = link.source = find(nodeById, source);
if (typeof target !== "object") target = link.target = find(nodeById, target);
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues(graph) {
graph.nodes.forEach(function(node) {
node.value = Math.max(
d3Array.sum(node.sourceLinks, value),
d3Array.sum(node.targetLinks, value)
);
});
}
// Iteratively assign the depth (x-position) for each node.
// Nodes are assigned the maximum depth of incoming neighbors plus one;
// nodes with no incoming links are assigned depth zero, while
// nodes with no outgoing links are assigned the maximum depth.
function computeNodeDepths(graph) {
var nodes, next, x;
for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
nodes.forEach(function(node) {
node.depth = x;
node.sourceLinks.forEach(function(link) {
if (next.indexOf(link.target) < 0) {
next.push(link.target);
}
});
});
}
for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
nodes.forEach(function(node) {
node.height = x;
node.targetLinks.forEach(function(link) {
if (next.indexOf(link.source) < 0) {
next.push(link.source);
}
});
});
}
var kx = (x1 - x0 - dx) / (x - 1);
graph.nodes.forEach(function(node) {
node.x1 = (node.x0 = x0 + Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))) * kx) + dx;
});
}
function computeNodeBreadths(graph) {
var columns = d3Collection.nest()
.key(function(d) { return d.x0; })
.sortKeys(d3Array.ascending)
.entries(graph.nodes)
.map(function(d) { return d.values; });
//
initializeNodeBreadth();
resolveCollisions();
for (var alpha = 1, n = iterations; n > 0; --n) {
relaxRightToLeft(alpha *= 0.99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeBreadth() {
var ky = d3Array.min(columns, function(nodes) {
return (y1 - y0 - (nodes.length - 1) * py) / d3Array.sum(nodes, value);
});
columns.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y1 = (node.y0 = i) + node.value * ky;
});
});
graph.links.forEach(function(link) {
link.width = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
columns.forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
var dy = (d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value) - nodeCenter(node)) * alpha;
node.y0 += dy, node.y1 += dy;
}
});
});
}
function relaxRightToLeft(alpha) {
columns.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var dy = (d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value) - nodeCenter(node)) * alpha;
node.y0 += dy, node.y1 += dy;
}
});
});
}
function resolveCollisions() {
columns.forEach(function(nodes) {
var node,
dy,
y = y0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingBreadth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y - node.y0;
if (dy > 0) node.y0 += dy, node.y1 += dy;
y = node.y1 + py;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y - py - y1;
if (dy > 0) {
y = (node.y0 -= dy), node.y1 -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y1 + py - y;
if (dy > 0) node.y0 -= dy, node.y1 -= dy;
y = node.y0;
}
}
});
}
}
function computeLinkBreadths(graph) {
graph.nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetBreadth);
node.targetLinks.sort(ascendingSourceBreadth);
});
graph.nodes.forEach(function(node) {
var y0 = node.y0, y1 = y0;
node.sourceLinks.forEach(function(link) {
link.y0 = y0 + link.width / 2, y0 += link.width;
});
node.targetLinks.forEach(function(link) {
link.y1 = y1 + link.width / 2, y1 += link.width;
});
});
}
return sankey;
};
function horizontalSource(d) {
return [d.source.x1, d.y0];
}
function horizontalTarget(d) {
return [d.target.x0, d.y1];
}
var sankeyLinkHorizontal = function() {
return d3Shape.linkHorizontal()
.source(horizontalSource)
.target(horizontalTarget);
};
exports.sankey = sankey;
exports.sankeyCenter = center;
exports.sankeyLeft = left;
exports.sankeyRight = right;
exports.sankeyJustify = justify;
exports.sankeyLinkHorizontal = sankeyLinkHorizontal;
Object.defineProperty(exports, '__esModule', { value: true });
})));
{
"links": [
{
"group": "coal",
"source": 0,
"target": 1,
"value": 72.91
},
{
"group": "coal",
"source": 2,
"target": 1,
"value": 10.16
},
{
"group": "coal",
"source": 1,
"target": 3,
"value": 37.83
},
{
"group": "coal",
"source": 4,
"target": 1,
"value": 6.85
},
{
"group": "coal",
"source": 1,
"target": 5,
"value": 52.09
},
{
"group": "coal",
"source": 5,
"target": 6,
"value": 25.51
},
{
"group": "coal",
"source": 6,
"target": 7,
"value": 23.5853498518822
},
{
"group": "coal",
"source": 5,
"target": 8,
"value": 11.7029802832
},
{
"group": "coal",
"source": 5,
"target": 9,
"value": 12.3329269618
},
{
"group": "coal",
"source": 5,
"target": 10,
"value": 2.53954240224588
},
{
"group": "coal",
"source": 6,
"target": 11,
"value": 1.51641500232235
},
{
"group": "coal",
"source": 6,
"target": 12,
"value": 1.22967892703738
},
{
"group": "coal",
"source": 6,
"target": 13,
"value": 0.344830082219614
},
{
"group": "coal",
"source": 6,
"target": 14,
"value": 0.00203452815635588
},
{
"group": "renew",
"source": 15,
"target": 16,
"value": 201.842188349912
},
{
"group": "renew",
"source": 16,
"target": 9,
"value": 194.19161113316798
},
{
"group": "renew",
"source": 17,
"target": 18,
"value": 93.2560248967486
},
{
"group": "renew",
"source": 18,
"target": 9,
"value": 93.2560248967486
},
{
"group": "renew",
"source": 19,
"target": 20,
"value": 58.2725728809738
},
{
"group": "renew",
"source": 21,
"target": 7,
"value": 50.0563513833349
},
{
"group": "renew",
"source": 21,
"target": 13,
"value": 8.77924198800265
},
{
"group": "renew",
"source": 22,
"target": 23,
"value": 8.31857884522661
},
{
"group": "renew",
"source": 23,
"target": 9,
"value": 8.31857884522661
},
{
"group": "renew",
"source": 20,
"target": 9,
"value": 4.655398534704
},
{
"group": "renew",
"source": 24,
"target": 25,
"value": 2.71299534443175
},
{
"group": "renew",
"source": 21,
"target": 12,
"value": 2.52764695342229
},
{
"group": "renew",
"source": 25,
"target": 9,
"value": 2.3827337044317503
},
{
"group": "renew",
"source": 26,
"target": 9,
"value": 0.9677179296
},
{
"group": "renew",
"source": 21,
"target": 11,
"value": 0.5987728782537
},
{
"group": "renew",
"source": 27,
"target": 28,
"value": 0.55012484128
},
{
"group": "renew",
"source": 28,
"target": 9,
"value": 0.18612484128
},
{
"group": "renew",
"source": 29,
"target": 30,
"value": 0.1297618163
},
{
"group": "renew",
"source": 31,
"target": 9,
"value": 0.0415153152
},
{
"group": "renew",
"source": 16,
"target": 21,
"value": 7.650577216744011
},
{
"group": "renew",
"source": 20,
"target": 21,
"value": 53.6171743462698
},
{
"group": "renew",
"source": 25,
"target": 21,
"value": 0.3302616399999998
},
{
"group": "renew",
"source": 28,
"target": 21,
"value": 0.36400000000000005
},
{
"group": "renew",
"source": 30,
"target": 21,
"value": 0.1297618163
},
{
"group": "gas",
"source": 32,
"target": 33,
"value": 191.711482912155
},
{
"group": "gas",
"source": 33,
"target": 7,
"value": 65.7357550527379
},
{
"group": "gas",
"source": 33,
"target": 9,
"value": 37.7614082274
},
{
"group": "gas",
"source": 32,
"target": 34,
"value": 12.105877
},
{
"group": "gas",
"source": 33,
"target": 12,
"value": 8.1170846088
},
{
"group": "gas",
"source": 33,
"target": 13,
"value": 6.359754884
},
{
"group": "gas",
"source": 32,
"target": 35,
"value": 6.08504868784498
},
{
"group": "gas",
"source": 33,
"target": 11,
"value": 1.605870431
},
{
"group": "gas",
"source": 32,
"target": 36,
"value": 0.966338119
},
{
"group": "gas",
"source": 33,
"target": 14,
"value": 0.01619479
},
{
"group": "electricity",
"source": 9,
"target": 37,
"value": 153.32523905299996
},
{
"group": "electricity",
"source": 37,
"target": 13,
"value": 44.01958529
},
{
"group": "electricity",
"source": 37,
"target": 12,
"value": 34.328962
},
{
"group": "electricity",
"source": 37,
"target": 7,
"value": 52.286727823
},
{
"group": "electricity",
"source": 37,
"target": 11,
"value": 9.234959447
},
{
"group": "electricity",
"source": 37,
"target": 38,
"value": 11.029882872000002
},
{
"group": "electricity",
"source": 37,
"target": 39,
"value": 0.444780426
},
{
"group": "oil",
"source": 40,
"target": 41,
"value": 82.49
},
{
"group": "oil",
"source": 42,
"target": 41,
"value": 353.8
},
{
"group": "oil",
"source": 43,
"target": 14,
"value": 217.74833275700001
},
{
"group": "oil",
"source": 43,
"target": 11,
"value": 18.978621454
},
{
"group": "oil",
"source": 43,
"target": 7,
"value": 19.987096126
},
{
"group": "oil",
"source": 43,
"target": 12,
"value": 6.792869294
},
{
"group": "oil",
"source": 43,
"target": 13,
"value": 3.3883199639999995
},
{
"group": "oil",
"source": 43,
"target": 37,
"value": 0.02322498
},
{
"group": "oil",
"source": 43,
"target": 44,
"value": 4.62
},
{
"group": "oil",
"source": 43,
"target": 45,
"value": 7.33
},
{
"group": "oil",
"source": 43,
"target": 46,
"value": 10.41
},
{
"group": "oil",
"source": 43,
"target": 47,
"value": 4.08
},
{
"group": "oil",
"source": 41,
"target": 48,
"value": 72.58
},
{
"group": "oil",
"source": 41,
"target": 49,
"value": 8.74
},
{
"group": "oil",
"source": 41,
"target": 50,
"value": 61.7
},
{
"group": "oil",
"source": 41,
"target": 43,
"value": 293.7
}
],
"nodes": [
{
"group": "coal",
"name": "coal production",
"node": 0
},
{
"group": "coal",
"name": "coal primary",
"node": 1
},
{
"group": "coal",
"name": "coal imports",
"node": 2
},
{
"group": "coal",
"name": "coal exports",
"node": 3
},
{
"group": "coal",
"name": "coal oil stock change",
"node": 4
},
{
"group": "coal",
"name": "coal supply",
"node": 5
},
{
"group": "coal",
"name": "coal demand",
"node": 6
},
{
"group": "mixed",
"name": "industrial",
"node": 7
},
{
"group": "coal",
"name": "other transformation",
"node": 8
},
{
"group": "mixed",
"name": "electricity transformation",
"node": 9
},
{
"group": "coal",
"name": "production losses and own use",
"node": 10
},
{
"group": "mixed",
"name": "agriculture",
"node": 11
},
{
"group": "mixed",
"name": "commercial",
"node": 12
},
{
"group": "mixed",
"name": "residential",
"node": 13
},
{
"group": "mixed",
"name": "transport",
"node": 14
},
{
"group": "renew",
"name": "geothermal supply",
"node": 15
},
{
"group": "renew",
"name": "geothermal production",
"node": 16
},
{
"group": "renew",
"name": "hydro supply",
"node": 17
},
{
"group": "renew",
"name": "hydro production",
"node": 18
},
{
"group": "renew",
"name": "woody biomass supply",
"node": 19
},
{
"group": "renew",
"name": "woody biomass production",
"node": 20
},
{
"group": "renew",
"name": "renew demand",
"node": 21
},
{
"group": "renew",
"name": "wind supply",
"node": 22
},
{
"group": "renew",
"name": "wind production",
"node": 23
},
{
"group": "renew",
"name": "biogas supply",
"node": 24
},
{
"group": "renew",
"name": "biogas production",
"node": 25
},
{
"group": "renew",
"name": "sludge biogas production",
"node": 26
},
{
"group": "renew",
"name": "solar supply",
"node": 27
},
{
"group": "renew",
"name": "solar production",
"node": 28
},
{
"group": "renew",
"name": "liquid biofuels supply",
"node": 29
},
{
"group": "renew",
"name": "liquid biofuels production",
"node": 30
},
{
"group": "renew",
"name": "landfill biogas production",
"node": 31
},
{
"group": "gas",
"name": "gas gross production",
"node": 32
},
{
"group": "gas",
"name": "gas net production",
"node": 33
},
{
"group": "gas",
"name": "gas reinjected",
"node": 34
},
{
"group": "gas",
"name": "lpg extracted",
"node": 35
},
{
"group": "gas",
"name": "gas stock change",
"node": 36
},
{
"group": "mixed",
"name": "electricity generation",
"node": 37
},
{
"group": "electricity",
"name": "lines losses",
"node": 38
},
{
"group": "electricity",
"name": "unallocated",
"node": 39
},
{
"group": "oil",
"name": "oil production",
"node": 40
},
{
"group": "oil",
"name": "oil primary",
"node": 41
},
{
"group": "oil",
"name": "oil imports",
"node": 42
},
{
"group": "oil",
"name": "oil supply",
"node": 43
},
{
"group": "oil",
"name": "fuel production",
"node": 44
},
{
"group": "oil",
"name": "losses and own use",
"node": 45
},
{
"group": "oil",
"name": "non-energy use",
"node": 46
},
{
"group": "oil",
"name": "stat diff",
"node": 47
},
{
"group": "oil",
"name": "oil exports",
"node": 48
},
{
"group": "oil",
"name": "oil stock change",
"node": 49
},
{
"group": "oil",
"name": "international transport",
"node": 50
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment