TreemapView.js 26.0 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 18
    var DRAG_THRESHOLD = 3;

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

        type: 'treemap',

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

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

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

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

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

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

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

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

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

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

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

87 88
            var payloadType = payload && payload.type;
            var layoutInfo = seriesModel.layoutInfo;
P
tweak  
pah100 已提交
89
            var isInit = !this._oldTree;
90 91 92 93 94

            var containerGroup = this._giveContainerGroup(layoutInfo);

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

P
tweak  
pah100 已提交
95
            (!isInit && (!payloadType || payloadType === 'treemapZoomToNode'))
96
                ? this._doAnimation(containerGroup, renderResult, seriesModel)
97
                : renderResult.renderFinally();
P
pah100 已提交
98

99 100
            this._resetController(api);

P
pah100 已提交
101
            var targetInfo = helper.retrieveTargetInfo(payload, seriesModel);
102 103 104 105 106 107 108 109
            this._renderBreadcrumb(seriesModel, api, targetInfo);
        },

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

119
            return containerGroup;
P
pah100 已提交
120 121 122 123 124
        },

        /**
         * @private
         */
125 126
        _doRender: function (containerGroup, seriesModel) {
            var thisTree = seriesModel.getData().tree;
P
pah100 已提交
127 128
            var oldTree = this._oldTree;

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

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

            // Process all removing.
P
pah100 已提交
156
            var willDeleteEls = clearStorage(oldStorage);
P
pah100 已提交
157 158 159 160

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

161
            return {
162
                lastsForAnimation: lastsForAnimation,
P
pah100 已提交
163 164
                willDeleteEls: willDeleteEls,
                renderFinally: renderFinally
165
            };
P
pah100 已提交
166

P
pah100 已提交
167 168 169 170 171 172
            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;
173
                    each(thisViewChildren, function (child, index) {
P
pah100 已提交
174
                        !child.isRemoved() && processNode(index, index);
P
pah100 已提交
175 176
                    });
                }
P
pah100 已提交
177 178
                // Diff hierarchically (diff only in each subtree, but not whole).
                // because, consistency of view is important.
P
pah100 已提交
179
                else {
P
pah100 已提交
180 181 182
                    (new DataDiffer(oldViewChildren, thisViewChildren, getKey))
                        .add(processNode)
                        .update(processNode)
183 184
                        .remove(zrUtil.curry(processNode, null))
                        .execute();
P
pah100 已提交
185 186 187 188
                }

                function getKey(node) {
                    // Identify by name or raw index.
P
pah100 已提交
189
                    return node.getId();
P
pah100 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202
                }

                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;
                    }

203 204
                    var group = renderNode(thisNode, oldNode, parentGroup);

205
                    group && dualTravel(
P
pah100 已提交
206 207 208
                        thisNode && thisNode.viewChildren || [],
                        oldNode && oldNode.viewChildren || [],
                        group,
P
pah100 已提交
209
                        sameTree,
P
pah100 已提交
210 211 212 213
                        subInView
                    );
                }
            }
P
pah100 已提交
214 215

            function clearStorage(storage) {
216 217 218 219 220
                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 已提交
221 222
                    });
                });
P
pah100 已提交
223
                return willDeleteEls;
P
pah100 已提交
224
            }
225

P
pah100 已提交
226
            function renderFinally() {
227 228 229 230
                each(willDeleteEls, function (els) {
                    each(els, function (el) {
                        el.parent && el.parent.remove(el);
                    });
P
pah100 已提交
231
                });
232 233 234
                // Theoritically there is no intersection between willInvisibleEls
                // and willVisibleEls have, but we set visible after for robustness.
                each(willInvisibleEls, function (el) {
235
                    el.invisible = true;
236 237 238 239 240 241 242
                    // 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();
243 244
                });
            }
P
pah100 已提交
245 246 247 248 249
        },

        /**
         * @private
         */
