/** * echarts组件:提示框 * * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) * */ define(function (require) { var Base = require('./base'); // 图形依赖 var CrossShape = require('../util/shape/Cross'); var LineShape = require('zrender/shape/Line'); var RectangleShape = require('zrender/shape/Rectangle'); var rectangleInstance = new RectangleShape({}); var ecConfig = require('../config'); // 提示框 ecConfig.tooltip = { zlevel: 1, // 一级层叠,频繁变化的tooltip指示器在pc上独立一层 z: 8, // 二级层叠 show: true, showContent: true, // tooltip主体内容 trigger: 'item', // 触发类型,默认数据触发,见下图,可选为:'item' ¦ 'axis' // position: null // 位置 {Array} | {Function} // formatter: null // 内容格式器:{string}(Template) ¦ {Function} islandFormatter: '{a}
{b} : {c}', // 数据孤岛内容格式器 showDelay: 20, // 显示延迟,添加显示延迟可以避免频繁切换,单位ms hideDelay: 100, // 隐藏延迟,单位ms transitionDuration: 0.4, // 动画变换时间,单位s enterable: false, backgroundColor: 'rgba(0,0,0,0.7)', // 提示背景颜色,默认为透明度为0.7的黑色 borderColor: '#333', // 提示边框颜色 borderRadius: 4, // 提示边框圆角,单位px,默认为4 borderWidth: 0, // 提示边框线宽,单位px,默认为0(无边框) padding: 5, // 提示内边距,单位px,默认各方向内边距为5, // 接受数组分别设定上右下左边距,同css axisPointer: { // 坐标轴指示器,坐标轴触发有效 type: 'line', // 默认为直线,可选为:'line' | 'shadow' | 'cross' lineStyle: { // 直线指示器样式设置 color: '#48b', width: 2, type: 'solid' }, crossStyle: { color: '#1e90ff', width: 1, type: 'dashed' }, shadowStyle: { // 阴影指示器样式设置 color: 'rgba(150,150,150,0.3)', // 阴影颜色 width: 'auto', // 阴影大小 type: 'default' } }, textStyle: { color: '#fff' } }; var ecData = require('../util/ecData'); var zrConfig = require('zrender/config'); var zrEvent = require('zrender/tool/event'); var zrArea = require('zrender/tool/area'); var zrColor = require('zrender/tool/color'); var zrUtil = require('zrender/tool/util'); var zrShapeBase = require('zrender/shape/Base'); /** * 构造函数 * @param {Object} messageCenter echart消息中心 * @param {ZRender} zr zrender实例 * @param {Object} option 提示框参数 * @param {HtmlElement} dom 目标对象 * @param {ECharts} myChart 当前图表实例 */ function Tooltip(ecTheme, messageCenter, zr, option, myChart) { Base.call(this, ecTheme, messageCenter, zr, option, myChart); this.dom = myChart.dom; var self = this; self._onmousemove = function (param) { return self.__onmousemove(param); }; self._onglobalout = function (param) { return self.__onglobalout(param); }; this.zr.on(zrConfig.EVENT.MOUSEMOVE, self._onmousemove); this.zr.on(zrConfig.EVENT.GLOBALOUT, self._onglobalout); self._hide = function (param) { return self.__hide(param); }; self._tryShow = function(param) { return self.__tryShow(param); }; self._refixed = function(param) { return self.__refixed(param); }; self._setContent = function(ticket, res) { return self.__setContent(ticket, res); }; this._tDom = this._tDom || document.createElement('div'); // 避免拖拽时页面选中的尴尬 this._tDom.onselectstart = function() { return false; }; this._tDom.onmouseover = function() { self._mousein = true; }; this._tDom.onmouseout = function() { self._mousein = false; }; this._tDom.className = 'echarts-tooltip'; this._tDom.style.position = 'absolute'; // 不是多余的,别删! this.hasAppend = false; this._axisLineShape && this.zr.delShape(this._axisLineShape.id); this._axisLineShape = new LineShape({ zlevel: this.getZlevelBase(), z: this.getZBase(), invisible: true, hoverable: false }); this.shapeList.push(this._axisLineShape); this.zr.addShape(this._axisLineShape); this._axisShadowShape && this.zr.delShape(this._axisShadowShape.id); this._axisShadowShape = new LineShape({ zlevel: this.getZlevelBase(), z: 1, // grid上,chart下 invisible: true, hoverable: false }); this.shapeList.push(this._axisShadowShape); this.zr.addShape(this._axisShadowShape); this._axisCrossShape && this.zr.delShape(this._axisCrossShape.id); this._axisCrossShape = new CrossShape({ zlevel: this.getZlevelBase(), z: this.getZBase(), invisible: true, hoverable: false }); this.shapeList.push(this._axisCrossShape); this.zr.addShape(this._axisCrossShape); this.showing = false; this.refresh(option); } Tooltip.prototype = { type: ecConfig.COMPONENT_TYPE_TOOLTIP, // 通用样式 _gCssText: 'position:absolute;display:block;border-style:solid;white-space:nowrap;', /** * 根据配置设置dom样式 */ _style: function (opt) { if (!opt) { return ''; } var cssText = []; if (opt.transitionDuration) { var transitionText = 'left ' + opt.transitionDuration + 's,' + 'top ' + opt.transitionDuration + 's'; cssText.push( 'transition:' + transitionText ); cssText.push( '-moz-transition:' + transitionText ); cssText.push( '-webkit-transition:' + transitionText ); cssText.push( '-o-transition:' + transitionText ); } if (opt.backgroundColor) { // for sb ie~ cssText.push( 'background-Color:' + zrColor.toHex( opt.backgroundColor ) ); cssText.push('filter:alpha(opacity=70)'); cssText.push('background-Color:' + opt.backgroundColor); } if (opt.borderWidth != null) { cssText.push('border-width:' + opt.borderWidth + 'px'); } if (opt.borderColor != null) { cssText.push('border-color:' + opt.borderColor); } if (opt.borderRadius != null) { cssText.push( 'border-radius:' + opt.borderRadius + 'px' ); cssText.push( '-moz-border-radius:' + opt.borderRadius + 'px' ); cssText.push( '-webkit-border-radius:' + opt.borderRadius + 'px' ); cssText.push( '-o-border-radius:' + opt.borderRadius + 'px' ); } var textStyle = opt.textStyle; if (textStyle) { textStyle.color && cssText.push('color:' + textStyle.color); textStyle.decoration && cssText.push( 'text-decoration:' + textStyle.decoration ); textStyle.align && cssText.push( 'text-align:' + textStyle.align ); textStyle.fontFamily && cssText.push( 'font-family:' + textStyle.fontFamily ); textStyle.fontSize && cssText.push( 'font-size:' + textStyle.fontSize + 'px' ); textStyle.fontSize && cssText.push( 'line-height:' + Math.round(textStyle.fontSize*3/2) + 'px' ); textStyle.fontStyle && cssText.push( 'font-style:' + textStyle.fontStyle ); textStyle.fontWeight && cssText.push( 'font-weight:' + textStyle.fontWeight ); } var padding = opt.padding; if (padding != null) { padding = this.reformCssArray(padding); cssText.push( 'padding:' + padding[0] + 'px ' + padding[1] + 'px ' + padding[2] + 'px ' + padding[3] + 'px' ); } cssText = cssText.join(';') + ';'; return cssText; }, __hide: function () { this._lastDataIndex = -1; this._lastSeriesIndex = -1; this._lastItemTriggerId = -1; if (this._tDom) { this._tDom.style.display = 'none'; } var needRefresh = false; if (!this._axisLineShape.invisible) { this._axisLineShape.invisible = true; this.zr.modShape(this._axisLineShape.id); needRefresh = true; } if (!this._axisShadowShape.invisible) { this._axisShadowShape.invisible = true; this.zr.modShape(this._axisShadowShape.id); needRefresh = true; } if (!this._axisCrossShape.invisible) { this._axisCrossShape.invisible = true; this.zr.modShape(this._axisCrossShape.id); needRefresh = true; } if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { this.zr.delShape(this._lastTipShape.tipShape); this._lastTipShape = false; this.shapeList.length = 2; } needRefresh && this.zr.refreshNextFrame(); this.showing = false; }, _show: function (position, x, y, specialCssText) { var domHeight = this._tDom.offsetHeight; var domWidth = this._tDom.offsetWidth; if (position) { if (typeof position === 'function') { position = position([x, y]); } if (position instanceof Array) { x = position[0]; y = position[1]; } } if (x + domWidth > this._zrWidth) { // 太靠右 //x = this._zrWidth - domWidth; x -= (domWidth + 40); } if (y + domHeight > this._zrHeight) { // 太靠下 //y = this._zrHeight - domHeight; y -= (domHeight - 20); } if (y < 20) { y = 0; } this._tDom.style.cssText = this._gCssText + this._defaultCssText + (specialCssText ? specialCssText : '') + 'left:' + x + 'px;top:' + y + 'px;'; if (domHeight < 10 || domWidth < 10) { // this._zrWidth - x < 100 || this._zrHeight - y < 100 setTimeout(this._refixed, 20); } this.showing = true; }, __refixed: function () { if (this._tDom) { var cssText = ''; var domHeight = this._tDom.offsetHeight; var domWidth = this._tDom.offsetWidth; if (this._tDom.offsetLeft + domWidth > this._zrWidth) { cssText += 'left:' + (this._zrWidth - domWidth - 20) + 'px;'; } if (this._tDom.offsetTop + domHeight > this._zrHeight) { cssText += 'top:' + (this._zrHeight - domHeight - 10) + 'px;'; } if (cssText !== '') { this._tDom.style.cssText += cssText; } } }, __tryShow: function () { var needShow; var trigger; if (!this._curTarget) { // 坐标轴事件 this._findPolarTrigger() || this._findAxisTrigger(); } else { // 数据项事件 if (this._curTarget._type === 'island' && this.option.tooltip.show) { this._showItemTrigger(); return; } var serie = ecData.get(this._curTarget, 'series'); var data = ecData.get(this._curTarget, 'data'); needShow = this.deepQuery( [data, serie, this.option], 'tooltip.show' ); if (serie == null || data == null || !needShow) { // 不响应tooltip的数据对象延时隐藏 clearTimeout(this._hidingTicket); clearTimeout(this._showingTicket); this._hidingTicket = setTimeout(this._hide, this._hideDelay); } else { trigger = this.deepQuery( [data, serie, this.option], 'tooltip.trigger' ); trigger === 'axis' ? this._showAxisTrigger( serie.xAxisIndex, serie.yAxisIndex, ecData.get(this._curTarget, 'dataIndex') ) : this._showItemTrigger(); } } }, /** * 直角系 */ _findAxisTrigger: function () { if (!this.component.xAxis || !this.component.yAxis) { this._hidingTicket = setTimeout(this._hide, this._hideDelay); return; } var series = this.option.series; var xAxisIndex; var yAxisIndex; for (var i = 0, l = series.length; i < l; i++) { // 找到第一个axis触发tooltip的系列 if (this.deepQuery([series[i], this.option], 'tooltip.trigger') === 'axis') { xAxisIndex = series[i].xAxisIndex || 0; yAxisIndex = series[i].yAxisIndex || 0; if (this.component.xAxis.getAxis(xAxisIndex) && this.component.xAxis.getAxis(xAxisIndex).type === ecConfig.COMPONENT_TYPE_AXIS_CATEGORY ) { // 横轴为类目轴 this._showAxisTrigger(xAxisIndex, yAxisIndex, this._getNearestDataIndex( 'x', this.component.xAxis.getAxis(xAxisIndex) ) ); return; } else if (this.component.yAxis.getAxis(yAxisIndex) && this.component.yAxis.getAxis(yAxisIndex).type === ecConfig.COMPONENT_TYPE_AXIS_CATEGORY ) { // 纵轴为类目轴 this._showAxisTrigger(xAxisIndex, yAxisIndex, this._getNearestDataIndex( 'y', this.component.yAxis.getAxis(yAxisIndex) ) ); return; } else { // 双数值轴 this._showAxisTrigger(xAxisIndex, yAxisIndex, -1); return; } } } if (this.option.tooltip.axisPointer.type === 'cross') { this._showAxisTrigger(-1, -1, -1); } }, /** * 极坐标 */ _findPolarTrigger: function () { if (!this.component.polar) { return false; } var x = zrEvent.getX(this._event); var y = zrEvent.getY(this._event); var polarIndex = this.component.polar.getNearestIndex([x, y]); var valueIndex; if (polarIndex) { valueIndex = polarIndex.valueIndex; polarIndex = polarIndex.polarIndex; } else { polarIndex = -1; } if (polarIndex != -1) { return this._showPolarTrigger(polarIndex, valueIndex); } return false; }, /** * 根据坐标轴事件带的属性获取最近的axisDataIndex */ _getNearestDataIndex: function (direction, categoryAxis) { var dataIndex = -1; var x = zrEvent.getX(this._event); var y = zrEvent.getY(this._event); if (direction === 'x') { // 横轴为类目轴 var left; var right; var xEnd = this.component.grid.getXend(); var curCoord = categoryAxis.getCoordByIndex(dataIndex); while (curCoord < xEnd) { right = curCoord; if (curCoord <= x) { left = curCoord; } else { break; } curCoord = categoryAxis.getCoordByIndex(++dataIndex); } if (dataIndex <= 0) { dataIndex = 0; } else if (x - left <= right - x) { dataIndex -= 1; } else { // 离右边近,看是否为最后一个 if (categoryAxis.getNameByIndex(dataIndex) == null) { dataIndex -= 1; } } return dataIndex; } else { // 纵轴为类目轴 var top; var bottom; var yStart = this.component.grid.getY(); var curCoord = categoryAxis.getCoordByIndex(dataIndex); while (curCoord > yStart) { top = curCoord; if (curCoord >= y) { bottom = curCoord; } else { break; } curCoord = categoryAxis.getCoordByIndex(++dataIndex); } if (dataIndex <= 0) { dataIndex = 0; } else if (y - top >= bottom - y) { dataIndex -= 1; } else { // 离上方边近,看是否为最后一个 if (categoryAxis.getNameByIndex(dataIndex) == null) { dataIndex -= 1; } } return dataIndex; } return -1; }, /** * 直角系 */ _showAxisTrigger: function (xAxisIndex, yAxisIndex, dataIndex) { !this._event.connectTrigger && this.messageCenter.dispatch( ecConfig.EVENT.TOOLTIP_IN_GRID, this._event, null, this.myChart ); if (this.component.xAxis == null || this.component.yAxis == null || xAxisIndex == null || yAxisIndex == null // || dataIndex < 0 ) { // 不响应tooltip的数据对象延时隐藏 clearTimeout(this._hidingTicket); clearTimeout(this._showingTicket); this._hidingTicket = setTimeout(this._hide, this._hideDelay); return; } var series = this.option.series; var seriesArray = []; var seriesIndex = []; var categoryAxis; var formatter; var position; var showContent; var specialCssText = ''; if (this.option.tooltip.trigger === 'axis') { if (!this.option.tooltip.show) { return; } formatter = this.option.tooltip.formatter; position = this.option.tooltip.position; } var axisLayout = xAxisIndex != -1 && this.component.xAxis.getAxis(xAxisIndex).type === ecConfig.COMPONENT_TYPE_AXIS_CATEGORY ? 'xAxis' // 横轴为类目轴,找到所有用这条横轴并且axis触发的系列数据 : yAxisIndex != -1 && this.component.yAxis.getAxis(yAxisIndex).type === ecConfig.COMPONENT_TYPE_AXIS_CATEGORY ? 'yAxis' // 纵轴为类目轴,找到所有用这条纵轴并且axis触发的系列数据 : false; var x; var y; if (axisLayout) { var axisIndex = axisLayout == 'xAxis' ? xAxisIndex : yAxisIndex; categoryAxis = this.component[axisLayout].getAxis(axisIndex); for (var i = 0, l = series.length; i < l; i++) { if (!this._isSelected(series[i].name)) { continue; } if (series[i][axisLayout + 'Index'] === axisIndex && this.deepQuery([series[i], this.option], 'tooltip.trigger') === 'axis' ) { showContent = this.query(series[i], 'tooltip.showContent') || showContent; formatter = this.query(series[i], 'tooltip.formatter') || formatter; position = this.query(series[i], 'tooltip.position') || position; specialCssText += this._style(this.query(series[i], 'tooltip')); if (series[i].stack != null && axisLayout == 'xAxis') { seriesArray.unshift(series[i]); seriesIndex.unshift(i); } else { seriesArray.push(series[i]); seriesIndex.push(i); } } } // 寻找高亮元素 this.messageCenter.dispatch( ecConfig.EVENT.TOOLTIP_HOVER, this._event, { seriesIndex: seriesIndex, dataIndex: dataIndex }, this.myChart ); var rect; if (axisLayout == 'xAxis') { x = this.subPixelOptimize( categoryAxis.getCoordByIndex(dataIndex), this._axisLineWidth ); y = zrEvent.getY(this._event); rect = [ x, this.component.grid.getY(), x, this.component.grid.getYend() ]; } else { x = zrEvent.getX(this._event); y = this.subPixelOptimize( categoryAxis.getCoordByIndex(dataIndex), this._axisLineWidth ); rect = [ this.component.grid.getX(), y, this.component.grid.getXend(), y ]; } this._styleAxisPointer( seriesArray, rect[0], rect[1], rect[2], rect[3], categoryAxis.getGap(), x, y ); } else { // 双数值轴 x = zrEvent.getX(this._event); y = zrEvent.getY(this._event); this._styleAxisPointer( series, this.component.grid.getX(), y, this.component.grid.getXend(), y, 0, x, y ); if (dataIndex >= 0) { this._showItemTrigger(true); } else { clearTimeout(this._hidingTicket); clearTimeout(this._showingTicket); this._tDom.style.display = 'none'; } } if (seriesArray.length > 0) { // 复位item trigger和axis trigger间短距离来回变换时的不响应 this._lastItemTriggerId = -1; // 相同dataIndex seriesIndex时不再触发内容更新 if (this._lastDataIndex != dataIndex || this._lastSeriesIndex != seriesIndex[0]) { this._lastDataIndex = dataIndex; this._lastSeriesIndex = seriesIndex[0]; var data; var value; if (typeof formatter === 'function') { var params = []; for (var i = 0, l = seriesArray.length; i < l; i++) { data = seriesArray[i].data[dataIndex]; value = this.getDataFromOption(data, '-'); params.push({ seriesIndex: seriesIndex[i], seriesName: seriesArray[i].name || '', series: seriesArray[i], dataIndex: dataIndex, data: data, name: categoryAxis.getNameByIndex(dataIndex), value: value, // 向下兼容 0: seriesArray[i].name || '', 1: categoryAxis.getNameByIndex(dataIndex), 2: value, 3: data }); } this._curTicket = 'axis:' + dataIndex; this._tDom.innerHTML = formatter.call( this.myChart, params, this._curTicket, this._setContent ); } else if (typeof formatter === 'string') { this._curTicket = NaN; formatter = formatter.replace('{a}','{a0}') .replace('{b}','{b0}') .replace('{c}','{c0}'); for (var i = 0, l = seriesArray.length; i < l; i++) { formatter = formatter.replace( '{a' + i + '}', this._encodeHTML(seriesArray[i].name || '') ); formatter = formatter.replace( '{b' + i + '}', this._encodeHTML(categoryAxis.getNameByIndex(dataIndex)) ); data = seriesArray[i].data[dataIndex]; data = this.getDataFromOption(data, '-'); formatter = formatter.replace( '{c' + i + '}', data instanceof Array ? data : this.numAddCommas(data) ); } this._tDom.innerHTML = formatter; } else { this._curTicket = NaN; formatter = this._encodeHTML( categoryAxis.getNameByIndex(dataIndex) ); for (var i = 0, l = seriesArray.length; i < l; i++) { formatter += '
' + this._encodeHTML(seriesArray[i].name || '') + ' : '; data = seriesArray[i].data[dataIndex]; data = this.getDataFromOption(data, '-'); formatter += data instanceof Array ? data : this.numAddCommas(data); } this._tDom.innerHTML = formatter; } } // don't modify, just false, showContent == undefined == true if (showContent === false || !this.option.tooltip.showContent) { // 只用tooltip的行为,不显示主体 return; } if (!this.hasAppend) { this._tDom.style.left = this._zrWidth / 2 + 'px'; this._tDom.style.top = this._zrHeight / 2 + 'px'; this.dom.firstChild.appendChild(this._tDom); this.hasAppend = true; } this._show(position, x + 10, y + 10, specialCssText); } }, /** * 极坐标 */ _showPolarTrigger: function (polarIndex, dataIndex) { if (this.component.polar == null || polarIndex == null || dataIndex == null || dataIndex < 0 ) { return false; } var series = this.option.series; var seriesArray = []; var seriesIndex = []; var formatter; var position; var showContent; var specialCssText = ''; if (this.option.tooltip.trigger === 'axis') { if (!this.option.tooltip.show) { return false; } formatter = this.option.tooltip.formatter; position = this.option.tooltip.position; } var indicatorName = this.option.polar[polarIndex].indicator[dataIndex].text; // 找到所有用这个极坐标并且axis触发的系列数据 for (var i = 0, l = series.length; i < l; i++) { if (!this._isSelected(series[i].name)) { continue; } if (series[i].polarIndex === polarIndex && this.deepQuery([series[i], this.option], 'tooltip.trigger') === 'axis' ) { showContent = this.query(series[i], 'tooltip.showContent') || showContent; formatter = this.query(series[i], 'tooltip.formatter') || formatter; position = this.query(series[i], 'tooltip.position') || position; specialCssText += this._style(this.query(series[i], 'tooltip')); seriesArray.push(series[i]); seriesIndex.push(i); } } if (seriesArray.length > 0) { var polarData; var data; var value; var params = []; for (var i = 0, l = seriesArray.length; i < l; i++) { polarData = seriesArray[i].data; for (var j = 0, k = polarData.length; j < k; j++) { data = polarData[j]; if (!this._isSelected(data.name)) { continue; } data = data != null ? data : {name:'', value: {dataIndex:'-'}}; value = this.getDataFromOption(data.value[dataIndex]); params.push({ seriesIndex: seriesIndex[i], seriesName: seriesArray[i].name || '', series: seriesArray[i], dataIndex: dataIndex, data: data, name: data.name, indicator: indicatorName, value: value, // 向下兼容 0: seriesArray[i].name || '', 1: data.name, 2: value, 3: indicatorName }); } } if (params.length <= 0) { return; } // 复位item trigger和axis trigger间短距离来回变换时的不响应 this._lastItemTriggerId = -1; // 相同dataIndex seriesIndex时不再触发内容更新 if (this._lastDataIndex != dataIndex || this._lastSeriesIndex != seriesIndex[0]) { this._lastDataIndex = dataIndex; this._lastSeriesIndex = seriesIndex[0]; if (typeof formatter === 'function') { this._curTicket = 'axis:' + dataIndex; this._tDom.innerHTML = formatter.call( this.myChart, params, this._curTicket, this._setContent ); } else if (typeof formatter === 'string') { formatter = formatter.replace('{a}','{a0}') .replace('{b}','{b0}') .replace('{c}','{c0}') .replace('{d}','{d0}'); for (var i = 0, l = params.length; i < l; i++) { formatter = formatter.replace( '{a' + i + '}', this._encodeHTML(params[i].seriesName) ); formatter = formatter.replace( '{b' + i + '}', this._encodeHTML(params[i].name) ); formatter = formatter.replace( '{c' + i + '}', this.numAddCommas(params[i].value) ); formatter = formatter.replace( '{d' + i + '}', this._encodeHTML(params[i].indicator) ); } this._tDom.innerHTML = formatter; } else { formatter = this._encodeHTML(params[0].name) + '
' + this._encodeHTML(params[0].indicator) + ' : ' + this.numAddCommas(params[0].value); for (var i = 1, l = params.length; i < l; i++) { formatter += '
' + this._encodeHTML(params[i].name) + '
'; formatter += this._encodeHTML(params[i].indicator) + ' : ' + this.numAddCommas(params[i].value); } this._tDom.innerHTML = formatter; } } // don't modify, just false, showContent == undefined == true if (showContent === false || !this.option.tooltip.showContent) { // 只用tooltip的行为,不显示主体 return; } if (!this.hasAppend) { this._tDom.style.left = this._zrWidth / 2 + 'px'; this._tDom.style.top = this._zrHeight / 2 + 'px'; this.dom.firstChild.appendChild(this._tDom); this.hasAppend = true; } this._show( position, zrEvent.getX(this._event), zrEvent.getY(this._event), specialCssText ); return true; } }, /** * @parma {boolean} axisTrigger */ _showItemTrigger: function (axisTrigger) { if (!this._curTarget) { return; } var serie = ecData.get(this._curTarget, 'series'); var seriesIndex = ecData.get(this._curTarget, 'seriesIndex'); var data = ecData.get(this._curTarget, 'data'); var dataIndex = ecData.get(this._curTarget, 'dataIndex'); var name = ecData.get(this._curTarget, 'name'); var value = ecData.get(this._curTarget, 'value'); var special = ecData.get(this._curTarget, 'special'); var special2 = ecData.get(this._curTarget, 'special2'); var queryTarget = [data, serie, this.option]; // 从低优先级往上找到trigger为item的formatter和样式 var formatter; var position; var showContent; var specialCssText = ''; if (this._curTarget._type != 'island') { // 全局 var trigger = axisTrigger ? 'axis' : 'item'; if (this.option.tooltip.trigger === trigger) { formatter = this.option.tooltip.formatter; position = this.option.tooltip.position; } // 系列 if (this.query(serie, 'tooltip.trigger') === trigger) { showContent = this.query(serie, 'tooltip.showContent') || showContent; formatter = this.query(serie, 'tooltip.formatter') || formatter; position = this.query(serie, 'tooltip.position') || position; specialCssText += this._style(this.query(serie, 'tooltip')); } // 数据项 showContent = this.query(data, 'tooltip.showContent') || showContent; formatter = this.query(data, 'tooltip.formatter') || formatter; position = this.query(data, 'tooltip.position') || position; specialCssText += this._style(this.query(data, 'tooltip')); } else { this._lastItemTriggerId = NaN; showContent = this.deepQuery(queryTarget, 'tooltip.showContent'); formatter = this.deepQuery(queryTarget, 'tooltip.islandFormatter'); position = this.deepQuery(queryTarget, 'tooltip.islandPosition'); } // 复位item trigger和axis trigger间短距离来回变换时的不响应 this._lastDataIndex = -1; this._lastSeriesIndex = -1; // 相同dataIndex seriesIndex时不再触发内容更新 if (this._lastItemTriggerId !== this._curTarget.id) { this._lastItemTriggerId = this._curTarget.id; if (typeof formatter === 'function') { this._curTicket = (serie.name || '') + ':' + dataIndex; this._tDom.innerHTML = formatter.call( this.myChart, { seriesIndex: seriesIndex, seriesName: serie.name || '', series: serie, dataIndex: dataIndex, data: data, name: name, value: value, percent: special, // 饼图 indicator: special, // 雷达图 value2: special2, indicator2: special2, // 向下兼容 0: serie.name || '', 1: name, 2: value, 3: special, 4: special2, 5: data, 6: seriesIndex, 7: dataIndex }, this._curTicket, this._setContent ); } else if (typeof formatter === 'string') { this._curTicket = NaN; formatter = formatter.replace('{a}', '{a0}') .replace('{b}', '{b0}') .replace('{c}', '{c0}'); formatter = formatter.replace('{a0}', this._encodeHTML(serie.name || '')) .replace('{b0}', this._encodeHTML(name)) .replace( '{c0}', value instanceof Array ? value : this.numAddCommas(value) ); formatter = formatter.replace('{d}', '{d0}') .replace('{d0}', special || ''); formatter = formatter.replace('{e}', '{e0}') .replace( '{e0}', ecData.get(this._curTarget, 'special2') || '' ); this._tDom.innerHTML = formatter; } else { this._curTicket = NaN; if (serie.type === ecConfig.CHART_TYPE_RADAR && special) { this._tDom.innerHTML = this._itemFormatter.radar.call( this, serie, name, value, special ); } // chord 处理暂时跟 force 一样 // else if (serie.type === ecConfig.CHART_TYPE_CHORD) { // this._tDom.innerHTML = this._itemFormatter.chord.call( // this, serie, name, value, special, special2 // ); // } else if (serie.type === ecConfig.CHART_TYPE_EVENTRIVER) { this._tDom.innerHTML = this._itemFormatter.eventRiver.call( this, serie, name, value, data ); } else { this._tDom.innerHTML = '' + (serie.name != null ? (this._encodeHTML(serie.name) + '
') : '') + (name === '' ? '' : (this._encodeHTML(name) + ' : ')) + (value instanceof Array ? value : this.numAddCommas(value)); } } } var x = zrEvent.getX(this._event); var y = zrEvent.getY(this._event); if (this.deepQuery(queryTarget, 'tooltip.axisPointer.show') && this.component.grid ) { this._styleAxisPointer( [serie], this.component.grid.getX(), y, this.component.grid.getXend(), y, 0, x, y ); } else { this._hide(); } // don't modify, just false, showContent == undefined == true if (showContent === false || !this.option.tooltip.showContent) { // 只用tooltip的行为,不显示主体 return; } if (!this.hasAppend) { this._tDom.style.left = this._zrWidth / 2 + 'px'; this._tDom.style.top = this._zrHeight / 2 + 'px'; this.dom.firstChild.appendChild(this._tDom); this.hasAppend = true; } this._show(position, x + 20, y - 20, specialCssText); }, _itemFormatter: { radar: function(serie, name, value, indicator){ var html = ''; html += this._encodeHTML(name === '' ? (serie.name || '') : name); html += html === '' ? '' : '
'; for (var i = 0 ; i < indicator.length; i ++) { html += this._encodeHTML(indicator[i].text) + ' : ' + this.numAddCommas(value[i]) + '
'; } return html; }, chord: function(serie, name, value, special, special2) { if (special2 == null) { // 外环上 return this._encodeHTML(name) + ' (' + this.numAddCommas(value) + ')'; } else { var name1 = this._encodeHTML(name); var name2 = this._encodeHTML(special); // 内部弦上 return '' + (serie.name != null ? (this._encodeHTML(serie.name) + '
') : '') + name1 + ' -> ' + name2 + ' (' + this.numAddCommas(value) + ')' + '
' + name2 + ' -> ' + name1 + ' (' + this.numAddCommas(special2) + ')'; } }, eventRiver: function(serie, name, value, data) { var html = ''; html += this._encodeHTML(serie.name === '' ? '' : (serie.name + ' : ') ); html += this._encodeHTML(name); html += html === '' ? '' : '
'; data = data.evolution; for (var i = 0, l = data.length; i < l; i++) { html += '
'; if (!data[i].detail) { continue; } if (data[i].detail.img) { html += ''; } html += '
' + data[i].time + '
'; html += ''; html += data[i].detail.text + '
'; html += '
'; } return html; } }, /** * 设置坐标轴指示器样式 */ _styleAxisPointer: function (seriesArray, xStart, yStart, xEnd, yEnd, gap, x, y) { if (seriesArray.length > 0) { var queryTarget; var curType; var axisPointer = this.option.tooltip.axisPointer; var pointType = axisPointer.type; var style = { line: {}, cross: {}, shadow: {} }; for (var pType in style) { style[pType].color = axisPointer[pType + 'Style'].color; style[pType].width = axisPointer[pType + 'Style'].width; style[pType].type = axisPointer[pType + 'Style'].type; } for (var i = 0, l = seriesArray.length; i < l; i++) { //if (this.deepQuery([seriesArray[i], this.option], 'tooltip.trigger') === 'axis') { queryTarget = seriesArray[i]; curType = this.query(queryTarget, 'tooltip.axisPointer.type'); pointType = curType || pointType; if (curType) { style[curType].color = this.query( queryTarget, 'tooltip.axisPointer.' + curType + 'Style.color' ) || style[curType].color; style[curType].width = this.query( queryTarget, 'tooltip.axisPointer.' + curType + 'Style.width' ) || style[curType].width; style[curType].type = this.query( queryTarget, 'tooltip.axisPointer.' + curType + 'Style.type' ) || style[curType].type; } //} } if (pointType === 'line') { var lineWidth = style.line.width; var isVertical = xStart == xEnd; this._axisLineShape.style = { xStart: isVertical ? this.subPixelOptimize(xStart, lineWidth) : xStart, yStart: isVertical ? yStart : this.subPixelOptimize(yStart, lineWidth), xEnd: isVertical ? this.subPixelOptimize(xEnd, lineWidth) : xEnd, yEnd: isVertical ? yEnd : this.subPixelOptimize(yEnd, lineWidth), strokeColor: style.line.color, lineWidth: lineWidth, lineType: style.line.type }; this._axisLineShape.invisible = false; this.zr.modShape(this._axisLineShape.id); } else if (pointType === 'cross') { var crossWidth = style.cross.width; this._axisCrossShape.style = { brushType: 'stroke', rect: this.component.grid.getArea(), x: this.subPixelOptimize(x, crossWidth), y: this.subPixelOptimize(y, crossWidth), text: ('( ' + this.component.xAxis.getAxis(0).getValueFromCoord(x) + ' , ' + this.component.yAxis.getAxis(0).getValueFromCoord(y) + ' )' ).replace(' , ', ' ').replace(' , ', ' '), textPosition: 'specific', strokeColor: style.cross.color, lineWidth: crossWidth, lineType: style.cross.type }; if (this.component.grid.getXend() - x > 100) { // 右侧有空间 this._axisCrossShape.style.textAlign = 'left'; this._axisCrossShape.style.textX = x + 10; } else { this._axisCrossShape.style.textAlign = 'right'; this._axisCrossShape.style.textX = x - 10; } if (y - this.component.grid.getY() > 50) { // 上方有空间 this._axisCrossShape.style.textBaseline = 'bottom'; this._axisCrossShape.style.textY = y - 10; } else { this._axisCrossShape.style.textBaseline = 'top'; this._axisCrossShape.style.textY = y + 10; } this._axisCrossShape.invisible = false; this.zr.modShape(this._axisCrossShape.id); } else if (pointType === 'shadow') { if (style.shadow.width == null || style.shadow.width === 'auto' || isNaN(style.shadow.width) ) { style.shadow.width = gap; } if (xStart === xEnd) { // 纵向 if (Math.abs(this.component.grid.getX() - xStart) < 2) { // 最左边 style.shadow.width /= 2; xStart = xEnd = xEnd + style.shadow.width / 2; } else if (Math.abs(this.component.grid.getXend() - xStart) < 2) { // 最右边 style.shadow.width /= 2; xStart = xEnd = xEnd - style.shadow.width / 2; } } else if (yStart === yEnd) { // 横向 if (Math.abs(this.component.grid.getY() - yStart) < 2) { // 最上边 style.shadow.width /= 2; yStart = yEnd = yEnd + style.shadow.width / 2; } else if (Math.abs(this.component.grid.getYend() - yStart) < 2) { // 最右边 style.shadow.width /= 2; yStart = yEnd = yEnd - style.shadow.width / 2; } } this._axisShadowShape.style = { xStart: xStart, yStart: yStart, xEnd: xEnd, yEnd: yEnd, strokeColor: style.shadow.color, lineWidth: style.shadow.width }; this._axisShadowShape.invisible = false; this.zr.modShape(this._axisShadowShape.id); } this.zr.refreshNextFrame(); } }, __onmousemove: function (param) { clearTimeout(this._hidingTicket); clearTimeout(this._showingTicket); if (this._mousein && this._enterable) { return; } var target = param.target; var mx = zrEvent.getX(param.event); var my = zrEvent.getY(param.event); if (!target) { // 判断是否落到直角系里,axis触发的tooltip this._curTarget = false; this._event = param.event; // this._event._target = this._event.target || this._event.toElement; this._event.zrenderX = mx; this._event.zrenderY = my; if (this._needAxisTrigger && this.component.grid && zrArea.isInside(rectangleInstance, this.component.grid.getArea(), mx, my) ) { this._showingTicket = setTimeout(this._tryShow, this._showDelay); } else if (this._needAxisTrigger && this.component.polar && this.component.polar.isInside([mx, my]) != -1 ) { this._showingTicket = setTimeout(this._tryShow, this._showDelay); } else { !this._event.connectTrigger && this.messageCenter.dispatch( ecConfig.EVENT.TOOLTIP_OUT_GRID, this._event, null, this.myChart ); this._hidingTicket = setTimeout(this._hide, this._hideDelay); } } else { this._curTarget = target; this._event = param.event; // this._event._target = this._event.target || this._event.toElement; this._event.zrenderX = mx; this._event.zrenderY = my; var polarIndex; if (this._needAxisTrigger && this.component.polar && (polarIndex = this.component.polar.isInside([mx, my])) != -1 ) { // 看用这个polar的系列数据是否是axis触发,如果是设置_curTarget为nul var series = this.option.series; for (var i = 0, l = series.length; i < l; i++) { if (series[i].polarIndex === polarIndex && this.deepQuery( [series[i], this.option], 'tooltip.trigger' ) === 'axis' ) { this._curTarget = null; break; } } } this._showingTicket = setTimeout(this._tryShow, this._showDelay); } }, /** * zrender事件响应:鼠标离开绘图区域 */ __onglobalout: function () { clearTimeout(this._hidingTicket); clearTimeout(this._showingTicket); this._hidingTicket = setTimeout(this._hide, this._hideDelay); }, /** * 异步回调填充内容 */ __setContent: function (ticket, content) { if (!this._tDom) { return; } if (ticket === this._curTicket) { this._tDom.innerHTML = content; } setTimeout(this._refixed, 20); }, ontooltipHover: function (param, tipShape) { if (!this._lastTipShape // 不存在或者存在但dataIndex发生变化才需要重绘 || (this._lastTipShape && this._lastTipShape.dataIndex != param.dataIndex) ) { if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { this.zr.delShape(this._lastTipShape.tipShape); this.shapeList.length = 2; } for (var i = 0, l = tipShape.length; i < l; i++) { tipShape[i].zlevel = this.getZlevelBase(); tipShape[i].z = this.getZBase(); tipShape[i].style = zrShapeBase.prototype.getHighlightStyle( tipShape[i].style, tipShape[i].highlightStyle ); tipShape[i].draggable = false; tipShape[i].hoverable = false; tipShape[i].clickable = false; tipShape[i].ondragend = null; tipShape[i].ondragover = null; tipShape[i].ondrop = null; this.shapeList.push(tipShape[i]); this.zr.addShape(tipShape[i]); } this._lastTipShape = { dataIndex: param.dataIndex, tipShape: tipShape }; } }, ondragend: function () { this._hide(); }, /** * 图例选择 */ onlegendSelected: function (param) { this._selectedMap = param.selected; }, _setSelectedMap: function () { if (this.component.legend) { this._selectedMap = zrUtil.clone(this.component.legend.getSelectedMap()); } else { this._selectedMap = {}; } }, _isSelected: function (itemName) { if (this._selectedMap[itemName] != null) { return this._selectedMap[itemName]; } else { return true; // 没在legend里定义的都为true啊~ } }, /** * 模拟tooltip hover方法 * {object} params 参数 * {seriesIndex: 0, seriesName:'', dataInex:0} line、bar、scatter、k、radar * {seriesIndex: 0, seriesName:'', name:''} map、pie、chord */ showTip: function (params) { if (!params) { return; } var seriesIndex; var series = this.option.series; if (params.seriesIndex != null) { seriesIndex = params.seriesIndex; } else { var seriesName = params.seriesName; for (var i = 0, l = series.length; i < l; i++) { if (series[i].name === seriesName) { seriesIndex = i; break; } } } var serie = series[seriesIndex]; if (serie == null) { return; } var chart = this.myChart.chart[serie.type]; var isAxisTrigger = this.deepQuery( [serie, this.option], 'tooltip.trigger' ) === 'axis'; if (!chart) { return; } if (isAxisTrigger) { // axis trigger var dataIndex = params.dataIndex; switch (chart.type) { case ecConfig.CHART_TYPE_LINE : case ecConfig.CHART_TYPE_BAR : case ecConfig.CHART_TYPE_K : case ecConfig.CHART_TYPE_TREEMAP : if (this.component.xAxis == null || this.component.yAxis == null || serie.data.length <= dataIndex ) { return; } var xAxisIndex = serie.xAxisIndex || 0; var yAxisIndex = serie.yAxisIndex || 0; if (this.component.xAxis.getAxis(xAxisIndex).type === ecConfig.COMPONENT_TYPE_AXIS_CATEGORY ) { // 横轴是类目 this._event = { zrenderX: this.component.xAxis.getAxis(xAxisIndex) .getCoordByIndex(dataIndex), zrenderY: this.component.grid.getY() + (this.component.grid.getYend() - this.component.grid.getY() ) / 4 }; } else { // 纵轴是类目 this._event = { zrenderX: this.component.grid.getX() + (this.component.grid.getXend() - this.component.grid.getX() ) / 4, zrenderY: this.component.yAxis.getAxis(yAxisIndex) .getCoordByIndex(dataIndex) }; } this._showAxisTrigger( xAxisIndex, yAxisIndex, dataIndex ); break; case ecConfig.CHART_TYPE_RADAR : if (this.component.polar == null || serie.data[0].value.length <= dataIndex ) { return; } var polarIndex = serie.polarIndex || 0; var vector = this.component.polar.getVector( polarIndex, dataIndex, 'max' ); this._event = { zrenderX: vector[0], zrenderY: vector[1] }; this._showPolarTrigger( polarIndex, dataIndex ); break; } } else { // item trigger var shapeList = chart.shapeList; var x; var y; switch (chart.type) { case ecConfig.CHART_TYPE_LINE : case ecConfig.CHART_TYPE_BAR : case ecConfig.CHART_TYPE_K : case ecConfig.CHART_TYPE_TREEMAP : case ecConfig.CHART_TYPE_SCATTER : var dataIndex = params.dataIndex; for (var i = 0, l = shapeList.length; i < l; i++) { if (shapeList[i]._mark == null && ecData.get(shapeList[i], 'seriesIndex') == seriesIndex && ecData.get(shapeList[i], 'dataIndex') == dataIndex ) { this._curTarget = shapeList[i]; x = shapeList[i].style.x; y = chart.type != ecConfig.CHART_TYPE_K ? shapeList[i].style.y : shapeList[i].style.y[0]; break; } } break; case ecConfig.CHART_TYPE_RADAR : var dataIndex = params.dataIndex; for (var i = 0, l = shapeList.length; i < l; i++) { if (shapeList[i].type === 'polygon' && ecData.get(shapeList[i], 'seriesIndex') == seriesIndex && ecData.get(shapeList[i], 'dataIndex') == dataIndex ) { this._curTarget = shapeList[i]; var vector = this.component.polar.getCenter( serie.polarIndex || 0 ); x = vector[0]; y = vector[1]; break; } } break; case ecConfig.CHART_TYPE_PIE : var name = params.name; for (var i = 0, l = shapeList.length; i < l; i++) { if (shapeList[i].type === 'sector' && ecData.get(shapeList[i], 'seriesIndex') == seriesIndex && ecData.get(shapeList[i], 'name') == name ) { this._curTarget = shapeList[i]; var style = this._curTarget.style; var midAngle = (style.startAngle + style.endAngle) / 2 * Math.PI / 180; x = this._curTarget.style.x + Math.cos(midAngle) * style.r / 1.5; y = this._curTarget.style.y - Math.sin(midAngle) * style.r / 1.5; break; } } break; case ecConfig.CHART_TYPE_MAP : var name = params.name; var mapType = serie.mapType; for (var i = 0, l = shapeList.length; i < l; i++) { if (shapeList[i].type === 'text' && shapeList[i]._mapType === mapType && shapeList[i].style._name === name ) { this._curTarget = shapeList[i]; x = this._curTarget.style.x + this._curTarget.position[0]; y = this._curTarget.style.y + this._curTarget.position[1]; break; } } break; case ecConfig.CHART_TYPE_CHORD: var name = params.name; for (var i = 0, l = shapeList.length; i < l; i++) { if (shapeList[i].type === 'sector' && ecData.get(shapeList[i], 'name') == name ) { this._curTarget = shapeList[i]; var style = this._curTarget.style; var midAngle = (style.startAngle + style.endAngle) / 2 * Math.PI / 180; x = this._curTarget.style.x + Math.cos(midAngle) * (style.r - 2); y = this._curTarget.style.y - Math.sin(midAngle) * (style.r - 2); this.zr.trigger( zrConfig.EVENT.MOUSEMOVE, { zrenderX: x, zrenderY: y } ); return; } } break; case ecConfig.CHART_TYPE_FORCE: var name = params.name; for (var i = 0, l = shapeList.length; i < l; i++) { if (shapeList[i].type === 'circle' && ecData.get(shapeList[i], 'name') == name ) { this._curTarget = shapeList[i]; x = this._curTarget.position[0]; y = this._curTarget.position[1]; break; } } break; } if (x != null && y != null) { this._event = { zrenderX: x, zrenderY: y }; this.zr.addHoverShape(this._curTarget); this.zr.refreshHover(); this._showItemTrigger(); } } }, /** * 关闭,公开接口 */ hideTip: function () { this._hide(); }, /** * 刷新 */ refresh: function (newOption) { // this._selectedMap; // this._defaultCssText; // css样式缓存 // this._needAxisTrigger; // 坐标轴触发 // this._curTarget; // this._event; // this._curTicket; // 异步回调标识,用来区分多个请求 // 缓存一些高宽数据 this._zrHeight = this.zr.getHeight(); this._zrWidth = this.zr.getWidth(); if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { this.zr.delShape(this._lastTipShape.tipShape); } this._lastTipShape = false; this.shapeList.length = 2; this._lastDataIndex = -1; this._lastSeriesIndex = -1; this._lastItemTriggerId = -1; if (newOption) { this.option = newOption; this.option.tooltip = this.reformOption(this.option.tooltip); this.option.tooltip.textStyle = zrUtil.merge( this.option.tooltip.textStyle, this.ecTheme.textStyle ); this._needAxisTrigger = false; if (this.option.tooltip.trigger === 'axis') { this._needAxisTrigger = true; } var series = this.option.series; for (var i = 0, l = series.length; i < l; i++) { if (this.query(series[i], 'tooltip.trigger') === 'axis') { this._needAxisTrigger = true; break; } } // this._hidingTicket; // this._showingTicket; this._showDelay = this.option.tooltip.showDelay; // 显示延迟 this._hideDelay = this.option.tooltip.hideDelay; // 隐藏延迟 this._defaultCssText = this._style(this.option.tooltip); this._setSelectedMap(); this._axisLineWidth = this.option.tooltip.axisPointer.lineStyle.width; this._enterable = this.option.tooltip.enterable; } if (this.showing) { var self = this; setTimeout(function(){ self.zr.trigger(zrConfig.EVENT.MOUSEMOVE, self.zr.handler._event); },50); } }, /** * 释放后实例不可用,重载基类方法 */ onbeforDispose: function () { if (this._lastTipShape && this._lastTipShape.tipShape.length > 0) { this.zr.delShape(this._lastTipShape.tipShape); } clearTimeout(this._hidingTicket); clearTimeout(this._showingTicket); this.zr.un(zrConfig.EVENT.MOUSEMOVE, this._onmousemove); this.zr.un(zrConfig.EVENT.GLOBALOUT, this._onglobalout); if (this.hasAppend && !!this.dom.firstChild) { this.dom.firstChild.removeChild(this._tDom); } this._tDom = null; }, /** * html转码的方法 */ _encodeHTML: function (source) { return String(source) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } }; zrUtil.inherits(Tooltip, Base); require('../component').define('tooltip', Tooltip); return Tooltip; });