TreemapView.js 31.4 KB
Newer Older
P
pah100 已提交
1
 define(function(require) {
P
pah100 已提交
2 3 4

    var zrUtil = require('zrender/core/util');
    var graphic = require('../../util/graphic');
P
pah100 已提交
5
    var DataDiffer = require('../../data/DataDiffer');
P
pah100 已提交
6
    var helper = require('./helper');
P
pah100 已提交
7
    var Breadcrumb = require('./Breadcrumb');
P
pah100 已提交
8 9 10
    var RoamController = require('../../component/helper/RoamController');
    var BoundingRect = require('zrender/core/BoundingRect');
    var matrix = require('zrender/core/matrix');
11
    var animationUtil = require('../../util/animation');
P
pah100 已提交
12
    var bind = zrUtil.bind;
P
pah100 已提交
13 14
    var Group = graphic.Group;
    var Rect = graphic.Rect;
15
    var each = zrUtil.each;
P
pah100 已提交
16

P
pah100 已提交
17
    var DRAG_THRESHOLD = 3;
18
    var PATH_LABEL_NOAMAL = ['label', 'normal'];
19
    var PATH_LABEL_EMPHASIS = ['label', 'emphasis'];
20 21
    var PATH_UPPERLABEL_NORMAL = ['upperLabel', 'normal'];
    var PATH_UPPERLABEL_EMPHASIS = ['upperLabel', 'emphasis'];
22
    var Z_BASE = 10; // Should bigger than every z.
P
pah100 已提交
23 24
    var Z_BG = 1;
    var Z_CONTENT = 2;
P
pah100 已提交
25

P
pah100 已提交
26 27 28 29 30 31 32
    return require('../../echarts').extendChartView({

        type: 'treemap',

        /**
         * @override
         */
P
pah100 已提交
33
        init: function (o, api) {
P
pah100 已提交
34

P
pah100 已提交
35 36 37 38
            /**
             * @private
             * @type {module:zrender/container/Group}
             */
P
pah100 已提交
39
            this._containerGroup;
P
pah100 已提交
40 41 42 43 44

            /**
             * @private
             * @type {Object.<string, Array.<module:zrender/container/Group>>}
             */
P
pah100 已提交
45
            this._storage = createStorage();
P
pah100 已提交
46

P
pah100 已提交
47 48 49 50 51
            /**
             * @private
             * @type {module:echarts/data/Tree}
             */
            this._oldTree;
P
pah100 已提交
52 53 54 55 56 57

            /**
             * @private
             * @type {module:echarts/chart/treemap/Breadcrumb}
             */
            this._breadcrumb;
P
pah100 已提交
58 59 60 61 62 63 64 65

            /**
             * @private
             * @type {module:echarts/component/helper/RoamController}
             */
            this._controller;

            /**
P
pah100 已提交
66
             * 'ready', 'animating'
P
pah100 已提交
67 68 69
             * @private
             */
            this._state = 'ready';
P
pah100 已提交
70 71 72 73 74
        },

        /**
         * @override
         */
P
pah100 已提交
75
        render: function (seriesModel, ecModel, api, payload) {
76 77 78 79 80

            var models = ecModel.findComponents({
                mainType: 'series', subType: 'treemap', query: payload
            });
            if (zrUtil.indexOf(models, seriesModel) < 0) {
P
pah100 已提交
81 82 83
                return;
            }

P
pah100 已提交
84
            this.seriesModel = seriesModel;
P
pah100 已提交
85
            this.api = api;
P
pah100 已提交
86
            this.ecModel = ecModel;
P
pah100 已提交
87

P
pah100 已提交
88
            var targetInfo = helper.retrieveTargetInfo(payload, seriesModel);
89 90
            var payloadType = payload && payload.type;
            var layoutInfo = seriesModel.layoutInfo;
P
tweak  
pah100 已提交
91
            var isInit = !this._oldTree;
P
pah100 已提交
92 93 94 95 96 97 98 99 100
            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;
101 102 103

            var containerGroup = this._giveContainerGroup(layoutInfo);

P
pah100 已提交
104 105 106 107 108 109 110 111 112
            var renderResult = this._doRender(containerGroup, seriesModel, reRoot);
            (
                !isInit && (
                    !payloadType
                    || payloadType === 'treemapZoomToNode'
                    || payloadType === 'treemapRootToNode'
                )
            )
                ? this._doAnimation(containerGroup, renderResult, seriesModel, reRoot)
113
                : renderResult.renderFinally();
P
pah100 已提交
114

115 116 117 118 119 120 121 122 123 124
            this._resetController(api);

            this._renderBreadcrumb(seriesModel, api, targetInfo);
        },

        /**
         * @private
         */
        _giveContainerGroup: function (layoutInfo) {
            var containerGroup = this._containerGroup;
P
pah100 已提交
125
            if (!containerGroup) {
P
pah100 已提交
126 127
                // FIXME
                // 加一层containerGroup是为了clip,但是现在clip功能并没有实现。
P
pah100 已提交
128 129
                containerGroup = this._containerGroup = new Group();
                this._initEvents(containerGroup);
P
pah100 已提交
130
                this.group.add(containerGroup);
P
pah100 已提交
131
            }
L
lang 已提交
132
            containerGroup.attr('position', [layoutInfo.x, layoutInfo.y]);
P
pah100 已提交
133

134
            return containerGroup;
P
pah100 已提交
135 136 137 138 139
        },

        /**
         * @private
         */
P
pah100 已提交
140
        _doRender: function (containerGroup, seriesModel, reRoot) {
141
            var thisTree = seriesModel.getData().tree;
P
pah100 已提交
142 143
            var oldTree = this._oldTree;

P
pah100 已提交
144
            // Clear last shape records.
145
            var lastsForAnimation = createStorage();
P
pah100 已提交
146
            var thisStorage = createStorage();
P
pah100 已提交
147
            var oldStorage = this._storage;
148
            var willInvisibleEls = [];
149
            var doRenderNode = zrUtil.curry(
150
                renderNode, seriesModel,
P
pah100 已提交
151
                thisStorage, oldStorage, reRoot,
152
                lastsForAnimation, willInvisibleEls
153
            );
P
pah100 已提交
154

P
typo  
pah100 已提交
155
            // Notice: when thisTree and oldTree are the same tree (see list.cloneShallow),
P
pah100 已提交
156 157 158 159
            // 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.

P
pah100 已提交
160 161 162 163 164
            dualTravel(
                thisTree.root ? [thisTree.root] : [],
                (oldTree && oldTree.root) ? [oldTree.root] : [],
                containerGroup,
                thisTree === oldTree || !oldTree,
P
pah100 已提交
165
                0
P
pah100 已提交
166 167 168
            );

            // Process all removing.
P
pah100 已提交
169
            var willDeleteEls = clearStorage(oldStorage);
P
pah100 已提交
170 171 172 173

            this._oldTree = thisTree;
            this._storage = thisStorage;

174
            return {
175
                lastsForAnimation: lastsForAnimation,
P
pah100 已提交
176 177
                willDeleteEls: willDeleteEls,
                renderFinally: renderFinally
178
            };
P
pah100 已提交
179

180
            function dualTravel(thisViewChildren, oldViewChildren, parentGroup, sameTree, depth) {
P
pah100 已提交
181 182 183 184 185
                // When 'render' is triggered by action,
                // 'this' and 'old' may be the same tree,
                // we use rawIndex in that case.
                if (sameTree) {
                    oldViewChildren = thisViewChildren;
186
                    each(thisViewChildren, function (child, index) {
P
pah100 已提交
187
                        !child.isRemoved() && processNode(index, index);
P
pah100 已提交
188 189
                    });
                }
P
pah100 已提交
190 191
                // Diff hierarchically (diff only in each subtree, but not whole).
                // because, consistency of view is important.
P
pah100 已提交
192
                else {
P
pah100 已提交
193
                    (new DataDiffer(oldViewChildren, thisViewChildren, getKey, getKey))
P
pah100 已提交
194 195
                        .add(processNode)
                        .update(processNode)
196 197
                        .remove(zrUtil.curry(processNode, null))
                        .execute();
P
pah100 已提交
198 199 200 201
                }

                function getKey(node) {
                    // Identify by name or raw index.
P
pah100 已提交
202
                    return node.getId();
P
pah100 已提交
203 204 205 206 207 208
                }

                function processNode(newIndex, oldIndex) {
                    var thisNode = newIndex != null ? thisViewChildren[newIndex] : null;
                    var oldNode = oldIndex != null ? oldViewChildren[oldIndex] : null;

209
                    var group = doRenderNode(thisNode, oldNode, parentGroup, depth);
210

211
                    group && dualTravel(
P
pah100 已提交
212 213 214
                        thisNode && thisNode.viewChildren || [],
                        oldNode && oldNode.viewChildren || [],
                        group,
P
pah100 已提交
215
                        sameTree,
216
                        depth + 1
P
pah100 已提交
217 218 219
                    );
                }
            }
P
pah100 已提交
220 221

            function clearStorage(storage) {
222 223 224 225
                var willDeleteEls = createStorage();
                storage && each(storage, function (store, storageName) {
                    var delEls = willDeleteEls[storageName];
                    each(store, function (el) {
226
                        el && (delEls.push(el), el.__tmWillDelete = 1);
P
pah100 已提交
227 228
                    });
                });
P
pah100 已提交
229
                return willDeleteEls;
P
pah100 已提交
230
            }
231

P
pah100 已提交
232
            function renderFinally() {
233 234 235 236
                each(willDeleteEls, function (els) {
                    each(els, function (el) {
                        el.parent && el.parent.remove(el);
                    });
P
pah100 已提交
237
                });
238
                each(willInvisibleEls, function (el) {
239
                    el.invisible = true;
240 241
                    // Setting invisible is for optimizing, so no need to set dirty,
                    // just mark as invisible.
P
pah100 已提交
242
                    el.dirty();
243
                });
244
            }
P
pah100 已提交
245 246
        },

P
pah100 已提交
247 248 249
        /**
         * @private
         */
P
pah100 已提交
250
        _doAnimation: function (containerGroup, renderResult, seriesModel, reRoot) {
251
            if (!seriesModel.get('animation')) {
P
pah100 已提交
252 253 254
                return;
            }

255 256 257 258 259
            var duration = seriesModel.get('animationDurationUpdate');
            var easing = seriesModel.get('animationEasing');
            var animationWrap = animationUtil.createWrap();

            // Make delete animations.
P
pah100 已提交
260 261
            each(renderResult.willDeleteEls, function (store, storageName) {
                each(store, function (el, rawIndex) {
262
                    if (el.invisible) {
P
pah100 已提交
263 264 265 266 267 268
                        return;
                    }

                    var parent = el.parent; // Always has parent, and parent is nodeGroup.
                    var target;

P
pah100 已提交
269 270 271 272 273 274
                    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.
                            ? {
P
pah100 已提交
275
                                shape: {
276 277 278 279
                                    x: 0,
                                    y: 0,
                                    width: parent.__tmNodeWidth,
                                    height: parent.__tmNodeHeight
P
pah100 已提交
280 281 282
                                },
                                style: {
                                    opacity: 0
P
pah100 已提交
283
                                }
P
pah100 已提交
284 285 286
                            }
                            // Others.
                            : {style: {opacity: 0}};
P
pah100 已提交
287 288 289 290 291 292 293 294
                    }
                    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.
P
pah100 已提交
295
                            // Divided by 2 for reRoot rolling up effect.
P
pah100 已提交
296 297 298
                            targetX = parent.__tmNodeWidth / 2;
                            targetY = parent.__tmNodeHeight / 2;
                        }
299

P
pah100 已提交
300 301 302 303 304 305 306 307 308 309
                        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);
                });
310 311 312 313 314 315
            });

            // Make other animations
            each(this._storage, function (store, storageName) {
                each(store, function (el, rawIndex) {
                    var last = renderResult.lastsForAnimation[storageName][rawIndex];
P
pah100 已提交
316
                    var target = {};
P
pah100 已提交
317 318 319 320

                    if (!last) {
                        return;
                    }
321 322

                    if (storageName === 'nodeGroup') {
P
pah100 已提交
323 324
                        if (last.old) {
                            target.position = el.position.slice();
L
lang 已提交
325
                            el.attr('position', last.old);
P
pah100 已提交
326
                        }
P
pah100 已提交
327
                    }
328
                    else {
P
pah100 已提交
329 330 331 332
                        if (last.old) {
                            target.shape = zrUtil.extend({}, el.shape);
                            el.setShape(last.old);
                        }
333 334 335 336 337

                        if (last.fadein) {
                            el.setStyle('opacity', 0);
                            target.style = {opacity: 1};
                        }
P
pah100 已提交
338 339 340 341 342
                        // 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};
                        }
P
pah100 已提交
343
                    }
344

345
                    animationWrap.add(el, target, duration, easing);
P
pah100 已提交
346
                });
347
            }, this);
P
pah100 已提交
348

349
            this._state = 'animating';
P
pah100 已提交
350

351 352 353
            animationWrap
                .done(bind(function () {
                    this._state = 'ready';
354
                    renderResult.renderFinally();
355 356
                }, this))
                .start();
P
pah100 已提交
357 358 359 360 361
        },

        /**
         * @private
         */
