TreemapView.js 30.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 19
    var PATH_LABEL_NORMAL = ['label', 'normal'];
    var PATH_LABEL_EMPHASIS = ['label', 'emphasis'];
P
pah100 已提交
20

P
pah100 已提交
21 22 23 24 25 26 27
    return require('../../echarts').extendChartView({

        type: 'treemap',

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

P
pah100 已提交
30 31 32 33
            /**
             * @private
             * @type {module:zrender/container/Group}
             */
P
pah100 已提交
34
            this._containerGroup;
P
pah100 已提交
35 36 37 38 39

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

P
pah100 已提交
42 43 44 45 46
            /**
             * @private
             * @type {module:echarts/data/Tree}
             */
            this._oldTree;
P
pah100 已提交
47 48 49 50 51 52

            /**
             * @private
             * @type {module:echarts/chart/treemap/Breadcrumb}
             */
            this._breadcrumb;
P
pah100 已提交
53 54 55 56 57 58 59 60

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

            /**
P
pah100 已提交
61
             * 'ready', 'animating'
P
pah100 已提交
62 63 64
             * @private
             */
            this._state = 'ready';
P
pah100 已提交
65

P
pah100 已提交
66 67 68 69 70
            /**
             * @private
             * @type {boolean}
             */
            this._mayClick;
P
pah100 已提交
71 72 73 74 75
        },

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

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

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

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

            var containerGroup = this._giveContainerGroup(layoutInfo);

P
pah100 已提交
105
            var renderResult = this._doRender(containerGroup, seriesModel, reRoot);
106

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

117 118 119 120 121 122 123 124 125 126
            this._resetController(api);

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

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

136
            return containerGroup;
P
pah100 已提交
137 138 139 140 141
        },

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

P
pah100 已提交
146
            // Clear last shape records.
147
            var lastsForAnimation = createStorage();
P
pah100 已提交
148
            var thisStorage = createStorage();
P
pah100 已提交
149
            var oldStorage = this._storage;
150
            var willInvisibleEls = [];
151
            var willVisibleEls = [];
P
pah100 已提交
152
            var willDeleteEls = [];
153 154
            var doRenderNode = zrUtil.curry(
                renderNode, this.seriesModel,
P
pah100 已提交
155 156
                thisStorage, oldStorage, reRoot,
                lastsForAnimation, willInvisibleEls, willVisibleEls
157
            );
P
pah100 已提交
158
            var viewRoot = seriesModel.getViewRoot();
P
pah100 已提交
159
            var viewPath = helper.getPathToRoot(viewRoot);
P
pah100 已提交
160

P
pah100 已提交
161 162 163 164 165
            // Notice: when thisTree and oldTree are the same tree (see list.cloneShadow),
            // 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

P
pah100 已提交
186
            function dualTravel(thisViewChildren, oldViewChildren, parentGroup, sameTree, viewPathIndex) {
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 215
                }

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

                    // Whether under viewRoot.
P
pah100 已提交
216 217 218 219 220 221 222 223
                    if (!thisNode
                        || isNaN(viewPathIndex)
                        || (viewPathIndex < viewPath.length && viewPath[viewPathIndex] !== 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;
P
pah100 已提交
224 225
                    }

226
                    var group = doRenderNode(thisNode, oldNode, parentGroup);
227

228
                    group && dualTravel(
P
pah100 已提交
229 230 231
                        thisNode && thisNode.viewChildren || [],
                        oldNode && oldNode.viewChildren || [],
                        group,
P
pah100 已提交
232
                        sameTree,
P
pah100 已提交
233
                        viewPathIndex + 1
P
pah100 已提交
234 235 236
                    );
                }
            }
P
pah100 已提交
237 238

            function clearStorage(storage) {
239 240 241 242 243
                var willDeleteEls = createStorage();
                storage && each(storage, function (store, storageName) {
                    var delEls = willDeleteEls[storageName];
                    each(store, function (el) {
                        el && (delEls.push(el), el.__tmWillDelete = storageName);
P
pah100 已提交
244 245
                    });
                });
P
pah100 已提交
246
                return willDeleteEls;
P
pah100 已提交
247
            }
