// 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) { if (a.partOfCycle === b.partOfCycle) { return a.y0 - b.y0 } else { if (a.circularLinkType === 'top' || b.circularLinkType === 'bottom') { return -1 } else { return 1 } } } function value (d) { return d.value } function nodeCenter (node) { return (node.y0 + node.y1) / 2 } function linkSourceCenter (link) { return nodeCenter(link.source) } function linkTargetCenter (link) { return nodeCenter(link.target) } 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, // nodePadding scale = 1, id = defaultId, align = justify, nodes = defaultNodes, links = defaultLinks, iterations = 32 var padding = Infinity var paddingRatio = 0.1 function sankey () { var graph = { nodes: nodes.apply(null, arguments), links: links.apply(null, arguments) } computeNodeLinks(graph) identifyCircles(graph) selectCircularLinkTypes(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.scale = function (_) { return arguments.length ? ((scale = +_), sankey) : scale; } 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 } sankey.nodePaddingRatio = function (_) { return arguments.length ? ((paddingRatio = +_), sankey) : paddingRatio } // 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 var 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) and cycleness of each node by summing the associated links. function computeNodeValues (graph) { graph.nodes.forEach(function (node) { node.partOfCycle = false node.value = Math.max( d3Array.sum(node.sourceLinks, value), d3Array.sum(node.targetLinks, value) ) node.sourceLinks.forEach(function (link) { if (link.circular) { node.partOfCycle = true node.circularLinkType = link.circularLinkType } }) node.targetLinks.forEach(function (link) { if (link.circular) { node.partOfCycle = true node.circularLinkType = link.circularLinkType } }) }) } // 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 && !link.circular) { 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 && !link.circular) { 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 *= 0.99)) //resolveCollisions() relaxLeftAndRight((alpha *= 0.99)) resolveCollisions() } function initializeNodeBreadth () { columns.forEach(function (nodes) { let thisPadding = y1 * paddingRatio / (nodes.length + 1); padding = thisPadding < padding ? thisPadding : padding; }) py = padding var ky = d3Array.min(columns, function (nodes) { return (y1 - y0 - (nodes.length - 1) * py) / d3Array.sum(nodes, value) }) ky = ky * scale; columns.forEach(function (nodes) { var nodesLength = nodes.length nodes.forEach(function (node, i) { if (node.partOfCycle) { if (node.circularLinkType == 'top') { node.y0 = y0 + i node.y1 = node.y0 + node.value * ky } else { node.y0 = y1 - node.value - i node.y1 = node.y0 + node.value * ky } } else { // node.y1 = (node.y0 = i) + node.value * ky node.y0 = (y1 - y0) / 2 - nodesLength / 2 + i node.y1 = node.y0 + node.value * ky } }) }) graph.links.forEach(function (link) { link.width = link.value * ky }) } function relaxLeftAndRight (alpha) { let columnsLength = columns.length //console.log("cols: " + columnsLength); columns.forEach(function (nodes, i) { let n = nodes.length let depth = nodes[0].depth //console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') //console.log(depth + ': ' + n) nodes.forEach(function (node) { //console.log('~~~~~~~~~~~~~~~~~') //console.log(node.depth + ': ' + node.name + ' ' + node.partOfCycle) //console.log(node.sourceLinks) //check the node is not an orphan if (node.sourceLinks.length || node.targetLinks.length) { if (node.partOfCycle && n > 1 /*&& depth != 0*/) { //console.log('do nothing') } else if (depth == 0 && n == 1 ) { let nodeHeight = node.y1 - node.y0; node.y0 = (y1/2) - (nodeHeight/2); node.y1 = (y1/2) + (nodeHeight/2); } else if (depth == (columnsLength - 1) && n == 1 ) { let nodeHeight = node.y1 - node.y0; node.y0 = (y1/2) - (nodeHeight/2); node.y1 = (y1/2) + (nodeHeight/2); } else { let avg = 0; //console.log('before: ' + node.y0 + ' ' + node.y1) let avgTargetY = d3Array.mean(node.sourceLinks, linkTargetCenter); let avgSourceY = d3Array.mean(node.targetLinks, linkSourceCenter); if (avgTargetY && avgSourceY) { avg = (avgTargetY + avgSourceY)/2 } else { avg = avgTargetY ? avgTargetY : avgSourceY }; //console.log('avg Target : ' + avgTargetY) //console.log('avg Source : ' + avgSourceY) //console.log('avg Overall: ' + avg) //console.log('node centre: ' + nodeCenter(node)) let dy = (avg - nodeCenter(node)) * alpha //console.log(dy) // positive if it node needs to move down // let dy = (nodeCenter(node.sourceLinks[0].target) - nodeCenter(node.sourceLinks[0].source) / 2) * alpha; node.y0 += dy node.y1 += dy //console.log('after: ' + node.y0 + ' ' + node.y1) } } }) }) } function relaxLeftToRight (alpha) { let columnsLength = columns.length columns.forEach(function (nodes, i) { let n = nodes.length let depth = nodes[0].depth console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~') console.log(depth + ': ' + n) nodes.forEach(function (node) { console.log('~~~~~~~~~~~~~~~~~') console.log(node.depth + ': ' + node.name + ' ' + node.partOfCycle) console.log(node.sourceLinks) if (node.sourceLinks.length) { if (node.partOfCycle /*&& n > 1 && depth != 0*/) { console.log('do nothing') } else { /* if (node.sourceLinks.length > 0) */ console.log("do Tom's") console.log('before: ' + node.y0 + ' ' + node.y1) let avgTargetY = d3Array.mean( node.sourceLinks, linkTargetCenter ) console.log('avg: ' + avgTargetY) console.log('node centre: ' + nodeCenter(node)) let dy = (avgTargetY - nodeCenter(node)) * alpha console.log(dy) // positive if it node needs to move down // let dy = (nodeCenter(node.sourceLinks[0].target) - nodeCenter(node.sourceLinks[0].source) / 2) * alpha; node.y0 += dy node.y1 += dy console.log('after: ' + node.y0 + ' ' + node.y1) } // else { // console.log("do d3-sankey") // let sumLinkTargetsWeightedSource = d3Array.sum(node.targetLinks, weightedSource); // let sumLinkTargetsValues = d3Array.sum(node.targetLinks, value); // let nodeCentre = nodeCenter(node); // let dy = ((sumLinkTargetsWeightedSource / sumLinkTargetsValues) - nodeCentre) * alpha // /*var dy = // (d3Array.sum(node.targetLinks, weightedSource) / // d3Array.sum(node.targetLinks, value) - // nodeCenter(node)) * // alpha*/ // node.y0 += dy // node.y1 += dy // } } }) }) } function relaxRightToLeft (alpha) { let columnsLength = columns.length columns.slice().reverse().forEach(function (nodes, i) { let n = nodes.length let depth = nodes[0].depth nodes.forEach(function (node) { if (node.targetLinks.length) { if (node.partOfCycle /*&& n > 1 && depth != 0*/) { // do nothing for now } else { let avgTargetY = d3Array.mean(node.targetLinks, linkSourceCenter) let dy = (avgTargetY - nodeCenter(node)) * alpha node.y0 += dy node.y1 += dy } // else { // 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 var y1 = y0 // start from the bottom of the node for cycle links var y0cycle = node.y1 var y1cycle = y0cycle node.sourceLinks.forEach(function (link) { if (link.circular) { link.y0 = y0cycle - link.width / 2 y0cycle = y0cycle - link.width } else { link.y0 = y0 + link.width / 2 y0 += link.width } }) node.targetLinks.forEach(function (link) { if (link.circular) { link.y1 = y1cycle - link.width / 2 y1cycle = y1cycle - link.width } else { link.y1 = y1 + link.width / 2 y1 += link.width } }) }) } return sankey } /// ///////////////////////////////////////////////////////////////////////////////// // Cycle functions // Identify circles in the link objects function identifyCircles (graph) { var addedLinks = [] var circularLinkID = 0 graph.links.forEach(function (link) { if (createsCycle(link.source, link.target, addedLinks)) { link.circular = true link.circularLinkID = circularLinkID circularLinkID = circularLinkID + 1 } else { link.circular = false addedLinks.push(link) } }) } function selectCircularLinkTypes (graph) { let numberOfTops = 0 let numberOfBottoms = 0 graph.links.forEach(function (link) { if (link.circular) { // if either souce or target has type already use that if (link.source.circularLinkType || link.target.circularLinkType) { // default to source type if available link.circularLinkType = link.source.circularLinkType ? link.source.circularLinkType : link.target.circularLinkType } else { link.circularLinkType = numberOfTops < numberOfBottoms ? 'top' : 'bottom' } if (link.circularLinkType == 'top') { numberOfTops = numberOfTops + 1 } else { numberOfBottoms = numberOfBottoms + 1 } graph.nodes.forEach(function (node) { if (node.name == link.source.name || node.name == link.target.name) { node.circularLinkType = link.circularLinkType } }) } }) } // Checks if link creates a cycle function createsCycle (originalSource, nodeToCheck, graph) { if (graph.length == 0) { return false } var nextLinks = findLinksOutward(nodeToCheck, graph) // leaf node check if (nextLinks.length == 0) { return false } // cycle check for (var i = 0; i < nextLinks.length; i++) { var nextLink = nextLinks[i] if (nextLink.target === originalSource) { return true } // Recurse if (createsCycle(originalSource, nextLink.target, graph)) { return true } } // Exhausted all links return false } /* Given a node, find all links for which this is a source in the current 'known' graph */ function findLinksOutward (node, graph) { var children = [] for (var i = 0; i < graph.length; i++) { if (node == graph[i].source) { children.push(graph[i]) } } return children } /// ///////////////////////////////////////////////////////////////////////////////// /* var sankeyLinkHorizontal = function () { return d3Shape .linkHorizontal() .source(horizontalSource) .target(horizontalTarget) } function horizontalSource (d) { return [d.source.x1, d.y0] } function horizontalTarget (d) { return [d.target.x0, d.y1] } */ exports.sankey = sankey exports.sankeyCenter = center exports.sankeyLeft = left exports.sankeyRight = right exports.sankeyJustify = justify // exports.sankeyLinkHorizontal = sankeyLinkHorizontal // exports.curveSankeyLink = curveSankeyLink Object.defineProperty(exports, '__esModule', { value: true }) })