362
        _resetController: function (api) {
P
pah100 已提交
363 364 365 366 367
            var controller = this._controller;

            // Init controller.
            if (!controller) {
                controller = this._controller = new RoamController(api.getZr());
368
                controller.enable(this.seriesModel.get('roam'));
369 370
                controller.on('pan', bind(this._onPan, this));
                controller.on('zoom', bind(this._onZoom, this));
P
pah100 已提交
371 372
            }

373
            var rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());
P
pah100 已提交
374
            controller.setPointerChecker(function (e, x, y) {
375 376
                return rect.contain(x, y);
            });
377
        },
P
pah100 已提交
378

379 380 381 382 383 384
        /**
         * @private
         */
        _clearController: function () {
            var controller = this._controller;
            if (controller) {
1
100pah 已提交
385
                controller.dispose();
386
                controller = null;
P
pah100 已提交
387
            }
P
pah100 已提交
388
        },
P
pah100 已提交
389

P
pah100 已提交
390 391 392 393 394 395 396 397
        /**
         * @private
         */
        _onPan: function (dx, dy) {
            if (this._state !== 'animating'
                && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)
            ) {
                // These param must not be cached.
P
pah100 已提交
398
                var root = this.seriesModel.getData().tree.root;
399

P
pah100 已提交
400
                if (!root) {
P
pah100 已提交
401
                    return;
P
pah100 已提交
402
                }
403

P
pah100 已提交
404
                var rootLayout = root.getLayout();
405

P
pah100 已提交
406 407 408 409
                if (!rootLayout) {
                    return;
                }

L
lang 已提交
410
                this.api.dispatchAction({
411 412
                    type: 'treemapMove',
                    from: this.uid,
P
tweak  
pah100 已提交
413
                    seriesId: this.seriesModel.id,
414
                    rootRect: {
P
tweak  
pah100 已提交
415
                        x: rootLayout.x + dx, y: rootLayout.y + dy,
416 417 418
                        width: rootLayout.width, height: rootLayout.height
                    }
                });
P
pah100 已提交
419
            }