248

P
pah100 已提交
249
            function renderFinally() {
250 251 252 253
                each(willDeleteEls, function (els) {
                    each(els, function (el) {
                        el.parent && el.parent.remove(el);
                    });
P
pah100 已提交
254
                });
255 256 257
                // Theoritically there is no intersection between willInvisibleEls
                // and willVisibleEls have, but we set visible after for robustness.
                each(willInvisibleEls, function (el) {
258
                    el.invisible = true;
259 260
                    // Setting invisible is for optimizing, so no need to set dirty,
                    // just mark as invisible.
P
pah100 已提交
261
                    el.dirty();
262 263 264 265 266
                });
                each(willVisibleEls, function (el) {
                    el.invisible = false;
                    el.__tmWillVisible = false;
                    el.dirty();
267 268
                });
            }
P
pah100 已提交
269 270
        },

P
pah100 已提交
271 272 273
        /**
         * @private
         */
P
pah100 已提交
274
        _doAnimation: function (containerGroup, renderResult, seriesModel, reRoot) {
275
            if (!seriesModel.get('animation')) {
P
pah100 已提交
276 277 278
                return;
            }

279 280 281 282 283
            var duration = seriesModel.get('animationDurationUpdate');
            var easing = seriesModel.get('animationEasing');
            var animationWrap = animationUtil.createWrap();

            // Make delete animations.
P
pah100 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
            each(renderResult.willDeleteEls, function (store, storageName) {
                each(store, function (el, rawIndex) {
                    var storageName;

                    if (el.invisible || !(storageName = el.__tmWillDelete)) {
                        return;
                    }

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

                    if (reRoot && reRoot.direction === 'drilldown') {
                        if (parent === reRoot.rootNodeGroup) {
                            // Only 'content' will enter this branch, but not nodeGroup.
                            target = {
                                shape: {
                                    x: 0, y: 0,
                                    width: parent.__tmNodeWidth, height: parent.__tmNodeHeight
                                }
                            };
                            el.z = 2;
                        }
                        else {
                            target = {style: {opacity: 0}};
                            el.z = 1;
                        }
                    }
                    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 rollup 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);
                });
332 333 334 335 336 337
            });

            // Make other animations
            each(this._storage, function (store, storageName) {
                each(store, function (el, rawIndex) {
                    var last = renderResult.lastsForAnimation[storageName][rawIndex];
P
pah100 已提交
338
                    var target = {};
P
pah100 已提交
339 340 341 342

                    if (!last) {
                        return;
                    }
343 344

                    if (storageName === 'nodeGroup') {
P
pah100 已提交
345 346 347 348
                        if (last.old) {
                            target.position = el.position.slice();
                            el.position = last.old;
                        }
P
pah100 已提交
349
                    }
350
                    else {
P
pah100 已提交
351 352 353 354
                        if (last.old) {
                            target.shape = zrUtil.extend({}, el.shape);
                            el.setShape(last.old);
                        }
355 356 357 358 359

                        if (last.fadein) {
                            el.setStyle('opacity', 0);
                            target.style = {opacity: 1};
                        }
P
pah100 已提交
360 361 362 363 364
                        // 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 已提交
365
                    }
366
                    animationWrap.add(el, target, duration, easing);
P
pah100 已提交
367
                });
368
            }, this);
P
pah100 已提交
369

370
            this._state = 'animating';
P
pah100 已提交
371

372 373 374
            animationWrap
                .done(bind(function () {
                    this._state = 'ready';
375
                    renderResult.renderFinally();
376 377
                }, this))
                .start();
P
pah100 已提交
378 379 380 381 382
        },

        /**
         * @private
         */
