TreemapView.js 25.9 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 parsePercent = require('../../util/number').parsePercent;
P
pah100 已提交
8
    var Breadcrumb = require('./Breadcrumb');
P
pah100 已提交
9 10 11
    var RoamController = require('../../component/helper/RoamController');
    var BoundingRect = require('zrender/core/BoundingRect');
    var matrix = require('zrender/core/matrix');
12
    var animationUtil = require('../../util/animation');
P
pah100 已提交
13
    var bind = zrUtil.bind;
P
pah100 已提交
14 15
    var Group = graphic.Group;
    var Rect = graphic.Rect;
16
    var each = zrUtil.each;
P
pah100 已提交
17

P
pah100 已提交
18 19
    var DRAG_THRESHOLD = 3;

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

        type: 'treemap',

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

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

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

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

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

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

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

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

        /**
         * @override
         */
P
pah100 已提交
75 76 77 78 79
        render: function (seriesModel, ecModel, api, payload) {
            if (helper.irrelevant(payload, seriesModel)) {
                return;
            }

P
pah100 已提交
80
            this.seriesModel = seriesModel;
P
pah100 已提交
81
            this.api = api;
P
pah100 已提交
82
            this.ecModel = ecModel;
P
pah100 已提交
83

84 85 86 87 88 89 90 91
            var payloadType = payload && payload.type;
            var layoutInfo = seriesModel.layoutInfo;

            var containerGroup = this._giveContainerGroup(layoutInfo);

            var renderResult = this._doRender(containerGroup, seriesModel);

            (!payloadType || payloadType === 'treemapZoomToNode')
92
                ? this._doAnimation(containerGroup, renderResult, seriesModel)
93
                : renderResult.renderFinally();
P
pah100 已提交
94

95 96 97 98 99 100 101 102 103 104 105
            this._resetController(api);

            var targetInfo = helper.retrieveTargetNodeInfo(payload, seriesModel);
            this._renderBreadcrumb(seriesModel, api, targetInfo);
        },

        /**
         * @private
         */
        _giveContainerGroup: function (layoutInfo) {
            var containerGroup = this._containerGroup;
P
pah100 已提交
106
            if (!containerGroup) {
P
pah100 已提交
107 108
                // FIXME
                // 加一层containerGroup是为了clip,但是现在clip功能并没有实现。
P
pah100 已提交
109 110
                containerGroup = this._containerGroup = new Group();
                this._initEvents(containerGroup);
P
pah100 已提交
111
                this.group.add(containerGroup);
P
pah100 已提交
112
            }
113
            containerGroup.position = [layoutInfo.x, layoutInfo.y];
P
pah100 已提交
114

115
            return containerGroup;
P
pah100 已提交
116 117 118 119 120
        },

        /**
         * @private
         */
