TreemapView.js 32.5 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

P
pah100 已提交
71 72 73 74 75
            /**
             * @private
             * @type {boolean}
             */
            this._mayClick;
P
pah100 已提交
76 77 78 79 80
        },

        /**
         * @override
         */
P
pah100 已提交
81
        render: function (seriesModel, ecModel, api, payload) {
82 83 84 85 86

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

P
pah100 已提交
90
            this.seriesModel = seriesModel;
P
pah100 已提交
91
            this.api = api;
P
pah100 已提交
92
            this.ecModel = ecModel;
P
pah100 已提交
93

P
pah100 已提交
94
            var targetInfo = helper.retrieveTargetInfo(payload, seriesModel);
95 96
            var payloadType = payload && payload.type;
            var layoutInfo = seriesModel.layoutInfo;
P
tweak  
pah100 已提交
97
            var isInit = !this._oldTree;
P
pah100 已提交
98 99 100 101 102 103 104 105 106
            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;
107 108 109

            var containerGroup = this._giveContainerGroup(layoutInfo);

P
pah100 已提交
110 111 112 113 114 115 116 117 118
            var renderResult = this._doRender(containerGroup, seriesModel, reRoot);
            (
                !isInit && (
                    !payloadType
                    || payloadType === 'treemapZoomToNode'
                    || payloadType === 'treemapRootToNode'
                )
            )
                ? this._doAnimation(containerGroup, renderResult, seriesModel, reRoot)
119
                : renderResult.renderFinally();
P
pah100 已提交
120

121 122 123 124 125 126 127 128 129 130
            this._resetController(api);

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

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

140
            return containerGroup;
P
pah100 已提交
141 142 143 144 145
        },

        /**
         * @private
         */
P
pah100 已提交
146
        _doRender: function (containerGroup, seriesModel, reRoot) {
147
            var thisTree = seriesModel.getData().tree;
P
pah100 已提交
148 149
            var oldTree = this._oldTree;

P
pah100 已提交
150
            // Clear last shape records.
151
            var lastsForAnimation = createStorage();
P
pah100 已提交
152
            var thisStorage = createStorage();
P
pah100 已提交
153
            var oldStorage = this._storage;
154
            var willInvisibleEls = [];
155
            var doRenderNode = zrUtil.curry(
156
                renderNode, seriesModel,
P
pah100 已提交
157
                thisStorage, oldStorage, reRoot,
158
                lastsForAnimation, willInvisibleEls
159
            );
P
pah100 已提交
160

P
typo  
pah100 已提交
161
            // Notice: when thisTree and oldTree are the same tree (see list.cloneShallow),
P
pah100 已提交
162 163 164 165
            // 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 已提交
166 167 168 169 170
            dualTravel(
                thisTree.root ? [thisTree.root] : [],
                (oldTree && oldTree.root) ? [oldTree.root] : [],
                containerGroup,
                thisTree === oldTree || !oldTree,
P
pah100 已提交
171
                0
P
pah100 已提交
172 173 174
            );

            // Process all removing.
P
pah100 已提交
175
            var willDeleteEls = clearStorage(oldStorage);
P
pah100 已提交
176 177 178 179

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

180
            return {
181
                lastsForAnimation: lastsForAnimation,
P
pah100 已提交
182 183
                willDeleteEls: willDeleteEls,
                renderFinally: renderFinally
184
            };
P
pah100 已提交
185

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

                function getKey(node) {
                    // Identify by name or raw index.
P
pah100 已提交
208
                    return node.getId();
P
pah100 已提交
209 210 211 212 213 214
                }

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

215
                    var group = doRenderNode(thisNode, oldNode, parentGroup, depth);
216

217
                    group && dualTravel(
P
pah100 已提交
218 219 220
                        thisNode && thisNode.viewChildren || [],
                        oldNode && oldNode.viewChildren || [],
                        group,
P
pah100 已提交
221
                        sameTree,
222
                        depth + 1
P
pah100 已提交
223 224 225
                    );
                }
            }
P
pah100 已提交
226 227

            function clearStorage(storage) {
228 229 230 231
                var willDeleteEls = createStorage();
                storage && each(storage, function (store, storageName) {
                    var delEls = willDeleteEls[storageName];
                    each(store, function (el) {
232
                        el && (delEls.push(el), el.__tmWillDelete = 1);
P
pah100 已提交
233 234
                    });
                });
P
pah100 已提交
235
                return willDeleteEls;
P
pah100 已提交
236
            }
