Comparing version 0.4.2 to 0.5.0
@@ -1,98 +0,112 @@ | ||
// https://github.com/d3/d3-sankey Version 0.4.2. Copyright 2017 Mike Bostock. | ||
// https://github.com/d3/d3-sankey Version 0.5.0. Copyright 2017 Mike Bostock. | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-interpolate')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-interpolate'], 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,d3Interpolate) { 'use strict'; | ||
}(this, (function (exports,d3Array,d3Collection,d3Shape) { 'use strict'; | ||
function constant(x) { | ||
return function() { | ||
return x; | ||
}; | ||
} | ||
function ascendingSourceDepth(a, b) { | ||
return a.source.y - b.source.y; | ||
} | ||
function ascendingTargetDepth(a, b) { | ||
return a.target.y - b.target.y; | ||
} | ||
function ascendingDepth(a, b) { | ||
return a.y - b.y; | ||
} | ||
function value(d) { | ||
return d.value; | ||
} | ||
function nodeCenter(node) { | ||
return node.y + node.dy / 2; | ||
} | ||
function weightedSource(link) { | ||
return nodeCenter(link.source) * link.value; | ||
} | ||
function weightedTarget(link) { | ||
return nodeCenter(link.target) * link.value; | ||
} | ||
function defaultNodes(graph) { | ||
return graph.nodes; | ||
} | ||
function defaultLinks(graph) { | ||
return graph.links; | ||
} | ||
var sankey = function() { | ||
var sankey = {}, | ||
nodeWidth = 24, | ||
nodePadding = 8, | ||
size = [1, 1], | ||
nodes = [], | ||
links = []; | ||
var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent | ||
dx = 24, // nodeWidth | ||
py = 8, // nodePadding | ||
nodes = defaultNodes, | ||
links = defaultLinks, | ||
iterations = 32; | ||
function sankey() { | ||
var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)}; | ||
computeNodeLinks(graph); | ||
computeNodeValues(graph); | ||
computeNodeBreadths(graph); | ||
computeNodeDepths(graph, iterations); | ||
computeLinkDepths(graph); | ||
return graph; | ||
} | ||
sankey.update = function(graph) { | ||
computeLinkDepths(graph); | ||
return graph; | ||
}; | ||
sankey.nodeWidth = function(_) { | ||
if (!arguments.length) return nodeWidth; | ||
nodeWidth = +_; | ||
return sankey; | ||
return arguments.length ? (dx = +_, sankey) : dx; | ||
}; | ||
sankey.nodePadding = function(_) { | ||
if (!arguments.length) return nodePadding; | ||
nodePadding = +_; | ||
return sankey; | ||
return arguments.length ? (py = +_, sankey) : py; | ||
}; | ||
sankey.nodes = function(_) { | ||
if (!arguments.length) return nodes; | ||
nodes = _; | ||
return sankey; | ||
return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes; | ||
}; | ||
sankey.links = function(_) { | ||
if (!arguments.length) return links; | ||
links = _; | ||
return sankey; | ||
return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links; | ||
}; | ||
sankey.size = function(_) { | ||
if (!arguments.length) return size; | ||
size = _; | ||
return sankey; | ||
return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0]; | ||
}; | ||
sankey.layout = function(iterations) { | ||
computeNodeLinks(); | ||
computeNodeValues(); | ||
computeNodeBreadths(); | ||
computeNodeDepths(iterations); | ||
computeLinkDepths(); | ||
return sankey; | ||
sankey.extent = function(_) { | ||
return arguments.length ? (x0 = +_[0][0], x1 = +_[1][0], y0 = +_[0][1], y1 = +_[1][1], sankey) : [[x0, y0], [x1, y1]]; | ||
}; | ||
sankey.relayout = function() { | ||
computeLinkDepths(); | ||
return sankey; | ||
sankey.iterations = function(_) { | ||
return arguments.length ? (iterations = +_, sankey) : iterations; | ||
}; | ||
sankey.link = function() { | ||
var curvature = .5; | ||
function link(d) { | ||
var x0 = d.source.x + d.source.dx, | ||
x1 = d.target.x, | ||
xi = d3Interpolate.interpolateNumber(x0, x1), | ||
x2 = xi(curvature), | ||
x3 = xi(1 - curvature), | ||
y0 = d.source.y + d.sy + d.dy / 2, | ||
y1 = d.target.y + d.ty + d.dy / 2; | ||
return "M" + x0 + "," + y0 | ||
+ "C" + x2 + "," + y0 | ||
+ " " + x3 + "," + y1 | ||
+ " " + x1 + "," + y1; | ||
} | ||
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) { | ||
function computeNodeLinks(graph) { | ||
graph.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]; | ||
graph.links.forEach(function(link) { | ||
var source = link.source, target = link.target; | ||
if (typeof source === "number") source = link.source = graph.nodes[link.source]; | ||
if (typeof target === "number") target = link.target = graph.nodes[link.target]; | ||
source.sourceLinks.push(link); | ||
@@ -104,4 +118,4 @@ target.targetLinks.push(link); | ||
// Compute the value (size) of each node by summing the associated links. | ||
function computeNodeValues() { | ||
nodes.forEach(function(node) { | ||
function computeNodeValues(graph) { | ||
graph.nodes.forEach(function(node) { | ||
node.value = Math.max( | ||
@@ -118,4 +132,4 @@ d3Array.sum(node.sourceLinks, value), | ||
// nodes with no outgoing links are assigned the maximum breadth. | ||
function computeNodeBreadths() { | ||
var remainingNodes = nodes, | ||
function computeNodeBreadths(graph) { | ||
var remainingNodes = graph.nodes, | ||
nextNodes, | ||
@@ -128,3 +142,3 @@ x = 0; | ||
node.x = x; | ||
node.dx = nodeWidth; | ||
node.dx = dx; | ||
node.sourceLinks.forEach(function(link) { | ||
@@ -141,8 +155,8 @@ if (nextNodes.indexOf(link.target) < 0) { | ||
// | ||
moveSinksRight(x); | ||
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1)); | ||
moveSinksRight(graph, x); | ||
scaleNodeBreadths(graph, (x1 - x0 - dx) / (x - 1)); | ||
} | ||
// function moveSourcesRight() { | ||
// nodes.forEach(function(node) { | ||
// function moveSourcesRight(graph) { | ||
// graph.nodes.forEach(function(node) { | ||
// if (!node.targetLinks.length) { | ||
@@ -154,4 +168,4 @@ // node.x = min(node.sourceLinks, function(d) { return d.target.x; }) - 1; | ||
function moveSinksRight(x) { | ||
nodes.forEach(function(node) { | ||
function moveSinksRight(graph, x) { | ||
graph.nodes.forEach(function(node) { | ||
if (!node.sourceLinks.length) { | ||
@@ -163,13 +177,13 @@ node.x = x - 1; | ||
function scaleNodeBreadths(kx) { | ||
nodes.forEach(function(node) { | ||
node.x *= kx; | ||
function scaleNodeBreadths(graph, kx) { | ||
graph.nodes.forEach(function(node) { | ||
node.x = x0 + node.x * kx; | ||
}); | ||
} | ||
function computeNodeDepths(iterations) { | ||
function computeNodeDepths(graph) { | ||
var nodesByBreadth = d3Collection.nest() | ||
.key(function(d) { return d.x; }) | ||
.sortKeys(d3Array.ascending) | ||
.entries(nodes) | ||
.entries(graph.nodes) | ||
.map(function(d) { return d.values; }); | ||
@@ -180,4 +194,4 @@ | ||
resolveCollisions(); | ||
for (var alpha = 1; iterations > 0; --iterations) { | ||
relaxRightToLeft(alpha *= .99); | ||
for (var alpha = 1, n = iterations; n > 0; --n) { | ||
relaxRightToLeft(alpha *= 0.99); | ||
resolveCollisions(); | ||
@@ -190,3 +204,3 @@ relaxLeftToRight(alpha); | ||
var ky = d3Array.min(nodesByBreadth, function(nodes) { | ||
return (size[1] - (nodes.length - 1) * nodePadding) / d3Array.sum(nodes, value); | ||
return (y1 - y0 - (nodes.length - 1) * py) / d3Array.sum(nodes, value); | ||
}); | ||
@@ -201,3 +215,3 @@ | ||
links.forEach(function(link) { | ||
graph.links.forEach(function(link) { | ||
link.dy = link.value * ky; | ||
@@ -211,11 +225,6 @@ }); | ||
if (node.targetLinks.length) { | ||
var y = d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value); | ||
node.y += (y - center(node)) * alpha; | ||
node.y += (d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value) - nodeCenter(node)) * alpha; | ||
} | ||
}); | ||
}); | ||
function weightedSource(link) { | ||
return center(link.source) * link.value; | ||
} | ||
} | ||
@@ -227,11 +236,6 @@ | ||
if (node.sourceLinks.length) { | ||
var y = d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value); | ||
node.y += (y - center(node)) * alpha; | ||
node.y += (d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value) - nodeCenter(node)) * alpha; | ||
} | ||
}); | ||
}); | ||
function weightedTarget(link) { | ||
return center(link.target) * link.value; | ||
} | ||
} | ||
@@ -243,3 +247,3 @@ | ||
dy, | ||
y0 = 0, | ||
y = y0, | ||
n = nodes.length, | ||
@@ -252,11 +256,11 @@ i; | ||
node = nodes[i]; | ||
dy = y0 - node.y; | ||
dy = y - node.y; | ||
if (dy > 0) node.y += dy; | ||
y0 = node.y + node.dy + nodePadding; | ||
y = node.y + node.dy + py; | ||
} | ||
// If the bottommost node goes outside the bounds, push it back up. | ||
dy = y0 - nodePadding - size[1]; | ||
dy = y - py - y1; | ||
if (dy > 0) { | ||
y0 = node.y -= dy; | ||
y = node.y -= dy; | ||
@@ -266,5 +270,5 @@ // Push any overlapping nodes back up. | ||
node = nodes[i]; | ||
dy = node.y + node.dy + nodePadding - y0; | ||
dy = node.y + node.dy + py - y; | ||
if (dy > 0) node.y -= dy; | ||
y0 = node.y; | ||
y = node.y; | ||
} | ||
@@ -274,14 +278,10 @@ } | ||
} | ||
function ascendingDepth(a, b) { | ||
return a.y - b.y; | ||
} | ||
} | ||
function computeLinkDepths() { | ||
nodes.forEach(function(node) { | ||
function computeLinkDepths(graph) { | ||
graph.nodes.forEach(function(node) { | ||
node.sourceLinks.sort(ascendingTargetDepth); | ||
node.targetLinks.sort(ascendingSourceDepth); | ||
}); | ||
nodes.forEach(function(node) { | ||
graph.nodes.forEach(function(node) { | ||
var sy = 0, ty = 0; | ||
@@ -297,24 +297,23 @@ node.sourceLinks.forEach(function(link) { | ||
}); | ||
} | ||
function ascendingSourceDepth(a, b) { | ||
return a.source.y - b.source.y; | ||
} | ||
return sankey; | ||
}; | ||
function ascendingTargetDepth(a, b) { | ||
return a.target.y - b.target.y; | ||
} | ||
} | ||
function horizontalSource(d) { | ||
return [d.source.x + d.source.dx, d.source.y + d.sy + d.dy / 2]; | ||
} | ||
function center(node) { | ||
return node.y + node.dy / 2; | ||
} | ||
function horizontalTarget(d) { | ||
return [d.target.x, d.target.y + d.ty + d.dy / 2]; | ||
} | ||
function value(link) { | ||
return link.value; | ||
} | ||
return sankey; | ||
var sankeyLinkHorizontal = function() { | ||
return d3Shape.linkHorizontal() | ||
.source(horizontalSource) | ||
.target(horizontalTarget); | ||
}; | ||
exports.sankey = sankey; | ||
exports.sankeyLinkHorizontal = sankeyLinkHorizontal; | ||
@@ -321,0 +320,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
@@ -1,2 +0,2 @@ | ||
// https://github.com/d3/d3-sankey Version 0.4.2. Copyright 2017 Mike Bostock. | ||
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("d3-array"),require("d3-collection"),require("d3-interpolate")):"function"==typeof define&&define.amd?define(["exports","d3-array","d3-collection","d3-interpolate"],t):t(n.d3=n.d3||{},n.d3,n.d3,n.d3)}(this,function(n,t,r,e){"use strict";var u=function(){function n(){v.forEach(function(n){n.sourceLinks=[],n.targetLinks=[]}),k.forEach(function(n){var t=n.source,r=n.target;"number"==typeof t&&(t=n.source=v[n.source]),"number"==typeof r&&(r=n.target=v[n.target]),t.sourceLinks.push(n),r.targetLinks.push(n)})}function u(){v.forEach(function(n){n.value=Math.max(t.sum(n.sourceLinks,y),t.sum(n.targetLinks,y))})}function o(){for(var n,t=v,r=0;t.length;)n=[],t.forEach(function(t){t.x=r,t.dx=h,t.sourceLinks.forEach(function(t){n.indexOf(t.target)<0&&n.push(t.target)})}),t=n,++r;c(r),i((g[0]-h)/(r-1))}function c(n){v.forEach(function(t){t.sourceLinks.length||(t.x=n-1)})}function i(n){v.forEach(function(t){t.x*=n})}function f(n){function e(){var n=t.min(f,function(n){return(g[1]-(n.length-1)*l)/t.sum(n,y)});f.forEach(function(t){t.forEach(function(t,r){t.y=r,t.dy=t.value*n})}),k.forEach(function(t){t.dy=t.value*n})}function u(n){function r(n){return s(n.source)*n.value}f.forEach(function(e){e.forEach(function(e){if(e.targetLinks.length){var u=t.sum(e.targetLinks,r)/t.sum(e.targetLinks,y);e.y+=(u-s(e))*n}})})}function o(n){function r(n){return s(n.target)*n.value}f.slice().reverse().forEach(function(e){e.forEach(function(e){if(e.sourceLinks.length){var u=t.sum(e.sourceLinks,r)/t.sum(e.sourceLinks,y);e.y+=(u-s(e))*n}})})}function c(){f.forEach(function(n){var t,r,e,u=0,o=n.length;for(n.sort(i),e=0;e<o;++e)t=n[e],r=u-t.y,r>0&&(t.y+=r),u=t.y+t.dy+l;if(r=u-l-g[1],r>0)for(u=t.y-=r,e=o-2;e>=0;--e)t=n[e],r=t.y+t.dy+l-u,r>0&&(t.y-=r),u=t.y})}function i(n,t){return n.y-t.y}var f=r.nest().key(function(n){return n.x}).sortKeys(t.ascending).entries(v).map(function(n){return n.values});e(),c();for(var a=1;n>0;--n)o(a*=.99),c(),u(a),c()}function a(){function n(n,t){return n.source.y-t.source.y}function t(n,t){return n.target.y-t.target.y}v.forEach(function(r){r.sourceLinks.sort(t),r.targetLinks.sort(n)}),v.forEach(function(n){var t=0,r=0;n.sourceLinks.forEach(function(n){n.sy=t,t+=n.dy}),n.targetLinks.forEach(function(n){n.ty=r,r+=n.dy})})}function s(n){return n.y+n.dy/2}function y(n){return n.value}var d={},h=24,l=8,g=[1,1],v=[],k=[];return d.nodeWidth=function(n){return arguments.length?(h=+n,d):h},d.nodePadding=function(n){return arguments.length?(l=+n,d):l},d.nodes=function(n){return arguments.length?(v=n,d):v},d.links=function(n){return arguments.length?(k=n,d):k},d.size=function(n){return arguments.length?(g=n,d):g},d.layout=function(t){return n(),u(),o(),f(t),a(),d},d.relayout=function(){return a(),d},d.link=function(){function n(n){var r=n.source.x+n.source.dx,u=n.target.x,o=e.interpolateNumber(r,u),c=o(t),i=o(1-t),f=n.source.y+n.sy+n.dy/2,a=n.target.y+n.ty+n.dy/2;return"M"+r+","+f+"C"+c+","+f+" "+i+","+a+" "+u+","+a}var t=.5;return n.curvature=function(r){return arguments.length?(t=+r,n):t},n},d};n.sankey=u,Object.defineProperty(n,"__esModule",{value:!0})}); | ||
// https://github.com/d3/d3-sankey Version 0.5.0. Copyright 2017 Mike Bostock. | ||
!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("d3-array"),require("d3-collection"),require("d3-shape")):"function"==typeof define&&define.amd?define(["exports","d3-array","d3-collection","d3-shape"],t):t(n.d3=n.d3||{},n.d3,n.d3,n.d3)}(this,function(n,t,e,r){"use strict";function o(n){return function(){return n}}function u(n,t){return n.source.y-t.source.y}function i(n,t){return n.target.y-t.target.y}function c(n,t){return n.y-t.y}function f(n){return n.value}function s(n){return n.y+n.dy/2}function a(n){return s(n.source)*n.value}function d(n){return s(n.target)*n.value}function y(n){return n.nodes}function l(n){return n.links}function h(n){return[n.source.x+n.source.dx,n.source.y+n.sy+n.dy/2]}function g(n){return[n.target.x,n.target.y+n.ty+n.dy/2]}var k=function(){function n(){var n={nodes:j.apply(null,arguments),links:H.apply(null,arguments)};return r(n),h(n),g(n),v(n),E(n),n}function r(n){n.nodes.forEach(function(n){n.sourceLinks=[],n.targetLinks=[]}),n.links.forEach(function(t){var e=t.source,r=t.target;"number"==typeof e&&(e=t.source=n.nodes[t.source]),"number"==typeof r&&(r=t.target=n.nodes[t.target]),e.sourceLinks.push(t),r.targetLinks.push(t)})}function h(n){n.nodes.forEach(function(n){n.value=Math.max(t.sum(n.sourceLinks,f),t.sum(n.targetLinks,f))})}function g(n){for(var t,e=n.nodes,r=0;e.length;)t=[],e.forEach(function(n){n.x=r,n.dx=q,n.sourceLinks.forEach(function(n){t.indexOf(n.target)<0&&t.push(n.target)})}),e=t,++r;k(n,r),p(n,(m-L-q)/(r-1))}function k(n,t){n.nodes.forEach(function(n){n.sourceLinks.length||(n.x=t-1)})}function p(n,t){n.nodes.forEach(function(n){n.x=L+n.x*t})}function v(n){function r(){o.forEach(function(n){var t,e,r,o=x,u=n.length;for(n.sort(c),r=0;r<u;++r)t=n[r],e=o-t.y,e>0&&(t.y+=e),o=t.y+t.dy+z;if((e=o-z-b)>0)for(o=t.y-=e,r=u-2;r>=0;--r)t=n[r],e=t.y+t.dy+z-o,e>0&&(t.y-=e),o=t.y})}var o=e.nest().key(function(n){return n.x}).sortKeys(t.ascending).entries(n.nodes).map(function(n){return n.values});!function(){var e=t.min(o,function(n){return(b-x-(n.length-1)*z)/t.sum(n,f)});o.forEach(function(n){n.forEach(function(n,t){n.y=t,n.dy=n.value*e})}),n.links.forEach(function(n){n.dy=n.value*e})}(),r();for(var u=1,i=M;i>0;--i)!function(n){o.slice().reverse().forEach(function(e){e.forEach(function(e){e.sourceLinks.length&&(e.y+=(t.sum(e.sourceLinks,d)/t.sum(e.sourceLinks,f)-s(e))*n)})})}(u*=.99),r(),function(n){o.forEach(function(e){e.forEach(function(e){e.targetLinks.length&&(e.y+=(t.sum(e.targetLinks,a)/t.sum(e.targetLinks,f)-s(e))*n)})})}(u),r()}function E(n){n.nodes.forEach(function(n){n.sourceLinks.sort(i),n.targetLinks.sort(u)}),n.nodes.forEach(function(n){var t=0,e=0;n.sourceLinks.forEach(function(n){n.sy=t,t+=n.dy}),n.targetLinks.forEach(function(n){n.ty=e,e+=n.dy})})}var L=0,x=0,m=1,b=1,q=24,z=8,j=y,H=l,M=32;return n.update=function(n){return E(n),n},n.nodeWidth=function(t){return arguments.length?(q=+t,n):q},n.nodePadding=function(t){return arguments.length?(z=+t,n):z},n.nodes=function(t){return arguments.length?(j="function"==typeof t?t:o(t),n):j},n.links=function(t){return arguments.length?(H="function"==typeof t?t:o(t),n):H},n.size=function(t){return arguments.length?(L=x=0,m=+t[0],b=+t[1],n):[m-L,b-x]},n.extent=function(t){return arguments.length?(L=+t[0][0],m=+t[1][0],x=+t[0][1],b=+t[1][1],n):[[L,x],[m,b]]},n.iterations=function(t){return arguments.length?(M=+t,n):M},n},p=function(){return r.linkHorizontal().source(h).target(g)};n.sankey=k,n.sankeyLinkHorizontal=p,Object.defineProperty(n,"__esModule",{value:!0})}); |
export {default as sankey} from "./src/sankey"; | ||
export {default as sankeyLinkHorizontal} from "./src/sankeyLinkHorizontal"; |
{ | ||
"name": "d3-sankey", | ||
"version": "0.4.2", | ||
"version": "0.5.0", | ||
"description": "Visualize flow between nodes in a directed acyclic network.", | ||
@@ -12,8 +12,8 @@ "keywords": [ | ||
"name": "Mike Bostock", | ||
"url": "http://bost.ocks.org/mike" | ||
"url": "https://bost.ocks.org/mike/" | ||
}, | ||
"license": "BSD-3-Clause", | ||
"main": "build/d3-sankey.js", | ||
"module": "index", | ||
"jsnext:main": "index", | ||
"module": "index", | ||
"homepage": "https://github.com/d3/d3-sankey", | ||
@@ -25,3 +25,3 @@ "repository": { | ||
"scripts": { | ||
"pretest": "rm -rf build && mkdir build && rollup --banner \"$(preamble)\" -g d3-array:d3,d3-collection:d3,d3-interpolate:d3 -f umd -n d3 -o build/d3-sankey.js -- index.js", | ||
"pretest": "rm -rf build && mkdir build && rollup --banner \"$(preamble)\" -g d3-array:d3,d3-collection:d3,d3-shape:d3 -f umd -n d3 -o build/d3-sankey.js -- index.js", | ||
"test": "tape 'test/**/*-test.js' && eslint index.js src", | ||
@@ -34,7 +34,8 @@ "prepublish": "npm run test && uglifyjs --preamble \"$(preamble)\" build/d3-sankey.js -c -m -o build/d3-sankey.min.js", | ||
"d3-collection": "1", | ||
"d3-interpolate": "1" | ||
"d3-interpolate": "1", | ||
"d3-shape": "^1.2.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "3", | ||
"package-preamble": "0.0.2", | ||
"package-preamble": "0.1.0", | ||
"rollup": "0.41", | ||
@@ -41,0 +42,0 @@ "tape": "4", |
137
README.md
# d3-sankey | ||
D3 4.0 implementation of the Sankey plugin to visualize the flow between nodes in a directed acyclic network. | ||
Sankey diagrams visualize the directed flow between nodes in an acyclic network. For example, this diagram shows a possible scenario of UK energy production and consumption in 2050: | ||
[<img alt="Sankey diagram" src="https://raw.githubusercontent.com/d3/d3-sankey/master/img/energy.png" width="960" height="500">](https://bl.ocks.org/mbostock/ca9a0bb7ba204d12974bca90acc507c0) | ||
Source: Department of Energy & Climate Change, Tom Counsell. | ||
## Installing | ||
@@ -11,4 +15,7 @@ | ||
```javascript | ||
<script src="https://d3js.org/d3.v4.js"></script> | ||
```html | ||
<script src="https://unpkg.com/d3-array@1"></script> | ||
<script src="https://unpkg.com/d3-collection@1"></script> | ||
<script src="https://unpkg.com/d3-path@1"></script> | ||
<script src="https://unpkg.com/d3-shape@1"></script> | ||
<script src="https://unpkg.com/d3-sankey@0"></script> | ||
@@ -22,60 +29,128 @@ <script> | ||
## Demo | ||
Here is Mike Bostock's famous example [recreated with d3-sankey](http://bl.ocks.org/xaranke/9ada4c74a87b57ae7308). | ||
## API Reference | ||
Clone or download the block, then run `npm install` and `npm run build` to create `d3.min.js`. | ||
<a href="#sankey" name="sankey">#</a> d3.<b>sankey</b>() [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L41 "Source") | ||
## API Reference | ||
Constructs a new Sankey generator with the default settings. | ||
<a href="#sankey" name="sankey">#</a> <b>sankey</b>() | ||
<a href="#_sankey" name="_sankey">#</a> <i>sankey</i>(<i>arguments</i>…) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L49 "Source") | ||
Constructs a new sankey generator with the default settings. | ||
Computes the node and link positions for the given *arguments*, returning a *graph* representing the Sankey layout. The returned *graph* has the following properties: | ||
<a name="sankey_nodeWidth" href="#sankey_nodeWidth">#</a> <i>sankey</i>.<b>nodeWidth</b>([<i>width</i>]) | ||
* *graph*.nodes - the array of [nodes](#sankey_nodes) | ||
* *graph*.links - the array of [links](#sankey_links) | ||
If <i>width</i> is specified, sets the node width to the specified function or number and returns this sankey generator. If <i>width</i> is not specified, returns the current node width accessor, which defaults to: | ||
<a href="#sankey_update" name="sankey_update">#</a> <i>sankey</i>.<b>update</b>(<i>graph</i>) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L59 "Source") | ||
Recomputes the specified *graph*’s links’ positions, updating the following properties of each *link*: | ||
* *link*.sy - the link’s vertical starting position (at source node) | ||
* *link*.ty - the link’s vertical end position (at target node) | ||
This method is intended to be called after computing the initial [Sankey layout](#_sankey), for example when the diagram is repositioned interactively. | ||
<a name="sankey_nodes" href="#sankey_nodes">#</a> <i>sankey</i>.<b>nodes</b>([<i>nodes</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L72 "Source") | ||
If *nodes* is specified, sets the Sankey generator’s nodes accessor to the specified function or array and returns this Sankey generator. If *nodes* is not specified, returns the current nodes accessor, which defaults to: | ||
```js | ||
function nodeWidth() { | ||
return 24; | ||
function nodes(graph) { | ||
return graph.nodes; | ||
} | ||
``` | ||
<a name="sankey_nodePadding" href="#sankey_nodePadding">#</a> <i>sankey</i>.<b>nodePadding</b>([<i>padding</i>]) | ||
If *nodes* is specified as a function, the function is invoked when the Sankey layout is [generated](#_sankey), being passed any arguments passed to the Sankey generator. This function must return an array of nodes. If *nodes* is not a function, it must be a constant array of *nodes*. | ||
If <i>padding</i> is specified, sets the node padding to the specified function or number and returns this sankey generator. If <i>padding</i> is not specified, returns the current node padding accessor, which defaults to: | ||
Each *node* must be an object. The following properties are assigned by the [Sankey generator](#_sankey): | ||
* *node*.sourceLinks - the array of outgoing [links](#sankey_links) which have this node as their source | ||
* *node*.targetLinks - the array of incoming [links](#sankey_links) which have this node as their target | ||
* *node*.value - the node’s value; the sum of *link*.value for the node’s incoming [links](#sankey_links) | ||
* *node*x - the node’s horizontal position (derived from the graph topology) | ||
* *node*.dx - the node’s node width | ||
* *node*.y - the node’s vertical position | ||
* *node*.dy - the node’s height (proportional to its value) | ||
See also [*sankey*.links](#sankey_links). | ||
<a name="sankey_links" href="#sankey_links">#</a> <i>sankey</i>.<b>links</b>([<i>links</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L76 "Source") | ||
If *links* is specified, sets the Sankey generator’s links accessor to the specified function or array and returns this Sankey generator. If *links* is not specified, returns the current links accessor, which defaults to: | ||
```js | ||
function nodePadding() { | ||
return 8; | ||
function links(graph) { | ||
return graph.links; | ||
} | ||
``` | ||
Here padding refers to the vertical space between nodes that occupy the same horizontal space. | ||
<a name="sankey_nodes" href="#sankey_nodes">#</a> <i>sankey</i>.<b>nodes</b>([<i>nodes</i>]) | ||
If *links* is specified as a function, the function is invoked when the Sankey layout is [generated](#_sankey), being passed any arguments passed to the Sankey generator. This function must return an array of links. If *links* is not a function, it must be a constant array of *links*. | ||
If <i>nodes</i> is specified, sets the list of nodes to the specified function or array and returns this sankey generator. If <i>nodes</i> is not specified, returns the current accessor to the list of nodes, which defaults to: | ||
Each *link* must be an object with the following properties: | ||
* *link*.source - the link’s source [node](#sankey_nodes) | ||
* *link*.target - the link’s target [node](#sankey_nodes) | ||
* *link*.value - the link’s numeric value | ||
For convenience, a link’s source and target may be initialized using the zero-based index of corresponding node in the array of nodes passed to the [Sankey generator](#_sankey) rather than object references. The following properties are assigned to each link by the [Sankey generator](#_sankey): | ||
* *link*.dy - the link’s breadth (proportional to its value) | ||
* *link*.sy - the link’s vertical starting position (at source node) | ||
* *link*.ty - the link’s vertical end position (at target node) | ||
<a name="sankey_nodeWidth" href="#sankey_nodeWidth">#</a> <i>sankey</i>.<b>nodeWidth</b>([<i>width</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L64 "Source") | ||
If *width* is specified, sets the node width to the specified number and returns this Sankey generator. If *width* is not specified, returns the current node width, which defaults to 24. | ||
<a name="sankey_nodePadding" href="#sankey_nodePadding">#</a> <i>sankey</i>.<b>nodePadding</b>([<i>padding</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L68 "Source") | ||
If *padding* is specified, sets the vertical separation between nodes at each column to the specified number and returns this Sankey generator. If *padding* is not specified, returns the current node padding, which defaults to 8. | ||
<a name="sankey_extent" href="#sankey_extent">#</a> <i>sankey</i>.<b>extent</b>([<i>extent</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L84 "Source") | ||
If *extent* is specified, sets the extent of the Sankey layout to the specified bounds and returns the layout. The *extent* bounds are specified as an array \[\[<i>x0</i>, <i>y0</i>\], \[<i>x1</i>, <i>y1</i>\]\], where *x0* is the left side of the extent, *y0* is the top, *x1* is the right and *y1* is the bottom. If *extent* is not specified, returns the current extent which defaults to null. | ||
<a name="sankey_size" href="#sankey_size">#</a> <i>sankey</i>.<b>size</b>([<i>size</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L80 "Source") | ||
An alias for [*sankey*.extent](#sankey_extent) where the minimum *x* and *y* of the extent are ⟨0,0⟩. Equivalent to: | ||
```js | ||
function nodes() { | ||
return []; | ||
} | ||
sankey.extent([[0, 0], size]); | ||
``` | ||
<a name="sankey_links" href="#sankey_links">#</a> <i>sankey</i>.<b>links</b>([<i>links</i>]) | ||
<a name="sankey_iterations" href="#sankey_iterations">#</a> <i>sankey</i>.<b>iterations</b>([<i>iterations</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L88 "Source") | ||
If <i>links</i> is specified, sets the list of links to the specified function or array and returns this sankey generator. If <i>links</i> is not specified, returns the current accessor to the list of links, which defaults to: | ||
If *iterations* is specified, sets the number of relaxation iterations when [generating the layout](#_sankey) and returns this Sankey generator. If *iterations* is not specified, returns the current number of relaxation iterations, which defaults to 32. | ||
### Links | ||
<a name="sankeyLinkHorizontal" href="#sankeyLinkHorizontal">#</a> d3.<i>sankeyLinkHorizontal</i>() [<>](https://github.com/d3/d3-sankey/blob/master/src/sankeyLinkHorizontal.js "Source") | ||
Returns a [horizontal link shape](https://github.com/d3/d3-shape/blob/master/README.md#linkHorizontal) suitable for a Sankey diagram. The [source accessor](https://github.com/d3/d3-shape/blob/master/README.md#link_source) is defined as: | ||
```js | ||
function links() { | ||
return []; | ||
function source(d) { | ||
return [d.source.x + d.source.dx, d.source.y + d.sy + d.dy / 2]; | ||
} | ||
``` | ||
<a name="sankey_layout" href="#sankey_layout">#</a> <i>sankey</i>.<b>layout</b>([<i>iterations</i>]) | ||
The [target accessor](https://github.com/d3/d3-shape/blob/master/README.md#link_target) is defined as: | ||
Returns the current accessor to the SVG layout object. Here <i>iterations</i> is the number of times the converging function <b>computeNodeDepths</b> is run. | ||
```js | ||
function target(d) { | ||
return [d.target.x, d.target.y + d.ty + d.dy / 2]; | ||
} | ||
``` | ||
<a name="sankey_relayout" href="#sankey_relayout">#</a> <i>sankey</i>.<b>relayout</b>() | ||
For example, to render the links of a Sankey diagram in SVG, you might say: | ||
Similar to <b>layout</b> but only recalculates the depth of links. Primarily used when a node is moved vertically. | ||
```js | ||
svg.append("g") | ||
.attr("fill", "none") | ||
.attr("stroke", "#000") | ||
.attr("stroke-opacity", 0.2) | ||
.selectAll("path") | ||
.data(graph.links) | ||
.enter().append("path") | ||
.attr("d", d3.sankeyLinkHorizontal()) | ||
.attr("stroke-width", function(d) { return d.dy; }); | ||
``` |
import {ascending, min, sum} from "d3-array"; | ||
import {nest} from "d3-collection"; | ||
import {interpolateNumber} from "d3-interpolate"; | ||
import constant from "./constant"; | ||
function ascendingSourceDepth(a, b) { | ||
return a.source.y - b.source.y; | ||
} | ||
function ascendingTargetDepth(a, b) { | ||
return a.target.y - b.target.y; | ||
} | ||
function ascendingDepth(a, b) { | ||
return a.y - b.y; | ||
} | ||
function value(d) { | ||
return d.value; | ||
} | ||
function nodeCenter(node) { | ||
return node.y + node.dy / 2; | ||
} | ||
function weightedSource(link) { | ||
return nodeCenter(link.source) * link.value; | ||
} | ||
function weightedTarget(link) { | ||
return nodeCenter(link.target) * link.value; | ||
} | ||
function defaultNodes(graph) { | ||
return graph.nodes; | ||
} | ||
function defaultLinks(graph) { | ||
return graph.links; | ||
} | ||
export default function() { | ||
var sankey = {}, | ||
nodeWidth = 24, | ||
nodePadding = 8, | ||
size = [1, 1], | ||
nodes = [], | ||
links = []; | ||
var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent | ||
dx = 24, // nodeWidth | ||
py = 8, // nodePadding | ||
nodes = defaultNodes, | ||
links = defaultLinks, | ||
iterations = 32; | ||
function sankey() { | ||
var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)}; | ||
computeNodeLinks(graph); | ||
computeNodeValues(graph); | ||
computeNodeBreadths(graph); | ||
computeNodeDepths(graph, iterations); | ||
computeLinkDepths(graph); | ||
return graph; | ||
} | ||
sankey.update = function(graph) { | ||
computeLinkDepths(graph); | ||
return graph; | ||
}; | ||
sankey.nodeWidth = function(_) { | ||
if (!arguments.length) return nodeWidth; | ||
nodeWidth = +_; | ||
return sankey; | ||
return arguments.length ? (dx = +_, sankey) : dx; | ||
}; | ||
sankey.nodePadding = function(_) { | ||
if (!arguments.length) return nodePadding; | ||
nodePadding = +_; | ||
return sankey; | ||
return arguments.length ? (py = +_, sankey) : py; | ||
}; | ||
sankey.nodes = function(_) { | ||
if (!arguments.length) return nodes; | ||
nodes = _; | ||
return sankey; | ||
return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes; | ||
}; | ||
sankey.links = function(_) { | ||
if (!arguments.length) return links; | ||
links = _; | ||
return sankey; | ||
return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links; | ||
}; | ||
sankey.size = function(_) { | ||
if (!arguments.length) return size; | ||
size = _; | ||
return sankey; | ||
return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0]; | ||
}; | ||
sankey.layout = function(iterations) { | ||
computeNodeLinks(); | ||
computeNodeValues(); | ||
computeNodeBreadths(); | ||
computeNodeDepths(iterations); | ||
computeLinkDepths(); | ||
return sankey; | ||
sankey.extent = function(_) { | ||
return arguments.length ? (x0 = +_[0][0], x1 = +_[1][0], y0 = +_[0][1], y1 = +_[1][1], sankey) : [[x0, y0], [x1, y1]]; | ||
}; | ||
sankey.relayout = function() { | ||
computeLinkDepths(); | ||
return sankey; | ||
sankey.iterations = function(_) { | ||
return arguments.length ? (iterations = +_, sankey) : iterations; | ||
}; | ||
sankey.link = function() { | ||
var curvature = .5; | ||
function link(d) { | ||
var x0 = d.source.x + d.source.dx, | ||
x1 = d.target.x, | ||
xi = interpolateNumber(x0, x1), | ||
x2 = xi(curvature), | ||
x3 = xi(1 - curvature), | ||
y0 = d.source.y + d.sy + d.dy / 2, | ||
y1 = d.target.y + d.ty + d.dy / 2; | ||
return "M" + x0 + "," + y0 | ||
+ "C" + x2 + "," + y0 | ||
+ " " + x3 + "," + y1 | ||
+ " " + x1 + "," + y1; | ||
} | ||
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) { | ||
function computeNodeLinks(graph) { | ||
graph.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]; | ||
graph.links.forEach(function(link) { | ||
var source = link.source, target = link.target; | ||
if (typeof source === "number") source = link.source = graph.nodes[link.source]; | ||
if (typeof target === "number") target = link.target = graph.nodes[link.target]; | ||
source.sourceLinks.push(link); | ||
@@ -101,4 +109,4 @@ target.targetLinks.push(link); | ||
// Compute the value (size) of each node by summing the associated links. | ||
function computeNodeValues() { | ||
nodes.forEach(function(node) { | ||
function computeNodeValues(graph) { | ||
graph.nodes.forEach(function(node) { | ||
node.value = Math.max( | ||
@@ -115,4 +123,4 @@ sum(node.sourceLinks, value), | ||
// nodes with no outgoing links are assigned the maximum breadth. | ||
function computeNodeBreadths() { | ||
var remainingNodes = nodes, | ||
function computeNodeBreadths(graph) { | ||
var remainingNodes = graph.nodes, | ||
nextNodes, | ||
@@ -125,3 +133,3 @@ x = 0; | ||
node.x = x; | ||
node.dx = nodeWidth; | ||
node.dx = dx; | ||
node.sourceLinks.forEach(function(link) { | ||
@@ -138,8 +146,8 @@ if (nextNodes.indexOf(link.target) < 0) { | ||
// | ||
moveSinksRight(x); | ||
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1)); | ||
moveSinksRight(graph, x); | ||
scaleNodeBreadths(graph, (x1 - x0 - dx) / (x - 1)); | ||
} | ||
// function moveSourcesRight() { | ||
// nodes.forEach(function(node) { | ||
// function moveSourcesRight(graph) { | ||
// graph.nodes.forEach(function(node) { | ||
// if (!node.targetLinks.length) { | ||
@@ -151,4 +159,4 @@ // node.x = min(node.sourceLinks, function(d) { return d.target.x; }) - 1; | ||
function moveSinksRight(x) { | ||
nodes.forEach(function(node) { | ||
function moveSinksRight(graph, x) { | ||
graph.nodes.forEach(function(node) { | ||
if (!node.sourceLinks.length) { | ||
@@ -160,13 +168,13 @@ node.x = x - 1; | ||
function scaleNodeBreadths(kx) { | ||
nodes.forEach(function(node) { | ||
node.x *= kx; | ||
function scaleNodeBreadths(graph, kx) { | ||
graph.nodes.forEach(function(node) { | ||
node.x = x0 + node.x * kx; | ||
}); | ||
} | ||
function computeNodeDepths(iterations) { | ||
function computeNodeDepths(graph) { | ||
var nodesByBreadth = nest() | ||
.key(function(d) { return d.x; }) | ||
.sortKeys(ascending) | ||
.entries(nodes) | ||
.entries(graph.nodes) | ||
.map(function(d) { return d.values; }); | ||
@@ -177,4 +185,4 @@ | ||
resolveCollisions(); | ||
for (var alpha = 1; iterations > 0; --iterations) { | ||
relaxRightToLeft(alpha *= .99); | ||
for (var alpha = 1, n = iterations; n > 0; --n) { | ||
relaxRightToLeft(alpha *= 0.99); | ||
resolveCollisions(); | ||
@@ -187,3 +195,3 @@ relaxLeftToRight(alpha); | ||
var ky = min(nodesByBreadth, function(nodes) { | ||
return (size[1] - (nodes.length - 1) * nodePadding) / sum(nodes, value); | ||
return (y1 - y0 - (nodes.length - 1) * py) / sum(nodes, value); | ||
}); | ||
@@ -198,3 +206,3 @@ | ||
links.forEach(function(link) { | ||
graph.links.forEach(function(link) { | ||
link.dy = link.value * ky; | ||
@@ -208,11 +216,6 @@ }); | ||
if (node.targetLinks.length) { | ||
var y = sum(node.targetLinks, weightedSource) / sum(node.targetLinks, value); | ||
node.y += (y - center(node)) * alpha; | ||
node.y += (sum(node.targetLinks, weightedSource) / sum(node.targetLinks, value) - nodeCenter(node)) * alpha; | ||
} | ||
}); | ||
}); | ||
function weightedSource(link) { | ||
return center(link.source) * link.value; | ||
} | ||
} | ||
@@ -224,11 +227,6 @@ | ||
if (node.sourceLinks.length) { | ||
var y = sum(node.sourceLinks, weightedTarget) / sum(node.sourceLinks, value); | ||
node.y += (y - center(node)) * alpha; | ||
node.y += (sum(node.sourceLinks, weightedTarget) / sum(node.sourceLinks, value) - nodeCenter(node)) * alpha; | ||
} | ||
}); | ||
}); | ||
function weightedTarget(link) { | ||
return center(link.target) * link.value; | ||
} | ||
} | ||
@@ -240,3 +238,3 @@ | ||
dy, | ||
y0 = 0, | ||
y = y0, | ||
n = nodes.length, | ||
@@ -249,11 +247,11 @@ i; | ||
node = nodes[i]; | ||
dy = y0 - node.y; | ||
dy = y - node.y; | ||
if (dy > 0) node.y += dy; | ||
y0 = node.y + node.dy + nodePadding; | ||
y = node.y + node.dy + py; | ||
} | ||
// If the bottommost node goes outside the bounds, push it back up. | ||
dy = y0 - nodePadding - size[1]; | ||
dy = y - py - y1; | ||
if (dy > 0) { | ||
y0 = node.y -= dy; | ||
y = node.y -= dy; | ||
@@ -263,5 +261,5 @@ // Push any overlapping nodes back up. | ||
node = nodes[i]; | ||
dy = node.y + node.dy + nodePadding - y0; | ||
dy = node.y + node.dy + py - y; | ||
if (dy > 0) node.y -= dy; | ||
y0 = node.y; | ||
y = node.y; | ||
} | ||
@@ -271,14 +269,10 @@ } | ||
} | ||
function ascendingDepth(a, b) { | ||
return a.y - b.y; | ||
} | ||
} | ||
function computeLinkDepths() { | ||
nodes.forEach(function(node) { | ||
function computeLinkDepths(graph) { | ||
graph.nodes.forEach(function(node) { | ||
node.sourceLinks.sort(ascendingTargetDepth); | ||
node.targetLinks.sort(ascendingSourceDepth); | ||
}); | ||
nodes.forEach(function(node) { | ||
graph.nodes.forEach(function(node) { | ||
var sy = 0, ty = 0; | ||
@@ -294,21 +288,5 @@ node.sourceLinks.forEach(function(link) { | ||
}); | ||
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; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
245246
12
155
0
4
515
+ Addedd3-shape@^1.2.0
+ Addedd3-path@1.0.9(transitive)
+ Addedd3-shape@1.3.7(transitive)