121 122
        _doRender: function (containerGroup, seriesModel) {
            var thisTree = seriesModel.getData().tree;
P
pah100 已提交
123 124
            var oldTree = this._oldTree;

P
pah100 已提交
125
            // Clear last shape records.
126
            var lastsForAnimation = createStorage();
P
pah100 已提交
127
            var thisStorage = createStorage();
P
pah100 已提交
128
            var oldStorage = this._storage;
129
            var willInvisibleEls = [];
130
            var willVisibleEls = [];
P
pah100 已提交
131
            var willDeleteEls = [];
132 133
            var renderNode = bind(
                this._renderNode, this,
134
                thisStorage, oldStorage, lastsForAnimation, willInvisibleEls, willVisibleEls
135
            );
P
pah100 已提交
136 137
            var viewRoot = seriesModel.getViewRoot();

P
pah100 已提交
138 139 140 141 142
            // 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 已提交
143 144 145 146 147 148 149 150 151
            dualTravel(
                thisTree.root ? [thisTree.root] : [],
                (oldTree && oldTree.root) ? [oldTree.root] : [],
                containerGroup,
                thisTree === oldTree || !oldTree,
                viewRoot === thisTree.root
            );

            // Process all removing.
P
pah100 已提交
152
            var willDeleteEls = clearStorage(oldStorage);
P
pah100 已提交
153 154 155 156

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

157
            return {
158
                lastsForAnimation: lastsForAnimation,
P
pah100 已提交
159 160
                willDeleteEls: willDeleteEls,
                renderFinally: renderFinally
161
            };
P
pah100 已提交
162

P
pah100 已提交
163 164 165 166 167 168
            function dualTravel(thisViewChildren, oldViewChildren, parentGroup, sameTree, inView) {
                // When 'render' is triggered by action,
                // 'this' and 'old' may be the same tree,
                // we use rawIndex in that case.
                if (sameTree) {
                    oldViewChildren = thisViewChildren;
169
                    each(thisViewChildren, function (child, index) {
P
pah100 已提交
170
                        !child.isRemoved() && processNode(index, index);
P
pah100 已提交
171 172
                    });
                }
P
pah100 已提交
173 174
                // Diff hierarchically (diff only in each subtree, but not whole).
                // because, consistency of view is important.
P
pah100 已提交
175
                else {
P
pah100 已提交
176 177 178
                    (new DataDiffer(oldViewChildren, thisViewChildren, getKey))
                        .add(processNode)
                        .update(processNode)
179 180
                        .remove(zrUtil.curry(processNode, null))
                        .execute();
P
pah100 已提交
181 182 183 184 185
                }

                function getKey(node) {
                    // Identify by name or raw index.
                    return node.name != null ? node.name : node.getRawIndex();
P
pah100 已提交
186 187 188 189 190 191 192 193 194 195 196 197 198
                }

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

                    // Whether under viewRoot.
                    var subInView = inView || thisNode === viewRoot;
                    // If not under viewRoot, only remove.
                    if (!subInView) {
                        thisNode = null;
                    }

199 200
                    var group = renderNode(thisNode, oldNode, parentGroup);

201
                    group && dualTravel(
P
pah100 已提交
202 203 204
                        thisNode && thisNode.viewChildren || [],
                        oldNode && oldNode.viewChildren || [],
                        group,
P
pah100 已提交
205
                        sameTree,
P
pah100 已提交
206 207 208 209
                        subInView
                    );
                }
            }
P
pah100 已提交
210 211

            function clearStorage(storage) {
212 213 214 215 216
                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 已提交
217 218
                    });
                });
P
pah100 已提交
219
                return willDeleteEls;
P
pah100 已提交
220
            }
221

P
pah100 已提交
222
            function renderFinally() {
223 224 225 226
                each(willDeleteEls, function (els) {
                    each(els, function (el) {
                        el.parent && el.parent.remove(el);
                    });
P
pah100 已提交
227
                });
228 229 230
                // Theoritically there is no intersection between willInvisibleEls
                // and willVisibleEls have, but we set visible after for robustness.
                each(willInvisibleEls, function (el) {
231
                    el.invisible = true;
232 233 234 235 236 237 238
                    // Setting invisible is for optimizing, so no need to set dirty,
                    // just mark as invisible.
                });
                each(willVisibleEls, function (el) {
                    el.invisible = false;
                    el.__tmWillVisible = false;
                    el.dirty();
239 240
                });
            }
P
pah100 已提交
241 242 243 244 245
        },

        /**
         * @private
         */
