TreemapView.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. var echarts = require("../../echarts");
  20. var zrUtil = require("zrender/lib/core/util");
  21. var graphic = require("../../util/graphic");
  22. var DataDiffer = require("../../data/DataDiffer");
  23. var helper = require("../helper/treeHelper");
  24. var Breadcrumb = require("./Breadcrumb");
  25. var RoamController = require("../../component/helper/RoamController");
  26. var BoundingRect = require("zrender/lib/core/BoundingRect");
  27. var matrix = require("zrender/lib/core/matrix");
  28. var animationUtil = require("../../util/animation");
  29. var makeStyleMapper = require("../../model/mixin/makeStyleMapper");
  30. /*
  31. * Licensed to the Apache Software Foundation (ASF) under one
  32. * or more contributor license agreements. See the NOTICE file
  33. * distributed with this work for additional information
  34. * regarding copyright ownership. The ASF licenses this file
  35. * to you under the Apache License, Version 2.0 (the
  36. * "License"); you may not use this file except in compliance
  37. * with the License. You may obtain a copy of the License at
  38. *
  39. * http://www.apache.org/licenses/LICENSE-2.0
  40. *
  41. * Unless required by applicable law or agreed to in writing,
  42. * software distributed under the License is distributed on an
  43. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  44. * KIND, either express or implied. See the License for the
  45. * specific language governing permissions and limitations
  46. * under the License.
  47. */
  48. var bind = zrUtil.bind;
  49. var Group = graphic.Group;
  50. var Rect = graphic.Rect;
  51. var each = zrUtil.each;
  52. var DRAG_THRESHOLD = 3;
  53. var PATH_LABEL_NOAMAL = ['label'];
  54. var PATH_LABEL_EMPHASIS = ['emphasis', 'label'];
  55. var PATH_UPPERLABEL_NORMAL = ['upperLabel'];
  56. var PATH_UPPERLABEL_EMPHASIS = ['emphasis', 'upperLabel'];
  57. var Z_BASE = 10; // Should bigger than every z.
  58. var Z_BG = 1;
  59. var Z_CONTENT = 2;
  60. var getItemStyleEmphasis = makeStyleMapper([['fill', 'color'], // `borderColor` and `borderWidth` has been occupied,
  61. // so use `stroke` to indicate the stroke of the rect.
  62. ['stroke', 'strokeColor'], ['lineWidth', 'strokeWidth'], ['shadowBlur'], ['shadowOffsetX'], ['shadowOffsetY'], ['shadowColor']]);
  63. var getItemStyleNormal = function (model) {
  64. // Normal style props should include emphasis style props.
  65. var itemStyle = getItemStyleEmphasis(model); // Clear styles set by emphasis.
  66. itemStyle.stroke = itemStyle.fill = itemStyle.lineWidth = null;
  67. return itemStyle;
  68. };
  69. var _default = echarts.extendChartView({
  70. type: 'treemap',
  71. /**
  72. * @override
  73. */
  74. init: function (o, api) {
  75. /**
  76. * @private
  77. * @type {module:zrender/container/Group}
  78. */
  79. this._containerGroup;
  80. /**
  81. * @private
  82. * @type {Object.<string, Array.<module:zrender/container/Group>>}
  83. */
  84. this._storage = createStorage();
  85. /**
  86. * @private
  87. * @type {module:echarts/data/Tree}
  88. */
  89. this._oldTree;
  90. /**
  91. * @private
  92. * @type {module:echarts/chart/treemap/Breadcrumb}
  93. */
  94. this._breadcrumb;
  95. /**
  96. * @private
  97. * @type {module:echarts/component/helper/RoamController}
  98. */
  99. this._controller;
  100. /**
  101. * 'ready', 'animating'
  102. * @private
  103. */
  104. this._state = 'ready';
  105. },
  106. /**
  107. * @override
  108. */
  109. render: function (seriesModel, ecModel, api, payload) {
  110. var models = ecModel.findComponents({
  111. mainType: 'series',
  112. subType: 'treemap',
  113. query: payload
  114. });
  115. if (zrUtil.indexOf(models, seriesModel) < 0) {
  116. return;
  117. }
  118. this.seriesModel = seriesModel;
  119. this.api = api;
  120. this.ecModel = ecModel;
  121. var types = ['treemapZoomToNode', 'treemapRootToNode'];
  122. var targetInfo = helper.retrieveTargetInfo(payload, types, seriesModel);
  123. var payloadType = payload && payload.type;
  124. var layoutInfo = seriesModel.layoutInfo;
  125. var isInit = !this._oldTree;
  126. var thisStorage = this._storage; // Mark new root when action is treemapRootToNode.
  127. var reRoot = payloadType === 'treemapRootToNode' && targetInfo && thisStorage ? {
  128. rootNodeGroup: thisStorage.nodeGroup[targetInfo.node.getRawIndex()],
  129. direction: payload.direction
  130. } : null;
  131. var containerGroup = this._giveContainerGroup(layoutInfo);
  132. var renderResult = this._doRender(containerGroup, seriesModel, reRoot);
  133. !isInit && (!payloadType || payloadType === 'treemapZoomToNode' || payloadType === 'treemapRootToNode') ? this._doAnimation(containerGroup, renderResult, seriesModel, reRoot) : renderResult.renderFinally();
  134. this._resetController(api);
  135. this._renderBreadcrumb(seriesModel, api, targetInfo);
  136. },
  137. /**
  138. * @private
  139. */
  140. _giveContainerGroup: function (layoutInfo) {
  141. var containerGroup = this._containerGroup;
  142. if (!containerGroup) {
  143. // FIXME
  144. // 加一层containerGroup是为了clip,但是现在clip功能并没有实现。
  145. containerGroup = this._containerGroup = new Group();
  146. this._initEvents(containerGroup);
  147. this.group.add(containerGroup);
  148. }
  149. containerGroup.attr('position', [layoutInfo.x, layoutInfo.y]);
  150. return containerGroup;
  151. },
  152. /**
  153. * @private
  154. */
  155. _doRender: function (containerGroup, seriesModel, reRoot) {
  156. var thisTree = seriesModel.getData().tree;
  157. var oldTree = this._oldTree; // Clear last shape records.
  158. var lastsForAnimation = createStorage();
  159. var thisStorage = createStorage();
  160. var oldStorage = this._storage;
  161. var willInvisibleEls = [];
  162. var doRenderNode = zrUtil.curry(renderNode, seriesModel, thisStorage, oldStorage, reRoot, lastsForAnimation, willInvisibleEls); // Notice: when thisTree and oldTree are the same tree (see list.cloneShallow),
  163. // the oldTree is actually losted, so we can not find all of the old graphic
  164. // elements from tree. So we use this stragegy: make element storage, move
  165. // from old storage to new storage, clear old storage.
  166. dualTravel(thisTree.root ? [thisTree.root] : [], oldTree && oldTree.root ? [oldTree.root] : [], containerGroup, thisTree === oldTree || !oldTree, 0); // Process all removing.
  167. var willDeleteEls = clearStorage(oldStorage);
  168. this._oldTree = thisTree;
  169. this._storage = thisStorage;
  170. return {
  171. lastsForAnimation: lastsForAnimation,
  172. willDeleteEls: willDeleteEls,
  173. renderFinally: renderFinally
  174. };
  175. function dualTravel(thisViewChildren, oldViewChildren, parentGroup, sameTree, depth) {
  176. // When 'render' is triggered by action,
  177. // 'this' and 'old' may be the same tree,
  178. // we use rawIndex in that case.
  179. if (sameTree) {
  180. oldViewChildren = thisViewChildren;
  181. each(thisViewChildren, function (child, index) {
  182. !child.isRemoved() && processNode(index, index);
  183. });
  184. } // Diff hierarchically (diff only in each subtree, but not whole).
  185. // because, consistency of view is important.
  186. else {
  187. new DataDiffer(oldViewChildren, thisViewChildren, getKey, getKey).add(processNode).update(processNode).remove(zrUtil.curry(processNode, null)).execute();
  188. }
  189. function getKey(node) {
  190. // Identify by name or raw index.
  191. return node.getId();
  192. }
  193. function processNode(newIndex, oldIndex) {
  194. var thisNode = newIndex != null ? thisViewChildren[newIndex] : null;
  195. var oldNode = oldIndex != null ? oldViewChildren[oldIndex] : null;
  196. var group = doRenderNode(thisNode, oldNode, parentGroup, depth);
  197. group && dualTravel(thisNode && thisNode.viewChildren || [], oldNode && oldNode.viewChildren || [], group, sameTree, depth + 1);
  198. }
  199. }
  200. function clearStorage(storage) {
  201. var willDeleteEls = createStorage();
  202. storage && each(storage, function (store, storageName) {
  203. var delEls = willDeleteEls[storageName];
  204. each(store, function (el) {
  205. el && (delEls.push(el), el.__tmWillDelete = 1);
  206. });
  207. });
  208. return willDeleteEls;
  209. }
  210. function renderFinally() {
  211. each(willDeleteEls, function (els) {
  212. each(els, function (el) {
  213. el.parent && el.parent.remove(el);
  214. });
  215. });
  216. each(willInvisibleEls, function (el) {
  217. el.invisible = true; // Setting invisible is for optimizing, so no need to set dirty,
  218. // just mark as invisible.
  219. el.dirty();
  220. });
  221. }
  222. },
  223. /**
  224. * @private
  225. */
  226. _doAnimation: function (containerGroup, renderResult, seriesModel, reRoot) {
  227. if (!seriesModel.get('animation')) {
  228. return;
  229. }
  230. var duration = seriesModel.get('animationDurationUpdate');
  231. var easing = seriesModel.get('animationEasing');
  232. var animationWrap = animationUtil.createWrap(); // Make delete animations.
  233. each(renderResult.willDeleteEls, function (store, storageName) {
  234. each(store, function (el, rawIndex) {
  235. if (el.invisible) {
  236. return;
  237. }
  238. var parent = el.parent; // Always has parent, and parent is nodeGroup.
  239. var target;
  240. if (reRoot && reRoot.direction === 'drillDown') {
  241. target = parent === reRoot.rootNodeGroup // This is the content element of view root.
  242. // Only `content` will enter this branch, because
  243. // `background` and `nodeGroup` will not be deleted.
  244. ? {
  245. shape: {
  246. x: 0,
  247. y: 0,
  248. width: parent.__tmNodeWidth,
  249. height: parent.__tmNodeHeight
  250. },
  251. style: {
  252. opacity: 0
  253. } // Others.
  254. } : {
  255. style: {
  256. opacity: 0
  257. }
  258. };
  259. } else {
  260. var targetX = 0;
  261. var targetY = 0;
  262. if (!parent.__tmWillDelete) {
  263. // Let node animate to right-bottom corner, cooperating with fadeout,
  264. // which is appropriate for user understanding.
  265. // Divided by 2 for reRoot rolling up effect.
  266. targetX = parent.__tmNodeWidth / 2;
  267. targetY = parent.__tmNodeHeight / 2;
  268. }
  269. target = storageName === 'nodeGroup' ? {
  270. position: [targetX, targetY],
  271. style: {
  272. opacity: 0
  273. }
  274. } : {
  275. shape: {
  276. x: targetX,
  277. y: targetY,
  278. width: 0,
  279. height: 0
  280. },
  281. style: {
  282. opacity: 0
  283. }
  284. };
  285. }
  286. target && animationWrap.add(el, target, duration, easing);
  287. });
  288. }); // Make other animations
  289. each(this._storage, function (store, storageName) {
  290. each(store, function (el, rawIndex) {
  291. var last = renderResult.lastsForAnimation[storageName][rawIndex];
  292. var target = {};
  293. if (!last) {
  294. return;
  295. }
  296. if (storageName === 'nodeGroup') {
  297. if (last.old) {
  298. target.position = el.position.slice();
  299. el.attr('position', last.old);
  300. }
  301. } else {
  302. if (last.old) {
  303. target.shape = zrUtil.extend({}, el.shape);
  304. el.setShape(last.old);
  305. }
  306. if (last.fadein) {
  307. el.setStyle('opacity', 0);
  308. target.style = {
  309. opacity: 1
  310. };
  311. } // When animation is stopped for succedent animation starting,
  312. // el.style.opacity might not be 1
  313. else if (el.style.opacity !== 1) {
  314. target.style = {
  315. opacity: 1
  316. };
  317. }
  318. }
  319. animationWrap.add(el, target, duration, easing);
  320. });
  321. }, this);
  322. this._state = 'animating';
  323. animationWrap.done(bind(function () {
  324. this._state = 'ready';
  325. renderResult.renderFinally();
  326. }, this)).start();
  327. },
  328. /**
  329. * @private
  330. */
  331. _resetController: function (api) {
  332. var controller = this._controller; // Init controller.
  333. if (!controller) {
  334. controller = this._controller = new RoamController(api.getZr());
  335. controller.enable(this.seriesModel.get('roam'));
  336. controller.on('pan', bind(this._onPan, this));
  337. controller.on('zoom', bind(this._onZoom, this));
  338. }
  339. var rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());
  340. controller.setPointerChecker(function (e, x, y) {
  341. return rect.contain(x, y);
  342. });
  343. },
  344. /**
  345. * @private
  346. */
  347. _clearController: function () {
  348. var controller = this._controller;
  349. if (controller) {
  350. controller.dispose();
  351. controller = null;
  352. }
  353. },
  354. /**
  355. * @private
  356. */
  357. _onPan: function (e) {
  358. if (this._state !== 'animating' && (Math.abs(e.dx) > DRAG_THRESHOLD || Math.abs(e.dy) > DRAG_THRESHOLD)) {
  359. // These param must not be cached.
  360. var root = this.seriesModel.getData().tree.root;
  361. if (!root) {
  362. return;
  363. }
  364. var rootLayout = root.getLayout();
  365. if (!rootLayout) {
  366. return;
  367. }
  368. this.api.dispatchAction({
  369. type: 'treemapMove',
  370. from: this.uid,
  371. seriesId: this.seriesModel.id,
  372. rootRect: {
  373. x: rootLayout.x + e.dx,
  374. y: rootLayout.y + e.dy,
  375. width: rootLayout.width,
  376. height: rootLayout.height
  377. }
  378. });
  379. }
  380. },
  381. /**
  382. * @private
  383. */
  384. _onZoom: function (e) {
  385. var mouseX = e.originX;
  386. var mouseY = e.originY;
  387. if (this._state !== 'animating') {
  388. // These param must not be cached.
  389. var root = this.seriesModel.getData().tree.root;
  390. if (!root) {
  391. return;
  392. }
  393. var rootLayout = root.getLayout();
  394. if (!rootLayout) {
  395. return;
  396. }
  397. var rect = new BoundingRect(rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height);
  398. var layoutInfo = this.seriesModel.layoutInfo; // Transform mouse coord from global to containerGroup.
  399. mouseX -= layoutInfo.x;
  400. mouseY -= layoutInfo.y; // Scale root bounding rect.
  401. var m = matrix.create();
  402. matrix.translate(m, m, [-mouseX, -mouseY]);
  403. matrix.scale(m, m, [e.scale, e.scale]);
  404. matrix.translate(m, m, [mouseX, mouseY]);
  405. rect.applyTransform(m);
  406. this.api.dispatchAction({
  407. type: 'treemapRender',
  408. from: this.uid,
  409. seriesId: this.seriesModel.id,
  410. rootRect: {
  411. x: rect.x,
  412. y: rect.y,
  413. width: rect.width,
  414. height: rect.height
  415. }
  416. });
  417. }
  418. },
  419. /**
  420. * @private
  421. */
  422. _initEvents: function (containerGroup) {
  423. containerGroup.on('click', function (e) {
  424. if (this._state !== 'ready') {
  425. return;
  426. }
  427. var nodeClick = this.seriesModel.get('nodeClick', true);
  428. if (!nodeClick) {
  429. return;
  430. }
  431. var targetInfo = this.findTarget(e.offsetX, e.offsetY);
  432. if (!targetInfo) {
  433. return;
  434. }
  435. var node = targetInfo.node;
  436. if (node.getLayout().isLeafRoot) {
  437. this._rootToNode(targetInfo);
  438. } else {
  439. if (nodeClick === 'zoomToNode') {
  440. this._zoomToNode(targetInfo);
  441. } else if (nodeClick === 'link') {
  442. var itemModel = node.hostTree.data.getItemModel(node.dataIndex);
  443. var link = itemModel.get('link', true);
  444. var linkTarget = itemModel.get('target', true) || 'blank';
  445. link && window.open(link, linkTarget);
  446. }
  447. }
  448. }, this);
  449. },
  450. /**
  451. * @private
  452. */
  453. _renderBreadcrumb: function (seriesModel, api, targetInfo) {
  454. if (!targetInfo) {
  455. targetInfo = seriesModel.get('leafDepth', true) != null ? {
  456. node: seriesModel.getViewRoot() // FIXME
  457. // better way?
  458. // Find breadcrumb tail on center of containerGroup.
  459. } : this.findTarget(api.getWidth() / 2, api.getHeight() / 2);
  460. if (!targetInfo) {
  461. targetInfo = {
  462. node: seriesModel.getData().tree.root
  463. };
  464. }
  465. }
  466. (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group))).render(seriesModel, api, targetInfo.node, bind(onSelect, this));
  467. function onSelect(node) {
  468. if (this._state !== 'animating') {
  469. helper.aboveViewRoot(seriesModel.getViewRoot(), node) ? this._rootToNode({
  470. node: node
  471. }) : this._zoomToNode({
  472. node: node
  473. });
  474. }
  475. }
  476. },
  477. /**
  478. * @override
  479. */
  480. remove: function () {
  481. this._clearController();
  482. this._containerGroup && this._containerGroup.removeAll();
  483. this._storage = createStorage();
  484. this._state = 'ready';
  485. this._breadcrumb && this._breadcrumb.remove();
  486. },
  487. dispose: function () {
  488. this._clearController();
  489. },
  490. /**
  491. * @private
  492. */
  493. _zoomToNode: function (targetInfo) {
  494. this.api.dispatchAction({
  495. type: 'treemapZoomToNode',
  496. from: this.uid,
  497. seriesId: this.seriesModel.id,
  498. targetNode: targetInfo.node
  499. });
  500. },
  501. /**
  502. * @private
  503. */
  504. _rootToNode: function (targetInfo) {
  505. this.api.dispatchAction({
  506. type: 'treemapRootToNode',
  507. from: this.uid,
  508. seriesId: this.seriesModel.id,
  509. targetNode: targetInfo.node
  510. });
  511. },
  512. /**
  513. * @public
  514. * @param {number} x Global coord x.
  515. * @param {number} y Global coord y.
  516. * @return {Object} info If not found, return undefined;
  517. * @return {number} info.node Target node.
  518. * @return {number} info.offsetX x refer to target node.
  519. * @return {number} info.offsetY y refer to target node.
  520. */
  521. findTarget: function (x, y) {
  522. var targetInfo;
  523. var viewRoot = this.seriesModel.getViewRoot();
  524. viewRoot.eachNode({
  525. attr: 'viewChildren',
  526. order: 'preorder'
  527. }, function (node) {
  528. var bgEl = this._storage.background[node.getRawIndex()]; // If invisible, there might be no element.
  529. if (bgEl) {
  530. var point = bgEl.transformCoordToLocal(x, y);
  531. var shape = bgEl.shape; // For performance consideration, dont use 'getBoundingRect'.
  532. if (shape.x <= point[0] && point[0] <= shape.x + shape.width && shape.y <= point[1] && point[1] <= shape.y + shape.height) {
  533. targetInfo = {
  534. node: node,
  535. offsetX: point[0],
  536. offsetY: point[1]
  537. };
  538. } else {
  539. return false; // Suppress visit subtree.
  540. }
  541. }
  542. }, this);
  543. return targetInfo;
  544. }
  545. });
  546. /**
  547. * @inner
  548. */
  549. function createStorage() {
  550. return {
  551. nodeGroup: [],
  552. background: [],
  553. content: []
  554. };
  555. }
  556. /**
  557. * @inner
  558. * @return Return undefined means do not travel further.
  559. */
  560. function renderNode(seriesModel, thisStorage, oldStorage, reRoot, lastsForAnimation, willInvisibleEls, thisNode, oldNode, parentGroup, depth) {
  561. // Whether under viewRoot.
  562. if (!thisNode) {
  563. // Deleting nodes will be performed finally. This method just find
  564. // element from old storage, or create new element, set them to new
  565. // storage, and set styles.
  566. return;
  567. } // -------------------------------------------------------------------
  568. // Start of closure variables available in "Procedures in renderNode".
  569. var thisLayout = thisNode.getLayout();
  570. if (!thisLayout || !thisLayout.isInView) {
  571. return;
  572. }
  573. var thisWidth = thisLayout.width;
  574. var thisHeight = thisLayout.height;
  575. var borderWidth = thisLayout.borderWidth;
  576. var thisInvisible = thisLayout.invisible;
  577. var thisRawIndex = thisNode.getRawIndex();
  578. var oldRawIndex = oldNode && oldNode.getRawIndex();
  579. var thisViewChildren = thisNode.viewChildren;
  580. var upperHeight = thisLayout.upperHeight;
  581. var isParent = thisViewChildren && thisViewChildren.length;
  582. var itemStyleNormalModel = thisNode.getModel('itemStyle');
  583. var itemStyleEmphasisModel = thisNode.getModel('emphasis.itemStyle'); // End of closure ariables available in "Procedures in renderNode".
  584. // -----------------------------------------------------------------
  585. // Node group
  586. var group = giveGraphic('nodeGroup', Group);
  587. if (!group) {
  588. return;
  589. }
  590. parentGroup.add(group); // x,y are not set when el is above view root.
  591. group.attr('position', [thisLayout.x || 0, thisLayout.y || 0]);
  592. group.__tmNodeWidth = thisWidth;
  593. group.__tmNodeHeight = thisHeight;
  594. if (thisLayout.isAboveViewRoot) {
  595. return group;
  596. } // Background
  597. var bg = giveGraphic('background', Rect, depth, Z_BG);
  598. bg && renderBackground(group, bg, isParent && thisLayout.upperHeight); // No children, render content.
  599. if (!isParent) {
  600. var content = giveGraphic('content', Rect, depth, Z_CONTENT);
  601. content && renderContent(group, content);
  602. }
  603. return group; // ----------------------------
  604. // | Procedures in renderNode |
  605. // ----------------------------
  606. function renderBackground(group, bg, useUpperLabel) {
  607. // For tooltip.
  608. bg.dataIndex = thisNode.dataIndex;
  609. bg.seriesIndex = seriesModel.seriesIndex;
  610. bg.setShape({
  611. x: 0,
  612. y: 0,
  613. width: thisWidth,
  614. height: thisHeight
  615. });
  616. var visualBorderColor = thisNode.getVisual('borderColor', true);
  617. var emphasisBorderColor = itemStyleEmphasisModel.get('borderColor');
  618. updateStyle(bg, function () {
  619. var normalStyle = getItemStyleNormal(itemStyleNormalModel);
  620. normalStyle.fill = visualBorderColor;
  621. var emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel);
  622. emphasisStyle.fill = emphasisBorderColor;
  623. if (useUpperLabel) {
  624. var upperLabelWidth = thisWidth - 2 * borderWidth;
  625. prepareText(normalStyle, emphasisStyle, visualBorderColor, upperLabelWidth, upperHeight, {
  626. x: borderWidth,
  627. y: 0,
  628. width: upperLabelWidth,
  629. height: upperHeight
  630. });
  631. } // For old bg.
  632. else {
  633. normalStyle.text = emphasisStyle.text = null;
  634. }
  635. bg.setStyle(normalStyle);
  636. graphic.setHoverStyle(bg, emphasisStyle);
  637. });
  638. group.add(bg);
  639. }
  640. function renderContent(group, content) {
  641. // For tooltip.
  642. content.dataIndex = thisNode.dataIndex;
  643. content.seriesIndex = seriesModel.seriesIndex;
  644. var contentWidth = Math.max(thisWidth - 2 * borderWidth, 0);
  645. var contentHeight = Math.max(thisHeight - 2 * borderWidth, 0);
  646. content.culling = true;
  647. content.setShape({
  648. x: borderWidth,
  649. y: borderWidth,
  650. width: contentWidth,
  651. height: contentHeight
  652. });
  653. var visualColor = thisNode.getVisual('color', true);
  654. updateStyle(content, function () {
  655. var normalStyle = getItemStyleNormal(itemStyleNormalModel);
  656. normalStyle.fill = visualColor;
  657. var emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel);
  658. prepareText(normalStyle, emphasisStyle, visualColor, contentWidth, contentHeight);
  659. content.setStyle(normalStyle);
  660. graphic.setHoverStyle(content, emphasisStyle);
  661. });
  662. group.add(content);
  663. }
  664. function updateStyle(element, cb) {
  665. if (!thisInvisible) {
  666. // If invisible, do not set visual, otherwise the element will
  667. // change immediately before animation. We think it is OK to
  668. // remain its origin color when moving out of the view window.
  669. cb();
  670. if (!element.__tmWillVisible) {
  671. element.invisible = false;
  672. }
  673. } else {
  674. // Delay invisible setting utill animation finished,
  675. // avoid element vanish suddenly before animation.
  676. !element.invisible && willInvisibleEls.push(element);
  677. }
  678. }
  679. function prepareText(normalStyle, emphasisStyle, visualColor, width, height, upperLabelRect) {
  680. var nodeModel = thisNode.getModel();
  681. var text = zrUtil.retrieve(seriesModel.getFormattedLabel(thisNode.dataIndex, 'normal', null, null, upperLabelRect ? 'upperLabel' : 'label'), nodeModel.get('name'));
  682. if (!upperLabelRect && thisLayout.isLeafRoot) {
  683. var iconChar = seriesModel.get('drillDownIcon', true);
  684. text = iconChar ? iconChar + ' ' + text : text;
  685. }
  686. var normalLabelModel = nodeModel.getModel(upperLabelRect ? PATH_UPPERLABEL_NORMAL : PATH_LABEL_NOAMAL);
  687. var emphasisLabelModel = nodeModel.getModel(upperLabelRect ? PATH_UPPERLABEL_EMPHASIS : PATH_LABEL_EMPHASIS);
  688. var isShow = normalLabelModel.getShallow('show');
  689. graphic.setLabelStyle(normalStyle, emphasisStyle, normalLabelModel, emphasisLabelModel, {
  690. defaultText: isShow ? text : null,
  691. autoColor: visualColor,
  692. isRectText: true
  693. });
  694. upperLabelRect && (normalStyle.textRect = zrUtil.clone(upperLabelRect));
  695. normalStyle.truncate = isShow && normalLabelModel.get('ellipsis') ? {
  696. outerWidth: width,
  697. outerHeight: height,
  698. minChar: 2
  699. } : null;
  700. }
  701. function giveGraphic(storageName, Ctor, depth, z) {
  702. var element = oldRawIndex != null && oldStorage[storageName][oldRawIndex];
  703. var lasts = lastsForAnimation[storageName];
  704. if (element) {
  705. // Remove from oldStorage
  706. oldStorage[storageName][oldRawIndex] = null;
  707. prepareAnimationWhenHasOld(lasts, element, storageName);
  708. } // If invisible and no old element, do not create new element (for optimizing).
  709. else if (!thisInvisible) {
  710. element = new Ctor({
  711. z: calculateZ(depth, z)
  712. });
  713. element.__tmDepth = depth;
  714. element.__tmStorageName = storageName;
  715. prepareAnimationWhenNoOld(lasts, element, storageName);
  716. } // Set to thisStorage
  717. return thisStorage[storageName][thisRawIndex] = element;
  718. }
  719. function prepareAnimationWhenHasOld(lasts, element, storageName) {
  720. var lastCfg = lasts[thisRawIndex] = {};
  721. lastCfg.old = storageName === 'nodeGroup' ? element.position.slice() : zrUtil.extend({}, element.shape);
  722. } // If a element is new, we need to find the animation start point carefully,
  723. // otherwise it will looks strange when 'zoomToNode'.
  724. function prepareAnimationWhenNoOld(lasts, element, storageName) {
  725. var lastCfg = lasts[thisRawIndex] = {};
  726. var parentNode = thisNode.parentNode;
  727. if (parentNode && (!reRoot || reRoot.direction === 'drillDown')) {
  728. var parentOldX = 0;
  729. var parentOldY = 0; // New nodes appear from right-bottom corner in 'zoomToNode' animation.
  730. // For convenience, get old bounding rect from background.
  731. var parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()];
  732. if (!reRoot && parentOldBg && parentOldBg.old) {
  733. parentOldX = parentOldBg.old.width;
  734. parentOldY = parentOldBg.old.height;
  735. } // When no parent old shape found, its parent is new too,
  736. // so we can just use {x:0, y:0}.
  737. lastCfg.old = storageName === 'nodeGroup' ? [0, parentOldY] : {
  738. x: parentOldX,
  739. y: parentOldY,
  740. width: 0,
  741. height: 0
  742. };
  743. } // Fade in, user can be aware that these nodes are new.
  744. lastCfg.fadein = storageName !== 'nodeGroup';
  745. }
  746. } // We can not set all backgroud with the same z, Because the behaviour of
  747. // drill down and roll up differ background creation sequence from tree
  748. // hierarchy sequence, which cause that lowser background element overlap
  749. // upper ones. So we calculate z based on depth.
  750. // Moreover, we try to shrink down z interval to [0, 1] to avoid that
  751. // treemap with large z overlaps other components.
  752. function calculateZ(depth, zInLevel) {
  753. var zb = depth * Z_BASE + zInLevel;
  754. return (zb - 1) / zb;
  755. }
  756. module.exports = _default;