237

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

P
pah100 已提交
253 254 255
        /**
         * @private
         */
P
pah100 已提交
256
        _doAnimation: function (containerGroup, renderResult, seriesModel, reRoot) {
257
            if (!seriesModel.get('animation')) {
P
pah100 已提交
258 259 260
                return;
            }

261 262 263 264 265
            var duration = seriesModel.get('animationDurationUpdate');
            var easing = seriesModel.get('animationEasing');
            var animationWrap = animationUtil.createWrap();

            // Make delete animations.
P
pah100 已提交
266 267
            each(renderResult.willDeleteEls, function (store, storageName) {
                each(store, function (el, rawIndex) {
268
                    if (el.invisible) {
P
pah100 已提交
269 270 271 272 273 274
                        return;
                    }

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

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

P
pah100 已提交
306 307 308 309 310 311 312 313 314 315
                        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);
                });
316 317 318 319 320 321
            });

            // Make other animations
            each(this._storage, function (store, storageName) {
                each(store, function (el, rawIndex) {
                    var last = renderResult.lastsForAnimation[storageName][rawIndex];
P
pah100 已提交
322
                    var target = {};
P
pah100 已提交
323 324 325 326

                    if (!last) {
                        return;
                    }
327 328

                    if (storageName === 'nodeGroup') {
P
pah100 已提交
329 330
                        if (last.old) {
                            target.position = el.position.slice();
L
lang 已提交
331
                            el.attr('position', last.old);
P
pah100 已提交
332
                        }
P
pah100 已提交
333
                    }
334
                    else {
P
pah100 已提交
335 336 337 338
                        if (last.old) {
                            target.shape = zrUtil.extend({}, el.shape);
                            el.setShape(last.old);
                        }
339 340 341 342 343

                        if (last.fadein) {
                            el.setStyle('opacity', 0);
                            target.style = {opacity: 1};
                        }
P
pah100 已提交
344 345 346 347 348
                        // 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 已提交
349
                    }
350

351
                    animationWrap.add(el, target, duration, easing);
P
pah100 已提交
352
                });
353
            }, this);
P
pah100 已提交
354

355
            this._state = 'animating';
P
pah100 已提交
356

357 358 359
            animationWrap
                .done(bind(function () {
                    this._state = 'ready';
360
                    renderResult.renderFinally();
361 362
                }, this))
                .start();
P
pah100 已提交
363 364 365 366 367
        },

        /**
         * @private
         */
368
        _resetController: function (api) {
P
pah100 已提交
369 370 371 372 373
            var controller = this._controller;

            // Init controller.
            if (!controller) {
                controller = this._controller = new RoamController(api.getZr());
374
                controller.enable(this.seriesModel.get('roam'));
375 376
                controller.on('pan', bind(this._onPan, this));
                controller.on('zoom', bind(this._onZoom, this));
P
pah100 已提交
377 378
            }

379
            var rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());
P
pah100 已提交
380
            controller.setPointerChecker(function (e, x, y) {
381 382
                return rect.contain(x, y);
            });
383
        },
P
pah100 已提交
384

385 386 387 388 389 390
        /**
         * @private
         */
        _clearController: function () {
            var controller = this._controller;
            if (controller) {
1
100pah 已提交
391
                controller.dispose();
392
                controller = null;
P
pah100 已提交
393
            }
P
pah100 已提交
394
        },
P
pah100 已提交
395

P
pah100 已提交
396 397 398 399
        /**
         * @private
         */
        _onPan: function (dx, dy) {
400 401
            this._mayClick = false;

P
pah100 已提交
402 403 404 405
            if (this._state !== 'animating'
                && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)
            ) {
                // These param must not be cached.
P
pah100 已提交
406
                var root = this.seriesModel.getData().tree.root;
407

P
pah100 已提交
408
                if (!root) {
P
pah100 已提交
409
                    return;
P
pah100 已提交
410
                }
411

P
pah100 已提交
412
                var rootLayout = root.getLayout();
413

P
pah100 已提交
414 415 416 417
                if (!rootLayout) {
                    return;
                }

L
lang 已提交
418
                this.api.dispatchAction({
419 420
                    type: 'treemapMove',
                    from: this.uid,
P
tweak  
pah100 已提交
421
                    seriesId: this.seriesModel.id,
422
                    rootRect: {
P
tweak  
pah100 已提交
423
                        x: rootLayout.x + dx, y: rootLayout.y + dy,
424 425 426
                        width: rootLayout.width, height: rootLayout.height
                    }
                });
P
pah100 已提交
427
            }