246
        _renderNode: function (
247
            thisStorage, oldStorage, lastsForAnimation, willInvisibleEls, willVisibleEls,
248 249
            thisNode, oldNode, parentGroup
        ) {
P
pah100 已提交
250 251 252
            var thisRawIndex = thisNode && thisNode.getRawIndex();
            var oldRawIndex = oldNode && oldNode.getRawIndex();

253 254
            // Deleting things will performed finally. This method just find element from
            // old storage, or create new element, set them to new storage, and set styles.
P
pah100 已提交
255 256 257 258 259
            if (!thisNode) {
                return;
            }

            var layout = thisNode.getLayout();
P
pah100 已提交
260 261
            var thisWidth = layout.width;
            var thisHeight = layout.height;
262
            var invisible = layout.invisible;
P
pah100 已提交
263

P
pah100 已提交
264
            // Node group
265 266 267 268
            var group = giveGraphic('nodeGroup', Group);
            if (!group) {
                return;
            }
P
pah100 已提交
269
            parentGroup.add(group);
P
pah100 已提交
270
            group.position = [layout.x, layout.y];
271 272
            group.__tmNodeWidth = thisWidth;
            group.__tmNodeHeight = thisHeight;
P
pah100 已提交
273

P
pah100 已提交
274
            // Background
275 276 277 278 279 280
            var bg = giveGraphic('background', Rect);
            if (bg) {
                bg.setShape({x: 0, y: 0, width: thisWidth, height: thisHeight});
                updateStyle(bg, {fill: thisNode.getVisual('borderColor', true)});
                group.add(bg);
            }
P
pah100 已提交
281 282

            var thisViewChildren = thisNode.viewChildren;
P
pah100 已提交
283 284

            // No children, render content.
P
pah100 已提交
285
            if (!thisViewChildren || !thisViewChildren.length) {
P
pah100 已提交
286
                var borderWidth = layout.borderWidth;
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
                var content = giveGraphic('content', Rect);

                if (content) {
                    var contentWidth = Math.max(thisWidth - 2 * borderWidth, 0);
                    var contentHeight = Math.max(thisHeight - 2 * borderWidth, 0);
                    var labelModel = thisNode.getModel('label.normal');
                    var textStyleModel = thisNode.getModel('label.normal.textStyle');
                    var text = thisNode.getModel().get('name');
                    var textRect = textStyleModel.getTextRect(text);
                    var showLabel = labelModel.get('show');

                    if (!showLabel
                        || (
                            showLabel !== 'always'
                            && (textRect.width > contentWidth || textRect.height > contentHeight)
                        )
                    ) {
                        text = '';
                    }
P
pah100 已提交
306

307 308 309
                    // For tooltip.
                    content.dataIndex = thisNode.dataIndex;
                    content.seriesIndex = this.seriesModel.seriesIndex;
P
pah100 已提交
310

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
                    content.culling = true;
                    content.setShape({
                        x: borderWidth,
                        y: borderWidth,
                        width: contentWidth,
                        height: contentHeight
                    });
                    updateStyle(content, {
                        fill: thisNode.getVisual('color', true),
                        text: text,
                        textPosition: this._getTextPosition(labelModel, thisWidth, thisHeight),
                        textFill: textStyleModel.get('color'),
                        textAlign: textStyleModel.get('align'),
                        textFont: textStyleModel.getFont()
                    });
                    group.add(content);
                }
P
pah100 已提交
328 329
            }

P
pah100 已提交
330 331
            return group;

332 333 334
            function giveGraphic(storageName, Ctor) {
                var element = oldRawIndex != null && oldStorage[storageName][oldRawIndex];
                var lasts = lastsForAnimation[storageName];
P
pah100 已提交
335

336
                if (element) {
P
pah100 已提交
337
                    // Remove from oldStorage
338 339
                    oldStorage[storageName][oldRawIndex] = null;
                    prepareAnimationWhenHasOld(lasts, element, storageName);
P
pah100 已提交
340
                }
341 342 343 344
                // If invisible and no old element, do not create new element (for optimizing).
                else if (!invisible) {
                    element = new Ctor();
                    prepareAnimationWhenNoOld(lasts, element, storageName);
P
pah100 已提交
345 346 347
                }

                // Set to thisStorage
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
                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 parentNode = thisNode.parentNode;
                    var parentOldBg;
                    var parentOldX = 0;
                    var parentOldY = 0;
                    // For convenient, get old bounding rect from background.
                    if (parentNode && (
                        parentOldBg = lastsForAnimation.background[parentNode.getRawIndex()]
                    )) {
                        parentOldX = parentOldBg.old.width;
                        parentOldY = parentOldBg.old.height;
                    }
                    // When no parent old shape found, its parent is new too,
                    // so we can just use {x:0, y:0}.
                    var lastCfg = lasts[thisRawIndex] = {};
                    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 已提交
389
            }
390 391 392 393 394 395 396

            function updateStyle(element, style) {
                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.
                    element.setStyle(style);
397 398 399
                    if (!element.__tmWillVisible) {
                        element.invisible = false;
                    }
400 401 402 403 404 405 406
                }
                else {
                    // Delay invisible setting utill animation finished,
                    // avoid element vanish suddenly before animation.
                    !element.invisible && willInvisibleEls.push(element);
                }
            }
P
pah100 已提交
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
        },

        /**
         * @private
         */
        _getTextPosition: function (labelModel, nodeWidth, nodeHeight) {
            var position = labelModel.get('position');

            if (zrUtil.isArray(position)) {
                position = [
                    parsePercent(position[0], nodeWidth),
                    parsePercent(position[1], nodeHeight)
                ];
            }
            return position;
P
pah100 已提交
422 423
        },

