TreemapView.js 29 KB

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