941 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			941 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
 | ||
| /*
 | ||
| * Licensed to the Apache Software Foundation (ASF) under one
 | ||
| * or more contributor license agreements.  See the NOTICE file
 | ||
| * distributed with this work for additional information
 | ||
| * regarding copyright ownership.  The ASF licenses this file
 | ||
| * to you under the Apache License, Version 2.0 (the
 | ||
| * "License"); you may not use this file except in compliance
 | ||
| * with the License.  You may obtain a copy of the License at
 | ||
| *
 | ||
| *   http://www.apache.org/licenses/LICENSE-2.0
 | ||
| *
 | ||
| * Unless required by applicable law or agreed to in writing,
 | ||
| * software distributed under the License is distributed on an
 | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 | ||
| * KIND, either express or implied.  See the License for the
 | ||
| * specific language governing permissions and limitations
 | ||
| * under the License.
 | ||
| */
 | ||
| 
 | ||
| var echarts = require("../../echarts");
 | ||
| 
 | ||
| var zrUtil = require("zrender/lib/core/util");
 | ||
| 
 | ||
| var graphic = require("../../util/graphic");
 | ||
| 
 | ||
| var DataDiffer = require("../../data/DataDiffer");
 | ||
| 
 | ||
| var helper = require("../helper/treeHelper");
 | ||
| 
 | ||
| var Breadcrumb = require("./Breadcrumb");
 | ||
| 
 | ||
| var RoamController = require("../../component/helper/RoamController");
 | ||
| 
 | ||
| var BoundingRect = require("zrender/lib/core/BoundingRect");
 | ||
| 
 | ||
| var matrix = require("zrender/lib/core/matrix");
 | ||
| 
 | ||
| var animationUtil = require("../../util/animation");
 | ||
| 
 | ||
| var makeStyleMapper = require("../../model/mixin/makeStyleMapper");
 | ||
| 
 | ||
| var _format = require("../../util/format");
 | ||
| 
 | ||
| var windowOpen = _format.windowOpen;
 | ||
| 
 | ||
| /*
 | ||
| * Licensed to the Apache Software Foundation (ASF) under one
 | ||
| * or more contributor license agreements.  See the NOTICE file
 | ||
| * distributed with this work for additional information
 | ||
| * regarding copyright ownership.  The ASF licenses this file
 | ||
| * to you under the Apache License, Version 2.0 (the
 | ||
| * "License"); you may not use this file except in compliance
 | ||
| * with the License.  You may obtain a copy of the License at
 | ||
| *
 | ||
| *   http://www.apache.org/licenses/LICENSE-2.0
 | ||
| *
 | ||
| * Unless required by applicable law or agreed to in writing,
 | ||
| * software distributed under the License is distributed on an
 | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 | ||
| * KIND, either express or implied.  See the License for the
 | ||
| * specific language governing permissions and limitations
 | ||
| * under the License.
 | ||
| */
 | ||
| var bind = zrUtil.bind;
 | ||
| var Group = graphic.Group;
 | ||
| var Rect = graphic.Rect;
 | ||
| var each = zrUtil.each;
 | ||
| var DRAG_THRESHOLD = 3;
 | ||
| var PATH_LABEL_NOAMAL = ['label'];
 | ||
| var PATH_LABEL_EMPHASIS = ['emphasis', 'label'];
 | ||
| var PATH_UPPERLABEL_NORMAL = ['upperLabel'];
 | ||
| var PATH_UPPERLABEL_EMPHASIS = ['emphasis', 'upperLabel'];
 | ||
| var Z_BASE = 10; // Should bigger than every z.
 | ||
| 
 | ||
| var Z_BG = 1;
 | ||
| var Z_CONTENT = 2;
 | ||
| var getItemStyleEmphasis = makeStyleMapper([['fill', 'color'], // `borderColor` and `borderWidth` has been occupied,
 | ||
| // so use `stroke` to indicate the stroke of the rect.
 | ||
| ['stroke', 'strokeColor'], ['lineWidth', 'strokeWidth'], ['shadowBlur'], ['shadowOffsetX'], ['shadowOffsetY'], ['shadowColor']]);
 | ||
| 
 | ||
| var getItemStyleNormal = function (model) {
 | ||
|   // Normal style props should include emphasis style props.
 | ||
|   var itemStyle = getItemStyleEmphasis(model); // Clear styles set by emphasis.
 | ||
| 
 | ||
|   itemStyle.stroke = itemStyle.fill = itemStyle.lineWidth = null;
 | ||
|   return itemStyle;
 | ||
| };
 | ||
| 
 | ||