P
pah100 已提交
424 425 426
        /**
         * @private
         */
427 428
        _doAnimation: function (containerGroup, renderResult, seriesModel) {
            if (!seriesModel.get('animation')) {
P
pah100 已提交
429 430 431
                return;
            }

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
            var duration = seriesModel.get('animationDurationUpdate');
            var easing = seriesModel.get('animationEasing');

            var animationWrap = animationUtil.createWrap();

            // Make delete animations.
            var viewRoot = this.seriesModel.getViewRoot();
            var rootGroup = this._storage.nodeGroup[viewRoot.getRawIndex()];
            rootGroup && rootGroup.traverse(function (el) {
                var storageName;
                if (el.invisible || !(storageName = el.__tmWillDelete)) {
                    return;
                }
                var targetX = 0;
                var targetY = 0;
                var parent = el.parent; // Always has parent, and parent is nodeGroup.
                if (!parent.__tmWillDelete) {
                    // Let node animate to right-bottom corner, cooperating with fadeout,
                    // which is perfect for user understanding.
                    targetX = parent.__tmNodeWidth;
                    targetY = parent.__tmNodeHeight;
                }
                var target = storageName === 'nodeGroup'
                    ? {position: [targetX, targetY], style: {opacity: 0}}
                    : {shape: {x: targetX, y: targetY, width: 0, height: 0}, style: {opacity: 0}};
                animationWrap.add(el, target, duration, easing);
            });

            // Make other animations
            each(this._storage, function (store, storageName) {
                each(store, function (el, rawIndex) {
                    var last = renderResult.lastsForAnimation[storageName][rawIndex];
                    var target;
P
pah100 已提交
465 466 467 468

                    if (!last) {
                        return;
                    }
469 470 471 472 473

                    if (storageName === 'nodeGroup') {
                        target = {position: el.position.slice()};
                        el.position = last.old;
                        animationWrap.add(el, target, duration, easing);
P
pah100 已提交
474
                    }
475 476 477 478 479 480 481 482
                    else {
                        target = {shape: zrUtil.extend({}, el.shape)};
                        el.setShape(last.old);

                        if (last.fadein) {
                            el.setStyle('opacity', 0);
                            target.style = {opacity: 1};
                        }
P
pah100 已提交
483
                    }
484
                    animationWrap.add(el, target, duration, easing);
P
pah100 已提交
485
                });
486
            }, this);
P
pah100 已提交
487

488
            this._state = 'animating';
P
pah100 已提交
489

490 491 492
            animationWrap
                .done(bind(function () {
                    this._state = 'ready';
493
                    renderResult.renderFinally();
494 495
                }, this))
                .start();
P
pah100 已提交
496 497 498 499 500
        },

        /**
         * @private
         */
501
        _resetController: function (api) {
P
pah100 已提交
502 503 504 505 506
            var controller = this._controller;

            // Init controller.
            if (!controller) {
                controller = this._controller = new RoamController(api.getZr());
P
pah100 已提交
507 508 509 510 511 512 513
                controller.on('pan', bind(handle, this, this._onPan));
                controller.on('zoom', bind(handle, this, this._onZoom));
            }

            function handle(handler) {
                this._mayClick = false;
                return handler.apply(this, Array.prototype.slice.call(arguments, 1));
P
pah100 已提交
514 515
            }

P
pah100 已提交
516 517 518
            controller.rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());

            if (!this.seriesModel.get('roam')) {
P
pah100 已提交
519 520 521 522
                controller.off('pan').off('zoom');
                this._controller = null;
                return;
            }
P
pah100 已提交
523
        },
P
pah100 已提交
524

