TreemapView.js 32 KB

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