383
        _resetController: function (api) {
P
pah100 已提交
384 385 386 387 388
            var controller = this._controller;

            // Init controller.
            if (!controller) {
                controller = this._controller = new RoamController(api.getZr());
389
                controller.enable(this.seriesModel.get('roam'));
390 391
                controller.on('pan', bind(this._onPan, this));
                controller.on('zoom', bind(this._onZoom, this));
P
pah100 已提交
392 393
            }

394 395 396 397
            var rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());
            controller.rectProvider = function () {
                return rect;
            };
398
        },
P
pah100 已提交
399

400 401 402 403 404 405
        /**
         * @private
         */
        _clearController: function () {
            var controller = this._controller;
            if (controller) {
P
pah100 已提交
406
                controller.off('pan').off('zoom');
407
                controller = null;
P
pah100 已提交
408
            }
P
pah100 已提交
409
        },
P
pah100 已提交
410

P
pah100 已提交
411 412 413 414
        /**
         * @private
         */
        _onPan: function (dx, dy) {
415 416
            this._mayClick = false;

P
pah100 已提交
417 418 419 420
            if (this._state !== 'animating'
                && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)
            ) {
                // These param must not be cached.
P
tweak  
pah100 已提交
421
                var viewRoot = this.seriesModel.getViewRoot();
422

P
tweak  
pah100 已提交
423
                if (!viewRoot) {
P
pah100 已提交
424
                    return;
P
pah100 已提交
425
                }
426

P
tweak  
pah100 已提交
427
                var rootLayout = viewRoot.getLayout();
428

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

L
lang 已提交
433
                this.api.dispatchAction({
434 435
                    type: 'treemapMove',
                    from: this.uid,
P
tweak  
pah100 已提交
436
                    seriesId: this.seriesModel.id,
437
                    rootRect: {
P
tweak  
pah100 已提交
438
                        x: rootLayout.x + dx, y: rootLayout.y + dy,
439 440 441
                        width: rootLayout.width, height: rootLayout.height
                    }
                });
P
pah100 已提交
442
            }
P
pah100 已提交
443
        },
P
pah100 已提交
444

P
pah100 已提交
445 446 447 448
        /**
         * @private
         */
        _onZoom: function (scale, mouseX, mouseY) {
449 450
            this._mayClick = false;

P
pah100 已提交
451
            if (this._state !== 'animating') {
P
pah100 已提交
452
                // These param must not be cached.
P
tweak  
pah100 已提交
453
                var viewRoot = this.seriesModel.getViewRoot();
454

P
tweak  
pah100 已提交
455
                if (!viewRoot) {
456 457 458
                    return;
                }

P
tweak  
pah100 已提交
459
                var rootLayout = viewRoot.getLayout();
P
pah100 已提交
460 461 462 463 464

                if (!rootLayout) {
                    return;
                }

465
                var rect = new BoundingRect(
P
tweak  
pah100 已提交
466
                    rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height
467
                );
P
tweak  
pah100 已提交
468
                var layoutInfo = this.seriesModel.layoutInfo;
P
pah100 已提交
469

470 471 472 473 474
                // Transform mouse coord from global to containerGroup.
                mouseX -= layoutInfo.x;
                mouseY -= layoutInfo.y;

                // Scale root bounding rect.
P
pah100 已提交
475 476 477 478 479 480 481
                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 已提交
482
                this.api.dispatchAction({
P
pah100 已提交
483 484
                    type: 'treemapRender',
                    from: this.uid,
P
tweak  
pah100 已提交
485
                    seriesId: this.seriesModel.id,
486 487 488
                    rootRect: {
                        x: rect.x, y: rect.y,
                        width: rect.width, height: rect.height
P
pah100 已提交
489 490
                    }
                });
P
pah100 已提交
491 492 493 494 495 496
            }
        },

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

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

P
pah100 已提交
506
            containerGroup.on('mousedown', function (e) {
507 508
                this._state === 'ready' && (this._mayClick = true);
            }, this);
