diff --git a/README.md b/README.md index 17eb86d..5a91298 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,6 @@ var sankey = d3.sankey(); ``` -## API Reference - # d3.sankey() [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L52 "Source") Constructs a new Sankey generator with the default settings. diff --git a/build/d3-sankey.js b/build/d3-sankey.js new file mode 100644 index 0000000..4701c8f --- /dev/null +++ b/build/d3-sankey.js @@ -0,0 +1,344 @@ +// https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2019 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, + maxPaddedSpace = 2 / 3; // Defined as a fraction of the total available space + + 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 L = d3Array.max(columns, function(nodes) { + return nodes.length; + }); + var maxNodePadding = maxPaddedSpace * (y1 - y0) / (L - 1); + if(py > maxNodePadding) py = maxNodePadding; + 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 }); + +}))); diff --git a/build/d3-sankey.min.js b/build/d3-sankey.min.js new file mode 100644 index 0000000..5d87e18 --- /dev/null +++ b/build/d3-sankey.min.js @@ -0,0 +1,2 @@ +// https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2019 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,g,k,t){"use strict";function e(n){return n.target.depth}function p(n,t){return n.sourceLinks.length?n.depth:t-1}function v(n){return function(){return n}}function L(n,t){return E(n.source,t.source)||n.index-t.index}function x(n,t){return E(n.target,t.target)||n.index-t.index}function E(n,t){return n.y0-t.y0}function m(n){return n.value}function w(n){return(n.y0+n.y1)/2}function M(n){return w(n.source)*n.value}function b(n){return w(n.target)*n.value}function j(n){return n.index}function q(n){return n.nodes}function z(n){return n.links}function O(n,t){var e=n.get(t);if(!e)throw new Error("missing: "+t);return e}function r(n){return[n.source.x1,n.y0]}function o(n){return[n.target.x0,n.y1]}n.sankey=function(){var u=0,c=0,i=1,f=1,s=24,a=8,t=j,h=p,e=q,r=z,d=32,l=2/3;function o(){var n={nodes:e.apply(null,arguments),links:r.apply(null,arguments)};return function(n){n.nodes.forEach(function(n,t){n.index=t,n.sourceLinks=[],n.targetLinks=[]});var o=k.map(n.nodes,t);n.links.forEach(function(n,t){n.index=t;var e=n.source,r=n.target;"object"!=typeof e&&(e=n.source=O(o,e)),"object"!=typeof r&&(r=n.target=O(o,r)),e.sourceLinks.push(n),r.targetLinks.push(n)})}(n),n.nodes.forEach(function(n){n.value=Math.max(g.sum(n.sourceLinks,m),g.sum(n.targetLinks,m))}),function(n){var t,e,r;for(t=n.nodes,e=[],r=0;t.length;++r,t=e,e=[])t.forEach(function(n){n.depth=r,n.sourceLinks.forEach(function(n){e.indexOf(n.target)<0&&e.push(n.target)})});for(t=n.nodes,e=[],r=0;t.length;++r,t=e,e=[])t.forEach(function(n){n.height=r,n.targetLinks.forEach(function(n){e.indexOf(n.source)<0&&e.push(n.source)})});var o=(i-u-s)/(r-1);n.nodes.forEach(function(n){n.x1=(n.x0=u+Math.max(0,Math.min(r-1,Math.floor(h.call(null,n,r))))*o)+s})}(n),function(r){var o=k.nest().key(function(n){return n.x0}).sortKeys(g.ascending).entries(r.nodes).map(function(n){return n.values});(function(){var n=g.max(o,function(n){return n.length}),t=l*(f-c)/(n-1);t maxNodePadding) py = maxNodePadding; var ky = min(columns, function(nodes) { return (y1 - y0 - (nodes.length - 1) * py) / sum(nodes, value); });