| var _default = echarts.extendChartView({
 | ||
|   type: 'treemap',
 | ||
| 
 | ||
|   /**
 | ||
|    * @override
 | ||
|    */
 | ||
|   init: function (o, api) {
 | ||
|     /**
 | ||
|      * @private
 | ||
|      * @type {module:zrender/container/Group}
 | ||
|      */
 | ||
|     this._containerGroup;
 | ||
|     /**
 | ||
|      * @private
 | ||
|      * @type {Object.<string, Array.<module:zrender/container/Group>>}
 | ||
|      */
 | ||
| 
 | ||
|     this._storage = createStorage();
 | ||
|     /**
 | ||
|      * @private
 | ||
|      * @type {module:echarts/data/Tree}
 | ||
|      */
 | ||
| 
 | ||
|     this._oldTree;
 | ||
|     /**
 | ||
|      * @private
 | ||
|      * @type {module:echarts/chart/treemap/Breadcrumb}
 | ||
|      */
 | ||
| 
 | ||
|     this._breadcrumb;
 | ||
|     /**
 | ||
|      * @private
 | ||
|      * @type {module:echarts/component/helper/RoamController}
 | ||
|      */
 | ||
| 
 | ||
|     this._controller;
 | ||
|     /**
 | ||
|      * 'ready', 'animating'
 | ||
|      * @private
 | ||
|      */
 | ||
| 
 | ||
|     this._state = 'ready';
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @override
 | ||
|    */
 | ||
|   render: function (seriesModel, ecModel, api, payload) {
 | ||
|     var models = ecModel.findComponents({
 | ||
|       mainType: 'series',
 | ||
|       subType: 'treemap',
 | ||
|       query: payload
 | ||
|     });
 | ||
| 
 | ||
|     if (zrUtil.indexOf(models, seriesModel) < 0) {
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|     this.seriesModel = seriesModel;
 | ||
|     this.api = api;
 | ||
|     this.ecModel = ecModel;
 | ||
|     var types = ['treemapZoomToNode', 'treemapRootToNode'];
 | ||
|     var targetInfo = helper.retrieveTargetInfo(payload, types, seriesModel);
 | ||
|     var payloadType = payload && payload.type;
 | ||
|     var layoutInfo = seriesModel.layoutInfo;
 | ||
|     var isInit = !this._oldTree;
 | ||
|     var thisStorage = this._storage; // Mark new root when action is treemapRootToNode.
 | ||
| 
 | ||
|     var reRoot = payloadType === 'treemapRootToNode' && targetInfo && thisStorage ? {
 | ||
|       rootNodeGroup: thisStorage.nodeGroup[targetInfo.node.getRawIndex()],
 | ||
|       direction: payload.direction
 | ||
|     } : null;
 | ||
| 
 | ||
|     var containerGroup = this._giveContainerGroup(layoutInfo);
 | ||
| 
 | ||
|     var renderResult = this._doRender(containerGroup, seriesModel, reRoot);
 | ||
| 
 | ||
|     !isInit && (!payloadType || payloadType === 'treemapZoomToNode' || payloadType === 'treemapRootToNode') ? this._doAnimation(containerGroup, renderResult, seriesModel, reRoot) : renderResult.renderFinally();
 | ||
| 
 | ||
|     this._resetController(api);
 | ||
| 
 | ||
|     this._renderBreadcrumb(seriesModel, api, targetInfo);
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _giveContainerGroup: function (layoutInfo) {
 | ||
|     var containerGroup = this._containerGroup;
 | ||
| 
 | ||
|     if (!containerGroup) {
 | ||
|       // FIXME
 | ||
|       // 加一层containerGroup是为了clip,但是现在clip功能并没有实现。
 | ||
|       containerGroup = this._containerGroup = new Group();
 | ||
| 
 | ||
|       this._initEvents(containerGroup);
 | ||
| 
 | ||
|       this.group.add(containerGroup);
 | ||
|     }
 | ||
| 
 | ||
|     containerGroup.attr('position', [layoutInfo.x, layoutInfo.y]);
 | ||
|     return containerGroup;
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _doRender: function (containerGroup, seriesModel, reRoot) {
 | ||
|     var thisTree = seriesModel.getData().tree;
 | ||
|     var oldTree = this._oldTree; // Clear last shape records.
 | ||
| 
 | ||
|     var lastsForAnimation = createStorage();
 | ||
|     var thisStorage = createStorage();
 | ||
|     var oldStorage = this._storage;
 | ||
|     var willInvisibleEls = [];
 | ||
|     var doRenderNode = zrUtil.curry(renderNode, seriesModel, thisStorage, oldStorage, reRoot, lastsForAnimation, willInvisibleEls); // Notice: when thisTree and oldTree are the same tree (see list.cloneShallow),
 | ||
|     // the oldTree is actually losted, so we can not find all of the old graphic
 | ||
|     // elements from tree. So we use this stragegy: make element storage, move
 | ||
|     // from old storage to new storage, clear old storage.
 | ||
| 
 | ||
|     dualTravel(thisTree.root ? [thisTree.root] : [], oldTree && oldTree.root ? [oldTree.root] : [], containerGroup, thisTree === oldTree || !oldTree, 0); // Process all removing.
 | ||
| 
 | ||
|     var willDeleteEls = clearStorage(oldStorage);
 | ||
|     this._oldTree = thisTree;
 | ||
|     this._storage = thisStorage;
 | ||
|     return {
 | ||
|       lastsForAnimation: lastsForAnimation,
 | ||
|       willDeleteEls: willDeleteEls,
 | ||
|       renderFinally: renderFinally
 | ||
|     };
 | ||
| 
 | ||
|     function dualTravel(thisViewChildren, oldViewChildren, parentGroup, sameTree, depth) {
 | ||
|       // When 'render' is triggered by action,
 | ||
|       // 'this' and 'old' may be the same tree,
 | ||
|       // we use rawIndex in that case.
 | ||
|       if (sameTree) {
 | ||
|         oldViewChildren = thisViewChildren;
 | ||
|         each(thisViewChildren, function (child, index) {
 | ||
|           !child.isRemoved() && processNode(index, index);
 | ||
|         });
 | ||
|       } // Diff hierarchically (diff only in each subtree, but not whole).
 | ||
|       // because, consistency of view is important.
 | ||
|       else {
 | ||
|           new DataDiffer(oldViewChildren, thisViewChildren, getKey, getKey).add(processNode).update(processNode).remove(zrUtil.curry(processNode, null)).execute();
 | ||
|         }
 | ||
| 
 | ||
|       function getKey(node) {
 | ||
|         // Identify by name or raw index.
 | ||
|         return node.getId();
 | ||
|       }
 | ||
| 
 | ||
|       function processNode(newIndex, oldIndex) {
 | ||
|         var thisNode = newIndex != null ? thisViewChildren[newIndex] : null;
 | ||
|         var oldNode = oldIndex != null ? oldViewChildren[oldIndex] : null;
 | ||
|         var group = doRenderNode(thisNode, oldNode, parentGroup, depth);
 | ||
|         group && dualTravel(thisNode && thisNode.viewChildren || [], oldNode && oldNode.viewChildren || [], group, sameTree, depth + 1);
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     function clearStorage(storage) {
 | ||
|       var willDeleteEls = createStorage();
 | ||
|       storage && each(storage, function (store, storageName) {
 | ||
|         var delEls = willDeleteEls[storageName];
 | ||
|         each(store, function (el) {
 | ||
|           el && (delEls.push(el), el.__tmWillDelete = 1);
 | ||
|         });
 | ||
|       });
 | ||
|       return willDeleteEls;
 | ||
|     }
 | ||
| 
 | ||
|     function renderFinally() {
 | ||
|       each(willDeleteEls, function (els) {
 | ||
|         each(els, function (el) {
 | ||
|           el.parent && el.parent.remove(el);
 | ||
|         });
 | ||
|       });
 | ||
|       each(willInvisibleEls, function (el) {
 | ||
|         el.invisible = true; // Setting invisible is for optimizing, so no need to set dirty,
 | ||
|         // just mark as invisible.
 | ||
| 
 | ||
|         el.dirty();
 | ||
|       });
 | ||
|     }
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _doAnimation: function (containerGroup, renderResult, seriesModel, reRoot) {
 | ||
|     if (!seriesModel.get('animation')) {
 | ||
|       return;
 | ||
|     }
 | ||
| 
 | ||
|     var duration = seriesModel.get('animationDurationUpdate');
 | ||
|     var easing = seriesModel.get('animationEasing');
 | ||
|     var animationWrap = animationUtil.createWrap(); // Make delete animations.
 | ||
| 
 | ||
|     each(renderResult.willDeleteEls, function (store, storageName) {
 | ||
|       each(store, function (el, rawIndex) {
 | ||
|         if (el.invisible) {
 | ||
|           return;
 | ||
|         }
 | ||
| 
 | ||
|         var parent = el.parent; // Always has parent, and parent is nodeGroup.
 | ||
| 
 | ||
|         var target;
 | ||
| 
 | ||
|         if (reRoot && reRoot.direction === 'drillDown') {
 | ||
|           target = parent === reRoot.rootNodeGroup // This is the content element of view root.
 | ||
|           // Only `content` will enter this branch, because
 | ||
|           // `background` and `nodeGroup` will not be deleted.
 | ||
|           ? {
 | ||
|             shape: {
 | ||
|               x: 0,
 | ||
|               y: 0,
 | ||
|               width: parent.__tmNodeWidth,
 | ||
|               height: parent.__tmNodeHeight
 | ||
|             },
 | ||
|             style: {
 | ||
|               opacity: 0
 | ||
|             } // Others.
 | ||
| 
 | ||
|           } : {
 | ||
|             style: {
 | ||
|               opacity: 0
 | ||
|             }
 | ||
|           };
 | ||
|         } else {
 | ||
|           var targetX = 0;
 | ||
|           var targetY = 0;
 | ||
| 
 | ||
|           if (!parent.__tmWillDelete) {
 | ||
|             // Let node animate to right-bottom corner, cooperating with fadeout,
 | ||
|             // which is appropriate for user understanding.
 | ||
|             // Divided by 2 for reRoot rolling up effect.
 | ||
|             targetX = parent.__tmNodeWidth / 2;
 | ||
|             targetY = parent.__tmNodeHeight / 2;
 | ||
|           }
 | ||
| 
 | ||
|           target = storageName === 'nodeGroup' ? {
 | ||
|             position: [targetX, targetY],
 | ||
|             style: {
 | ||
|               opacity: 0
 | ||
|             }
 | ||
|           } : {
 | ||
|             shape: {
 | ||
|               x: targetX,
 | ||
|               y: targetY,
 | ||
|               width: 0,
 | ||
|               height: 0
 | ||
|             },
 | ||
|             style: {
 | ||
|               opacity: 0
 | ||
|             }
 | ||
|           };
 | ||
|         }
 | ||
| 
 | ||
|         target && animationWrap.add(el, target, duration, easing);
 | ||
|       });
 | ||
|     }); // Make other animations
 | ||
| 
 | ||
|     each(this._storage, function (store, storageName) {
 | ||
|       each(store, function (el, rawIndex) {
 | ||
|         var last = renderResult.lastsForAnimation[storageName][rawIndex];
 | ||
|         var target = {};
 | ||
| 
 | ||
|         if (!last) {
 | ||
|           return;
 | ||
|         }
 | ||
| 
 | ||
|         if (storageName === 'nodeGroup') {
 | ||
|           if (last.old) {
 | ||
|             target.position = el.position.slice();
 | ||
|             el.attr('position', last.old);
 | ||
|           }
 | ||
|         } else {
 | ||
|           if (last.old) {
 | ||
|             target.shape = zrUtil.extend({}, el.shape);
 | ||
|             el.setShape(last.old);
 | ||
|           }
 | ||
| 
 | ||
|           if (last.fadein) {
 | ||
|             el.setStyle('opacity', 0);
 | ||
|             target.style = {
 | ||
|               opacity: 1
 | ||
|             };
 | ||
|           } // When animation is stopped for succedent animation starting,
 | ||
|           // el.style.opacity might not be 1
 | ||
|           else if (el.style.opacity !== 1) {
 | ||
|               target.style = {
 | ||
|                 opacity: 1
 | ||
|               };
 | ||
|             }
 | ||
|         }
 | ||
| 
 | ||
|         animationWrap.add(el, target, duration, easing);
 | ||
|       });
 | ||
|     }, this);
 | ||
|     this._state = 'animating';
 | ||
|     animationWrap.done(bind(function () {
 | ||
|       this._state = 'ready';
 | ||
|       renderResult.renderFinally();
 | ||
|     }, this)).start();
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _resetController: function (api) {
 | ||
|     var controller = this._controller; // Init controller.
 | ||
| 
 | ||
|     if (!controller) {
 | ||
|       controller = this._controller = new RoamController(api.getZr());
 | ||
|       controller.enable(this.seriesModel.get('roam'));
 | ||
|       controller.on('pan', bind(this._onPan, this));
 | ||
|       controller.on('zoom', bind(this._onZoom, this));
 | ||
|     }
 | ||
| 
 | ||
|     var rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());
 | ||
|     controller.setPointerChecker(function (e, x, y) {
 | ||
|       return rect.contain(x, y);
 | ||
|     });
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _clearController: function () {
 | ||
|     var controller = this._controller;
 | ||
| 
 | ||
|     if (controller) {
 | ||
|       controller.dispose();
 | ||
|       controller = null;
 | ||
|     }
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _onPan: function (e) {
 | ||
|     if (this._state !== 'animating' && (Math.abs(e.dx) > DRAG_THRESHOLD || Math.abs(e.dy) > DRAG_THRESHOLD)) {
 | ||
|       // These param must not be cached.
 | ||
|       var root = this.seriesModel.getData().tree.root;
 | ||
| 
 | ||
|       if (!root) {
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       var rootLayout = root.getLayout();
 | ||
| 
 | ||
|       if (!rootLayout) {
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       this.api.dispatchAction({
 | ||
|         type: 'treemapMove',
 | ||
|         from: this.uid,
 | ||
|         seriesId: this.seriesModel.id,
 | ||
|         rootRect: {
 | ||
|           x: rootLayout.x + e.dx,
 | ||
|           y: rootLayout.y + e.dy,
 | ||
|           width: rootLayout.width,
 | ||
|           height: rootLayout.height
 | ||
|         }
 | ||
|       });
 | ||
|     }
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _onZoom: function (e) {
 | ||
|     var mouseX = e.originX;
 | ||
|     var mouseY = e.originY;
 | ||
| 
 | ||
|     if (this._state !== 'animating') {
 | ||
|       // These param must not be cached.
 | ||
|       var root = this.seriesModel.getData().tree.root;
 | ||
| 
 | ||
|       if (!root) {
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       var rootLayout = root.getLayout();
 | ||
| 
 | ||
|       if (!rootLayout) {
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       var rect = new BoundingRect(rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height);
 | ||
|       var layoutInfo = this.seriesModel.layoutInfo; // Transform mouse coord from global to containerGroup.
 | ||
| 
 | ||
|       mouseX -= layoutInfo.x;
 | ||
|       mouseY -= layoutInfo.y; // Scale root bounding rect.
 | ||
| 
 | ||
|       var m = matrix.create();
 | ||
|       matrix.translate(m, m, [-mouseX, -mouseY]);
 | ||
|       matrix.scale(m, m, [e.scale, e.scale]);
 | ||
|       matrix.translate(m, m, [mouseX, mouseY]);
 | ||
|       rect.applyTransform(m);
 | ||
|       this.api.dispatchAction({
 | ||
|         type: 'treemapRender',
 | ||
|         from: this.uid,
 | ||
|         seriesId: this.seriesModel.id,
 | ||
|         rootRect: {
 | ||
|           x: rect.x,
 | ||
|           y: rect.y,
 | ||
|           width: rect.width,
 | ||
|           height: rect.height
 | ||
|         }
 | ||
|       });
 | ||
|     }
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _initEvents: function (containerGroup) {
 | ||
|     containerGroup.on('click', function (e) {
 | ||
|       if (this._state !== 'ready') {
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       var nodeClick = this.seriesModel.get('nodeClick', true);
 | ||
| 
 | ||
|       if (!nodeClick) {
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       var targetInfo = this.findTarget(e.offsetX, e.offsetY);
 | ||
| 
 | ||
|       if (!targetInfo) {
 | ||
|         return;
 | ||
|       }
 | ||
| 
 | ||
|       var node = targetInfo.node;
 | ||
| 
 | ||
|       if (node.getLayout().isLeafRoot) {
 | ||
|         this._rootToNode(targetInfo);
 | ||
|       } else {
 | ||
|         if (nodeClick === 'zoomToNode') {
 | ||
|           this._zoomToNode(targetInfo);
 | ||
|         } else if (nodeClick === 'link') {
 | ||
|           var itemModel = node.hostTree.data.getItemModel(node.dataIndex);
 | ||
|           var link = itemModel.get('link', true);
 | ||
|           var linkTarget = itemModel.get('target', true) || 'blank';
 | ||
|           link && windowOpen(link, linkTarget);
 | ||
|         }
 | ||
|       }
 | ||
|     }, this);
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _renderBreadcrumb: function (seriesModel, api, targetInfo) {
 | ||
|     if (!targetInfo) {
 | ||
|       targetInfo = seriesModel.get('leafDepth', true) != null ? {
 | ||
|         node: seriesModel.getViewRoot() // FIXME
 | ||
|         // better way?
 | ||
|         // Find breadcrumb tail on center of containerGroup.
 | ||
| 
 | ||
|       } : this.findTarget(api.getWidth() / 2, api.getHeight() / 2);
 | ||
| 
 | ||
|       if (!targetInfo) {
 | ||
|         targetInfo = {
 | ||
|           node: seriesModel.getData().tree.root
 | ||
|         };
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group))).render(seriesModel, api, targetInfo.node, bind(onSelect, this));
 | ||
| 
 | ||
|     function onSelect(node) {
 | ||
|       if (this._state !== 'animating') {
 | ||
|         helper.aboveViewRoot(seriesModel.getViewRoot(), node) ? this._rootToNode({
 | ||
|           node: node
 | ||
|         }) : this._zoomToNode({
 | ||
|           node: node
 | ||
|         });
 | ||
|       }
 | ||
|     }
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @override
 | ||
|    */
 | ||
|   remove: function () {
 | ||
|     this._clearController();
 | ||
| 
 | ||
|     this._containerGroup && this._containerGroup.removeAll();
 | ||
|     this._storage = createStorage();
 | ||
|     this._state = 'ready';
 | ||
|     this._breadcrumb && this._breadcrumb.remove();
 | ||
|   },
 | ||
|   dispose: function () {
 | ||
|     this._clearController();
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _zoomToNode: function (targetInfo) {
 | ||
|     this.api.dispatchAction({
 | ||
|       type: 'treemapZoomToNode',
 | ||
|       from: this.uid,
 | ||
|       seriesId: this.seriesModel.id,
 | ||
|       targetNode: targetInfo.node
 | ||
|     });
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @private
 | ||
|    */
 | ||
|   _rootToNode: function (targetInfo) {
 | ||
|     this.api.dispatchAction({
 | ||
|       type: 'treemapRootToNode',
 | ||
|       from: this.uid,
 | ||
|       seriesId: this.seriesModel.id,
 | ||
|       targetNode: targetInfo.node
 | ||
|     });
 | ||
|   },
 | ||
| 
 | ||
|   /**
 | ||
|    * @public
 | ||
|    * @param {number} x Global coord x.
 | ||
|    * @param {number} y Global coord y.
 | ||
|    * @return {Object} info If not found, return undefined;
 | ||
|    * @return {number} info.node Target node.
 | ||
|    * @return {number} info.offsetX x refer to target node.
 | ||
|    * @return {number} info.offsetY y refer to target node.
 | ||
|    */
 | ||
|   findTarget: function (x, y) {
 | ||
|     var targetInfo;
 | ||
|     var viewRoot = this.seriesModel.getViewRoot();
 | ||
|     viewRoot.eachNode({
 | ||
|       attr: 'viewChildren',
 | ||
|       order: 'preorder'
 | ||
|     }, function (node) {
 | ||
|       var bgEl = this._storage.background[node.getRawIndex()]; // If invisible, there might be no element.
 | ||
| 
 | ||
| 
 | ||
|       if (bgEl) {
 | ||
|         var point = bgEl.transformCoordToLocal(x, y);
 | ||
|         var shape = bgEl.shape; // For performance consideration, dont use 'getBoundingRect'.
 | ||
| 
 | ||
|         if (shape.x <= point[0] && point[0] <= shape.x + shape.width && shape.y <= point[1] && point[1] <= shape.y + shape.height) {
 | ||
|           targetInfo = {
 | ||
|             node: node,
 | ||
|             offsetX: point[0],
 | ||
|             offsetY: point[1]
 | ||
|           };
 | ||
|         } else {
 | ||
|           return false; // Suppress visit subtree.
 | ||
|         }
 | ||
|       }
 | ||
|     }, this);
 | ||
|     return targetInfo;
 | ||
|   }
 | ||
| });
 | ||
| /**
 | ||
|  * @inner
 | ||
|  */
 | ||
| 
 | ||
| 
 | ||
| function createStorage() {
 | ||
|   return {
 | ||
|     nodeGroup: [],
 | ||
|     background: [],
 | ||
|     content: []
 | ||
|   };
 | ||
| }
 | ||
| /**
 | ||
|  * @inner
 | ||
|  * @return Return undefined means do not travel further.
 | ||
|  */
 | ||
| 
 | ||
| 
 | ||
| function renderNode(seriesModel, thisStorage, oldStorage, reRoot, lastsForAnimation, willInvisibleEls, thisNode, oldNode, parentGroup, depth) {
 | ||
|   // Whether under viewRoot.
 | ||
|   if (!thisNode) {
 | ||
|     // Deleting nodes will be performed finally. This method just find
 | ||
|     // element from old storage, or create new element, set them to new
 | ||
|     // storage, and set styles.
 | ||
|     return;
 | ||
|   } // -------------------------------------------------------------------
 | ||
|   // Start of closure variables available in "Procedures in renderNode".
 | ||
| 
 | ||
| 
 | ||
|   var thisLayout = thisNode.getLayout();
 | ||
|   var data = seriesModel.getData(); // Only for enabling highlight/downplay. Clear firstly.
 | ||
|   // Because some node will not be rendered.
 | ||
| 
 | ||
|   data.setItemGraphicEl(thisNode.dataIndex, null);
 | ||
| 
 | ||
|   if (!thisLayout || !thisLayout.isInView) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   var thisWidth = thisLayout.width;
 | ||
|   var thisHeight = thisLayout.height;
 | ||
|   var borderWidth = thisLayout.borderWidth;
 | ||
|   var thisInvisible = thisLayout.invisible;
 | ||
|   var thisRawIndex = thisNode.getRawIndex();
 | ||
|   var oldRawIndex = oldNode && oldNode.getRawIndex();
 | ||
|   var thisViewChildren = thisNode.viewChildren;
 | ||
|   var upperHeight = thisLayout.upperHeight;
 | ||
|   var isParent = thisViewChildren && thisViewChildren.length;
 | ||
|   var itemStyleNormalModel = thisNode.getModel('itemStyle');
 | ||
|   var itemStyleEmphasisModel = thisNode.getModel('emphasis.itemStyle'); // End of closure ariables available in "Procedures in renderNode".
 | ||
|   // -----------------------------------------------------------------
 | ||
|   // Node group
 | ||
| 
 | ||
|   var group = giveGraphic('nodeGroup', Group);
 | ||
| 
 | ||
|   if (!group) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   parentGroup.add(group); // x,y are not set when el is above view root.
 | ||
| 
 | ||
|   group.attr('position', [thisLayout.x || 0, thisLayout.y || 0]);
 | ||
|   group.__tmNodeWidth = thisWidth;
 | ||
|   group.__tmNodeHeight = thisHeight;
 | ||
| 
 | ||
|   if (thisLayout.isAboveViewRoot) {
 | ||
|     return group;
 | ||
|   }
 | ||
| 
 | ||
|   var nodeModel = thisNode.getModel(); // Background
 | ||
| 
 | ||
|   var bg = giveGraphic('background', Rect, depth, Z_BG);
 | ||
|   bg && renderBackground(group, bg, isParent && thisLayout.upperLabelHeight); // No children, render content.
 | ||
| 
 | ||
|   if (isParent) {
 | ||
|     // Because of the implementation about "traverse" in graphic hover style, we
 | ||
|     // can not set hover listener on the "group" of non-leaf node. Otherwise the
 | ||
|     // hover event from the descendents will be listenered.
 | ||
|     if (graphic.isHighDownDispatcher(group)) {
 | ||
|       graphic.setAsHighDownDispatcher(group, false);
 | ||
|     }
 | ||
| 
 | ||
|     if (bg) {
 | ||
|       graphic.setAsHighDownDispatcher(bg, true); // Only for enabling highlight/downplay.
 | ||
| 
 | ||
|       data.setItemGraphicEl(thisNode.dataIndex, bg);
 | ||
|     }
 | ||
|   } else {
 | ||
|     var content = giveGraphic('content', Rect, depth, Z_CONTENT);
 | ||
|     content && renderContent(group, content);
 | ||
| 
 | ||
|     if (bg && graphic.isHighDownDispatcher(bg)) {
 | ||
|       graphic.setAsHighDownDispatcher(bg, false);
 | ||
|     }
 | ||
| 
 | ||
|     graphic.setAsHighDownDispatcher(group, true); // Only for enabling highlight/downplay.
 | ||
| 
 | ||
|     data.setItemGraphicEl(thisNode.dataIndex, group);
 | ||
|   }
 | ||
| 
 | ||
|   return group; // ----------------------------
 | ||
|   // | Procedures in renderNode |
 | ||
|   // ----------------------------
 | ||
| 
 | ||
|   function renderBackground(group, bg, useUpperLabel) {
 | ||
|     // For tooltip.
 | ||
|     bg.dataIndex = thisNode.dataIndex;
 | ||
|     bg.seriesIndex = seriesModel.seriesIndex;
 | ||
|     bg.setShape({
 | ||
|       x: 0,
 | ||
|       y: 0,
 | ||
|       width: thisWidth,
 | ||
|       height: thisHeight
 | ||
|     });
 | ||
| 
 | ||
|     if (thisInvisible) {
 | ||
|       // If invisible, do not set visual, otherwise the element will
 | ||
|       // change immediately before animation. We think it is OK to
 | ||
|       // remain its origin color when moving out of the view window.
 | ||
|       processInvisible(bg);
 | ||
|     } else {
 | ||
|       bg.invisible = false;
 | ||
|       var visualBorderColor = thisNode.getVisual('borderColor', true);
 | ||
|       var emphasisBorderColor = itemStyleEmphasisModel.get('borderColor');
 | ||
|       var normalStyle = getItemStyleNormal(itemStyleNormalModel);
 | ||
|       normalStyle.fill = visualBorderColor;
 | ||
|       var emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel);
 | ||
|       emphasisStyle.fill = emphasisBorderColor;
 | ||
| 
 | ||
|       if (useUpperLabel) {
 | ||
|         var upperLabelWidth = thisWidth - 2 * borderWidth;
 | ||
|         prepareText(normalStyle, emphasisStyle, visualBorderColor, upperLabelWidth, upperHeight, {
 | ||
|           x: borderWidth,
 | ||
|           y: 0,
 | ||
|           width: upperLabelWidth,
 | ||
|           height: upperHeight
 | ||
|         });
 | ||
|       } // For old bg.
 | ||
|       else {
 | ||
|           normalStyle.text = emphasisStyle.text = null;
 | ||
|         }
 | ||
| 
 | ||
|       bg.setStyle(normalStyle);
 | ||
|       graphic.setElementHoverStyle(bg, emphasisStyle);
 | ||
|     }
 | ||
| 
 | ||
|     group.add(bg);
 | ||
|   }
 | ||
| 
 | ||
|   function renderContent(group, content) {
 | ||
|     // For tooltip.
 | ||
|     content.dataIndex = thisNode.dataIndex;
 | ||
|     content.seriesIndex = seriesModel.seriesIndex;
 | ||
|     var contentWidth = Math.max(thisWidth - 2 * borderWidth, 0);
 | ||
|     var contentHeight = Math.max(thisHeight - 2 * borderWidth, 0);
 | ||
|     content.culling = true;
 | ||
|     content.setShape({
 | ||
|       x: borderWidth,
 | ||
|       y: borderWidth,
 | ||
|       width: contentWidth,
 | ||
|       height: contentHeight
 | ||
|     });
 | ||
| 
 | ||
|     if (thisInvisible) {
 | ||
|       // If invisible, do not set visual, otherwise the element will
 | ||
|       // change immediately before animation. We think it is OK to
 | ||
|       // remain its origin color when moving out of the view window.
 | ||
|       processInvisible(content);
 | ||
|     } else {
 | ||
|       content.invisible = false;
 | ||
|       var visualColor = thisNode.getVisual('color', true);
 | ||
|       var normalStyle = getItemStyleNormal(itemStyleNormalModel);
 | ||
|       normalStyle.fill = visualColor;
 | ||
|       var emphasisStyle = getItemStyleEmphasis(itemStyleEmphasisModel);
 | ||
|       prepareText(normalStyle, emphasisStyle, visualColor, contentWidth, contentHeight);
 | ||
|       content.setStyle(normalStyle);
 | ||
|       graphic.setElementHoverStyle(content, emphasisStyle);
 | ||
|     }
 | ||
| 
 | ||
|     group.add(content);
 | ||
|   }
 | ||
| 
 | ||
|   function processInvisible(element) {
 | ||
|     // Delay invisible setting utill animation finished,
 | ||
|     // avoid element vanish suddenly before animation.
 | ||
|     !element.invisible && willInvisibleEls.push(element);
 | ||
|   }
 | ||
| 
 | ||
|   function prepareText(normalStyle, emphasisStyle, visualColor, width, height, upperLabelRect) {
 | ||
|     var defaultText = nodeModel.get('name');
 | ||
|     var normalLabelModel = nodeModel.getModel(upperLabelRect ? PATH_UPPERLABEL_NORMAL : PATH_LABEL_NOAMAL);
 | ||
|     var emphasisLabelModel = nodeModel.getModel(upperLabelRect ? PATH_UPPERLABEL_EMPHASIS : PATH_LABEL_EMPHASIS);
 | ||
|     var isShow = normalLabelModel.getShallow('show');
 | ||
|     graphic.setLabelStyle(normalStyle, emphasisStyle, normalLabelModel, emphasisLabelModel, {
 | ||
|       defaultText: isShow ? defaultText : null,
 | ||
|       autoColor: visualColor,
 | ||
|       isRectText: true,
 | ||
|       labelFetcher: seriesModel,
 | ||
|       labelDataIndex: thisNode.dataIndex,
 | ||
|       labelProp: upperLabelRect ? 'upperLabel' : 'label'
 | ||
|     });
 | ||
|     addDrillDownIcon(normalStyle, upperLabelRect, thisLayout);
 | ||
|     addDrillDownIcon(emphasisStyle, upperLabelRect, thisLayout);
 | ||
|     upperLabelRect && (normalStyle.textRect = zrUtil.clone(upperLabelRect));
 | ||
|     normalStyle.truncate = isShow && normalLabelModel.get('ellipsis') ? {
 | ||
|       outerWidth: width,
 | ||
|       outerHeight: height,
 | ||
|       minChar: 2
 | ||
|     } : null;
 | ||
|   }
 | ||
| 
 | ||
|   function addDrillDownIcon(style, upperLabelRect, thisLayout) {
 | ||
|     var text = style.text;
 | ||
| 
 | ||
|     if (!upperLabelRect && thisLayout.isLeafRoot && text != null) {
 | ||
|       var iconChar = seriesModel.get('drillDownIcon', true);
 | ||
|       style.text = iconChar ? iconChar + ' ' + text : text;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   function giveGraphic(storageName, Ctor, depth, z) {
 | ||
|     var element = oldRawIndex != null && oldStorage[storageName][oldRawIndex];
 | ||
|     var lasts = lastsForAnimation[storageName];
 | ||
| 
 | ||
|     if (element) {
 | ||
|       // Remove from oldStorage
 | ||
|       oldStorage[storageName][oldRawIndex] = null;
 | ||
|       prepareAnimationWhenHasOld(lasts, element, storageName);
 | ||
|     } // If invisible and no old element, do not create new element (for optimizing).
 | ||
|     else if (!thisInvisible) {
 | ||
|         element = new Ctor({
 | ||
|           z: calculateZ(depth, z)
 | ||
|         });
 | ||
|         element.__tmDepth = depth;
 | ||
|         element.__tmStorageName = storageName;
 | ||
|         prepareAnimationWhenNoOld(lasts, element, storageName);
 | ||
|       } // Set to thisStorage
 | ||
| 
 | ||
| 
 | ||
|     return thisStorage[storageName][thisRawIndex] = element;
 | ||
|   }
 | ||
| 
 | ||
|   function prepareAnimationWhenHasOld(lasts, element, storageName) {
 | ||
|     var lastCfg = lasts[thisRawIndex] = {};
 | ||
|     lastCfg.old = storageName === 'nodeGroup' ? element.position.slice() : zrUtil.extend({}, element.shape);
 | ||
|   } // If a element is new, we need to find the animation start point carefully,
 | ||
|   // otherwise it will looks strange when 'zoomToNode'.
 | ||
| 
 | ||
| 
 | ||
|   function prepareAnimationWhenNoOld(lasts, element, storageName) {
 | ||
|     var lastCfg = lasts[thisRawIndex] = {};
 | ||
|     var parentNode = thisNode.parentNode;
 | ||
| 
 | ||
|     if (parentNode && (!reRoot || reRoot.direction === 'drillDown')) {
 | ||
|       var parentOldX = 0;
 | ||
|       var parentOldY = 0; // New nodes appear from right-bottom corner in 'zoomToNode' animation.
 | ||
|       // For convenience, get old bounding rect from background.
 | ||
| 
 | ||
|       var parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()];
 | ||
| 
 | ||
|       if (!reRoot && parentOldBg && parentOldBg.old) {
 | ||
|         parentOldX = parentOldBg.old.width;
 | ||
|         parentOldY = parentOldBg.old.height;
 | ||
|       } // When no parent old shape found, its parent is new too,
 | ||
|       // so we can just use {x:0, y:0}.
 | ||
| 
 | ||
| 
 | ||
|       lastCfg.old = storageName === 'nodeGroup' ? [0, parentOldY] : {
 | ||
|         x: parentOldX,
 | ||
|         y: parentOldY,
 | ||
|         width: 0,
 | ||
|         height: 0
 | ||
|       };
 | ||
|     } // Fade in, user can be aware that these nodes are new.
 | ||
| 
 | ||
| 
 | ||
|     lastCfg.fadein = storageName !== 'nodeGroup';
 | ||
|   }
 | ||
| } // We can not set all backgroud with the same z, Because the behaviour of
 | ||
| // drill down and roll up differ background creation sequence from tree
 | ||
| // hierarchy sequence, which cause that lowser background element overlap
 | ||
| // upper ones. So we calculate z based on depth.
 | ||
| // Moreover, we try to shrink down z interval to [0, 1] to avoid that
 | ||
| // treemap with large z overlaps other components.
 | ||
| 
 | ||
| 
 | ||
| function calculateZ(depth, zInLevel) {
 | ||
|   var zb = depth * Z_BASE + zInLevel;
 | ||
|   return (zb - 1) / zb;
 | ||
| }
 | ||
| 
 | ||
| module.exports = _default; | 