P
pah100 已提交
525 526 527 528 529 530 531 532
        /**
         * @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
tweak  
pah100 已提交
533
                var viewRoot = this.seriesModel.getViewRoot();
534

P
tweak  
pah100 已提交
535
                if (!viewRoot) {
P
pah100 已提交
536
                    return;
P
pah100 已提交
537
                }
538

P
tweak  
pah100 已提交
539
                var rootLayout = viewRoot.getLayout();
540 541 542 543 544 545

                this.api.dispatch({
                    type: 'treemapMove',
                    from: this.uid,
                    seriesId: this.seriesModel.uid,
                    rootRect: {
P
tweak  
pah100 已提交
546
                        x: rootLayout.x + dx, y: rootLayout.y + dy,
547 548 549
                        width: rootLayout.width, height: rootLayout.height
                    }
                });
P
pah100 已提交
550
            }
P
pah100 已提交
551
        },
P
pah100 已提交
552

P
pah100 已提交
553 554 555 556
        /**
         * @private
         */
        _onZoom: function (scale, mouseX, mouseY) {
P
pah100 已提交
557
            if (this._state !== 'animating' && !this._controller.isDragging()) {
P
pah100 已提交
558
                // These param must not be cached.
P
tweak  
pah100 已提交
559
                var viewRoot = this.seriesModel.getViewRoot();
560

P
tweak  
pah100 已提交
561
                if (!viewRoot) {
562 563 564
                    return;
                }

P
tweak  
pah100 已提交
565
                var rootLayout = viewRoot.getLayout();
566
                var rect = new BoundingRect(
P
tweak  
pah100 已提交
567
                    rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height
568
                );
P
tweak  
pah100 已提交
569
                var layoutInfo = this.seriesModel.layoutInfo;
P
pah100 已提交
570

571 572 573 574 575
                // Transform mouse coord from global to containerGroup.
                mouseX -= layoutInfo.x;
                mouseY -= layoutInfo.y;

                // Scale root bounding rect.
P
pah100 已提交
576 577 578 579 580 581 582 583 584 585 586
                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);

                this.api.dispatch({
                    type: 'treemapRender',
                    from: this.uid,
                    seriesId: this.seriesModel.uid,
587 588 589
                    rootRect: {
                        x: rect.x, y: rect.y,
                        width: rect.width, height: rect.height
P
pah100 已提交
590 591
                    }
                });
P
pah100 已提交
592 593 594 595 596 597
            }
        },

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

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

P
pah100 已提交
607
            containerGroup.on('mousedown', function (e) {
608 609
                this._state === 'ready' && (this._mayClick = true);
            }, this);
P
pah100 已提交
610
            containerGroup.on('mouseup', function (e) {
611 612 613
                if (this._mayClick) {
                    this._mayClick = false;
                    this._state === 'ready' && onClick.call(this, e);
P
pah100 已提交
614
                }
615
            }, this);
P
pah100 已提交
616 617

            function onClick(e) {
618
                var targetInfo = this.findTarget(e.offsetX, e.offsetY);
P
pah100 已提交
619
                if (targetInfo) {
620
                    this._zoomToNode(targetInfo);
P
pah100 已提交
621 622 623 624
                }
            }
        },

P
pah100 已提交
625 626 627 628 629 630 631 632 633 634 635 636 637
        /**
         * @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};
                }
            }

638
            (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group, bind(onSelect, this))))
P
pah100 已提交
639 640 641
                .render(seriesModel, api, targetInfo.node);

            function onSelect(node) {
642
                this._zoomToNode({node: node});
P
pah100 已提交
643 644 645
            }
        },

P
pah100 已提交
646 647 648 649
        /**
         * @override
         */
        remove: function () {
P
pah100 已提交
650 651 652
            this._containerGroup.removeAll();
            this._storage = createStorage();
            this._state = 'ready';
P
pah100 已提交
653
            this._breadcrumb && this._breadcrumb.remove();
P
pah100 已提交
654 655
        },

P
pah100 已提交
656 657 658 659
        /**
         * @private
         */
        _zoomToNode: function (targetInfo) {
P
pah100 已提交
660
            this.api.dispatch({
P
pah100 已提交
661
                type: 'treemapZoomToNode',
P
pah100 已提交
662
                from: this.uid,
P
pah100 已提交
663 664
                seriesId: this.seriesModel.uid,
                targetInfo: targetInfo
P
pah100 已提交
665 666 667 668 669
            });
        },

        /**
         * @public
P
pah100 已提交
670 671
         * @param {number} x Global coord x.
         * @param {number} y Global coord y.
P
pah100 已提交
672 673 674 675 676 677 678 679 680 681
         * @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) {
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
                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 已提交
699 700 701 702
                }
            }, this);

            return targetInfo;
P
pah100 已提交
703 704 705
        }

    });
P
pah100 已提交
706

P
pah100 已提交
707 708 709 710
    function createStorage() {
        return {nodeGroup: [], background: [], content: []};
    }

P
pah100 已提交
711
});