P
pah100 已提交
509
            containerGroup.on('mouseup', function (e) {
510 511 512
                if (this._mayClick) {
                    this._mayClick = false;
                    this._state === 'ready' && onClick.call(this, e);
P
pah100 已提交
513
                }
514
            }, this);
P
pah100 已提交
515 516

            function onClick(e) {
517 518 519 520 521 522
                var nodeClick = this.seriesModel.get('nodeClick', true);

                if (!nodeClick) {
                    return;
                }

523
                var targetInfo = this.findTarget(e.offsetX, e.offsetY);
524

P
pah100 已提交
525 526 527 528 529 530 531 532 533
                if (!targetInfo) {
                    return;
                }

                var node = targetInfo.node;
                if (node.getLayout().isLeafRoot) {
                    this._rootToNode(targetInfo);
                }
                else {
534 535 536 537 538 539 540 541 542
                    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 已提交
543 544 545 546
                }
            }
        },

P
pah100 已提交
547 548 549 550 551 552 553 554 555 556 557 558 559
        /**
         * @private
         */
        _renderBreadcrumb: function (seriesModel, api, targetInfo) {
            if (!targetInfo) {
                // Find breadcrumb tail on center of containerGroup.
                targetInfo = this.findTarget(api.getWidth() / 2, api.getHeight() / 2);

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

560
            (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group, bind(onSelect, this))))
P
pah100 已提交
561 562 563
                .render(seriesModel, api, targetInfo.node);

            function onSelect(node) {
P
pah100 已提交
564 565 566 567 568
                if (this._state !== 'animating') {
                    helper.aboveViewRoot(seriesModel.getViewRoot(), node)
                        ? this._rootToNode({node: node})
                        : this._zoomToNode({node: node});
                }
P
pah100 已提交
569 570 571
            }
        },

P
pah100 已提交
572 573 574 575
        /**
         * @override
         */
        remove: function () {
576
            this._clearController();
L
lang 已提交
577
            this._containerGroup && this._containerGroup.removeAll();
P
pah100 已提交
578 579
            this._storage = createStorage();
            this._state = 'ready';
P
pah100 已提交
580
            this._breadcrumb && this._breadcrumb.remove();
P
pah100 已提交
581 582
        },

583 584 585 586
        dispose: function () {
            this._clearController();
        },

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

P
pah100 已提交
599 600 601 602 603 604 605 606 607 608 609 610
        /**
         * @private
         */
        _rootToNode: function (targetInfo) {
            this.api.dispatchAction({
                type: 'treemapRootToNode',
                from: this.uid,
                seriesId: this.seriesModel.id,
                targetNode: targetInfo.node
            });
        },

P
pah100 已提交
611 612
        /**
         * @public
P
pah100 已提交
613 614
         * @param {number} x Global coord x.
         * @param {number} y Global coord y.
P
pah100 已提交
615 616 617 618 619 620 621 622 623 624
         * @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) {
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
                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 已提交
642 643 644 645
                }
            }, this);

            return targetInfo;
P
pah100 已提交
646 647 648
        }

    });
P
pah100 已提交
649

650 651 652
    /**
     * @inner
     */
P
pah100 已提交
653 654 655
    function createStorage() {
        return {nodeGroup: [], background: [], content: []};
    }
P
pah100 已提交
656