P
pah100 已提交
420
        },
P
pah100 已提交
421

P
pah100 已提交
422 423 424 425
        /**
         * @private
         */
        _onZoom: function (scale, mouseX, mouseY) {
P
pah100 已提交
426
            if (this._state !== 'animating') {
P
pah100 已提交
427
                // These param must not be cached.
P
pah100 已提交
428
                var root = this.seriesModel.getData().tree.root;
429

P
pah100 已提交
430
                if (!root) {
431 432 433
                    return;
                }

P
pah100 已提交
434
                var rootLayout = root.getLayout();
P
pah100 已提交
435 436 437 438 439

                if (!rootLayout) {
                    return;
                }

440
                var rect = new BoundingRect(
P
tweak  
pah100 已提交
441
                    rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height
442
                );
P
tweak  
pah100 已提交
443
                var layoutInfo = this.seriesModel.layoutInfo;
P
pah100 已提交
444

445 446 447 448 449
                // Transform mouse coord from global to containerGroup.
                mouseX -= layoutInfo.x;
                mouseY -= layoutInfo.y;

                // Scale root bounding rect.
P
pah100 已提交
450 451 452 453 454 455 456
                var m = matrix.create();
                matrix.translate(m, m, [-mouseX, -mouseY]);
                matrix.scale(m, m, [scale, scale]);
                matrix.translate(m, m, [mouseX, mouseY]);

                rect.applyTransform(m);

L
lang 已提交
457
                this.api.dispatchAction({
P
pah100 已提交
458 459
                    type: 'treemapRender',
                    from: this.uid,
P
tweak  
pah100 已提交
460
                    seriesId: this.seriesModel.id,
461 462 463
                    rootRect: {
                        x: rect.x, y: rect.y,
                        width: rect.width, height: rect.height
P
pah100 已提交
464 465
                    }
                });
P
pah100 已提交
466 467 468 469 470 471
            }
        },

        /**
         * @private
         */