P
pah100 已提交
428
        },
P
pah100 已提交
429

P
pah100 已提交
430 431 432 433
        /**
         * @private
         */
        _onZoom: function (scale, mouseX, mouseY) {
434 435
            this._mayClick = false;

P
pah100 已提交
436
            if (this._state !== 'animating') {
P
pah100 已提交
437
                // These param must not be cached.
P
pah100 已提交
438
                var root = this.seriesModel.getData().tree.root;
439

P
pah100 已提交
440
                if (!root) {
441 442 443
                    return;
                }

P
pah100 已提交
444
                var rootLayout = root.getLayout();
P
pah100 已提交
445 446 447 448 449

                if (!rootLayout) {
                    return;
                }

450
                var rect = new BoundingRect(
P
tweak  
pah100 已提交
451
                    rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height
452
                );
P
tweak  
pah100 已提交
453
                var layoutInfo = this.seriesModel.layoutInfo;
P
pah100 已提交
454

455 456 457 458 459
                // Transform mouse coord from global to containerGroup.
                mouseX -= layoutInfo.x;
                mouseY -= layoutInfo.y;

                // Scale root bounding rect.
P
pah100 已提交
460 461 462 463 464 465 466
                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 已提交
467
                this.api.dispatchAction({
P
pah100 已提交
468 469
                    type: 'treemapRender',
                    from: this.uid,
P
tweak  
pah100 已提交
470
                    seriesId: this.seriesModel.id,
471 472 473
                    rootRect: {
                        x: rect.x, y: rect.y,
                        width: rect.width, height: rect.height
P
pah100 已提交
474 475
                    }
                });
P
pah100 已提交
476 477 478 479 480 481
            }
        },

        /**
         * @private
         */
P
pah100 已提交
482
        _initEvents: function (containerGroup) {
P
pah100 已提交
483 484 485 486 487
            // FIXME
            // 不用click以及silent的原因是,animate时视图设置silent true来避免click生效,
            // 但是animate中,按下鼠标,animate结束后(silent设回为false)松开鼠标,
            // 还是会触发click,期望是不触发。

P
pah100 已提交
488 489 490
            // Mousedown occurs when drag start, and mouseup occurs when drag end,
            // click event should not be triggered in that case.

P
pah100 已提交
491
            containerGroup.on('mousedown', function (e) {
492 493
                this._state === 'ready' && (this._mayClick = true);
            }, this);
P
pah100 已提交
494
            containerGroup.on('mouseup', function (e) {
495 496 497
                if (this._mayClick) {
                    this._mayClick = false;
                    this._state === 'ready' && onClick.call(this, e);
P
pah100 已提交
498
                }
499
            }, this);
P
pah100 已提交
500 501

            function onClick(e) {
502 503 504 505 506 507
                var nodeClick = this.seriesModel.get('nodeClick', true);

                if (!nodeClick) {
                    return;
                }

508
                var targetInfo = this.findTarget(e.offsetX, e.offsetY);
509

P
pah100 已提交
510 511 512 513 514 515 516 517 518
                if (!targetInfo) {
                    return;
                }

                var node = targetInfo.node;
                if (node.getLayout().isLeafRoot) {
                    this._rootToNode(targetInfo);
                }
                else {
519 520 521 522 523 524 525 526 527
                    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 已提交
528 529 530 531
                }
            }
        },

P
pah100 已提交
532 533 534 535 536
        /**
         * @private
         */
        _renderBreadcrumb: function (seriesModel, api, targetInfo) {
            if (!targetInfo) {
537 538 539 540 541 542
                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 已提交
543 544 545 546 547 548

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

549 550
            (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group)))
                .render(seriesModel, api, targetInfo.node, bind(onSelect, this));
P
pah100 已提交
551 552

            function onSelect(node) {
P
pah100 已提交
553 554 555 556 557
                if (this._state !== 'animating') {
                    helper.aboveViewRoot(seriesModel.getViewRoot(), node)
                        ? this._rootToNode({node: node})
                        : this._zoomToNode({node: node});
                }
P
pah100 已提交
558 559 560
            }
        },

P
pah100 已提交
561 562 563 564
        /**
         * @override
         */
        remove: function () {
565
            this._clearController();
L
lang 已提交
566
            this._containerGroup && this._containerGroup.removeAll();
P
pah100 已提交
567 568
            this._storage = createStorage();
            this._state = 'ready';
P
pah100 已提交
569
            this._breadcrumb && this._breadcrumb.remove();
P
pah100 已提交
570 571
        },

