From 0f17e9c6ccc51c5c59086f4b6c8903ff76615bd2 Mon Sep 17 00:00:00 2001 From: lang Date: Fri, 11 Sep 2015 17:21:44 +0800 Subject: [PATCH] Axis pointer of tooltip --- src/component/tooltip.js | 165 ++++++++++++++++++++++++-- src/component/tooltip/TooltipModel.js | 88 ++++++++++---- src/coord/Axis.js | 27 +++-- src/coord/cartesian/Cartesian.js | 2 +- src/coord/cartesian/Cartesian2D.js | 37 ++++-- src/coord/polar/Polar.js | 2 +- src/model/mixin/areaStyle.js | 10 +- src/scale/Interval.js | 9 ++ src/scale/Ordinal.js | 11 +- test/line.html | 49 ++++++-- test/scatter.html | 14 ++- 11 files changed, 338 insertions(+), 76 deletions(-) diff --git a/src/component/tooltip.js b/src/component/tooltip.js index 378b8cb09..593a2e809 100644 --- a/src/component/tooltip.js +++ b/src/component/tooltip.js @@ -1,7 +1,11 @@ define(function (require) { var TooltipContent = require('./tooltip/TooltipContent'); - var zrUtil = require('zrender/core/util'); + var graphic = require('../util/graphic'); + + function getAxisPointerKey(coordName, axisType) { + return coordName + axisType; + } require('./tooltip/TooltipModel'); @@ -9,15 +13,19 @@ define(function (require) { type: 'tooltip', - init: function (ecModel, api) { - var zr = api.getZr(); + _axisPointers: {}, - zr.on('mousemove', this._mouseMove, this); + init: function (ecModel, api) { + api.getZr().on('mousemove', this._mouseMove, this); this._tooltipContent = new TooltipContent(api.getDom()); }, render: function (tooltipModel, ecModel, api) { + // Reset + this.group.clear(); + this._axisPointers = {}; + this._tooltipModel = tooltipModel; this._ecModel = ecModel; @@ -36,19 +44,21 @@ define(function (require) { return; } - if (!el || !el.data) { + if (trigger === 'item') { - this._tooltipContent.hideLater(tooltipModel.get('hideDelay')); + if (!el || !el.data) { - return; - } + this._tooltipContent.hideLater(tooltipModel.get('hideDelay')); - var dataItem = el.data; + return; + } + + var dataItem = el.data; - if (trigger === 'item') { this._showItemTooltip(dataItem, e); } else { + this._showAxisTooltip(e); } }, @@ -60,7 +70,140 @@ define(function (require) { _showAxisTooltip: function (e) { var ecModel = this._ecModel; - + ecModel.eachSeries(function (seriesModel) { + // Try show the axis pointer + this.group.show(); + + var coordinateSystem = seriesModel.coordinateSystem; + + // If mouse position is not in the grid or polar + var point = [e.offsetX, e.offsetY]; + if (coordinateSystem && ! coordinateSystem.containPoint(point)) { + // Hide axis pointer + this._hideAxisTooltip(); + return; + } + + if (coordinateSystem.type === 'cartesian2d') { + this._showCartesianAxis(coordinateSystem, point); + } + else if (coordinateSystem.type === 'polar') { + this._showPolarAxis(coordinateSystem, point); + } + }, this) + }, + + /** + * Show tooltip on axis of cartesian coordinate + * @param {Object} e + */ + _showCartesianAxis: function (cartesian, point) { + var self = this; + + var tooltipModel = this._tooltipModel; + + var axisPointerModel = tooltipModel.getModel('axisPointer'); + + var cateogryAxis = cartesian.getAxesByScale('ordinal')[0]; + + var axisPointerType = axisPointerModel.get('type'); + + var value = cartesian.pointToData(point); + + // Make sure point is discrete on cateogry axis + if (cateogryAxis) { + point = cartesian.dataToPoint(value); + } + + if (axisPointerType === 'line') { + var axisType = axisPointerModel.get('axis'); + if (axisType === 'auto') { + axisType = (cateogryAxis && cateogryAxis.dim) || 'x'; + } + + var pointerAxis = cartesian.getAxis(axisType); + var otherAxis = cartesian.getOtherAxis(pointerAxis); + var otherExtent = otherAxis.getExtent(); + + moveLine(axisType, point, otherExtent); + } + else if (axisPointerType === 'cross') { + moveLine('x', point, cartesian.getAxis('y').getExtent()); + moveLine('y', point, cartesian.getAxis('x').getExtent()); + } + else if (axisPointerType === 'shadow') { + + } + + function moveLine(axisType, point, otherExtent) { + + var pointerEl = self._getPointerElement(cartesian, axisPointerModel, axisType) + var targetShape; + if (axisType === 'x') { + targetShape = { + x1: point[0], + y1: otherExtent[0], + x2: point[0], + y2: otherExtent[1] + }; + } + else { + targetShape = { + x1: otherExtent[0], + y1: point[1], + x2: otherExtent[1], + y2: point[1] + }; + } + + // pointerEl.animateTo({ + // shape: targetShape + // }, 100, 'cubicOut'); + pointerEl.attr({ + shape: targetShape + }) + } + }, + + /** + * Show tooltip on axis of polar coordinate + * @param {Object} e + */ + _showPolarAxis: function () { + + }, + + /** + * Hide axis tooltip + */ + _hideAxisTooltip: function () { + this.group.hide(); + }, + + _getPointerElement: function (coordSystem, pointerModel, axisType) { + var axisPointers = this._axisPointers; + var key = getAxisPointerKey(coordSystem.name, axisType); + if (axisPointers[key]) { + return axisPointers[key]; + } + + // Create if not exists + var pointerType = pointerModel.get('type'); + var styleModel = pointerModel.getModel(pointerType + 'Style'); + var isShadow = pointerType === 'shadow'; + var style = styleModel[isShadow ? 'getAreaStyle' : 'getLineStyle'](); + + var elementType = axisType === 'radius' + ? (isShadow ? 'Sector' : 'Circle') + : (isShadow ? 'Rect' : 'Line'); + + var el = axisPointers[key] = new graphic[elementType]({ + style: style, + silent: true + }); + + this.group.add(el); + return el; }, /** diff --git a/src/component/tooltip/TooltipModel.js b/src/component/tooltip/TooltipModel.js index fc5fd280e..6fa51526e 100644 --- a/src/component/tooltip/TooltipModel.js +++ b/src/component/tooltip/TooltipModel.js @@ -5,44 +5,88 @@ define(function (require) { type: 'tooltip', defaultOption: { - zlevel: 1, // 一级层叠,频繁变化的tooltip指示器在pc上独立一层 - z: 8, // 二级层叠 + // 一级层叠,频繁变化的tooltip指示器在pc上独立一层 + zlevel: 1, + + // 二级层叠 + z: 8, + show: true, - showContent: true, // tooltip主体内容 - trigger: 'item', // 触发类型,默认数据触发,见下图,可选为:'item' ¦ 'axis' + + // tooltip主体内容 + showContent: true, + + // 触发类型,默认数据触发,见下图,可选为:'item' ¦ 'axis' + trigger: 'item', // If the tooltip follow the mouse position // TODO // followMouse: true, - // position: null // 位置 {Array} | {Function} - // formatter: null // 内容格式器:{string}(Template) ¦ {Function} - islandFormatter: '{a}
{b} : {c}', // 数据孤岛内容格式器 - showDelay: 20, // 显示延迟,添加显示延迟可以避免频繁切换,单位ms - hideDelay: 100, // 隐藏延迟,单位ms - transitionDuration: 0.4, // 动画变换时间,单位s + // 位置 {Array} | {Function} + // position: null + + // 内容格式器:{string}(Template) ¦ {Function} + // formatter: null + // 数据孤岛内容格式器 + islandFormatter: '{a}
{b} : {c}', + + // 显示延迟,添加显示延迟可以避免频繁切换,单位ms + showDelay: 20, + + // 隐藏延迟,单位ms + hideDelay: 100, + + // 动画变换时间,单位s + transitionDuration: 0.4, + 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: { // 直线指示器样式设置 + + // 提示背景颜色,默认为透明度为0.7的黑色 + backgroundColor: 'rgba(0,0,0,0.7)', + + // 提示边框颜色 + borderColor: '#333', + + // 提示边框圆角,单位px,默认为4 + borderRadius: 4, + + // 提示边框线宽,单位px,默认为0(无边框) + borderWidth: 0, + + // 提示内边距,单位px,默认各方向内边距为5, + // 接受数组分别设定上右下左边距,同css + padding: 5, + + // 坐标轴指示器,坐标轴触发有效 + axisPointer: { + // 默认为直线 + // 可选为:'line' | 'shadow' | 'cross' + type: 'line', + + // type 为 line 的时候有效,指定 tooltip line 所在的轴,可选 + // 可选 'x' | 'y' | 'angle' | 'radius' | 'auto' + // 默认 'auto',会选择类型为 cateogry 的轴,对于双数值轴,笛卡尔坐标系会默认选择 x 轴 + // 极坐标系会默认选择 angle 轴 + axis: 'auto', + + // 直线指示器样式设置 + lineStyle: { color: '#48b', width: 2, type: 'solid' }, + crossStyle: { color: '#1e90ff', width: 1, type: 'dashed' }, - shadowStyle: { // 阴影指示器样式设置 - color: 'rgba(150,150,150,0.3)', // 阴影颜色 - width: 'auto', // 阴影大小 + + // 阴影指示器样式设置 + shadowStyle: { + color: 'rgba(150,150,150,0.3)', + width: 'auto', type: 'default' } }, diff --git a/src/coord/Axis.js b/src/coord/Axis.js index 46f19f93d..b9e76ab93 100644 --- a/src/coord/Axis.js +++ b/src/coord/Axis.js @@ -56,30 +56,31 @@ define(function (require) { */ contain: function (coord) { var extent = this._extent; - return coord >= extent[0] && coord <= extent[1]; + var min = Math.min(extent[0], extent[1]); + var max = Math.max(extent[0], extent[1]); + return coord >= min && coord <= max; }, /** - * Get coord extent + * Get coord extent. * @return {Array.} */ getExtent: function () { var ret = this._extent.slice(); - if (this.inverse) { - ret.reverse(); - } + this.inverse && ret.reverse(); + return ret; }, /** * Set coord extent - * @param {number} start - * @param {number} end + * @param {number} min + * @param {number} max */ - setExtent: function (start, end) { + setExtent: function (min, max) { var extent = this._extent; - extent[0] = start; - extent[1] = end; + extent[0] = min; + extent[1] = max; }, /** @@ -105,18 +106,18 @@ define(function (require) { /** * Convert coord to data. Data is the rank if it has a ordinal scale - * @param {number} mapped + * @param {number} coord * @param {boolean} clamp * @return {number} */ - coordToData: function (mapped, clamp) { + coordToData: function (coord, clamp) { var extent = this.getExtent(); if (this.onBand) { fixExtentWithBands(extent, this.scale.count()); } - var t = linearMap(mapped, extent, [0, 1], clamp); + var t = linearMap(coord, extent, [0, 1], clamp); return this.scale.scale(t); }, diff --git a/src/coord/cartesian/Cartesian.js b/src/coord/cartesian/Cartesian.js index 763f40714..d1c6815f1 100644 --- a/src/coord/cartesian/Cartesian.js +++ b/src/coord/cartesian/Cartesian.js @@ -101,7 +101,7 @@ define(function (require) { for (var i = 0; i < dimList.length; i++) { var dim = dimList[i]; - var axis = this._axes[axis]; + var axis = this._axes[dim]; output[dim] = axis[method](input[dim]); } diff --git a/src/coord/cartesian/Cartesian2D.js b/src/coord/cartesian/Cartesian2D.js index 62f39a9bd..ef81df201 100644 --- a/src/coord/cartesian/Cartesian2D.js +++ b/src/coord/cartesian/Cartesian2D.js @@ -17,9 +17,9 @@ define(function(require) { /** * If contain point */ - containPoint: function (x, y) { - return this.getAxis('x').contain(x) - && this.getAxis('y').contain(y); + containPoint: function (point) { + return this.getAxis('x').contain(point[0]) + && this.getAxis('y').contain(point[1]); }, /** @@ -30,17 +30,34 @@ define(function(require) { * `[[10, 10], [20, 20], [30, 30]]` */ dataToPoints: function (data) { - var xAxis = this.getAxis('x'); - var yAxis = this.getAxis('y'); - return data.map(function (dataItem) { - var coord = []; - coord[0] = xAxis.dataToCoord(dataItem.getX(true)); - coord[1] = yAxis.dataToCoord(dataItem.getY(true)); - return coord; + // PENDGING `MUST` Stack ? + return this.dataToPoint([dataItem.getX(true), dataItem.getY(true)]); }, this); }, + /** + * @param {Array.} data + * @return {Array.} + */ + dataToPoint: function (data) { + return [ + this.getAxis('x').dataToCoord(data[0]), + this.getAxis('y').dataToCoord(data[1]) + ]; + }, + + /** + * @param {Array.} point + * @return {Array.} + */ + pointToData: function (point) { + return [ + this.getAxis('x').coordToData(point[0]), + this.getAxis('y').coordToData(point[1]) + ]; + }, + /** * Get other axis * @param {module:echarts/coord/cartesian/Axis2D} axis diff --git a/src/coord/polar/Polar.js b/src/coord/polar/Polar.js index 7a5cdb065..196927795 100644 --- a/src/coord/polar/Polar.js +++ b/src/coord/polar/Polar.js @@ -83,7 +83,7 @@ define(function(require) { */ dataToPoints: function (data) { return data.map(function (dataItem) { - return this.dataToCoord([dataItem.getRadius(true), dataItem.getAngle(true)]); + return this.dataToPoint([dataItem.getRadius(true), dataItem.getAngle(true)]); }, this); }, diff --git a/src/model/mixin/areaStyle.js b/src/model/mixin/areaStyle.js index df674e443..b175b2733 100644 --- a/src/model/mixin/areaStyle.js +++ b/src/model/mixin/areaStyle.js @@ -1,6 +1,6 @@ -define({ - getAreaStyle: function () { - return require('./makeStyleMapper')( +define(function (require) { + return { + getAreaStyle: require('./makeStyleMapper')( [ ['fill', 'color'], ['shadowBlur'], @@ -8,6 +8,6 @@ define({ ['shadowOffsetY'], ['shadowColor'] ] - ); - } + ) + }; }); \ No newline at end of file diff --git a/src/scale/Interval.js b/src/scale/Interval.js index 4e01a6c20..951405360 100644 --- a/src/scale/Interval.js +++ b/src/scale/Interval.js @@ -41,6 +41,15 @@ define(function (require) { type: 'interval', + /** + * If scale extent contain give value + * @param {number} + */ + contain: function (pos) { + var extent = this._extent; + return pos >= extent[0] && pos <= extent[1]; + }, + /** * Normalize value to linear [0, 1] * @param {number} val diff --git a/src/scale/Ordinal.js b/src/scale/Ordinal.js index 311784da4..ce5a5a475 100644 --- a/src/scale/Ordinal.js +++ b/src/scale/Ordinal.js @@ -32,6 +32,15 @@ define(function (require) { type: 'ordinal', + /** + * If scale extent contain give value + * @param {number} + */ + contain: function (rank) { + var extent = this._extent; + return rank >= extent[0] && rank <= extent[1] && this._list[rank] != null; + }, + /** * Normalize given rank to linear [0, 1] * @return {number} [val] @@ -46,7 +55,7 @@ define(function (require) { }, /** - * Scale normalized value + * Scale normalized value to rank * @param {number} val * @return {number} */ diff --git a/test/line.html b/test/line.html index 3836410c9..dacc64360 100644 --- a/test/line.html +++ b/test/line.html @@ -18,7 +18,8 @@ 'echarts', 'echarts/chart/line', 'echarts/component/legend', - 'echarts/component/grid' + 'echarts/component/grid', + 'echarts/component/tooltip' ], function (echarts) { var chart = echarts.init(document.getElementById('main'), null, { @@ -30,44 +31,72 @@ var data2 = []; var data3 = []; - for (var i = 0; i < 20; i++) { + for (var i = 0; i < 10; i++) { xAxisData.push('类目' + i); - data1.push(Math.random()); - data2.push(Math.random()); - data3.push(Math.random()); + data1.push(+Math.random().toFixed(3)); + data2.push(+Math.random().toFixed(3)); + data3.push(+Math.random().toFixed(3)); } + var itemStyle = { + normal: { + borderColor: 'white', + borderWidth: 3, + shadowBlur: 10, + shadowOffsetX: 0, + shadowOffsetY: 5, + shadowColor: 'rgba(0, 0, 0, 0.4)', + lineStyle: { + width: 3, + shadowBlur: 10, + shadowOffsetX: 0, + shadowOffsetY: 5, + shadowColor: 'rgba(0, 0, 0, 0.4)' + } + } + }; + chart.setOption({ legend: { data: ['line', 'line2', 'line3'] }, + tooltip: { + trigger: 'axis' + }, xAxis: { // data: ['类目1', '类目2', '类目3', '类目4', '类目5',] - data: xAxisData + data: xAxisData, + boundaryGap: false }, yAxis: { splitLine: { - show: false + // show: false } }, series: [{ name: 'line', type: 'line', stack: 'all', + symbol: 'circle', symbolSize: 10, - data: data1 + data: data1, + itemStyle: itemStyle }, { name: 'line2', type: 'line', stack: 'all', + symbol: 'circle', symbolSize: 10, - data: data2 + data: data2, + itemStyle: itemStyle }, { name: 'line3', type: 'line', stack: 'all', + symbol: 'circle', symbolSize: 10, - data: data3 + data: data3, + itemStyle: itemStyle }] }); }) diff --git a/test/scatter.html b/test/scatter.html index 425456359..01f6fa5ff 100644 --- a/test/scatter.html +++ b/test/scatter.html @@ -18,7 +18,8 @@ 'echarts', 'echarts/chart/scatter', 'echarts/component/legend', - 'echarts/component/grid' + 'echarts/component/grid', + 'echarts/component/tooltip' ], function (echarts) { var chart = echarts.init(document.getElementById('main'), null, { @@ -39,11 +40,20 @@ legend: { data: ['scatter', 'scatter2', 'scatter3'] }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross' + } + }, xAxis: { type: 'value', splitLine: { show: false - } + }, + min: 0, + max: 15, + splitNumber: 30 }, yAxis: { type: 'value', -- GitLab