UNPKG

@progress/kendo-charts

Version:

Kendo UI platform-independent Charts library

410 lines (351 loc) 13.2 kB
import { deepExtend } from '../common'; var max = function (array, mapFn) { return Math.max.apply(null, array.map(mapFn)); }; var min = function (array, mapFn) { return Math.min.apply(null, array.map(mapFn)); }; var sum = function (array, mapFn) { return array.map(mapFn).reduce(function (acc, curr) { return (acc + curr); }, 0); }; var sortAsc = function (a, b) { return (a.y0 === b.y0 ? a.index - b.index : a.y0 + a.y1 - b.y0 - b.y1); }; var sortSource = function (a, b) { return sortAsc(a.source, b.source); }; var sortTarget = function (a, b) { return sortAsc(a.target, b.target); }; var value = function (node) { return node.value; }; function sortLinks(nodes) { nodes.forEach(function (node) { node.targetLinks.forEach(function (link) { link.source.sourceLinks.sort(sortTarget); }); node.sourceLinks.forEach(function (link) { link.target.targetLinks.sort(sortSource); }); }); } var calcLayer = function (node, maxDepth) { if (node.align === 'left') { return node.depth; } if (node.align === 'right') { return maxDepth - node.height; } return node.sourceLinks.length ? node.depth : maxDepth; }; var Sankey = function Sankey(options) { var ref = options.nodesOptions; var offset = ref.offset; if ( offset === void 0 ) offset = {}; var align = ref.align; this.data = { nodes: options.nodes.map(function (node) { return deepExtend({}, { offset: offset, align: align }, node); }), links: options.links.map(function (link) { return deepExtend({}, link); }) }; this.width = options.width; this.height = options.height; this.offsetX = options.offsetX || 0; this.offsetY = options.offsetY || 0; this.nodeWidth = options.nodesOptions.width; this.nodePadding = options.nodesOptions.padding; this.reverse = options.reverse; this.targetColumnIndex = options.targetColumnIndex; this.loops = options.loops; this.autoLayout = options.autoLayout; }; Sankey.prototype.calculate = function calculate () { var ref = this.data; var nodes = ref.nodes; var links = ref.links; this.connectLinksToNodes(nodes, links); this.calculateNodeValues(nodes); var circularLinks = this.calculateNodeHeights(nodes); if (circularLinks) { return { nodes: [], links: [], columns: [], circularLinks: circularLinks }; } this.calculateNodeDepths(nodes); var columns = this.calculateNodeColumns(nodes); this.calculateNodeBreadths(columns); this.applyNodesOffset(nodes); this.calculateLinkBreadths(nodes); return Object.assign({}, this.data, {columns: columns}); }; Sankey.prototype.connectLinksToNodes = function connectLinksToNodes (nodes, links) { var nodesMap = new Map(); nodes.forEach(function (node, i) { node.index = i; node.sourceLinks = []; node.targetLinks = []; node.id = node.id !== undefined ? node.id : node.label.text; nodesMap.set(node.id, node); }); links.forEach(function (link) { link.source = nodesMap.get(link.sourceId); link.target = nodesMap.get(link.targetId); link.source.sourceLinks.push(link); link.target.targetLinks.push(link); }); }; Sankey.prototype.calculateNodeValues = function calculateNodeValues (nodes) { nodes.forEach(function (node) { node.value = Math.max( sum(node.sourceLinks, value), sum(node.targetLinks, value) ); }); }; Sankey.prototype.calculateNodeDepths = function calculateNodeDepths (nodes) { var current = new Set(nodes); var next = new Set(); var currDepth = 0; while (current.size) { var currentNodes = Array.from(current); for (var n = 0; n < currentNodes.length; n++) { var node = currentNodes[n]; node.depth = currDepth; for (var l = 0; l < node.sourceLinks.length; l++) { var link = node.sourceLinks[l]; next.add(link.target); } } currDepth++; current = next; next = new Set(); } }; Sankey.prototype.calculateNodeHeights = function calculateNodeHeights (nodes) { var nodesLength = nodes.length; var current = new Set(nodes); var next = new Set; var currentHeight = 0; var eachNode = function (node) { node.height = currentHeight; node.targetLinks.forEach(function (link) { next.add(link.source); }); }; while (current.size) { current.forEach(eachNode); currentHeight++; if (currentHeight > nodesLength) { return true; } current = next; next = new Set; } return false; }; Sankey.prototype.calculateNodeColumns = function calculateNodeColumns (nodes) { var this$1 = this; var maxDepth = max(nodes, function (d) { return d.depth; }); var columnWidth = (this.width - this.offsetX - this.nodeWidth) / maxDepth; var columns = new Array(maxDepth + 1); for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; var layer = Math.max(0, Math.min(maxDepth, calcLayer(node, maxDepth))); node.x0 = this$1.offsetX + layer * columnWidth; node.x1 = node.x0 + this$1.nodeWidth; node.layer = layer; columns[layer] = columns[layer] || []; columns[layer].push(node); } return columns; }; Sankey.prototype.calculateNodeBreadths = function calculateNodeBreadths (columns) { var this$1 = this; var kSize = min(columns, function (c) { return (this$1.height - this$1.offsetY - (c.length - 1) * this$1.nodePadding) / sum(c, value); }); columns.forEach(function (nodes) { var y = this$1.offsetY; nodes.forEach(function (node) { node.y0 = y; node.y1 = y + node.value * kSize; y = node.y1 + this$1.nodePadding; node.sourceLinks.forEach(function (link) { link.width = link.value * kSize; }); }); y = (this$1.height - y + this$1.nodePadding) / (nodes.length + 1); nodes.forEach(function (node, i) { node.y0 += y * (i + 1); node.y1 += y * (i + 1); }); }); if (this.autoLayout !== false) { var loops = this.loops !== undefined ? this.loops : columns.length - 1; var targetColumnIndex = this.targetColumnIndex || 1; for (var i = 0; i < loops; i++) { if (!this$1.reverse) { this$1.uncurlLinksToLeft(columns, targetColumnIndex); this$1.uncurlLinksToRight(columns, targetColumnIndex); } else { this$1.uncurlLinksToRight(columns, targetColumnIndex); this$1.uncurlLinksToLeft(columns, targetColumnIndex); } } } columns.forEach(sortLinks); }; Sankey.prototype.applyNodesOffset = function applyNodesOffset (nodes) { nodes.forEach(function (node) { var offsetX = (node.offset ? node.offset.left : 0) || 0; var offsetY = (node.offset ? node.offset.top : 0) || 0; node.x0 += offsetX; node.x1 += offsetX; node.y0 += offsetY; node.y1 += offsetY; }); }; Sankey.prototype.calculateLinkBreadths = function calculateLinkBreadths (nodes) { nodes.forEach(function (node) { var sourceLinks = node.sourceLinks; var targetLinks = node.targetLinks; var y = node.y0; var y1 = y; sourceLinks.forEach(function (link) { link.x0 = link.source.x1; link.y0 = y + link.width / 2; y += link.width; }); targetLinks.forEach(function (link) { link.x1 = link.target.x0; link.y1 = y1 + link.width / 2; y1 += link.width; }); }); }; Sankey.prototype.uncurlLinksToRight = function uncurlLinksToRight (columns, targetColumnIndex) { var this$1 = this; var n = columns.length; for (var i = targetColumnIndex; i < n; i++) { var column = columns[i]; column.forEach(function (target) { var y = 0; var sum = 0; target.targetLinks.forEach(function (link) { var kValue = link.value * (target.layer - link.source.layer); y += this$1.targetTopPos(link.source, target) * kValue; sum += kValue; }); var dy = y === 0 ? 0 : (y / sum - target.y0); target.y0 += dy; target.y1 += dy; sortLinks([target]); }); column.sort(sortAsc); this$1.arrangeNodesVertically(column); } }; Sankey.prototype.uncurlLinksToLeft = function uncurlLinksToLeft (columns, targetColumnIndex) { var this$1 = this; var l = columns.length; var startIndex = l - 1 - targetColumnIndex; for (var i = startIndex; i >= 0; i--) { var column = columns[i]; var loop = function ( j ) { var source = column[j]; var y = 0; var sum = 0; source.sourceLinks.forEach(function (link) { var kValue = link.value * (link.target.layer - source.layer); y += this$1.sourceTopPos(source, link.target) * kValue; sum += kValue; }); var dy = y === 0 ? 0 : (y / sum - source.y0); source.y0 += dy; source.y1 += dy; sortLinks([source]); }; for (var j = 0; j < column.length; j++) loop( j ); column.sort(sortAsc); this$1.arrangeNodesVertically(column); } }; Sankey.prototype.arrangeNodesVertically = function arrangeNodesVertically (nodes) { var startIndex = 0; var endIndex = nodes.length - 1; this.arrangeUp(nodes, this.height, endIndex); this.arrangeDown(nodes, this.offsetY, startIndex); }; Sankey.prototype.arrangeDown = function arrangeDown (nodes, yPos, index) { var this$1 = this; var currentY = yPos; for (var i = index; i < nodes.length; i++) { var node = nodes[i]; var dy = Math.max(0, currentY - node.y0); node.y0 += dy; node.y1 += dy; currentY = node.y1 + this$1.nodePadding; } }; Sankey.prototype.arrangeUp = function arrangeUp (nodes, yPos, index) { var this$1 = this; var currentY = yPos; for (var i = index; i >= 0; --i) { var node = nodes[i]; var dy = Math.max(0, node.y1 - currentY); node.y0 -= dy; node.y1 -= dy; currentY = node.y0 - this$1.nodePadding; } }; Sankey.prototype.sourceTopPos = function sourceTopPos (source, target) { var this$1 = this; var y = target.y0 - ((target.targetLinks.length - 1) * this.nodePadding) / 2; for (var i = 0; i < target.targetLinks.length; i++) { var link = target.targetLinks[i]; if (link.source === source) { break; } y += link.width + this$1.nodePadding; } for (var i$1 = 0; i$1 < source.sourceLinks.length; i$1++) { var link$1 = source.sourceLinks[i$1]; if (link$1.target === target) { break; } y -= link$1.width; } return y; }; Sankey.prototype.targetTopPos = function targetTopPos (source, target) { var this$1 = this; var y = source.y0 - ((source.sourceLinks.length - 1) * this.nodePadding) / 2; for (var i = 0; i < source.sourceLinks.length; i++) { var link = source.sourceLinks[i]; if (link.target === target) { break; } y += link.width + this$1.nodePadding; } for (var i$1 = 0; i$1 < target.targetLinks.length; i$1++) { var link$1 = target.targetLinks[i$1]; if (link$1.source === source) { break; } y -= link$1.width; } return y; }; export var calculateSankey = function (options) { return new Sankey(options).calculate(); }; export var crossesValue = function (links) { var value = 0; var linksLength = links.length; for (var i = 0; i < linksLength; i++) { var link = links[i]; for (var lNext = i + 1; lNext < linksLength; lNext++) { var nextLink = links[lNext]; if (intersect(link, nextLink)) { value += Math.min(link.value, nextLink.value); } } } return value; }; function rotationDirection(p1x, p1y, p2x, p2y, p3x, p3y) { var expression1 = (p3y - p1y) * (p2x - p1x); var expression2 = (p2y - p1y) * (p3x - p1x); if (expression1 > expression2) { return 1; } else if (expression1 === expression2) { return 0; } return -1; } function intersect(link1, link2) { var f1 = rotationDirection(link1.x0, link1.y0, link1.x1, link1.y1, link2.x1, link2.y1); var f2 = rotationDirection(link1.x0, link1.y0, link1.x1, link1.y1, link2.x0, link2.y0); var f3 = rotationDirection(link1.x0, link1.y0, link2.x0, link2.y0, link2.x1, link2.y1); var f4 = rotationDirection(link1.x1, link1.y1, link2.x0, link2.y0, link2.x1, link2.y1); return f1 !== f2 && f3 !== f4; }