@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
410 lines (351 loc) • 13.2 kB
JavaScript
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;
}