250
        _renderNode: function (
P
tweak  
pah100 已提交
251 252
            thisStorage, oldStorage, lastsForAnimation,
            willInvisibleEls, willVisibleEls,
253 254
            thisNode, oldNode, parentGroup
        ) {
P
pah100 已提交
255 256 257
            var thisRawIndex = thisNode && thisNode.getRawIndex();
            var oldRawIndex = oldNode && oldNode.getRawIndex();

258 259
            // 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 已提交
260 261 262 263 264
            if (!thisNode) {
                return;
            }

            var layout = thisNode.getLayout();
P
pah100 已提交
265 266
            var thisWidth = layout.width;
            var thisHeight = layout.height;
267
            var invisible = layout.invisible;
P
pah100 已提交
268

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

P
pah100 已提交
279
            // Background
280 281 282 283 284 285
            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 已提交
286 287

            var thisViewChildren = thisNode.viewChildren;
P
pah100 已提交
288 289

            // No children, render content.
P
pah100 已提交
290
            if (!thisViewChildren || !thisViewChildren.length) {
P
pah100 已提交
291
                var borderWidth = layout.borderWidth;
292 293 294 295 296 297 298 299 300 301 302
                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');

P
pah100 已提交
303
                    if (!showLabel || textRect.height > contentHeight) {
304 305
                        text = '';
                    }
P
pah100 已提交
306 307 308 309
                    else if (textRect.width > contentWidth) {
                        text = textStyleModel.get('ellipsis')
                            ? textStyleModel.ellipsis(text, contentWidth) : '';
                    }
P
pah100 已提交
310

311 312 313
                    // For tooltip.
                    content.dataIndex = thisNode.dataIndex;
                    content.seriesIndex = this.seriesModel.seriesIndex;
P
pah100 已提交
314

315 316 317 318 319 320 321 322 323 324
                    content.culling = true;
                    content.setShape({
                        x: borderWidth,
                        y: borderWidth,
                        width: contentWidth,
                        height: contentHeight
                    });
                    updateStyle(content, {
                        fill: thisNode.getVisual('color', true),
                        text: text,
L
lang 已提交
325
                        textPosition: labelModel.get('position'),
326 327
                        textFill: textStyleModel.get('color'),
                        textAlign: textStyleModel.get('align'),
P
pah100 已提交
328
                        textBaseline: textStyleModel.get('baseline'),
329 330 331 332
                        textFont: textStyleModel.getFont()
                    });
                    group.add(content);
                }
P
pah100 已提交
333 334
            }

P
pah100 已提交
335 336
            return group;

337 338 339
            function giveGraphic(storageName, Ctor) {
                var element = oldRawIndex != null && oldStorage[storageName][oldRawIndex];
                var lasts = lastsForAnimation[storageName];
P
pah100 已提交
340

341
                if (element) {
P
pah100 已提交
342
                    // Remove from oldStorage
343 344
                    oldStorage[storageName][oldRawIndex] = null;
                    prepareAnimationWhenHasOld(lasts, element, storageName);
P
pah100 已提交
345
                }
346 347 348 349
                // 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 已提交
350 351 352
                }

                // Set to thisStorage
L
Tweak  
lang 已提交
353
                return (thisStorage[storageName][thisRawIndex] = element);
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 389 390 391 392 393
            }

            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 已提交
394
            }
395 396 397 398 399 400 401

            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);
402 403 404
                    if (!element.__tmWillVisible) {
                        element.invisible = false;
                    }
405 406 407 408 409 410 411
                }
                else {
                    // Delay invisible setting utill animation finished,
                    // avoid element vanish suddenly before animation.
                    !element.invisible && willInvisibleEls.push(element);
                }
            }
P
pah100 已提交
412 413
        },

P
pah100 已提交
414 415 416
        /**
         * @private
         */
417 418
        _doAnimation: function (containerGroup, renderResult, seriesModel) {
            if (!seriesModel.get('animation')) {
P
pah100 已提交
419 420 421
                return;
            }

422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
            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 已提交
455 456 457 458

                    if (!last) {
                        return;
                    }
459 460 461 462

                    if (storageName === 'nodeGroup') {
                        target = {position: el.position.slice()};
                        el.position = last.old;
P
pah100 已提交
463
                    }
464 465 466 467 468 469 470 471
                    else {
                        target = {shape: zrUtil.extend({}, el.shape)};
                        el.setShape(last.old);

                        if (last.fadein) {
                            el.setStyle('opacity', 0);
                            target.style = {opacity: 1};
                        }
P
pah100 已提交
472 473 474 475 476
                        // 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 已提交
477
                    }
478
                    animationWrap.add(el, target, duration, easing);
P
pah100 已提交
479
                });