P
pah100 已提交
472
        _initEvents: function (containerGroup) {
S
sushuang 已提交
473 474 475
            containerGroup.on('click', function (e) {
                if (this._state !== 'ready') {
                    return;
P
pah100 已提交
476 477
                }

478 479 480 481 482 483
                var nodeClick = this.seriesModel.get('nodeClick', true);

                if (!nodeClick) {
                    return;
                }

484
                var targetInfo = this.findTarget(e.offsetX, e.offsetY);
485

P
pah100 已提交
486 487 488 489 490 491 492 493 494
                if (!targetInfo) {
                    return;
                }

                var node = targetInfo.node;
                if (node.getLayout().isLeafRoot) {
                    this._rootToNode(targetInfo);
                }
                else {
495 496 497 498 499 500 501 502 503
                    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 && window.open(link, linkTarget);
                    }
P
pah100 已提交
504
                }
S
sushuang 已提交
505 506

            }, this);
P
pah100 已提交
507 508
        },

P
pah100 已提交
509 510 511 512 513
        /**
         * @private
         */
        _renderBreadcrumb: function (seriesModel, api, targetInfo) {
            if (!targetInfo) {
514 515 516 517 518 519
                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);
P
pah100 已提交
520 521 522 523 524 525

                if (!targetInfo) {
                    targetInfo = {node: seriesModel.getData().tree.root};
                }
            }

