TreemapView.js 26.8 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
                    (new DataDiffer(oldViewChildren, thisViewChildren, getKey, getKey))
P
pah100 已提交
181 182
                        .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'),
L
lang 已提交
326
                        textFill: textStyleModel.getTextColor(),
327
                        textAlign: textStyleModel.get('align'),
L
lang 已提交
328
                        textVerticalAlign: 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());
501
                controller.enable(this.seriesModel.get('roam'));
502 503
                controller.on('pan', bind(this._onPan, this));
                controller.on('zoom', bind(this._onZoom, this));
P
pah100 已提交
504 505
            }

P
pah100 已提交
506
            controller.rect = new BoundingRect(0, 0, api.getWidth(), api.getHeight());
507
        },
P
pah100 已提交
508

509 510 511 512 513 514
        /**
         * @private
         */
        _clearController: function () {
            var controller = this._controller;
            if (controller) {
P
pah100 已提交
515
                controller.off('pan').off('zoom');
516
                controller = null;
P
pah100 已提交
517
            }
P
pah100 已提交
518
        },
P
pah100 已提交
519

P
pah100 已提交
520 521 522 523
        /**
         * @private
         */
        _onPan: function (dx, dy) {
524 525
            this._mayClick = false;

P
pah100 已提交
526 527 528 529
            if (this._state !== 'animating'
                && (Math.abs(dx) > DRAG_THRESHOLD || Math.abs(dy) > DRAG_THRESHOLD)
            ) {
                // These param must not be cached.
P
tweak  
pah100 已提交
530
                var viewRoot = this.seriesModel.getViewRoot();
531

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

P
tweak  
pah100 已提交
536
                var rootLayout = viewRoot.getLayout();
537

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

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

P
pah100 已提交
554 555 556 557
        /**
         * @private
         */
        _onZoom: function (scale, mouseX, mouseY) {
558 559
            this._mayClick = false;

P
pah100 已提交
560
            if (this._state !== 'animating') {
P
pah100 已提交
561
                // These param must not be cached.
P
tweak  
pah100 已提交
562
                var viewRoot = this.seriesModel.getViewRoot();
563

P
tweak  
pah100 已提交
564
                if (!viewRoot) {
565 566 567
                    return;
                }

P
tweak  
pah100 已提交
568
                var rootLayout = viewRoot.getLayout();
P
pah100 已提交
569 570 571 572 573

                if (!rootLayout) {
                    return;
                }

574
                var rect = new BoundingRect(
P
tweak  
pah100 已提交
575
                    rootLayout.x, rootLayout.y, rootLayout.width, rootLayout.height
576
                );
P
tweak  
pah100 已提交
577
                var layoutInfo = this.seriesModel.layoutInfo;
P
pah100 已提交
578

579 580 581 582 583
                // Transform mouse coord from global to containerGroup.
                mouseX -= layoutInfo.x;
                mouseY -= layoutInfo.y;

                // Scale root bounding rect.
P
pah100 已提交
584 585 586 587 588 589 590
                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 已提交
591
                this.api.dispatchAction({
P
pah100 已提交
592 593
                    type: 'treemapRender',
                    from: this.uid,
P
tweak  
pah100 已提交
594
                    seriesId: this.seriesModel.id,
595 596 597
                    rootRect: {
                        x: rect.x, y: rect.y,
                        width: rect.width, height: rect.height
P
pah100 已提交
598 599
                    }
                });
P
pah100 已提交
600 601 602 603 604 605
            }
        },

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

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

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

            function onClick(e) {
626 627 628 629 630 631
                var nodeClick = this.seriesModel.get('nodeClick', true);

                if (!nodeClick) {
                    return;
                }

632
                var targetInfo = this.findTarget(e.offsetX, e.offsetY);
633

P
pah100 已提交
634
                if (targetInfo) {
635 636 637 638 639 640 641 642 643 644
                    if (nodeClick === 'zoomToNode') {
                        this._zoomToNode(targetInfo);
                    }
                    else if (nodeClick === 'link') {
                        var node = targetInfo.node;
                        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 已提交
645 646 647 648
                }
            }
        },

P
pah100 已提交
649 650 651 652 653 654 655 656 657 658 659 660 661
        /**
         * @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};
                }
            }

662
            (this._breadcrumb || (this._breadcrumb = new Breadcrumb(this.group, bind(onSelect, this))))
P
pah100 已提交
663 664 665
                .render(seriesModel, api, targetInfo.node);

            function onSelect(node) {
666
                this._zoomToNode({node: node});
P
pah100 已提交
667 668 669
            }
        },

P
pah100 已提交
670 671 672 673
        /**
         * @override
         */
        remove: function () {
674
            this._clearController();
L
lang 已提交
675
            this._containerGroup && this._containerGroup.removeAll();
P
pah100 已提交
676 677
            this._storage = createStorage();
            this._state = 'ready';
P
pah100 已提交
678
            this._breadcrumb && this._breadcrumb.remove();
P
pah100 已提交
679 680
        },

681 682 683 684
        dispose: function () {
            this._clearController();
        },

P
pah100 已提交
685 686 687 688
        /**
         * @private
         */
        _zoomToNode: function (targetInfo) {
L
lang 已提交
689
            this.api.dispatchAction({
P
pah100 已提交
690
                type: 'treemapZoomToNode',
P
pah100 已提交
691
                from: this.uid,
P
tweak  
pah100 已提交
692
                seriesId: this.seriesModel.id,
P
pah100 已提交
693
                targetNode: targetInfo.node
P
pah100 已提交
694 695 696 697 698
            });
        },

        /**
         * @public
P
pah100 已提交
699 700
         * @param {number} x Global coord x.
         * @param {number} y Global coord y.
P
pah100 已提交
701 702 703 704 705 706 707 708 709 710
         * @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) {
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
                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 已提交
728 729 730 731
                }
            }, this);

            return targetInfo;
P
pah100 已提交
732 733 734
        }

    });
P
pah100 已提交
735

P
pah100 已提交
736 737 738
    function createStorage() {
        return {nodeGroup: [], background: [], content: []};
    }
P
pah100 已提交
739
});