layout.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. /**
  2. * DevExtreme (viz/sankey/layout.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var _SPLINE_TENSION = .3;
  11. var _ALIGNMENT_CENTER = "center";
  12. var _ALIGNMENT_BOTTOM = "bottom";
  13. var _ALIGNMENT_DEFAULT = _ALIGNMENT_CENTER;
  14. var graphModule = require("./graph");
  15. var validatorModule = require("./data_validator");
  16. var layout = {
  17. _weightPerPixel: null,
  18. _getCascadeIdx: function(nodeTitle, cascadesConfig) {
  19. var nodeInfo = cascadesConfig.filter(function(c) {
  20. return c.name === nodeTitle
  21. })[0];
  22. if (nodeInfo.outgoing.length > 0) {
  23. return nodeInfo.lp
  24. } else {
  25. return graphModule.routines.maxOfArray(cascadesConfig.map(function(c) {
  26. return c.lp
  27. }))
  28. }
  29. },
  30. _getInWeightForNode: function(nodeTitle, links) {
  31. var w = 0;
  32. links.forEach(function(link) {
  33. if (link[1] === nodeTitle) {
  34. w += link[2]
  35. }
  36. });
  37. return w
  38. },
  39. _getOutWeightForNode: function(nodeTitle, links) {
  40. var w = 0;
  41. links.forEach(function(link) {
  42. if (link[0] === nodeTitle) {
  43. w += link[2]
  44. }
  45. });
  46. return w
  47. },
  48. _computeCascades: function(links) {
  49. var _this = this;
  50. var cascadesConfig = graphModule.struct.computeLongestPaths(links);
  51. var maxCascade = graphModule.routines.maxOfArray(cascadesConfig.map(function(c) {
  52. return c.lp
  53. }));
  54. var cascades = [];
  55. for (var i = 0; i < maxCascade + 1; i++) {
  56. cascades.push({})
  57. }
  58. links.forEach(function(link) {
  59. var cascade = cascades[_this._getCascadeIdx(link[0], cascadesConfig)];
  60. if (!cascade[link[0]]) {
  61. cascade[link[0]] = {
  62. nodeTitle: link[0]
  63. }
  64. }
  65. cascade = cascades[_this._getCascadeIdx(link[1], cascadesConfig)];
  66. if (!cascade[link[1]]) {
  67. cascade[link[1]] = {
  68. nodeTitle: link[1]
  69. }
  70. }
  71. });
  72. cascades.forEach(function(cascade) {
  73. Object.keys(cascade).forEach(function(nodeTitle) {
  74. var node = cascade[nodeTitle];
  75. node.inWeight = _this._getInWeightForNode(node.nodeTitle, links);
  76. node.outWeight = _this._getOutWeightForNode(node.nodeTitle, links);
  77. node.maxWeight = Math.max(node.inWeight, node.outWeight)
  78. })
  79. });
  80. return cascades
  81. },
  82. _getWeightForCascade: function(cascades, cascadeIdx) {
  83. var wMax = 0;
  84. var cascade = cascades[cascadeIdx];
  85. Object.keys(cascade).forEach(function(nodeTitle) {
  86. wMax += Math.max(cascade[nodeTitle].inWeight, cascade[nodeTitle].outWeight)
  87. });
  88. return wMax
  89. },
  90. _getMaxWeightThroughCascades: function(cascades) {
  91. var max = [];
  92. cascades.forEach(function(cascade) {
  93. var mW = 0;
  94. Object.keys(cascade).forEach(function(nodeTitle) {
  95. var node = cascade[nodeTitle];
  96. mW += Math.max(node.inWeight, node.outWeight)
  97. });
  98. max.push(mW)
  99. });
  100. return graphModule.routines.maxOfArray(max)
  101. },
  102. _computeNodes: function(cascades, options) {
  103. var _this2 = this;
  104. var rects = [];
  105. var maxWeight = this._getMaxWeightThroughCascades(cascades);
  106. var maxNodeNum = graphModule.routines.maxOfArray(cascades.map(function(nodesInCascade) {
  107. return Object.keys(nodesInCascade).length
  108. }));
  109. var nodePadding = options.nodePadding;
  110. var heightAvailable = options.height - nodePadding * (maxNodeNum - 1);
  111. if (heightAvailable < 0) {
  112. nodePadding = 0;
  113. heightAvailable = options.height - nodePadding * (maxNodeNum - 1)
  114. }
  115. this._weightPerPixel = maxWeight / heightAvailable;
  116. var cascadeIdx = 0;
  117. cascades.forEach(function(cascade) {
  118. var cascadeRects = [];
  119. var y = 0;
  120. var nodesInCascade = Object.keys(cascade).length;
  121. var cascadeHeight = _this2._getWeightForCascade(cascades, cascadeIdx) / _this2._weightPerPixel + nodePadding * (nodesInCascade - 1);
  122. var cascadeAlign = _ALIGNMENT_DEFAULT;
  123. if (Array.isArray(options.nodeAlign)) {
  124. cascadeAlign = cascadeIdx < options.nodeAlign.length ? options.nodeAlign[cascadeIdx] : _ALIGNMENT_DEFAULT
  125. } else {
  126. cascadeAlign = options.nodeAlign
  127. }
  128. if (cascadeAlign === _ALIGNMENT_BOTTOM) {
  129. y = options.height - cascadeHeight
  130. } else {
  131. if (cascadeAlign === _ALIGNMENT_CENTER) {
  132. y = .5 * (options.height - cascadeHeight)
  133. }
  134. }
  135. y = Math.round(y);
  136. Object.keys(cascade).forEach(function(nodeTitle) {
  137. cascade[nodeTitle].sort = _this2._sort && Object.prototype.hasOwnProperty.call(_this2._sort, nodeTitle) ? _this2._sort[nodeTitle] : 1
  138. });
  139. Object.keys(cascade).sort(function(a, b) {
  140. return cascade[a].sort - cascade[b].sort
  141. }).forEach(function(nodeTitle) {
  142. var node = cascade[nodeTitle];
  143. var height = Math.floor(heightAvailable * node.maxWeight / maxWeight);
  144. var x = Math.round(cascadeIdx * options.width / (cascades.length - 1)) - (0 === cascadeIdx ? 0 : options.nodeWidth);
  145. var rect = {};
  146. rect._name = nodeTitle;
  147. rect.width = options.nodeWidth;
  148. rect.height = height;
  149. rect.x = x + options.x;
  150. rect.y = y + options.y;
  151. y += height + nodePadding;
  152. cascadeRects.push(rect)
  153. });
  154. cascadeIdx++;
  155. rects.push(cascadeRects)
  156. });
  157. return rects
  158. },
  159. _findRectByName: function(rects, name) {
  160. for (var c = 0; c < rects.length; c++) {
  161. for (var r = 0; r < rects[c].length; r++) {
  162. if (name === rects[c][r]._name) {
  163. return rects[c][r]
  164. }
  165. }
  166. }
  167. return null
  168. },
  169. _findIndexByName: function(rects, nodeTitle) {
  170. var index = 0;
  171. for (var c = 0; c < rects.length; c++) {
  172. for (var r = 0; r < rects[c].length; r++) {
  173. if (nodeTitle === rects[c][r]._name) {
  174. return index
  175. }
  176. index++
  177. }
  178. }
  179. return null
  180. },
  181. _computeLinks: function(links, rects, cascades) {
  182. var _this3 = this;
  183. var yOffsets = {};
  184. var paths = [];
  185. var result = [];
  186. cascades.forEach(function(cascade) {
  187. Object.keys(cascade).forEach(function(nodeTitle) {
  188. yOffsets[nodeTitle] = {
  189. "in": 0,
  190. out: 0
  191. }
  192. })
  193. });
  194. rects.forEach(function(rectsOfCascade) {
  195. rectsOfCascade.forEach(function(nodeRect) {
  196. var nodeTitle = nodeRect._name;
  197. var rectFrom = _this3._findRectByName(rects, nodeTitle);
  198. var linksFromNode = links.filter(function(link) {
  199. return link[0] === nodeTitle
  200. });
  201. linksFromNode.forEach(function(link) {
  202. link.sort = _this3._findIndexByName(rects, link[1])
  203. });
  204. linksFromNode.sort(function(a, b) {
  205. return a.sort - b.sort
  206. }).forEach(function(link) {
  207. var rectTo = _this3._findRectByName(rects, link[1]);
  208. var height = Math.round(link[2] / _this3._weightPerPixel);
  209. var yOffsetFrom = yOffsets[link[0]].out;
  210. var yOffsetTo = yOffsets[link[1]].in;
  211. var heightFrom = yOffsets[link[0]].out + height > rectFrom.height ? rectFrom.height - yOffsets[link[0]].out : height;
  212. var heightTo = yOffsets[link[1]].in + height > rectTo.height ? rectTo.height - yOffsets[link[1]].in : height;
  213. paths.push({
  214. from: {
  215. x: rectFrom.x,
  216. y: rectFrom.y + yOffsetFrom,
  217. width: rectFrom.width,
  218. height: heightFrom,
  219. node: rectFrom,
  220. weight: link[2]
  221. },
  222. to: {
  223. x: rectTo.x,
  224. y: rectTo.y + yOffsetTo,
  225. width: rectTo.width,
  226. height: heightTo,
  227. node: rectTo
  228. }
  229. });
  230. yOffsets[link[0]].out += height;
  231. yOffsets[link[1]].in += height
  232. })
  233. })
  234. });
  235. paths.forEach(function(link) {
  236. var path = {
  237. d: _this3._spline(link.from, link.to),
  238. _boundingRect: {
  239. x: link.from.x + link.from.width,
  240. y: Math.min(link.from.y, link.to.y),
  241. width: link.to.x - (link.from.x + link.from.width),
  242. height: Math.max(link.from.x + link.from.height, link.to.y + link.to.height) - Math.min(link.from.y, link.to.y)
  243. },
  244. _weight: link.from.weight,
  245. _from: link.from.node,
  246. _to: link.to.node
  247. };
  248. result.push(path)
  249. });
  250. this._fitAllNodesHeight(rects, paths);
  251. return result
  252. },
  253. _fitNodeHeight: function(nodeName, nodeRects, paths) {
  254. var targetRect = this._findRectByName(nodeRects, nodeName);
  255. var heightOfLinksSummaryIn = 0;
  256. var heightOfLinksSummaryOut = 0;
  257. paths.forEach(function(path) {
  258. if (path.from.node._name === nodeName) {
  259. heightOfLinksSummaryOut += path.from.height
  260. }
  261. if (path.to.node._name === nodeName) {
  262. heightOfLinksSummaryIn += path.to.height
  263. }
  264. });
  265. targetRect.height = Math.max(heightOfLinksSummaryIn, heightOfLinksSummaryOut)
  266. },
  267. _fitAllNodesHeight: function(nodeRects, paths) {
  268. for (var c = 0; c < nodeRects.length; c++) {
  269. for (var r = 0; r < nodeRects[c].length; r++) {
  270. this._fitNodeHeight(nodeRects[c][r]._name, nodeRects, paths)
  271. }
  272. }
  273. },
  274. _spline: function(rectLeft, rectRight) {
  275. var p_UpLeft = {
  276. x: rectLeft.x + rectLeft.width,
  277. y: rectLeft.y
  278. };
  279. var p_DownLeft = {
  280. x: rectLeft.x + rectLeft.width,
  281. y: rectLeft.y + rectLeft.height
  282. };
  283. var p_UpRight = {
  284. x: rectRight.x,
  285. y: rectRight.y
  286. };
  287. var p_DownRight = {
  288. x: rectRight.x,
  289. y: rectRight.y + rectRight.height
  290. };
  291. var curve_width = _SPLINE_TENSION * (p_UpRight.x - p_UpLeft.x);
  292. 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");
  293. return result
  294. },
  295. computeLayout: function(linksData, sortData, options, incidentOccurred) {
  296. this._sort = sortData;
  297. var result = {};
  298. var validateResult = validatorModule.validate(linksData, incidentOccurred);
  299. if (!validateResult) {
  300. result.cascades = this._computeCascades(linksData);
  301. result.nodes = this._computeNodes(result.cascades, {
  302. width: options.availableRect.width,
  303. height: options.availableRect.height,
  304. x: options.availableRect.x,
  305. y: options.availableRect.y,
  306. nodePadding: options.nodePadding,
  307. nodeWidth: options.nodeWidth,
  308. nodeAlign: options.nodeAlign
  309. });
  310. result.links = this._computeLinks(linksData, result.nodes, result.cascades)
  311. } else {
  312. result.error = validateResult
  313. }
  314. return result
  315. },
  316. overlap: function(box1, box2) {
  317. 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)
  318. }
  319. };
  320. module.exports = layout;