480
            }, this);
P
pah100 已提交
481

482
            this._state = 'animating';
P
pah100 已提交
483

484 485 486
            animationWrap
                .done(bind(function () {
                    this._state = 'ready';
487
                    renderResult.renderFinally();
488 489
                }, this))
                .start();
P
pah100 已提交
490 491 492 493 494
        },

        /**
         * @private
         */
495
        _resetController: function (api) {
P
pah100 已提交
496 497 498 499 500
            var controller = this._controller;

            // Init controller.
            if (!controller) {
                controller = this._controller = new RoamController(api.getZr());
P
pah100 已提交
501
                controller.enable();
P
pah100 已提交
502 503 504 505 506 507 508
                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 已提交
509 510
            }

P
pah100 已提交
511 512 513
            controller.rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());

            if (!this.seriesModel.get('roam')) {
P
pah100 已提交
514 515 516 517
                controller.off('pan').off('zoom');
                this._controller = null;
                return;
            }
P
pah100 已提交
518
        },
P
pah100 已提交
519

P
pah100 已提交
520 521 522 523 524 525 526 527
        /**
         * @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 已提交
528
                var viewRoot = this.seriesModel.getViewRoot();
529

P
tweak  
pah100 已提交
530
                if (!viewRoot) {
P
pah100 已提交
531
                    return;
P
pah100 已提交
532
                }
533

P
tweak  
pah100 已提交
534
                var rootLayout = viewRoot.getLayout();
535

P
pah100 已提交
536 537 538 539
                if (!rootLayout) {
                    return;
                }

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

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

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

P
tweak  
pah100 已提交
564
                var rootLayout = viewRoot.getLayout();
P
pah100 已提交
565 566 567 568 569

                if (!rootLayout) {
                    return;
                }

570
                var rect = new BoundingRect(
P
tweak  
pah100 已提交
571
                    rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height
572
                );
P
tweak  
pah100 已提交
573
                var layoutInfo = this.seriesModel.layoutInfo;
P
pah100 已提交
574

575 576 577 578 579
                // Transform mouse coord from global to containerGroup.
                mouseX -= layoutInfo.x;
                mouseY -= layoutInfo.y;

                // Scale root bounding rect.
P
pah100 已提交
580 581 582 583 584 585 586 587 588 589
                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,
P
tweak  
pah100 已提交
590
                    seriesId: this.seriesModel.id,
591 592 593
                    rootRect: {
                        x: rect.x, y: rect.y,
                        width: rect.width, height: rect.height
P
pah100 已提交
594 595
                    }
                });
P
pah100 已提交
596 597 598 599 600 601
            }
        },

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

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

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

            function onClick(e) {
622
                var targetInfo = this.findTarget(e.offsetX, e.offsetY);
P
pah100 已提交
623
                if (targetInfo) {
624
                    this._zoomToNode(targetInfo);
P
pah100 已提交
625 626 627 628
                }
            }
        },

P
pah100 已提交
629 630 631 632 633 634 635 636 637 638 639 640 641
        /**
         * @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};
                }
            }

642
            (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group, bind(onSelect, this))))
P
pah100 已提交
643 644 645
                .render(seriesModel, api, targetInfo.node);

            function onSelect(node) {
646
                this._zoomToNode({node: node});
P
pah100 已提交
647 648 649
            }
        },

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

P
pah100 已提交
660 661 662 663
        /**
         * @private
         */
        _zoomToNode: function (targetInfo) {
P
pah100 已提交
664
            this.api.dispatch({
P
pah100 已提交
665
                type: 'treemapZoomToNode',
P
pah100 已提交
666
                from: this.uid,
P
tweak  
pah100 已提交
667
                seriesId: this.seriesModel.id,
P
pah100 已提交
668
                targetNode: targetInfo.node
P
pah100 已提交
669 670 671 672 673
            });
        },

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

            return targetInfo;
P
pah100 已提交
707 708 709
        }

    });
P
pah100 已提交
710

P
pah100 已提交
711 712 713
    function createStorage() {
        return {nodeGroup: [], background: [], content: []};
    }
P
pah100 已提交
714
});