572 573 574 575
        dispose: function () {
            this._clearController();
        },

P
pah100 已提交
576 577 578 579
        /**
         * @private
         */
        _zoomToNode: function (targetInfo) {
L
lang 已提交
580
            this.api.dispatchAction({
P
pah100 已提交
581
                type: 'treemapZoomToNode',
P
pah100 已提交
582
                from: this.uid,
P
tweak  
pah100 已提交
583
                seriesId: this.seriesModel.id,
P
pah100 已提交
584
                targetNode: targetInfo.node
P
pah100 已提交
585 586 587
            });
        },

P
pah100 已提交
588 589 590 591 592 593 594 595 596 597 598 599
        /**
         * @private
         */
        _rootToNode: function (targetInfo) {
            this.api.dispatchAction({
                type: 'treemapRootToNode',
                from: this.uid,
                seriesId: this.seriesModel.id,
                targetNode: targetInfo.node
            });
        },

P
pah100 已提交
600 601
        /**
         * @public
P
pah100 已提交
602 603
         * @param {number} x Global coord x.
         * @param {number} y Global coord y.
P
pah100 已提交
604 605 606 607 608 609 610 611 612 613
         * @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) {
614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
                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 已提交
631 632 633 634
                }
            }, this);

            return targetInfo;
P
pah100 已提交
635 636 637
        }

    });
P
pah100 已提交
638

639 640 641
    /**
     * @inner
     */
P
pah100 已提交
642 643 644
    function createStorage() {
        return {nodeGroup: [], background: [], content: []};
    }
P
pah100 已提交
645

646 647
    /**
     * @inner
648
     * @return Return undefined means do not travel further.
649 650 651
     */
    function renderNode(
        seriesModel, thisStorage, oldStorage, reRoot,
652 653
        lastsForAnimation, willInvisibleEls,
        thisNode, oldNode, parentGroup, depth
654
    ) {
655 656 657 658 659 660 661
        // 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;
        }
662

663 664 665
        // -------------------------------------------------------------------
        // Start of closure variables available in "Procedures in renderNode".

P
pah100 已提交
666
        var thisLayout = thisNode.getLayout();
667

P
pah100 已提交
668
        if (!thisLayout || !thisLayout.isInView) {
669 670 671
            return;
        }

P
pah100 已提交
672 673
        var thisWidth = thisLayout.width;
        var thisHeight = thisLayout.height;
674
        var borderWidth = thisLayout.borderWidth;
P
pah100 已提交
675
        var thisInvisible = thisLayout.invisible;
676

677 678 679
        var thisRawIndex = thisNode.getRawIndex();
        var oldRawIndex = oldNode && oldNode.getRawIndex();

680 681 682
        var thisViewChildren = thisNode.viewChildren;
        var upperHeight = thisLayout.upperHeight;
        var isParent = thisViewChildren && thisViewChildren.length;
683
        var itemStyleEmphasisModel = thisNode.getModel('itemStyle.emphasis');
684 685 686 687

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

688 689
        // Node group
        var group = giveGraphic('nodeGroup', Group);
690

691 692 693
        if (!group) {
            return;
        }
694

695
        parentGroup.add(group);
696
        // x,y are not set when el is above view root.
L
lang 已提交
697
        group.attr('position', [thisLayout.x || 0, thisLayout.y || 0]);
698 699 700
        group.__tmNodeWidth = thisWidth;
        group.__tmNodeHeight = thisHeight;

P
pah100 已提交
701
        if (thisLayout.isAboveViewRoot) {
702 703 704
            return group;
        }

705
        // Background
706
        var bg = giveGraphic('background', Rect, depth, Z_BG);
707
        bg && renderBackground(group, bg, isParent && thisLayout.upperHeight);
708 709

        // No children, render content.
710
        if (!isParent) {
711
            var content = giveGraphic('content', Rect, depth, Z_CONTENT);
712
            content && renderContent(group, content);
713 714 715 716 717 718 719 720
        }

        return group;

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

721 722 723 724 725 726 727
        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);
728
            var emphasisBorderColor = itemStyleEmphasisModel.get('borderColor');
