TreemapView.js 25 KB

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