526 527
            (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group)))
                .render(seriesModel, api, targetInfo.node, bind(onSelect, this));
P
pah100 已提交
528 529

            function onSelect(node) {
P
pah100 已提交
530 531 532 533 534
                if (this._state !== 'animating') {
                    helper.aboveViewRoot(seriesModel.getViewRoot(), node)
                        ? this._rootToNode({node: node})
                        : this._zoomToNode({node: node});
                }
P
pah100 已提交
535 536 537
            }
        },

P
pah100 已提交
538 539 540 541
        /**
         * @override
         */
        remove: function () {
542
            this._clearController();
L
lang 已提交
543
            this._containerGroup && this._containerGroup.removeAll();
P
pah100 已提交
544 545
            this._storage = createStorage();
            this._state = 'ready';
P
pah100 已提交
546
            this._breadcrumb && this._breadcrumb.remove();
P
pah100 已提交
547 548
        },

549 550 551 552
        dispose: function () {
            this._clearController();
        },

P
pah100 已提交
553 554 555 556
        /**
         * @private
         */
        _zoomToNode: function (targetInfo) {
L
lang 已提交
557
            this.api.dispatchAction({
P
pah100 已提交
558
                type: 'treemapZoomToNode',
P
pah100 已提交
559
                from: this.uid,
P
tweak  
pah100 已提交
560
                seriesId: this.seriesModel.id,
P
pah100 已提交
561
                targetNode: targetInfo.node
P
pah100 已提交
562 563 564
            });
        },

P
pah100 已提交
565 566 567 568 569 570 571 572 573 574 575 576
        /**
         * @private
         */
        _rootToNode: function (targetInfo) {
            this.api.dispatchAction({
                type: 'treemapRootToNode',
                from: this.uid,
                seriesId: this.seriesModel.id,
                targetNode: targetInfo.node
            });
        },