729 730

            updateStyle(bg, function () {
731 732
                var normalStyle = {fill: visualBorderColor};
                var emphasisStyle = {fill: emphasisBorderColor};
733 734 735 736 737 738 739 740 741 742 743

                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 {
744
                    normalStyle.text = emphasisStyle.text = '';
745
                }
746 747 748

                bg.setStyle(normalStyle);
                graphic.setHoverStyle(bg, emphasisStyle);
749 750 751 752 753 754
            });

            group.add(bg);
        }

        function renderContent(group, content) {
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
            // 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};
773
                var emphasisStyle = itemStyleEmphasisModel.getItemStyle();
774 775 776 777 778 779 780 781 782 783 784

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

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

            group.add(content);
        }

        function updateStyle(element, cb) {
P
pah100 已提交
785
            if (!thisInvisible) {
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801
                // 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);
            }
        }

802
        function prepareText(normalStyle, emphasisStyle, visualColor, width, height, upperLabelRect) {
803
            var nodeModel = thisNode.getModel();
804
            var text = zrUtil.retrieve(
805 806 807
                seriesModel.getFormattedLabel(
                    thisNode.dataIndex, 'normal', null, null, upperLabelRect ? 'upperLabel' : 'label'
                ),
808 809
                nodeModel.get('name')
            );
810
            if (!upperLabelRect && thisLayout.isLeafRoot) {
P
pah100 已提交
811
                var iconChar = seriesModel.get('drillDownIcon', true);
812
                text = iconChar ? iconChar + ' ' + text : text;
P
pah100 已提交
813
            }
814 815

            setText(
816 817
                text, normalStyle, nodeModel, upperLabelRect ? PATH_UPPERLABEL_NORMAL : PATH_LABEL_NOAMAL,
                visualColor, width, height, upperLabelRect
818 819
            );
            setText(
820 821
                text, emphasisStyle, nodeModel, upperLabelRect ? PATH_UPPERLABEL_EMPHASIS : PATH_LABEL_EMPHASIS,
                visualColor, width, height, upperLabelRect
822 823 824
            );
        }

825
        function setText(text, style, nodeModel, labelPath, visualColor, width, height, upperLabelRect) {
826 827 828 829
            var labelModel = nodeModel.getModel(labelPath);

            graphic.setText(style, labelModel, visualColor);

830
            upperLabelRect && (style.textPositionRect = zrUtil.clone(upperLabelRect));
831

P
pah100 已提交
832
            var textRect = labelModel.getTextRect(text);
833
            if (!labelModel.getShallow('show') || textRect.height > height) {
P
pah100 已提交
834
                style.text = null;
835
            }
836
            else if (textRect.width > width) {
P
pah100 已提交
837 838
                style.text = labelModel.get('ellipsis')
                    ? labelModel.truncateText(
839
                        text, width, null, {minChar: 2}
1
100pah 已提交
840
                    )
P
pah100 已提交
841
                    : null;
842 843 844 845 846 847
            }
            else {
                style.text = text;
            }
        }

848
        function giveGraphic(storageName, Ctor, depth, z) {
849 850 851 852 853 854 855 856 857
            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 已提交
858
            else if (!thisInvisible) {
859 860 861
                element = new Ctor({z: calculateZ(depth, z)});
                element.__tmDepth = depth;
                element.__tmStorageName = storageName;
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
                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) {
879 880 881
            var lastCfg = lasts[thisRawIndex] = {};
            var parentNode = thisNode.parentNode;

P
pah100 已提交
882
            if (parentNode && (!reRoot || reRoot.direction === 'drillDown')) {
883 884
                var parentOldX = 0;
                var parentOldY = 0;
P
pah100 已提交
885 886

                // New nodes appear from right-bottom corner in 'zoomToNode' animation.
887 888
                // For convenience, get old bounding rect from background.
                var parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()];
P
pah100 已提交
889 890 891
                if (!reRoot && parentOldBg && parentOldBg.old) {
                    parentOldX = parentOldBg.old.width;
                    parentOldY = parentOldBg.old.height;
892
                }
P
pah100 已提交
893

894 895 896
                // 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 已提交
897
                    ? [0, parentOldY]
898
                    : {x: parentOldX, y: parentOldY, width: 0, height: 0};
899
            }
900 901 902

            // Fade in, user can be aware that these nodes are new.
            lastCfg.fadein = storageName !== 'nodeGroup';
903 904 905
        }
    }

906
    // We can not set all backgroud with the same z, Because the behaviour of
P
pah100 已提交
907
    // drill down and roll up differ background creation sequence from tree
908 909 910 911 912 913 914 915 916
    // 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 已提交
917
});