tree_map.base.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. /**
  2. * DevExtreme (viz/tree_map/tree_map.base.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 _common = require("./common");
  11. var _node = require("./node");
  12. var _node2 = _interopRequireDefault(_node);
  13. var _tiling = require("./tiling");
  14. var _colorizing = require("./colorizing");
  15. var _utils = require("../core/utils");
  16. var _common2 = require("../../core/utils/common");
  17. var _type = require("../../core/utils/type");
  18. var _extend2 = require("../../core/utils/extend");
  19. function _interopRequireDefault(obj) {
  20. return obj && obj.__esModule ? obj : {
  21. "default": obj
  22. }
  23. }
  24. var _max = Math.max;
  25. var directions = {
  26. lefttoprightbottom: [1, 1],
  27. leftbottomrighttop: [1, -1],
  28. righttopleftbottom: [-1, 1],
  29. rightbottomlefttop: [-1, -1]
  30. };
  31. require("./tiling.squarified");
  32. require("./tiling").setDefaultAlgorithm("squarified");
  33. require("./colorizing.discrete");
  34. require("./colorizing").setDefaultColorizer("discrete");
  35. function pickPositiveInteger(val) {
  36. return val > 0 ? Math.round(val) : 0
  37. }
  38. var dxTreeMap = require("../core/base_widget").inherit({
  39. _handlers: {
  40. beginBuildNodes: _common2.noop,
  41. buildNode: _common2.noop,
  42. endBuildNodes: _common2.noop,
  43. setTrackerData: _common2.noop,
  44. calculateState: function(options) {
  45. return (0, _common.buildRectAppearance)(options)
  46. }
  47. },
  48. _rootClass: "dxtm-tree-map",
  49. _rootClassPrefix: "dxtm",
  50. _getDefaultSize: function() {
  51. return {
  52. width: 400,
  53. height: 400
  54. }
  55. },
  56. _setDeprecatedOptions: function() {
  57. this.callBase.apply(this, arguments);
  58. (0, _extend2.extend)(this._deprecatedOptions, {
  59. resolveLabelOverflow: {
  60. since: "19.1",
  61. message: "Use the 'tile.label.overflow' and 'group.label.textOverflow' option instead"
  62. }
  63. })
  64. },
  65. _themeSection: "treeMap",
  66. _fontFields: ["tile.label.font", "group.label.font"],
  67. _init: function() {
  68. var that = this;
  69. that._rectOffsets = {};
  70. that._handlers = Object.create(that._handlers);
  71. that._context = {
  72. suspend: function() {
  73. if (!that._applyingChanges) {
  74. that._suspendChanges()
  75. }
  76. },
  77. resume: function() {
  78. if (!that._applyingChanges) {
  79. that._resumeChanges()
  80. }
  81. },
  82. change: function(codes) {
  83. that._change(codes)
  84. },
  85. settings: [{}, {}],
  86. calculateState: that._handlers.calculateState,
  87. calculateLabelState: _common.buildTextAppearance
  88. };
  89. that._root = that._topNode = {
  90. nodes: []
  91. };
  92. that.callBase.apply(that, arguments)
  93. },
  94. _initialChanges: ["DATA_SOURCE"],
  95. _initCore: function() {
  96. var that = this;
  97. var renderer = that._renderer;
  98. that._createProxyType();
  99. that._tilesGroup = renderer.g().linkOn(renderer.root, "tiles").linkAppend();
  100. that._labelsGroup = renderer.g().linkOn(renderer.root, "labels").linkAppend()
  101. },
  102. _createProxyType: _common2.noop,
  103. _disposeCore: function() {
  104. var that = this;
  105. that._filter && that._filter.dispose();
  106. that._labelsGroup.linkOff();
  107. that._tilesGroup.linkOff()
  108. },
  109. _applySize: function(rect) {
  110. this._tilingRect = rect.slice();
  111. this._change(["TILING"])
  112. },
  113. _optionChangesMap: {
  114. dataSource: "DATA_SOURCE",
  115. valueField: "NODES_CREATE",
  116. childrenField: "NODES_CREATE",
  117. colorField: "TILES",
  118. colorizer: "TILES",
  119. labelField: "LABELS",
  120. tile: "TILE_SETTINGS",
  121. group: "GROUP_SETTINGS",
  122. maxDepth: "MAX_DEPTH",
  123. layoutAlgorithm: "TILING",
  124. layoutDirection: "TILING",
  125. resolveLabelOverflow: "LABEL_OVERFLOW"
  126. },
  127. _themeDependentChanges: ["TILE_SETTINGS", "GROUP_SETTINGS", "MAX_DEPTH"],
  128. _changeDataSource: function() {
  129. var that = this;
  130. that._isDataExpected = that._isSyncData = true;
  131. that._updateDataSource();
  132. that._isSyncData = false;
  133. if (that._isDataExpected) {
  134. that._suspendChanges()
  135. }
  136. },
  137. _dataSourceChangedHandler: function() {
  138. var that = this;
  139. if (that._isDataExpected) {
  140. that._isDataExpected = false;
  141. that._change(["NODES_CREATE"]);
  142. if (!that._isSyncData) {
  143. that._resumeChanges()
  144. }
  145. } else {
  146. that._requestChange(["NODES_CREATE"])
  147. }
  148. },
  149. _optionChangesOrder: ["DATA_SOURCE", "TILE_SETTINGS", "GROUP_SETTINGS", "MAX_DEPTH", "LABEL_OVERFLOW"],
  150. _change_DATA_SOURCE: function() {
  151. this._changeDataSource()
  152. },
  153. _change_TILE_SETTINGS: function() {
  154. this._changeTileSettings()
  155. },
  156. _change_GROUP_SETTINGS: function() {
  157. this._changeGroupSettings()
  158. },
  159. _change_LABEL_OVERFLOW: function() {
  160. this._changeTileSettings();
  161. this._changeGroupSettings()
  162. },
  163. _change_MAX_DEPTH: function() {
  164. this._changeMaxDepth()
  165. },
  166. _customChangesOrder: ["NODES_CREATE", "NODES_RESET", "TILES", "LABELS", "TILING", "LABELS_LAYOUT"],
  167. _change_NODES_CREATE: function() {
  168. this._buildNodes()
  169. },
  170. _change_NODES_RESET: function() {
  171. this._resetNodes()
  172. },
  173. _change_TILES: function() {
  174. this._applyTilesAppearance()
  175. },
  176. _change_LABELS: function() {
  177. this._applyLabelsAppearance()
  178. },
  179. _change_TILING: function() {
  180. this._performTiling()
  181. },
  182. _change_LABELS_LAYOUT: function() {
  183. this._performLabelsLayout()
  184. },
  185. _applyChanges: function() {
  186. var that = this;
  187. that.callBase.apply(that, arguments);
  188. if (!that._isDataExpected) {
  189. that._drawn()
  190. }
  191. that._context.forceReset = false
  192. },
  193. _buildNodes: function() {
  194. var that = this;
  195. var root = that._root = that._topNode = new _node2.default;
  196. root._id = 0;
  197. root.parent = {};
  198. root.data = {};
  199. root.level = root.index = -1;
  200. root.ctx = that._context;
  201. root.label = null;
  202. that._nodes = [root];
  203. that._handlers.beginBuildNodes();
  204. var processedData = that._processDataSourceItems(that._dataSourceItems() || []);
  205. traverseDataItems(root, processedData.items, 0, {
  206. itemsField: !processedData.isPlain && that._getOption("childrenField", true) || "items",
  207. valueField: that._getOption("valueField", true) || "value",
  208. buildNode: that._handlers.buildNode,
  209. ctx: that._context,
  210. nodes: that._nodes
  211. });
  212. that._onNodesCreated();
  213. that._handlers.endBuildNodes();
  214. that._change(["NODES_RESET"])
  215. },
  216. _onNodesCreated: _common2.noop,
  217. _processDataSourceItems: function(items) {
  218. return {
  219. items: items,
  220. isPlain: false
  221. }
  222. },
  223. _changeTileSettings: function() {
  224. var that = this;
  225. var options = that._getOption("tile");
  226. var offsets = that._rectOffsets;
  227. var borderWidth = pickPositiveInteger(options.border.width);
  228. var edgeOffset = borderWidth / 2;
  229. var innerOffset = 1 & borderWidth ? .5 : 0;
  230. var labelOptions = options.label;
  231. var settings = that._context.settings[0];
  232. that._change(["TILES", "LABELS"]);
  233. settings.state = that._handlers.calculateState(options);
  234. that._filter = that._filter || that._renderer.shadowFilter("-50%", "-50%", "200%", "200%");
  235. that._filter.attr(labelOptions.shadow);
  236. that._calculateLabelSettings(settings, labelOptions, that._filter.id);
  237. if (offsets.tileEdge !== edgeOffset || offsets.tileInner !== innerOffset) {
  238. offsets.tileEdge = edgeOffset;
  239. offsets.tileInner = innerOffset;
  240. that._change(["TILING"])
  241. }
  242. },
  243. _changeGroupSettings: function() {
  244. var that = this;
  245. var options = that._getOption("group");
  246. var labelOptions = options.label;
  247. var offsets = that._rectOffsets;
  248. var borderWidth = pickPositiveInteger(options.border.width);
  249. var edgeOffset = borderWidth / 2;
  250. var innerOffset = 1 & borderWidth ? .5 : 0;
  251. var headerHeight = 0;
  252. var groupPadding = pickPositiveInteger(options.padding);
  253. var settings = that._context.settings[1];
  254. that._change(["TILES", "LABELS"]);
  255. settings.state = that._handlers.calculateState(options);
  256. that._calculateLabelSettings(settings, labelOptions);
  257. if (options.headerHeight >= 0) {
  258. headerHeight = pickPositiveInteger(options.headerHeight)
  259. } else {
  260. headerHeight = settings.labelParams.height + 2 * pickPositiveInteger(labelOptions.paddingTopBottom)
  261. }
  262. if (that._headerHeight !== headerHeight) {
  263. that._headerHeight = headerHeight;
  264. that._change(["TILING"])
  265. }
  266. if (that._groupPadding !== groupPadding) {
  267. that._groupPadding = groupPadding;
  268. that._change(["TILING"])
  269. }
  270. if (offsets.headerEdge !== edgeOffset || offsets.headerInner !== innerOffset) {
  271. offsets.headerEdge = edgeOffset;
  272. offsets.headerInner = innerOffset;
  273. that._change(["TILING"])
  274. }
  275. },
  276. _calculateLabelSettings: function(settings, options, filter) {
  277. var bBox = this._getTextBBox(options.font);
  278. var paddingLeftRight = pickPositiveInteger(options.paddingLeftRight);
  279. var paddingTopBottom = pickPositiveInteger(options.paddingTopBottom);
  280. var tileLabelOptions = this._getOption("tile.label");
  281. var groupLabelOptions = this._getOption("group.label");
  282. settings.labelState = (0, _common.buildTextAppearance)(options, filter);
  283. settings.labelState.visible = !("visible" in options) || !!options.visible;
  284. this._suppressDeprecatedWarnings();
  285. settings.labelParams = {
  286. height: bBox.height,
  287. rtlEnabled: this._getOption("rtlEnabled", true),
  288. paddingTopBottom: paddingTopBottom,
  289. paddingLeftRight: paddingLeftRight,
  290. resolveLabelOverflow: this._getOption("resolveLabelOverflow", true),
  291. tileLabelWordWrap: tileLabelOptions.wordWrap,
  292. tileLabelOverflow: tileLabelOptions.textOverflow,
  293. groupLabelOverflow: groupLabelOptions.textOverflow
  294. };
  295. this._resumeDeprecatedWarnings()
  296. },
  297. _changeMaxDepth: function() {
  298. var maxDepth = this._getOption("maxDepth", true);
  299. maxDepth = maxDepth >= 1 ? Math.round(maxDepth) : 1 / 0;
  300. if (this._maxDepth !== maxDepth) {
  301. this._maxDepth = maxDepth;
  302. this._change(["NODES_RESET"])
  303. }
  304. },
  305. _resetNodes: function() {
  306. var that = this;
  307. that._tilesGroup.clear();
  308. that._renderer.initHatching();
  309. that._context.forceReset = true;
  310. that._context.minLevel = that._topNode.level + 1;
  311. that._context.maxLevel = that._context.minLevel + that._maxDepth - 1;
  312. that._change(["TILES", "LABELS", "TILING"])
  313. },
  314. _processNodes: function(context, process) {
  315. processNodes(context, this._topNode, process)
  316. },
  317. _applyTilesAppearance: function() {
  318. var that = this;
  319. var colorizer = (0, _colorizing.getColorizer)(that._getOption("colorizer"), that._themeManager, that._topNode);
  320. that._processNodes({
  321. renderer: that._renderer,
  322. group: that._tilesGroup,
  323. setTrackerData: that._handlers.setTrackerData,
  324. colorField: that._getOption("colorField", true) || "color",
  325. getColor: colorizer
  326. }, processTileAppearance)
  327. },
  328. _applyLabelsAppearance: function() {
  329. var that = this;
  330. that._labelsGroup.clear();
  331. that._processNodes({
  332. renderer: that._renderer,
  333. group: that._labelsGroup,
  334. setTrackerData: that._handlers.setTrackerData,
  335. labelField: that._getOption("labelField", true) || "name"
  336. }, processLabelAppearance);
  337. that._change(["LABELS_LAYOUT"])
  338. },
  339. _performTiling: function() {
  340. var that = this;
  341. var context = {
  342. algorithm: (0, _tiling.getAlgorithm)(that._getOption("layoutAlgorithm", true)),
  343. directions: directions[String(that._getOption("layoutDirection", true)).toLowerCase()] || directions.lefttoprightbottom,
  344. headerHeight: that._headerHeight,
  345. groupPadding: that._groupPadding,
  346. rectOffsets: that._rectOffsets
  347. };
  348. that._topNode.innerRect = that._tilingRect;
  349. calculateRects(context, that._topNode);
  350. that._processNodes(context, processTiling);
  351. that._change(["LABELS_LAYOUT"]);
  352. that._onTilingPerformed()
  353. },
  354. _onTilingPerformed: _common2.noop,
  355. _performLabelsLayout: function() {
  356. this._processNodes(null, processLabelsLayout)
  357. },
  358. _getTextBBox: function(fontOptions) {
  359. var renderer = this._renderer;
  360. var text = this._textForCalculations || renderer.text("0", 0, 0);
  361. this._textForCalculations = text;
  362. text.css((0, _utils.patchFontOptions)(fontOptions)).append(renderer.root);
  363. var bBox = text.getBBox();
  364. text.remove();
  365. return bBox
  366. }
  367. });
  368. function traverseDataItems(root, dataItems, level, params) {
  369. var nodes = [];
  370. var allNodes = params.nodes;
  371. var i;
  372. var ii = dataItems.length;
  373. var totalValue = 0;
  374. for (i = 0; i < ii; ++i) {
  375. var dataItem = dataItems[i];
  376. var node = new _node2.default;
  377. node._id = allNodes.length;
  378. node.ctx = params.ctx;
  379. node.parent = root;
  380. node.level = level;
  381. node.index = nodes.length;
  382. node.data = dataItem;
  383. params.buildNode(node);
  384. allNodes.push(node);
  385. nodes.push(node);
  386. var items = dataItem[params.itemsField];
  387. if (items && items.length) {
  388. traverseDataItems(node, items, level + 1, params)
  389. }
  390. if (dataItem[params.valueField] > 0) {
  391. node.value = Number(dataItem[params.valueField])
  392. }
  393. totalValue += node.value
  394. }
  395. root.nodes = nodes;
  396. root.value = totalValue
  397. }
  398. function processNodes(context, root, process) {
  399. var nodes = root.nodes;
  400. var i;
  401. var ii = nodes.length;
  402. for (i = 0; i < ii; ++i) {
  403. var node = nodes[i];
  404. process(context, node);
  405. if (node.isNode()) {
  406. processNodes(context, node, process)
  407. }
  408. }
  409. }
  410. function processTileAppearance(context, node) {
  411. node.color = node.data[context.colorField] || context.getColor(node) || node.parent.color;
  412. node.updateStyles();
  413. node.tile = !node.ctx.forceReset && node.tile || createTile[Number(node.isNode())](context, node);
  414. node.applyState()
  415. }
  416. var createTile = [createLeaf, createGroup];
  417. function createLeaf(context, node) {
  418. var tile = context.renderer.simpleRect().append(context.group);
  419. context.setTrackerData(node, tile);
  420. return tile
  421. }
  422. function createGroup(context, node) {
  423. var outer = context.renderer.simpleRect().append(context.group);
  424. var inner = context.renderer.simpleRect().append(context.group);
  425. context.setTrackerData(node, inner);
  426. return {
  427. outer: outer,
  428. inner: inner
  429. }
  430. }
  431. function processLabelAppearance(context, node) {
  432. node.updateLabelStyle();
  433. if (node.labelState.visible) {
  434. createLabel(context, node, node.labelState, node.labelParams)
  435. }
  436. }
  437. function createLabel(context, currentNode, settings, params) {
  438. var textData = currentNode.data[context.labelField];
  439. currentNode.label = textData ? String(textData) : null;
  440. textData = currentNode.customLabel || currentNode.label;
  441. if (textData) {
  442. currentNode.text = context.renderer.text(textData).attr(settings.attr).css(settings.css).append(context.group);
  443. context.setTrackerData(currentNode, currentNode.text)
  444. }
  445. }
  446. var emptyRect = [0, 0, 0, 0];
  447. function calculateRects(context, root) {
  448. var nodes = root.nodes;
  449. var items = [];
  450. var rects = [];
  451. var sum = 0;
  452. var i;
  453. var ii = items.length = rects.length = nodes.length;
  454. for (i = 0; i < ii; ++i) {
  455. sum += nodes[i].value;
  456. items[i] = {
  457. value: nodes[i].value,
  458. i: i
  459. }
  460. }
  461. if (sum > 0) {
  462. context.algorithm({
  463. items: items.slice(),
  464. sum: sum,
  465. rect: root.innerRect.slice(),
  466. isRotated: 1 & nodes[0].level,
  467. directions: context.directions
  468. })
  469. }
  470. for (i = 0; i < ii; ++i) {
  471. rects[i] = items[i].rect || emptyRect
  472. }
  473. root.rects = rects
  474. }
  475. function processTiling(context, node) {
  476. var rect = node.parent.rects[node.index];
  477. var rectOffsets = context.rectOffsets;
  478. if (node.isNode()) {
  479. setRectAttrs(node.tile.outer, buildTileRect(rect, node.parent.innerRect, rectOffsets.headerEdge, rectOffsets.headerInner));
  480. rect = marginateRect(rect, context.groupPadding);
  481. var headerHeight = Math.min(context.headerHeight, rect[3] - rect[1]);
  482. node.rect = [rect[0], rect[1], rect[2], rect[1] + headerHeight];
  483. setRectAttrs(node.tile.inner, marginateRect(node.rect, rectOffsets.headerEdge));
  484. rect[1] += headerHeight;
  485. node.innerRect = rect;
  486. calculateRects(context, node)
  487. } else {
  488. node.rect = rect;
  489. setRectAttrs(node.tile, buildTileRect(rect, node.parent.innerRect, rectOffsets.tileEdge, rectOffsets.tileInner))
  490. }
  491. }
  492. function marginateRect(rect, margin) {
  493. return [rect[0] + margin, rect[1] + margin, rect[2] - margin, rect[3] - margin]
  494. }
  495. function buildTileRect(rect, outer, edgeOffset, innerOffset) {
  496. return [rect[0] + (rect[0] === outer[0] ? edgeOffset : +innerOffset), rect[1] + (rect[1] === outer[1] ? edgeOffset : +innerOffset), rect[2] - (rect[2] === outer[2] ? edgeOffset : -innerOffset), rect[3] - (rect[3] === outer[3] ? edgeOffset : -innerOffset)]
  497. }
  498. function setRectAttrs(element, rect) {
  499. element.attr({
  500. x: rect[0],
  501. y: rect[1],
  502. width: _max(rect[2] - rect[0], 0),
  503. height: _max(rect[3] - rect[1], 0)
  504. })
  505. }
  506. function processLabelsLayout(context, node) {
  507. if (node.text && node.labelState.visible) {
  508. layoutTextNode(node, node.labelParams)
  509. }
  510. }
  511. function layoutTextNode(node, params) {
  512. var rect = node.rect;
  513. var text = node.text;
  514. var bBox = text.getBBox();
  515. var paddingLeftRight = params.paddingLeftRight;
  516. var paddingTopBottom = params.paddingTopBottom;
  517. var effectiveWidth = rect[2] - rect[0] - 2 * paddingLeftRight;
  518. var fitByHeight = bBox.height + paddingTopBottom <= rect[3] - rect[1];
  519. var fitByWidth = bBox.width <= effectiveWidth;
  520. var resolveLabelOverflow = params.resolveLabelOverflow;
  521. var groupLabelOverflow = params.groupLabelOverflow;
  522. var tileLabelOverflow = params.tileLabelOverflow;
  523. var tileLabelWordWrap = params.tileLabelWordWrap;
  524. if ((0, _type.isDefined)(resolveLabelOverflow)) {
  525. if ("ellipsis" === resolveLabelOverflow && fitByHeight) {
  526. text.setMaxSize(effectiveWidth, void 0, {
  527. wordWrap: "none",
  528. textOverflow: "ellipsis"
  529. });
  530. if (!fitByWidth) {
  531. bBox = text.getBBox();
  532. fitByWidth = bBox.width <= effectiveWidth
  533. }
  534. }
  535. } else {
  536. fitByWidth = true;
  537. fitByHeight = true;
  538. text.setMaxSize(effectiveWidth, rect[3] - rect[1] - paddingTopBottom, node.isNode() ? {
  539. textOverflow: groupLabelOverflow,
  540. wordWrap: "none"
  541. } : {
  542. textOverflow: tileLabelOverflow,
  543. wordWrap: tileLabelWordWrap,
  544. hideOverflowEllipsis: true
  545. })
  546. }
  547. text.attr({
  548. visibility: fitByHeight && fitByWidth ? "visible" : "hidden"
  549. });
  550. if (fitByHeight && fitByWidth) {
  551. text.move(params.rtlEnabled ? rect[2] - paddingLeftRight - bBox.x - bBox.width : rect[0] + paddingLeftRight - bBox.x, rect[1] + paddingTopBottom - bBox.y)
  552. }
  553. }
  554. require("../../core/component_registrator")("dxTreeMap", dxTreeMap);
  555. module.exports = dxTreeMap;
  556. dxTreeMap.addPlugin(require("../core/data_source").plugin);