P
pah100 已提交
577 578
        /**
         * @public
P
pah100 已提交
579 580
         * @param {number} x Global coord x.
         * @param {number} y Global coord y.
P
pah100 已提交
581 582 583 584 585 586 587 588 589 590
         * @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) {
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
                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.
                    }
P
pah100 已提交
608 609 610 611
                }
            }, this);

            return targetInfo;
P
pah100 已提交
612 613 614
        }

    });
P
pah100 已提交
615

616 617 618
    /**
     * @inner
     */
P
pah100 已提交
619 620 621
    function createStorage() {
        return {nodeGroup: [], background: [], content: []};
    }
P
pah100 已提交
622

623 624
    /**
     * @inner
625
     * @return Return undefined means do not travel further.
626 627 628
     */
    function renderNode(
        seriesModel, thisStorage, oldStorage, reRoot,
629 630
        lastsForAnimation, willInvisibleEls,
        thisNode, oldNode, parentGroup, depth
631
    ) {
632 633 634 635 636 637 638
        // 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;
        }
639

640 641 642
        // -------------------------------------------------------------------
        // Start of closure variables available in "Procedures in renderNode".

P
pah100 已提交
643
        var thisLayout = thisNode.getLayout();
644

P
pah100 已提交
645
        if (!thisLayout || !thisLayout.isInView) {
646 647 648
            return;
        }

P
pah100 已提交
649 650
        var thisWidth = thisLayout.width;
        var thisHeight = thisLayout.height;
651
        var borderWidth = thisLayout.borderWidth;
P
pah100 已提交
652
        var thisInvisible = thisLayout.invisible;
653

654 655 656
        var thisRawIndex = thisNode.getRawIndex();
        var oldRawIndex = oldNode && oldNode.getRawIndex();

657 658 659
        var thisViewChildren = thisNode.viewChildren;
        var upperHeight = thisLayout.upperHeight;
        var isParent = thisViewChildren && thisViewChildren.length;
660
        var itemStyleEmphasisModel = thisNode.getModel('itemStyle.emphasis');
661 662 663 664

        // End of closure ariables available in "Procedures in renderNode".
        // -----------------------------------------------------------------

665 666
        // Node group
        var group = giveGraphic('nodeGroup', Group);
667

668 669 670
        if (!group) {
            return;
        }
671

672
        parentGroup.add(group);
673
        // x,y are not set when el is above view root.
L
lang 已提交
674
        group.attr('position', [thisLayout.x || 0, thisLayout.y || 0]);
675 676 677
        group.__tmNodeWidth = thisWidth;
        group.__tmNodeHeight = thisHeight;

P
pah100 已提交
678
        if (thisLayout.isAboveViewRoot) {
679 680 681
            return group;
        }

682
        // Background
683
        var bg = giveGraphic('background', Rect, depth, Z_BG);
684
        bg && renderBackground(group, bg, isParent && thisLayout.upperHeight);
685 686

        // No children, render content.
687
        if (!isParent) {
688
            var content = giveGraphic('content', Rect, depth, Z_CONTENT);
689
            content && renderContent(group, content);
690 691 692 693 694 695 696 697
        }

        return group;

        // ----------------------------
        // | Procedures in renderNode |
        // ----------------------------

698 699 700 701 702 703 704
        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});
            var visualBorderColor = thisNode.getVisual('borderColor', true);
705
            var emphasisBorderColor = itemStyleEmphasisModel.get('borderColor');
706 707

            updateStyle(bg, function () {
708 709
                var normalStyle = {fill: visualBorderColor};
                var emphasisStyle = {fill: emphasisBorderColor};
710 711 712 713 714 715 716 717 718 719 720

                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 {
S
sushuang 已提交
721
                    normalStyle.text = emphasisStyle.text = null;
722
                }
723 724 725

                bg.setStyle(normalStyle);
                graphic.setHoverStyle(bg, emphasisStyle);
726 727 728 729 730 731
            });

            group.add(bg);
        }

        function renderContent(group, content) {
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
            // 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
            });

            var visualColor = thisNode.getVisual('color', true);
            updateStyle(content, function () {
                var normalStyle = {fill: visualColor};
750
                var emphasisStyle = itemStyleEmphasisModel.getItemStyle();
751 752 753 754 755 756 757 758 759 760 761

                prepareText(normalStyle, emphasisStyle, visualColor, contentWidth, contentHeight);

                content.setStyle(normalStyle);
                graphic.setHoverStyle(content, emphasisStyle);
            });

            group.add(content);
        }

        function updateStyle(element, cb) {
P
pah100 已提交
762
            if (!thisInvisible) {
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
                // 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.
                cb();

                if (!element.__tmWillVisible) {
                    element.invisible = false;
                }
            }
            else {
                // Delay invisible setting utill animation finished,
                // avoid element vanish suddenly before animation.
                !element.invisible && willInvisibleEls.push(element);
            }
        }

