TooltipView.js 28.4 KB
Newer Older
L
lang 已提交
1 2 3 4 5 6
define(function (require) {

    var TooltipContent = require('./TooltipContent');
    var zrUtil = require('zrender/core/util');
    var formatUtil = require('../../util/format');
    var numberUtil = require('../../util/number');
7
    var graphic = require('../../util/graphic');
P
pah100 已提交
8
    var findPointFromSeries = require('../axisPointer/findPointFromSeries');
P
pah100 已提交
9
    var layoutUtil = require('../../util/layout');
L
lang 已提交
10
    var env = require('zrender/core/env');
L
lang 已提交
11
    var Model = require('../../model/Model');
P
pah100 已提交
12
    var globalListener = require('../axisPointer/globalListener');
13
    var axisHelper = require('../../coord/axisHelper');
P
pah100 已提交
14
    var axisPointerViewHelper = require('../axisPointer/viewHelper');
L
lang 已提交
15

P
pah100 已提交
16 17
    var bind = zrUtil.bind;
    var each = zrUtil.each;
P
pah100 已提交
18
    var parsePercent = numberUtil.parsePercent;
L
lang 已提交
19 20


21 22 23 24
    var proxyRect = new graphic.Rect({
        shape: {x: -1, y: -1, width: 2, height: 2}
    });

L
lang 已提交
25 26 27 28 29
    require('../../echarts').extendComponentView({

        type: 'tooltip',

        init: function (ecModel, api) {
30
            if (env.node) {
L
lang 已提交
31 32
                return;
            }
L
lang 已提交
33 34 35 36 37
            var tooltipContent = new TooltipContent(api.getDom(), api);
            this._tooltipContent = tooltipContent;
        },

        render: function (tooltipModel, ecModel, api) {
38
            if (env.node) {
L
lang 已提交
39 40
                return;
            }
41

L
lang 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
            // Reset
            this.group.removeAll();

            /**
             * @private
             * @type {module:echarts/component/tooltip/TooltipModel}
             */
            this._tooltipModel = tooltipModel;

            /**
             * @private
             * @type {module:echarts/model/Global}
             */
            this._ecModel = ecModel;

            /**
             * @private
             * @type {module:echarts/ExtensionAPI}
             */
            this._api = api;

            /**
P
pah100 已提交
64
             * Should be cleaned when render.
L
lang 已提交
65
             * @private
P
pah100 已提交
66
             * @type {Array.<Array.<Object>>}
L
lang 已提交
67
             */
P
pah100 已提交
68
            this._lastDataByCoordSys = null;
P
pah100 已提交
69 70 71 72 73 74

            /**
             * @private
             * @type {boolean}
             */
            this._alwaysShowContent = tooltipModel.get('alwaysShowContent');
L
lang 已提交
75 76 77

            var tooltipContent = this._tooltipContent;
            tooltipContent.update();
P
pah100 已提交
78
            tooltipContent.setEnterable(tooltipModel.get('enterable'));
L
lang 已提交
79

P
pah100 已提交
80
            this._initGlobalListener();
L
lang 已提交
81

P
pah100 已提交
82 83
            this._keepShow();
        },
L
lang 已提交
84

P
pah100 已提交
85 86
        _initGlobalListener: function () {
            var tooltipModel = this._tooltipModel;
87
            var triggerOn = tooltipModel.get('triggerOn');
1
100pah 已提交
88

P
pah100 已提交
89 90 91 92
            globalListener.register(
                'itemTooltip',
                this._api,
                bind(function (currTrigger, e, dispatchAction) {
93 94 95 96 97 98 99 100
                    // If 'none', it is not controlled by mouse totally.
                    if (triggerOn !== 'none') {
                        if (triggerOn.indexOf(currTrigger) >= 0) {
                            this._tryShow(e, dispatchAction);
                        }
                        else if (currTrigger === 'leave') {
                            this._hide(dispatchAction);
                        }
P
pah100 已提交
101 102 103 104 105 106 107 108 109 110
                    }
                }, this)
            );
        },

        _keepShow: function () {
            var tooltipModel = this._tooltipModel;
            var ecModel = this._ecModel;
            var api = this._api;

111
            // Try to keep the tooltip show when refreshing
1
100pah 已提交
112 113 114
            if (this._lastX != null
                && this._lastY != null
                // When user is willing to control tooltip totally using API,
1
100pah 已提交
115
                // self.manuallyShowTip({x, y}) might cause tooltip hide,
1
100pah 已提交
116
                // which is not expected.
P
pah100 已提交
117
                && tooltipModel.get('triggerOn') !== 'none'
1
100pah 已提交
118
            ) {
L
tweak  
lang 已提交
119 120 121 122 123 124
                var self = this;
                clearTimeout(this._refreshUpdateTimeout);
                this._refreshUpdateTimeout = setTimeout(function () {
                    // Show tip next tick after other charts are rendered
                    // In case highlight action has wrong result
                    // FIXME
1
100pah 已提交
125
                    self.manuallyShowTip(tooltipModel, ecModel, api, {
L
tweak  
lang 已提交
126 127 128
                        x: self._lastX,
                        y: self._lastY
                    });
129 130
                });
            }
L
lang 已提交
131 132 133 134
        },

        /**
         * Show tip manually by
135 136 137 138 139
         * dispatchAction({
         *     type: 'showTip',
         *     x: 10,
         *     y: 10
         * });
L
lang 已提交
140
         * Or
141
         * dispatchAction({
L
lang 已提交
142 143
         *      type: 'showTip',
         *      seriesIndex: 0,
144 145
         *      dataIndex or dataIndexInside or name
         * });
146 147
         *
         *  TODO Batch
L
lang 已提交
148
         */
1
100pah 已提交
149
        manuallyShowTip: function (tooltipModel, ecModel, api, payload) {
P
pah100 已提交
150
            if (payload.from === this.uid || env.node) {
P
pah100 已提交
151 152 153 154 155 156 157 158
                return;
            }

            var dispatchAction = makeDispatchAction(payload, api);

            // Reset ticket
            this._ticket = '';

P
pah100 已提交
159
            // When triggered from axisPointer.
160
            var dataByCoordSys = payload.dataByCoordSys;
161 162 163 164 165 166 167 168 169 170 171 172 173 174

            if (payload.tooltip && payload.x != null && payload.y != null) {
                var el = proxyRect;
                el.position = [payload.x, payload.y];
                el.update();
                el.tooltip = payload.tooltip;
                // Manually show tooltip while view is not using zrender elements.
                this._tryShow({
                    offsetX: payload.x,
                    offsetY: payload.y,
                    target: el
                }, dispatchAction);
            }
            else if (dataByCoordSys) {
P
pah100 已提交
175 176 177 178 179
                this._tryShow({
                    offsetX: payload.x,
                    offsetY: payload.y,
                    position: payload.position,
                    event: {},
P
pah100 已提交
180
                    dataByCoordSys: payload.dataByCoordSys,
P
pah100 已提交
181 182 183
                    tooltipOption: payload.tooltipOption
                }, dispatchAction);
            }
184 185 186 187 188 189
            else if (payload.seriesIndex != null) {

                if (this._manuallyAxisShowTip(tooltipModel, ecModel, api, payload)) {
                    return;
                }

P
pah100 已提交
190 191 192
                var pointInfo = findPointFromSeries(payload, ecModel);
                var cx = pointInfo.point[0];
                var cy = pointInfo.point[1];
P
pah100 已提交
193 194 195 196 197
                if (cx != null && cy != null) {
                    this._tryShow({
                        offsetX: cx,
                        offsetY: cy,
                        position: payload.position,
P
pah100 已提交
198
                        target: pointInfo.el,
P
pah100 已提交
199 200
                        event: {}
                    }, dispatchAction);
L
lang 已提交
201 202
                }
            }
P
pah100 已提交
203
            else if (payload.x != null && payload.y != null) {
204 205 206 207 208 209 210 211
                // FIXME
                // should wrap dispatchAction like `axisPointer/globalListener` ?
                api.dispatchAction({
                    type: 'updateAxisPointer',
                    x: payload.x,
                    y: payload.y
                });

P
pah100 已提交
212 213 214 215
                this._tryShow({
                    offsetX: payload.x,
                    offsetY: payload.y,
                    position: payload.position,
P
pah100 已提交
216
                    target: api.getZr().findHover(payload.x, payload.y).target,
P
pah100 已提交
217 218 219
                    event: {}
                }, dispatchAction);
            }
L
lang 已提交
220 221
        },

1
100pah 已提交
222
        manuallyHideTip: function (tooltipModel, ecModel, api, payload) {
P
pah100 已提交
223
            var tooltipContent = this._tooltipContent;
224

P
pah100 已提交
225 226 227
            if (!this._alwaysShowContent) {
                tooltipContent.hideLater(this._tooltipModel.get('hideDelay'));
            }
L
lang 已提交
228

P
pah100 已提交
229
            this._lastX = this._lastY = null;
L
lang 已提交
230

P
pah100 已提交
231 232 233
            if (payload.from !== this.uid) {
                this._hide(makeDispatchAction(payload, api));
            }
L
lang 已提交
234 235
        },

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
        // Be compatible with previous design, that is, when tooltip.type is 'axis' and
        // dispatchAction 'showTip' with seriesIndex and dataIndex will trigger axis pointer
        // and tooltip.
        _manuallyAxisShowTip: function (tooltipModel, ecModel, api, payload) {
            var seriesIndex = payload.seriesIndex;
            var dataIndex = payload.dataIndex;
            var coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo;

            if (seriesIndex == null || dataIndex == null || coordSysAxesInfo == null) {
                return;
            }

            var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
            if (!seriesModel) {
                return;
            }

            var data = seriesModel.getData();
            var tooltipModel = buildTooltipModel([
                data.getItemModel(dataIndex),
                seriesModel,
                (seriesModel.coordinateSystem || {}).model,
                tooltipModel
            ]);

            if (tooltipModel.get('trigger') !== 'axis') {
                return;
            }

            api.dispatchAction({
                type: 'updateAxisPointer',
                seriesIndex: seriesIndex,
268 269
                dataIndex: dataIndex,
                position: payload.position
270 271 272 273 274
            });

            return true;
        },

P
pah100 已提交
275
        _tryShow: function (e, dispatchAction) {
L
lang 已提交
276 277 278 279 280 281 282
            var el = e.target;
            var tooltipModel = this._tooltipModel;

            if (!tooltipModel) {
                return;
            }

283 284 285 286
            // Save mouse x, mouse y. So we can try to keep showing the tip if chart is refreshed
            this._lastX = e.offsetX;
            this._lastY = e.offsetY;

P
pah100 已提交
287 288 289
            var dataByCoordSys = e.dataByCoordSys;
            if (dataByCoordSys && dataByCoordSys.length) {
                this._showAxisTooltip(dataByCoordSys, e);
P
pah100 已提交
290
            }
L
lang 已提交
291
            // Always show item tooltip if mouse is on the element with dataIndex
P
pah100 已提交
292
            else if (el && el.dataIndex != null) {
P
pah100 已提交
293
                this._lastDataByCoordSys = null;
P
pah100 已提交
294
                this._showSeriesItemTooltip(e, el, dispatchAction);
L
lang 已提交
295
            }
P
pah100 已提交
296
            // Tooltip provided directly. Like legend.
L
lang 已提交
297
            else if (el && el.tooltip) {
P
pah100 已提交
298
                this._lastDataByCoordSys = null;
P
pah100 已提交
299
                this._showComponentItemTooltip(e, el, dispatchAction);
L
lang 已提交
300
            }
L
lang 已提交
301
            else {
P
pah100 已提交
302
                this._lastDataByCoordSys = null;
P
pah100 已提交
303
                this._hide(dispatchAction);
L
lang 已提交
304 305 306
            }
        },

307 308 309 310 311 312 313 314 315 316 317 318 319
        _showOrMove: function (tooltipModel, cb) {
            // showDelay is used in this case: tooltip.enterable is set
            // as true. User intent to move mouse into tooltip and click
            // something. `showDelay` makes it easyer to enter the content
            // but tooltip do not move immediately.
            var delay = tooltipModel.get('showDelay');
            cb = zrUtil.bind(cb, this);
            clearTimeout(this._showTimout);
            delay > 0
                ? (this._showTimout = setTimeout(cb, delay))
                : cb();
        },

P
pah100 已提交
320
        _showAxisTooltip: function (dataByCoordSys, e) {
P
pah100 已提交
321
            var ecModel = this._ecModel;
P
pah100 已提交
322
            var globalTooltipModel = this._tooltipModel;
P
pah100 已提交
323
            var point = [e.offsetX, e.offsetY];
P
pah100 已提交
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
            var singleDefaultHTML = [];
            var singleParamsList = [];
            var singleTooltipModel = buildTooltipModel([
                e.tooltipOption,
                globalTooltipModel
            ]);

            each(dataByCoordSys, function (itemCoordSys) {
                // var coordParamList = [];
                // var coordDefaultHTML = [];
                // var coordTooltipModel = buildTooltipModel([
                //     e.tooltipOption,
                //     itemCoordSys.tooltipOption,
                //     ecModel.getComponent(itemCoordSys.coordSysMainType, itemCoordSys.coordSysIndex),
                //     globalTooltipModel
                // ]);
                // var displayMode = coordTooltipModel.get('displayMode');
                // var paramsList = displayMode === 'single' ? singleParamsList : [];

                each(itemCoordSys.dataByAxis, function (item) {
                    var axisModel = ecModel.getComponent(item.axisDim + 'Axis', item.axisIndex);
                    var axisValue = item.value;
                    var seriesDefaultHTML = [];

                    if (!axisModel || axisValue == null) {
                        return;
                    }
L
lang 已提交
351

P
pah100 已提交
352 353 354 355 356 357
                    var valueLabel = axisPointerViewHelper.getValueLabel(
                        axisValue, axisModel.axis, ecModel,
                        item.seriesDataIndices,
                        item.valueLabelOpt
                    );

P
pah100 已提交
358 359 360 361
                    zrUtil.each(item.seriesDataIndices, function (idxItem) {
                        var series = ecModel.getSeriesByIndex(idxItem.seriesIndex);
                        var dataIndex = idxItem.dataIndexInside;
                        var dataParams = series && series.getDataParams(dataIndex);
362 363 364 365 366
                        dataParams.axisDim = item.axisDim;
                        dataParams.axisIndex = item.axisIndex;
                        dataParams.axisType = item.axisType;
                        dataParams.axisId = item.axisId;
                        dataParams.axisValue = axisHelper.getAxisRawValue(axisModel.axis, axisValue);
P
pah100 已提交
367
                        dataParams.axisValueLabel = valueLabel;
368

P
pah100 已提交
369 370 371 372 373
                        if (dataParams) {
                            singleParamsList.push(dataParams);
                            seriesDefaultHTML.push(series.formatTooltip(dataIndex, true));
                        }
                    });
1
100pah 已提交
374

P
pah100 已提交
375 376 377 378
                    // Default tooltip content
                    // FIXME
                    // (1) shold be the first data which has name?
                    // (2) themeRiver, firstDataIndex is array, and first line is unnecessary.
P
pah100 已提交
379
                    var firstLine = valueLabel;
P
pah100 已提交
380 381 382 383
                    singleDefaultHTML.push(
                        (firstLine ? formatUtil.encodeHTML(firstLine) + '<br />' : '')
                        + seriesDefaultHTML.join('<br />')
                    );
L
lang 已提交
384
                });
P
pah100 已提交
385
            }, this);
D
deqingli 已提交
386

P
pah100 已提交
387
            // In most case, the second axis is shown upper than the first one.
P
pah100 已提交
388 389
            singleDefaultHTML.reverse();
            singleDefaultHTML = singleDefaultHTML.join('<br /><br />');
P
pah100 已提交
390 391

            var positionExpr = e.position;
P
pah100 已提交
392 393
            this._showOrMove(singleTooltipModel, function () {
                if (this._updateContentNotChangedOnAxis(dataByCoordSys)) {
394
                    this._updatePosition(
P
pah100 已提交
395
                        singleTooltipModel,
396 397 398
                        positionExpr,
                        point[0], point[1],
                        this._tooltipContent,
P
pah100 已提交
399
                        singleParamsList
400 401 402 403
                    );
                }
                else {
                    this._showTooltipContent(
P
pah100 已提交
404
                        singleTooltipModel, singleDefaultHTML, singleParamsList, Math.random(),
405 406 407 408
                        point[0], point[1], positionExpr
                    );
                }
            });
P
pah100 已提交
409 410 411

            // Do not trigger events here, because this branch only be entered
            // from dispatchAction.
L
lang 已提交
412 413
        },

P
pah100 已提交
414 415 416 417 418 419 420
        _showSeriesItemTooltip: function (e, el, dispatchAction) {
            var ecModel = this._ecModel;
            // Use dataModel in element if possible
            // Used when mouseover on a element like markPoint or edge
            // In which case, the data is not main data in series.
            var seriesIndex = el.seriesIndex;
            var seriesModel = ecModel.getSeriesByIndex(seriesIndex);
L
lang 已提交
421

P
pah100 已提交
422 423 424 425 426
            // For example, graph link.
            var dataModel = el.dataModel || seriesModel;
            var dataIndex = el.dataIndex;
            var dataType = el.dataType;
            var data = dataModel.getData();
L
lang 已提交
427

P
pah100 已提交
428 429 430 431 432 433
            var tooltipModel = buildTooltipModel([
                data.getItemModel(dataIndex),
                dataModel,
                seriesModel && (seriesModel.coordinateSystem || {}).model,
                this._tooltipModel
            ]);
L
lang 已提交
434

P
pah100 已提交
435 436
            var tooltipTrigger = tooltipModel.get('trigger');
            if (tooltipTrigger != null && tooltipTrigger !== 'item') {
437 438 439
                return;
            }

P
pah100 已提交
440 441 442
            var params = dataModel.getDataParams(dataIndex, dataType);
            var defaultHtml = dataModel.formatTooltip(dataIndex, false, dataType);
            var asyncTicket = 'item_' + dataModel.name + '_' + dataIndex;
L
lang 已提交
443

444 445 446 447 448 449
            this._showOrMove(tooltipModel, function () {
                this._showTooltipContent(
                    tooltipModel, defaultHtml, params, asyncTicket,
                    e.offsetX, e.offsetY, e.position, e.target
                );
            });
P
pah100 已提交
450 451 452 453

            // FIXME
            // duplicated showtip if manuallyShowTip is called from dispatchAction.
            dispatchAction({
L
tweak  
lang 已提交
454
                type: 'showTip',
455
                dataIndexInside: dataIndex,
P
pah100 已提交
456 457
                dataIndex: data.getRawIndex(dataIndex),
                seriesIndex: seriesIndex,
L
tweak  
lang 已提交
458 459
                from: this.uid
            });
L
lang 已提交
460 461
        },

P
tweak  
pah100 已提交
462
        _showComponentItemTooltip: function (e, el, dispatchAction) {
P
pah100 已提交
463
            var tooltipOpt = el.tooltip;
464
            if (typeof tooltipOpt === 'string') {
P
pah100 已提交
465
                var content = tooltipOpt;
466
                tooltipOpt = {
P
pah100 已提交
467 468 469
                    content: content,
                    // Fixed formatter
                    formatter: content
470
                };
L
lang 已提交
471
            }
P
pah100 已提交
472 473 474
            var subTooltipModel = new Model(tooltipOpt, this._tooltipModel, this._ecModel);
            var defaultHtml = subTooltipModel.get('content');
            var asyncTicket = Math.random();
L
lang 已提交
475

P
pah100 已提交
476 477 478
            // Do not check whether `trigger` is 'none' here, because `trigger`
            // only works on cooridinate system. In fact, we have not found case
            // that requires setting `trigger` nothing on component yet.
L
lang 已提交
479

480 481 482 483 484 485
            this._showOrMove(subTooltipModel, function () {
                this._showTooltipContent(
                    subTooltipModel, defaultHtml, subTooltipModel.get('formatterParams') || {},
                    asyncTicket, e.offsetX, e.offsetY, e.position, el
                );
            });
P
tweak  
pah100 已提交
486 487 488 489 490 491

            // If not dispatch showTip, tip may be hide triggered by axis.
            dispatchAction({
                type: 'showTip',
                from: this.uid
            });
L
lang 已提交
492 493 494
        },

        _showTooltipContent: function (
P
pah100 已提交
495
            tooltipModel, defaultHtml, params, asyncTicket, x, y, positionExpr, el
L
lang 已提交
496 497 498 499
        ) {
            // Reset ticket
            this._ticket = '';

P
pah100 已提交
500 501 502
            if (!tooltipModel.get('showContent') || !tooltipModel.get('show')) {
                return;
            }
L
lang 已提交
503

P
pah100 已提交
504
            var tooltipContent = this._tooltipContent;
L
lang 已提交
505

P
pah100 已提交
506 507 508 509 510 511 512 513 514 515 516 517 518 519
            var formatter = tooltipModel.get('formatter');
            positionExpr = positionExpr || tooltipModel.get('position');
            var html = defaultHtml;

            if (formatter && typeof formatter === 'string') {
                html = formatUtil.formatTpl(formatter, params, true);
            }
            else if (typeof formatter === 'function') {
                var callback = bind(function (cbTicket, html) {
                    if (cbTicket === this._ticket) {
                        tooltipContent.setContent(html);
                        this._updatePosition(
                            tooltipModel, positionExpr, x, y, tooltipContent, params, el
                        );
L
lang 已提交
520
                    }
P
pah100 已提交
521 522 523 524
                }, this);
                this._ticket = asyncTicket;
                html = formatter(params, asyncTicket, callback);
            }
L
lang 已提交
525

P
pah100 已提交
526
            tooltipContent.setContent(html);
527
            tooltipContent.show(tooltipModel);
L
lang 已提交
528

P
pah100 已提交
529 530 531
            this._updatePosition(
                tooltipModel, positionExpr, x, y, tooltipContent, params, el
            );
L
lang 已提交
532 533 534
        },

        /**
P
pah100 已提交
535 536 537 538 539 540 541 542
         * @param  {string|Function|Array.<number>} positionExpr
         * @param  {number} x Mouse x
         * @param  {number} y Mouse y
         * @param  {boolean} confine Whether confine tooltip content in view rect.
         * @param  {Object|<Array.<Object>} params
         * @param  {module:zrender/Element} el target element
         * @param  {module:echarts/ExtensionAPI} api
         * @return {Array.<number>}
L
lang 已提交
543
         */
P
pah100 已提交
544 545 546
        _updatePosition: function (tooltipModel, positionExpr, x, y, content, params, el) {
            var viewWidth = this._api.getWidth();
            var viewHeight = this._api.getHeight();
547
            positionExpr = positionExpr || tooltipModel.get('position');
P
pah100 已提交
548

P
pah100 已提交
549 550 551
            var contentSize = content.getSize();
            var align = tooltipModel.get('align');
            var vAlign = tooltipModel.get('verticalAlign');
P
pah100 已提交
552 553
            var rect = el && el.getBoundingRect().clone();
            el && rect.applyTransform(el.transform);
P
pah100 已提交
554

P
pah100 已提交
555 556
            if (typeof positionExpr === 'function') {
                // Callback of position can be an array or a string specify the position
P
pah100 已提交
557 558 559 560
                positionExpr = positionExpr([x, y], params, content.el, rect, {
                    viewSize: [viewWidth, viewHeight],
                    contentSize: contentSize.slice()
                });
L
lang 已提交
561 562
            }

P
pah100 已提交
563 564 565
            if (zrUtil.isArray(positionExpr)) {
                x = parsePercent(positionExpr[0], viewWidth);
                y = parsePercent(positionExpr[1], viewHeight);
L
lang 已提交
566
            }
P
pah100 已提交
567 568 569 570 571 572 573 574
            else if (zrUtil.isObject(positionExpr)) {
                positionExpr.width = contentSize[0];
                positionExpr.height = contentSize[1];
                var layoutRect = layoutUtil.getLayoutRect(
                    positionExpr, {width: viewWidth, height: viewHeight}
                );
                x = layoutRect.x;
                y = layoutRect.y;
P
tweak  
pah100 已提交
575 576 577 578
                align = null;
                // When positionExpr is left/top/right/bottom,
                // align and verticalAlign will not work.
                vAlign = null;
P
pah100 已提交
579
            }
P
pah100 已提交
580 581 582 583 584 585 586
            // Specify tooltip position by string 'top' 'bottom' 'left' 'right' around graphic element
            else if (typeof positionExpr === 'string' && el) {
                var pos = calcTooltipPosition(
                    positionExpr, rect, contentSize
                );
                x = pos[0];
                y = pos[1];
L
lang 已提交
587 588
            }
            else {
P
pah100 已提交
589
                var pos = refixTooltipPosition(
P
tweak  
pah100 已提交
590
                    x, y, content.el, viewWidth, viewHeight, align ? null : 20, vAlign ? null : 20
P
pah100 已提交
591 592 593
                );
                x = pos[0];
                y = pos[1];
L
lang 已提交
594 595
            }

P
pah100 已提交
596 597
            align && (x -= isCenterAlign(align) ? contentSize[0] / 2 : align === 'right' ? contentSize[0] : 0);
            vAlign && (y -= isCenterAlign(vAlign) ? contentSize[1] / 2 : vAlign === 'bottom' ? contentSize[1] : 0);
L
lang 已提交
598

P
pah100 已提交
599 600 601 602 603 604
            if (tooltipModel.get('confine')) {
                var pos = confineTooltipPosition(
                    x, y, content.el, viewWidth, viewHeight
                );
                x = pos[0];
                y = pos[1];
L
lang 已提交
605
            }
606

P
pah100 已提交
607 608 609 610 611
            content.moveTo(x, y);
        },

        // FIXME
        // Should we remove this but leave this to user?
P
pah100 已提交
612 613 614 615 616
        _updateContentNotChangedOnAxis: function (dataByCoordSys) {
            var lastCoordSys = this._lastDataByCoordSys;
            var contentNotChanged = !!lastCoordSys
                && lastCoordSys.length === dataByCoordSys.length;

P
pah100 已提交
617
            contentNotChanged && each(lastCoordSys, function (lastItemCoordSys, indexCoordSys) {
P
pah100 已提交
618 619 620 621 622
                var lastDataByAxis = lastItemCoordSys.dataByAxis || {};
                var thisItemCoordSys = dataByCoordSys[indexCoordSys] || {};
                var thisDataByAxis = thisItemCoordSys.dataByAxis || [];
                contentNotChanged &= lastDataByAxis.length === thisDataByAxis.length;

P
pah100 已提交
623
                contentNotChanged && each(lastDataByAxis, function (lastItem, indexAxis) {
P
pah100 已提交
624 625 626 627
                    var thisItem = thisDataByAxis[indexAxis] || {};
                    var lastIndices = lastItem.seriesDataIndices || [];
                    var newIndices = thisItem.seriesDataIndices || [];

P
pah100 已提交
628
                    contentNotChanged &=
P
pah100 已提交
629 630 631 632 633
                        lastItem.value === thisItem.value
                        && lastItem.axisType === thisItem.axisType
                        && lastItem.axisId === thisItem.axisId
                        && lastIndices.length === newIndices.length;

P
pah100 已提交
634
                    contentNotChanged && each(lastIndices, function (lastIdxItem, j) {
P
pah100 已提交
635 636 637 638 639
                        var newIdxItem = newIndices[j];
                        contentNotChanged &=
                            lastIdxItem.seriesIndex === newIdxItem.seriesIndex
                            && lastIdxItem.dataIndex === newIdxItem.dataIndex;
                    });
P
pah100 已提交
640 641 642
                });
            });

P
pah100 已提交
643
            this._lastDataByCoordSys = dataByCoordSys;
P
pah100 已提交
644 645 646 647 648 649 650 651 652 653

            return !!contentNotChanged;
        },

        _hide: function (dispatchAction) {
            // Do not directly hideLater here, because this behavior may be prevented
            // in dispatchAction when showTip is dispatched.

            // FIXME
            // duplicated hideTip if manuallyHideTip is called from dispatchAction.
P
pah100 已提交
654
            this._lastDataByCoordSys = null;
P
pah100 已提交
655
            dispatchAction({
656 657 658
                type: 'hideTip',
                from: this.uid
            });
L
lang 已提交
659 660
        },

L
lang 已提交
661
        dispose: function (ecModel, api) {
L
tweak  
lang 已提交
662 663 664
            if (env.node) {
                return;
            }
665
            this._tooltipContent.hide();
P
pah100 已提交
666
            globalListener.unregister('itemTooltip', api);
L
lang 已提交
667 668
        }
    });
P
pah100 已提交
669 670


P
pah100 已提交
671 672 673 674 675 676 677 678 679
    /**
     * @param {Array.<Object|module:echarts/model/Model>} modelCascade
     * From top to bottom. (the last one should be globalTooltipModel);
     */
    function buildTooltipModel(modelCascade) {
        var resultModel = modelCascade.pop();
        while (modelCascade.length) {
            var tooltipOpt = modelCascade.pop();
            if (tooltipOpt) {
680 681 682
                if (tooltipOpt instanceof Model) {
                    tooltipOpt = tooltipOpt.get('tooltip', true);
                }
P
pah100 已提交
683 684 685 686 687 688 689 690 691 692
                // In each data item tooltip can be simply write:
                // {
                //  value: 10,
                //  tooltip: 'Something you need to know'
                // }
                if (typeof tooltipOpt === 'string') {
                    tooltipOpt = {formatter: tooltipOpt};
                }
                resultModel = new Model(tooltipOpt, resultModel, resultModel.ecModel);
            }
P
pah100 已提交
693
        }
P
pah100 已提交
694
        return resultModel;
P
pah100 已提交
695 696 697 698 699 700 701 702 703 704
    }

    function makeDispatchAction(payload, api) {
        return payload.dispatchAction || zrUtil.bind(api.dispatchAction, api);
    }

    function refixTooltipPosition(x, y, el, viewWidth, viewHeight, gapH, gapV) {
        var width = el.clientWidth;
        var height = el.clientHeight;

P
tweak  
pah100 已提交
705 706 707 708 709 710 711
        if (gapH != null) {
            if (x + width + gapH > viewWidth) {
                x -= width + gapH;
            }
            else {
                x += gapH;
            }
P
pah100 已提交
712
        }
P
tweak  
pah100 已提交
713 714 715 716 717 718 719
        if (gapV != null) {
            if (y + height + gapV > viewHeight) {
                y -= height + gapV;
            }
            else {
                y += gapV;
            }
P
pah100 已提交
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767
        }
        return [x, y];
    }

    function confineTooltipPosition(x, y, el, viewWidth, viewHeight) {
        var width = el.clientWidth;
        var height = el.clientHeight;

        x = Math.min(x + width, viewWidth) - width;
        y = Math.min(y + height, viewHeight) - height;
        x = Math.max(x, 0);
        y = Math.max(y, 0);

        return [x, y];
    }

    function calcTooltipPosition(position, rect, contentSize) {
        var domWidth = contentSize[0];
        var domHeight = contentSize[1];
        var gap = 5;
        var x = 0;
        var y = 0;
        var rectWidth = rect.width;
        var rectHeight = rect.height;
        switch (position) {
            case 'inside':
                x = rect.x + rectWidth / 2 - domWidth / 2;
                y = rect.y + rectHeight / 2 - domHeight / 2;
                break;
            case 'top':
                x = rect.x + rectWidth / 2 - domWidth / 2;
                y = rect.y - domHeight - gap;
                break;
            case 'bottom':
                x = rect.x + rectWidth / 2 - domWidth / 2;
                y = rect.y + rectHeight + gap;
                break;
            case 'left':
                x = rect.x - domWidth - gap;
                y = rect.y + rectHeight / 2 - domHeight / 2;
                break;
            case 'right':
                x = rect.x + rectWidth + gap;
                y = rect.y + rectHeight / 2 - domHeight / 2;
        }
        return [x, y];
    }

P
pah100 已提交
768 769 770 771
    function isCenterAlign(align) {
        return align === 'center' || align === 'middle';
    }

L
lang 已提交
772
});