UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

326 lines (324 loc) • 13.7 kB
/** * DevExtreme (cjs/viz/sankey/layout.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; exports.layout = void 0; var _graph = _interopRequireDefault(require("./graph")); var _data_validator = _interopRequireDefault(require("./data_validator")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj } } var _SPLINE_TENSION = .3; var _ALIGNMENT_CENTER = "center"; var _ALIGNMENT_BOTTOM = "bottom"; var _ALIGNMENT_DEFAULT = _ALIGNMENT_CENTER; var layout = { _weightPerPixel: null, _getCascadeIdx: function(nodeTitle, cascadesConfig) { var nodeInfo = cascadesConfig.filter((function(c) { return c.name === nodeTitle }))[0]; if (nodeInfo.outgoing.length > 0) { return nodeInfo.lp } else { return _graph.default.routines.maxOfArray(cascadesConfig.map((function(c) { return c.lp }))) } }, _getInWeightForNode: function(nodeTitle, links) { var w = 0; links.forEach((function(link) { if (link[1] === nodeTitle) { w += link[2] } })); return w }, _getOutWeightForNode: function(nodeTitle, links) { var w = 0; links.forEach((function(link) { if (link[0] === nodeTitle) { w += link[2] } })); return w }, _computeCascades: function(links) { var _this = this; var cascadesConfig = _graph.default.struct.computeLongestPaths(links); var maxCascade = _graph.default.routines.maxOfArray(cascadesConfig.map((function(c) { return c.lp }))); var cascades = []; for (var i = 0; i < maxCascade + 1; i++) { cascades.push({}) } links.forEach((function(link) { var cascade = cascades[_this._getCascadeIdx(link[0], cascadesConfig)]; if (!cascade[link[0]]) { cascade[link[0]] = { nodeTitle: link[0] } } cascade = cascades[_this._getCascadeIdx(link[1], cascadesConfig)]; if (!cascade[link[1]]) { cascade[link[1]] = { nodeTitle: link[1] } } })); cascades.forEach((function(cascade) { Object.keys(cascade).forEach((function(nodeTitle) { var node = cascade[nodeTitle]; node.inWeight = _this._getInWeightForNode(node.nodeTitle, links); node.outWeight = _this._getOutWeightForNode(node.nodeTitle, links); node.maxWeight = Math.max(node.inWeight, node.outWeight) })) })); return cascades }, _getWeightForCascade: function(cascades, cascadeIdx) { var wMax = 0; var cascade = cascades[cascadeIdx]; Object.keys(cascade).forEach((function(nodeTitle) { wMax += Math.max(cascade[nodeTitle].inWeight, cascade[nodeTitle].outWeight) })); return wMax }, _getMaxWeightThroughCascades: function(cascades) { var max = []; cascades.forEach((function(cascade) { var mW = 0; Object.keys(cascade).forEach((function(nodeTitle) { var node = cascade[nodeTitle]; mW += Math.max(node.inWeight, node.outWeight) })); max.push(mW) })); return _graph.default.routines.maxOfArray(max) }, _computeNodes: function(cascades, options) { var _this2 = this; var rects = []; var maxWeight = this._getMaxWeightThroughCascades(cascades); var maxNodeNum = _graph.default.routines.maxOfArray(cascades.map((function(nodesInCascade) { return Object.keys(nodesInCascade).length }))); var nodePadding = options.nodePadding; var heightAvailable = options.height - nodePadding * (maxNodeNum - 1); if (heightAvailable < 0) { nodePadding = 0; heightAvailable = options.height - nodePadding * (maxNodeNum - 1) } this._weightPerPixel = maxWeight / heightAvailable; var cascadeIdx = 0; cascades.forEach((function(cascade) { var cascadeRects = []; var y = 0; var nodesInCascade = Object.keys(cascade).length; var cascadeHeight = _this2._getWeightForCascade(cascades, cascadeIdx) / _this2._weightPerPixel + nodePadding * (nodesInCascade - 1); var cascadeAlign; if (Array.isArray(options.nodeAlign)) { cascadeAlign = cascadeIdx < options.nodeAlign.length ? options.nodeAlign[cascadeIdx] : _ALIGNMENT_DEFAULT } else { cascadeAlign = options.nodeAlign } if (cascadeAlign === _ALIGNMENT_BOTTOM) { y = options.height - cascadeHeight } else if (cascadeAlign === _ALIGNMENT_CENTER) { y = .5 * (options.height - cascadeHeight) } y = Math.round(y); Object.keys(cascade).forEach((function(nodeTitle) { cascade[nodeTitle].sort = _this2._sort && Object.prototype.hasOwnProperty.call(_this2._sort, nodeTitle) ? _this2._sort[nodeTitle] : 1 })); Object.keys(cascade).sort((function(a, b) { return cascade[a].sort - cascade[b].sort })).forEach((function(nodeTitle) { var node = cascade[nodeTitle]; var height = Math.floor(heightAvailable * node.maxWeight / maxWeight); var x = Math.round(cascadeIdx * options.width / (cascades.length - 1)) - (0 === cascadeIdx ? 0 : options.nodeWidth); var rect = {}; rect._name = nodeTitle; rect.width = options.nodeWidth; rect.height = height; rect.x = x + options.x; rect.y = y + options.y; y += height + nodePadding; cascadeRects.push(rect) })); cascadeIdx++; rects.push(cascadeRects) })); return rects }, _findRectByName: function(rects, name) { for (var c = 0; c < rects.length; c++) { for (var r = 0; r < rects[c].length; r++) { if (name === rects[c][r]._name) { return rects[c][r] } } } return null }, _findIndexByName: function(rects, nodeTitle) { var index = 0; for (var c = 0; c < rects.length; c++) { for (var r = 0; r < rects[c].length; r++) { if (nodeTitle === rects[c][r]._name) { return index } index++ } } return null }, _computeLinks: function(links, rects, cascades) { var _this3 = this; var yOffsets = {}; var paths = []; var result = []; cascades.forEach((function(cascade) { Object.keys(cascade).forEach((function(nodeTitle) { yOffsets[nodeTitle] = { in: 0, out: 0 } })) })); rects.forEach((function(rectsOfCascade) { rectsOfCascade.forEach((function(nodeRect) { var nodeTitle = nodeRect._name; var rectFrom = _this3._findRectByName(rects, nodeTitle); var linksFromNode = links.filter((function(link) { return link[0] === nodeTitle })); linksFromNode.forEach((function(link) { link.sort = _this3._findIndexByName(rects, link[1]) })); linksFromNode.sort((function(a, b) { return a.sort - b.sort })).forEach((function(link) { var rectTo = _this3._findRectByName(rects, link[1]); var height = Math.round(link[2] / _this3._weightPerPixel); var yOffsetFrom = yOffsets[link[0]].out; var yOffsetTo = yOffsets[link[1]].in; var heightFrom = yOffsets[link[0]].out + height > rectFrom.height ? rectFrom.height - yOffsets[link[0]].out : height; var heightTo = yOffsets[link[1]].in + height > rectTo.height ? rectTo.height - yOffsets[link[1]].in : height; paths.push({ from: { x: rectFrom.x, y: rectFrom.y + yOffsetFrom, width: rectFrom.width, height: heightFrom, node: rectFrom, weight: link[2] }, to: { x: rectTo.x, y: rectTo.y + yOffsetTo, width: rectTo.width, height: heightTo, node: rectTo } }); yOffsets[link[0]].out += height; yOffsets[link[1]].in += height })) })) })); paths.forEach((function(link) { var path = { d: _this3._spline(link.from, link.to), _boundingRect: { x: link.from.x + link.from.width, y: Math.min(link.from.y, link.to.y), width: link.to.x - (link.from.x + link.from.width), height: Math.max(link.from.x + link.from.height, link.to.y + link.to.height) - Math.min(link.from.y, link.to.y) }, _weight: link.from.weight, _from: link.from.node, _to: link.to.node }; result.push(path) })); this._fitAllNodesHeight(rects, paths); return result }, _fitNodeHeight: function(nodeName, nodeRects, paths) { var targetRect = this._findRectByName(nodeRects, nodeName); var heightOfLinksSummaryIn = 0; var heightOfLinksSummaryOut = 0; paths.forEach((function(path) { if (path.from.node._name === nodeName) { heightOfLinksSummaryOut += path.from.height } if (path.to.node._name === nodeName) { heightOfLinksSummaryIn += path.to.height } })); targetRect.height = Math.max(heightOfLinksSummaryIn, heightOfLinksSummaryOut) }, _fitAllNodesHeight: function(nodeRects, paths) { for (var c = 0; c < nodeRects.length; c++) { for (var r = 0; r < nodeRects[c].length; r++) { this._fitNodeHeight(nodeRects[c][r]._name, nodeRects, paths) } } }, _spline: function(rectLeft, rectRight) { var p_UpLeft = { x: rectLeft.x + rectLeft.width, y: rectLeft.y }; var p_DownLeft = { x: rectLeft.x + rectLeft.width, y: rectLeft.y + rectLeft.height }; var p_UpRight = { x: rectRight.x, y: rectRight.y }; var p_DownRight = { x: rectRight.x, y: rectRight.y + rectRight.height }; var curve_width = _SPLINE_TENSION * (p_UpRight.x - p_UpLeft.x); var result = "M ".concat(p_UpLeft.x, " ").concat(p_UpLeft.y, " C ").concat(p_UpLeft.x + curve_width, " ").concat(p_UpLeft.y, " ").concat(p_UpRight.x - curve_width, " ").concat(p_UpRight.y, " ").concat(p_UpRight.x, " ").concat(p_UpRight.y, " L ").concat(p_DownRight.x, " ").concat(p_DownRight.y, " C ").concat(p_DownRight.x - curve_width, " ").concat(p_DownRight.y, " ").concat(p_DownLeft.x + curve_width, " ").concat(p_DownLeft.y, " ").concat(p_DownLeft.x, " ").concat(p_DownLeft.y, " Z"); return result }, computeLayout: function(linksData, sortData, options, incidentOccurred) { this._sort = sortData; var result = {}; var validateResult = _data_validator.default.validate(linksData, incidentOccurred); if (!validateResult) { result.cascades = this._computeCascades(linksData); result.nodes = this._computeNodes(result.cascades, { width: options.availableRect.width, height: options.availableRect.height, x: options.availableRect.x, y: options.availableRect.y, nodePadding: options.nodePadding, nodeWidth: options.nodeWidth, nodeAlign: options.nodeAlign }); result.links = this._computeLinks(linksData, result.nodes, result.cascades) } else { result.error = validateResult } return result }, overlap: function(box1, box2) { return !(box2.x > box1.x + box1.width || box2.x + box2.width < box1.x || box2.y >= box1.y + box1.height || box2.y + box2.height <= box1.y) } }; exports.layout = layout;