657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
    /**
     * @inner
     */
    function renderNode(
        seriesModel, thisStorage, oldStorage, reRoot,
        lastsForAnimation, willInvisibleEls, willVisibleEls,
        thisNode, oldNode, parentGroup
    ) {
        var thisRawIndex = thisNode && thisNode.getRawIndex();
        var oldRawIndex = oldNode && oldNode.getRawIndex();

        var layout = thisNode.getLayout();
        var thisWidth = layout.width;
        var thisHeight = layout.height;
        var invisible = layout.invisible;

        // Node group
        var group = giveGraphic('nodeGroup', Group);
        if (!group) {
            return;
        }
        parentGroup.add(group);
        group.position = [layout.x, layout.y];
        group.__tmNodeWidth = thisWidth;
        group.__tmNodeHeight = thisHeight;

        // Background
        var bg = giveGraphic('background', Rect, 0);
        if (bg) {
            bg.setShape({x: 0, y: 0, width: thisWidth, height: thisHeight});
            updateStyle(bg, function () {
                bg.setStyle('fill', thisNode.getVisual('borderColor', true));
            });
            group.add(bg);
        }

        var thisViewChildren = thisNode.viewChildren;

        // No children, render content.
        if (!thisViewChildren || !thisViewChildren.length) {
            var content = giveGraphic('content', Rect, 3);
            content && renderContent(layout, group, thisNode, thisWidth, thisHeight);
        }

        return group;

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

        function renderContent(layout, group, thisNode, thisWidth, thisHeight) {
            // For tooltip.
            content.dataIndex = thisNode.dataIndex;
            content.seriesIndex = seriesModel.seriesIndex;

            var borderWidth = layout.borderWidth;
            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};
                var emphasisStyle = thisNode.getModel('itemStyle.emphasis').getItemStyle();

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

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

            group.add(content);
        }

        function updateStyle(element, cb) {
            if (!invisible) {
                // 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);
            }
        }

        function prepareText(normalStyle, emphasisStyle, visualColor, contentWidth, contentHeight) {
            var nodeModel = thisNode.getModel();
            var text = nodeModel.get('name');

            setText(
                text, normalStyle, nodeModel, PATH_LABEL_NORMAL,
                visualColor, contentWidth, contentHeight
            );
            setText(
                text, emphasisStyle, nodeModel, PATH_LABEL_EMPHASIS,
                visualColor, contentWidth, contentHeight
            );
        }

        function setText(text, style, nodeModel, labelPath, visualColor, contentWidth, contentHeight) {
            var labelModel = nodeModel.getModel(labelPath);
            var labelTextStyleModel = labelModel.getModel('textStyle');

            graphic.setText(style, labelModel, visualColor);

            // text.align and text.baseline is not included by graphic.setText,
            // because in most cases the two attributes are not exposed to user,
            // except in treemap.
            style.textAlign = labelTextStyleModel.get('align');
            style.textVerticalAlign = labelTextStyleModel.get('baseline');

            var textRect = labelTextStyleModel.getTextRect(text);
            if (!labelModel.getShallow('show') || textRect.height > contentHeight) {
                style.text = '';
            }
            else if (textRect.width > contentWidth) {
                style.text = labelTextStyleModel.get('ellipsis')
                    ? labelTextStyleModel.ellipsis(text, contentWidth) : '';
            }
            else {
                style.text = text;
            }
        }

        function giveGraphic(storageName, Ctor, 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 (!invisible) {
                element = new Ctor({z: z});
                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) {
            // New background do not animate but delay show.
            if (storageName === 'background') {
                element.invisible = true;
                element.__tmWillVisible = true;
                willVisibleEls.push(element);
            }
            else {
                var lastCfg = lasts[thisRawIndex] = {};
                var parentNode = thisNode.parentNode;

                if (parentNode && (!reRoot || reRoot.direction === 'drilldown')) {
                    var parentOldX = 0;
                    var parentOldY = 0;
                    // For convenience, get old bounding rect from background.
                    var parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()];

                    if (parentOldBg && parentOldBg.old) {
                        parentOldX = parentOldBg.old.width / 2; // Devided by 2 for reRoot effect.
                        parentOldY = parentOldBg.old.height / 2;
                    }
                    // When no parent old shape found, its parent is new too,
                    // so we can just use {x:0, y:0}.
                    lastCfg.old = storageName === 'nodeGroup'
                        ? [parentOldX, parentOldY]
                        : {x: parentOldX, y: parentOldY, width: 0, height: 0};
                }

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

P
pah100 已提交
857
});