779
        function prepareText(normalStyle, emphasisStyle, visualColor, width, height, upperLabelRect) {
780
            var nodeModel = thisNode.getModel();
781
            var text = zrUtil.retrieve(
782 783 784
                seriesModel.getFormattedLabel(
                    thisNode.dataIndex, 'normal', null, null, upperLabelRect ? 'upperLabel' : 'label'
                ),
785 786
                nodeModel.get('name')
            );
787
            if (!upperLabelRect && thisLayout.isLeafRoot) {
P
pah100 已提交
788
                var iconChar = seriesModel.get('drillDownIcon', true);
789
                text = iconChar ? iconChar + ' ' + text : text;
P
pah100 已提交
790
            }
791

S
sushuang 已提交
792 793
            var normalLabelModel = nodeModel.getModel(
                upperLabelRect ? PATH_UPPERLABEL_NORMAL : PATH_LABEL_NOAMAL
794
            );
S
sushuang 已提交
795
            var emphasisLabelModel = nodeModel.getModel(
S
sushuang 已提交
796
                upperLabelRect ? PATH_UPPERLABEL_EMPHASIS : PATH_LABEL_EMPHASIS
S
sushuang 已提交
797
            );
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818

            var isShow = normalLabelModel.getShallow('show');

            graphic.setLabelStyle(
                normalStyle, emphasisStyle, normalLabelModel, emphasisLabelModel,
                {
                    defaultText: isShow ? text : null,
                    autoColor: visualColor,
                    isRectText: true
                }
            );

            upperLabelRect && (normalStyle.textRect = zrUtil.clone(upperLabelRect));

            normalStyle.truncate = (isShow && normalLabelModel.get('ellipsis'))
                ? {
                    outerWidth: width,
                    outerHeight: height,
                    minChar: 2
                }
                : null;
819 820
        }

821
        function giveGraphic(storageName, Ctor, depth, z) {
822 823 824 825 826 827 828 829 830
            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).
P
pah100 已提交
831
            else if (!thisInvisible) {
832 833 834
                element = new Ctor({z: calculateZ(depth, z)});
                element.__tmDepth = depth;
                element.__tmStorageName = storageName;
835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851
                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) {
852 853 854
            var lastCfg = lasts[thisRawIndex] = {};
            var parentNode = thisNode.parentNode;

P
pah100 已提交
855
            if (parentNode && (!reRoot || reRoot.direction === 'drillDown')) {
856 857
                var parentOldX = 0;
                var parentOldY = 0;
P
pah100 已提交
858 859

                // New nodes appear from right-bottom corner in 'zoomToNode' animation.
860 861
                // For convenience, get old bounding rect from background.
                var parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()];
P
pah100 已提交
862 863 864
                if (!reRoot && parentOldBg && parentOldBg.old) {
                    parentOldX = parentOldBg.old.width;
                    parentOldY = parentOldBg.old.height;
865
                }
P
pah100 已提交
866

867 868 869
                // When no parent old shape found, its parent is new too,
                // so we can just use {x:0, y:0}.
                lastCfg.old = storageName === 'nodeGroup'
P
pah100 已提交
870
                    ? [0, parentOldY]
871
                    : {x: parentOldX, y: parentOldY, width: 0, height: 0};
872
            }
873 874 875

            // Fade in, user can be aware that these nodes are new.
            lastCfg.fadein = storageName !== 'nodeGroup';
876 877 878
        }
    }

879
    // We can not set all backgroud with the same z, Because the behaviour of
P
pah100 已提交
880
    // drill down and roll up differ background creation sequence from tree
881 882 883 884 885 886 887 888 889
    // 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;
    }

P
pah100 已提交
890
});