TreemapView.js 33 KB

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