UNPKG

@amcharts/amcharts4

Version:
512 lines 19.2 kB
/** * Sankey diagram module. */ import { __extends, __read, __spread } from "tslib"; /** * ============================================================================ * IMPORTS * ============================================================================ * @hidden */ import { FlowDiagram, FlowDiagramDataItem } from "./FlowDiagram"; import { percent } from "../../core/utils/Percent"; import { registry } from "../../core/Registry"; import { SankeyNode } from "../elements/SankeyNode"; import { SankeyLink } from "../elements/SankeyLink"; import { Animation } from "../../core/utils/Animation"; import * as $iter from "../../core/utils/Iterator"; import * as $math from "../../core/utils/Math"; import * as $type from "../../core/utils/Type"; import * as $object from "../../core/utils/Object"; /** * ============================================================================ * DATA ITEM * ============================================================================ * @hidden */ //@todo rearange notes after dragged /** * Defines a [[DataItem]] for [[SankeyDiagram]]. * * @see {@link DataItem} */ var SankeyDiagramDataItem = /** @class */ (function (_super) { __extends(SankeyDiagramDataItem, _super); /** * Constructor */ function SankeyDiagramDataItem() { var _this = _super.call(this) || this; _this.className = "SankeyDiagramDataItem"; _this.applyTheme(); return _this; } return SankeyDiagramDataItem; }(FlowDiagramDataItem)); export { SankeyDiagramDataItem }; /** * ============================================================================ * MAIN CLASS * ============================================================================ * @hidden */ /** * Creates a Sankey Diagram chart. * * @see {@link ISankeyDiagramEvents} for a list of available Events * @see {@link ISankeyDiagramAdapters} for a list of available Adapters * @see {@link https://www.amcharts.com/docs/v4/chart-types/sankey-diagram/} for documentation * @important */ var SankeyDiagram = /** @class */ (function (_super) { __extends(SankeyDiagram, _super); /** * Constructor */ function SankeyDiagram() { var _this = // Init _super.call(this) || this; _this.className = "SankeyDiagram"; _this.orientation = "horizontal"; _this.nodeAlign = "middle"; _this.nodesContainer.width = percent(100); _this.nodesContainer.height = percent(100); _this.linksContainer.width = percent(100); _this.linksContainer.height = percent(100); // Apply theme _this.applyTheme(); return _this; } /** * (Re)validates chart's data, effectively causing the chart to redraw. * * @ignore Exclude from docs */ SankeyDiagram.prototype.validateData = function () { var _this = this; this._valueHeight = undefined; _super.prototype.validateData.call(this); this._levelCount = 0; this.nodes.each(function (key, node) { node.level = undefined; }); this.nodes.each(function (key, node) { node.level = _this.getNodeLevel(node, 0); _this._levelCount = $math.max(_this._levelCount, node.level); }); }; /** * Returns node's highest level. * * @param node Node * @param level Current level * @return New level */ SankeyDiagram.prototype.getNodeLevel = function (node, level) { var _this = this; var levels = [level]; $iter.each(node.incomingDataItems.iterator(), function (link) { if (link.fromNode) { if ($type.isNumber(link.fromNode.level)) { levels.push(link.fromNode.level + 1); } else { _this._counter = 0; _this.checkLoop(link.fromNode); if (_this._counter < _this.dataItems.length) { levels.push(_this.getNodeLevel(link.fromNode, level + 1)); } } } }); return Math.max.apply(Math, __spread(levels)); }; /** * Checks if there's no loop in the ancestor chain. * * @param node Node */ SankeyDiagram.prototype.checkLoop = function (node) { var _this = this; this._counter++; if (this._counter > this.dataItems.length) { return; } $iter.each(node.incomingDataItems.iterator(), function (link) { _this.checkLoop(link.fromNode); }); }; /** * Calculates relation between pixel height and total value. * * In Sankey the actual thickness of links and height of nodes will depend * on their values. */ SankeyDiagram.prototype.calculateValueHeight = function () { var _this = this; // calculate sums of each level this._levelSum = {}; this._levelNodesCount = {}; this.maxSum = 0; var total = this.dataItem.values.value.sum; $iter.each(this._sorted, function (strNode) { var node = strNode[1]; _this.getNodeValue(node); }); this.nodes.each(function (key, node) { var level = node.level; var value = Math.max(node.totalIncoming, node.totalOutgoing); if (value / total < _this.minNodeSize) { value = total * _this.minNodeSize; } if ($type.isNumber(_this._levelSum[level])) { _this._levelSum[level] += value; } else { _this._levelSum[level] = value; } if ($type.isNumber(_this._levelNodesCount[level])) { _this._levelNodesCount[level]++; } else { _this._levelNodesCount[level] = 1; } }); var availableHeight; if (this.orientation == "horizontal") { availableHeight = this.chartContainer.maxHeight - 1; } else { availableHeight = this.chartContainer.maxWidth - 1; } var maxSumLevel; var minHeight; $object.each(this._levelSum, function (key, value) { var realValue = value; var levelNodeCount = _this._levelNodesCount[key]; var valueHeight = (availableHeight - (levelNodeCount - 1) * _this.nodePadding) / realValue; if (valueHeight == Infinity) { valueHeight = 0; } if (minHeight > valueHeight || !$type.isNumber(minHeight)) { minHeight = valueHeight; _this.maxSum = realValue; maxSumLevel = $type.toNumber(key); } }); this._maxSumLevel = maxSumLevel; var maxSumLevelNodeCount = this._levelNodesCount[this._maxSumLevel]; var valueHeight = (availableHeight - (maxSumLevelNodeCount - 1) * this.nodePadding) / this.maxSum; if (valueHeight == Infinity) { valueHeight = 0; } if (!$type.isNumber(this.valueHeight)) { this.valueHeight = valueHeight; } else { var finalHeight = void 0; try { finalHeight = this._heightAnimation.animationOptions[0].to; } catch (err) { } // without animations it will be non-smooth as maxValue jumps from one column to another if (finalHeight != valueHeight) { var duration = this.interpolationDuration; try { duration = this.nodes.template.states.getKey("active").transitionDuration; } catch (err) { } this._heightAnimation = new Animation(this, { property: "valueHeight", from: this.valueHeight, to: valueHeight }, duration, this.interpolationEasing).start(); this._disposers.push(this._heightAnimation); } } }; /** * Redraws the chart. * * @ignore Exclude from docs */ SankeyDiagram.prototype.validate = function () { var _this = this; _super.prototype.validate.call(this); this.calculateValueHeight(); var container = this.nodesContainer; var nextCoordinate = {}; var maxSumLevelNodeCount = this._levelNodesCount[this._maxSumLevel]; var total = this.dataItem.values.value.sum; var availableHeight; if (this.orientation == "horizontal") { availableHeight = this.chartContainer.maxHeight - 1; } else { availableHeight = this.chartContainer.maxWidth - 1; } $iter.each(this._sorted, function (strNode) { var node = strNode[1]; var level = node.level; var levelCoordinate = 0; var nodeCount = _this._levelNodesCount[level]; switch (_this.nodeAlign) { case "bottom": levelCoordinate = (_this.maxSum - _this._levelSum[level]) * _this.valueHeight - (nodeCount - maxSumLevelNodeCount) * _this.nodePadding; break; case "middle": levelCoordinate = (_this.maxSum - _this._levelSum[level]) * _this.valueHeight / 2 - (nodeCount - maxSumLevelNodeCount) * _this.nodePadding / 2; break; } if (_this.maxSum == 0) { switch (_this.nodeAlign) { case "bottom": levelCoordinate = availableHeight - nodeCount * (_this.minNodeSize * availableHeight + _this.nodePadding); break; case "middle": levelCoordinate = availableHeight / 2 - nodeCount / 2 * (_this.minNodeSize * availableHeight + _this.nodePadding); break; } } node.parent = container; var delta; var x; var y; var value = Math.max(node.totalIncoming, node.totalOutgoing); if (value / total < _this.minNodeSize) { value = total * _this.minNodeSize; } if (_this.orientation == "horizontal") { delta = (_this.innerWidth - node.pixelWidth) / _this._levelCount; x = delta * node.level; y = nextCoordinate[level] || levelCoordinate; var h = value * _this.valueHeight; if (total == 0 && h == 0) { h = _this.minNodeSize * availableHeight; } node.height = h; node.minX = x; node.maxX = x; nextCoordinate[level] = y + h + _this.nodePadding; } else { delta = (_this.innerHeight - node.pixelHeight) / _this._levelCount; x = nextCoordinate[level] || levelCoordinate; y = delta * node.level; var w = value * _this.valueHeight; if (total == 0 && w == 0) { w = _this.minNodeSize * availableHeight; } node.width = w; node.minY = y; node.maxY = y; nextCoordinate[level] = x + w + _this.nodePadding; } node.x = x; node.y = y; }); }; /** * Performs actual operations to reveal this element. * * @ignore Exclude from docs * @param duration Fade in duration (ms) * @return Fade in duration (ms) */ SankeyDiagram.prototype.showReal = function (duration) { var _this = this; if (this.preventShow) { return; } if (this.interpolationDuration > 0) { var container_1 = this.nodesContainer; var i_1 = 0; $iter.each(this.links.iterator(), function (link) { link.hide(0); }); $iter.each(this._sorted, function (strNode) { var node = strNode[1]; var property; if (_this.orientation == "horizontal") { node.dx = -(container_1.pixelWidth - node.pixelWidth) / Math.max(_this._levelCount, 1); property = "dx"; } else { node.dy = -(container_1.pixelHeight - node.pixelHeight) / Math.max(_this._levelCount, 1); property = "dy"; } var delay = 0; var duration = _this.interpolationDuration; if (_this.sequencedInterpolation) { delay = _this.sequencedInterpolationDelay * i_1 + duration * i_1 / $iter.length(_this.nodes.iterator()); } node.opacity = 0; node.invalidateLinks(); node.animate([{ property: "opacity", from: 0, to: 1 }, { property: property, to: 0 }], _this.interpolationDuration, _this.interpolationEasing).delay(delay); $iter.each(node.outgoingDataItems.iterator(), function (dataItem) { var animation = dataItem.link.show(_this.interpolationDuration); if (animation && !animation.isFinished()) { animation.delay(delay); } }); $iter.each(node.incomingDataItems.iterator(), function (dataItem) { if (!dataItem.fromNode) { var animation = dataItem.link.show(_this.interpolationDuration); if (animation && !animation.isFinished()) { animation.delay(delay); } } }); i_1++; }); } return _super.prototype.showReal.call(this); }; /** * Changes the sort type of the nodes. * * This will actually reshuffle nodes using nice animation. */ SankeyDiagram.prototype.changeSorting = function () { var _this = this; this.sortNodes(); var nextCoordinate = {}; $iter.each(this._sorted, function (strNode) { var node = strNode[1]; var level = node.level; var levelCoordinate = (_this.maxSum - _this._levelSum[level]) * _this.valueHeight / 2; var property; var nodeHeight; if (_this.orientation == "horizontal") { property = "y"; nodeHeight = node.pixelHeight; } else { property = "x"; nodeHeight = node.pixelWidth; } node.animate({ property: property, to: nextCoordinate[level] || levelCoordinate }, _this.interpolationDuration, _this.interpolationEasing); nextCoordinate[level] = (nextCoordinate[level] || levelCoordinate) + nodeHeight + _this.nodePadding; node.invalidateLinks(); }); }; /** * Sets defaults that instantiate some objects that rely on parent, so they * cannot be set in constructor. */ SankeyDiagram.prototype.applyInternalDefaults = function () { _super.prototype.applyInternalDefaults.call(this); // Add a default screen reader title for accessibility // This will be overridden in screen reader if there are any `titles` set if (!$type.hasValue(this.readerTitle)) { this.readerTitle = this.language.translate("Sankey diagram"); } }; /** * Creates and returns a new data item. * * @return Data item */ SankeyDiagram.prototype.createDataItem = function () { return new SankeyDiagramDataItem(); }; Object.defineProperty(SankeyDiagram.prototype, "nodeAlign", { /** * @returns Returns nodeAlign value */ get: function () { return this.getPropertyValue("nodeAlign"); }, /** * How to align nodes. In case layout is vertical, top means left and bottom means right * * @param value Node sorting */ set: function (value) { this.setPropertyValue("nodeAlign", value); this.changeSorting(); }, enumerable: true, configurable: true }); Object.defineProperty(SankeyDiagram.prototype, "orientation", { /** * @return Orientation */ get: function () { return this.getPropertyValue("orientation"); }, /** * Orientation of the chart: "horizontal" or "vertical"; * * @param value Orientation */ set: function (value) { this.setPropertyValue("orientation", value, true); var nameLabel = this.nodes.template.nameLabel; if (value == "vertical") { this.nodes.template.width = undefined; nameLabel.label.horizontalCenter = "middle"; nameLabel.locationX = 0.5; } else { this.nodes.template.height = undefined; nameLabel.label.horizontalCenter = "left"; nameLabel.locationX = 1; } }, enumerable: true, configurable: true }); /** * @ignore */ SankeyDiagram.prototype.createNode = function () { var node = new SankeyNode(); this._disposers.push(node); return node; }; /** * @ignore */ SankeyDiagram.prototype.createLink = function () { var link = new SankeyLink(); this._disposers.push(link); return link; }; Object.defineProperty(SankeyDiagram.prototype, "valueHeight", { /** * @ignore */ get: function () { return this._valueHeight; }, /** * @ignore */ set: function (value) { if (value != this._valueHeight) { this._valueHeight = value; this.invalidate(); } }, enumerable: true, configurable: true }); /** * @ignore */ SankeyDiagram.prototype.disposeData = function () { _super.prototype.disposeData.call(this); this._sorted = this.nodes.iterator(); }; return SankeyDiagram; }(FlowDiagram)); export { SankeyDiagram }; /** * Register class in system, so that it can be instantiated using its name from * anywhere. * * @ignore */ registry.registeredClasses["SankeyDiagram"] = SankeyDiagram; //# sourceMappingURL=SankeyDiagram.js.map