// https://github.com/d3/d3-sankey v0.12.3 Copyright 2019 Mike Bostock (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-shape')) : typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-shape'], factory) : (global = global || self, factory(global.d3 = global.d3 || {}, global.d3, global.d3)); }(this, function (exports, d3Array, 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 defaultId(d) { return d.index; } function defaultNodes(graph) { return graph.nodes; } function defaultLinks(graph) { return graph.links; } function find(nodeById, id) { const node = nodeById.get(id); if (!node) throw new Error("missing: " + id); return node; } function computeLinkBreadths({nodes}) { for (const node of nodes) { let y0 = node.y0; let y1 = y0; for (const link of node.sourceLinks) { link.y0 = y0 + link.width / 2; y0 += link.width; } for (const link of node.targetLinks) { link.y1 = y1 + link.width / 2; y1 += link.width; } } } function Sankey() { let x0 = 0, y0 = 0, x1 = 1, y1 = 1; // extent let dx = 24; // nodeWidth let dy = 8, py; // nodePadding let id = defaultId; let align = justify; let sort; let linkSort; let nodes = defaultNodes; let links = defaultLinks; let iterations = 6; function sankey() { const graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)}; computeNodeLinks(graph); computeNodeValues(graph); computeNodeDepths(graph); computeNodeHeights(graph); computeNodeBreadths(graph); 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.nodeSort = function(_) { return arguments.length ? (sort = _, sankey) : sort; }; sankey.nodeWidth = function(_) { return arguments.length ? (dx = +_, sankey) : dx; }; sankey.nodePadding = function(_) { return arguments.length ? (dy = py = +_, sankey) : dy; }; 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.linkSort = function(_) { return arguments.length ? (linkSort = _, sankey) : linkSort; }; 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; }; function computeNodeLinks({nodes, links}) { for (const [i, node] of nodes.entries()) { node.index = i; node.sourceLinks = []; node.targetLinks = []; } const nodeById = new Map(nodes.map((d, i) => [id(d, i, nodes), d])); for (const [i, link] of links.entries()) { link.index = i; let {source, target} = link; 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); } if (linkSort != null) { for (const {sourceLinks, targetLinks} of nodes) { sourceLinks.sort(linkSort); targetLinks.sort(linkSort); } } } function computeNodeValues({nodes}) { for (const node of nodes) { node.value = node.fixedValue === undefined ? Math.max(d3Array.sum(node.sourceLinks, value), d3Array.sum(node.targetLinks, value)) : node.fixedValue; } } function computeNodeDepths({nodes}) { const n = nodes.length; let current = new Set(nodes); let next = new Set; let x = 0; while (current.size) { for (const node of current) { node.depth = x; for (const {target} of node.sourceLinks) { next.add(target); } } if (++x > n) throw new Error("circular link"); current = next; next = new Set; } } function computeNodeHeights({nodes}) { const n = nodes.length; let current = new Set(nodes); let next = new Set; let x = 0; while (current.size) { for (const node of current) { node.height = x; for (const {source} of node.targetLinks) { next.add(source); } } if (++x > n) throw new Error("circular link"); current = next; next = new Set; } } function computeNodeLayers({nodes}) { const x = d3Array.max(nodes, d => d.depth) + 1; const kx = (x1 - x0 - dx) / (x - 1); const columns = new Array(x); for (const node of nodes) { const i = Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))); node.layer = i; node.x0 = x0 + i * kx; node.x1 = node.x0 + dx; if (columns[i]) columns[i].push(node); else columns[i] = [node]; } if (sort) for (const column of columns) { column.sort(sort); } return columns; } function initializeNodeBreadths(columns) { const ky = d3Array.min(columns, c => (y1 - y0 - (c.length - 1) * py) / d3Array.sum(c, value)); for (const nodes of columns) { let y = y0; for (const node of nodes) { node.y0 = y; node.y1 = y + node.value * ky; y = node.y1 + py; for (const link of node.sourceLinks) { link.width = link.value * ky; } } y = (y1 - y + py) / (nodes.length + 1); for (let i = 0; i < nodes.length; ++i) { const node = nodes[i]; node.y0 += y * (i + 1); node.y1 += y * (i + 1); } reorderLinks(nodes); } } function computeNodeBreadths(graph) { const columns = computeNodeLayers(graph); py = Math.min(dy, (y1 - y0) / (d3Array.max(columns, c => c.length) - 1)); initializeNodeBreadths(columns); for (let i = 0; i < iterations; ++i) { const alpha = Math.pow(0.99, i); const beta = Math.max(1 - alpha, (i + 1) / iterations); relaxRightToLeft(columns, alpha, beta); relaxLeftToRight(columns, alpha, beta); } } // Reposition each node based on its incoming (target) links. function relaxLeftToRight(columns, alpha, beta) { for (let i = 1, n = columns.length; i < n; ++i) { const column = columns[i]; for (const target of column) { let y = 0; let w = 0; for (const {source, value} of target.targetLinks) { let v = value * (target.layer - source.layer); y += targetTop(source, target) * v; w += v; } if (!(w > 0)) continue; let dy = (y / w - target.y0) * alpha; target.y0 += dy; target.y1 += dy; reorderNodeLinks(target); } if (sort === undefined) column.sort(ascendingBreadth); resolveCollisions(column, beta); } } // Reposition each node based on its outgoing (source) links. function relaxRightToLeft(columns, alpha, beta) { for (let n = columns.length, i = n - 2; i >= 0; --i) { const column = columns[i]; for (const source of column) { let y = 0; let w = 0; for (const {target, value} of source.sourceLinks) { let v = value * (target.layer - source.layer); y += sourceTop(source, target) * v; w += v; } if (!(w > 0)) continue; let dy = (y / w - source.y0) * alpha; source.y0 += dy; source.y1 += dy; reorderNodeLinks(source); } if (sort === undefined) column.sort(ascendingBreadth); resolveCollisions(column, beta); } } function resolveCollisions(nodes, alpha) { const i = nodes.length >> 1; const subject = nodes[i]; resolveCollisionsBottomToTop(nodes, subject.y0 - py, i - 1, alpha); resolveCollisionsTopToBottom(nodes, subject.y1 + py, i + 1, alpha); resolveCollisionsBottomToTop(nodes, y1, nodes.length - 1, alpha); resolveCollisionsTopToBottom(nodes, y0, 0, alpha); } // Push any overlapping nodes down. function resolveCollisionsTopToBottom(nodes, y, i, alpha) { for (; i < nodes.length; ++i) { const node = nodes[i]; const dy = (y - node.y0) * alpha; if (dy > 1e-6) node.y0 += dy, node.y1 += dy; y = node.y1 + py; } } // Push any overlapping nodes up. function resolveCollisionsBottomToTop(nodes, y, i, alpha) { for (; i >= 0; --i) { const node = nodes[i]; const dy = (node.y1 - y) * alpha; if (dy > 1e-6) node.y0 -= dy, node.y1 -= dy; y = node.y0 - py; } } function reorderNodeLinks({sourceLinks, targetLinks}) { if (linkSort === undefined) { for (const {source: {sourceLinks}} of targetLinks) { sourceLinks.sort(ascendingTargetBreadth); } for (const {target: {targetLinks}} of sourceLinks) { targetLinks.sort(ascendingSourceBreadth); } } } function reorderLinks(nodes) { if (linkSort === undefined) { for (const {sourceLinks, targetLinks} of nodes) { sourceLinks.sort(ascendingTargetBreadth); targetLinks.sort(ascendingSourceBreadth); } } } // Returns the target.y0 that would produce an ideal link from source to target. function targetTop(source, target) { let y = source.y0 - (source.sourceLinks.length - 1) * py / 2; for (const {target: node, width} of source.sourceLinks) { if (node === target) break; y += width + py; } for (const {source: node, width} of target.targetLinks) { if (node === source) break; y -= width; } return y; } // Returns the source.y0 that would produce an ideal link from source to target. function sourceTop(source, target) { let y = target.y0 - (target.targetLinks.length - 1) * py / 2; for (const {source: node, width} of target.targetLinks) { if (node === source) break; y += width + py; } for (const {target: node, width} of source.sourceLinks) { if (node === target) break; y -= width; } return y; } return sankey; } function horizontalSource(d) { return [d.source.x1, d.y0]; } function horizontalTarget(d) { return [d.target.x0, d.y1]; } function sankeyLinkHorizontal() { return d3Shape.linkHorizontal() .source(horizontalSource) .target(horizontalTarget); } exports.sankey = Sankey; exports.sankeyCenter = center; exports.sankeyJustify = justify; exports.sankeyLeft = left; exports.sankeyLinkHorizontal = sankeyLinkHorizontal; exports.sankeyRight = right; Object.defineProperty(exports, '__esModule', { value: true }); }));