一、阴影指示器层级修改
如下代码, 开启了 X 坐标轴 的 阴影指示器, 但是触发 指示器 时, 会发现阴影是覆盖在图表上面的, 因为很明显的可以看到图表的颜色会变浅很多
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
// 开启坐标指示器
axisPointer: {
show: true,
type: 'shadow',
}
},
yAxis: { type: 'value' },
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
// 设置高亮时颜色保存一致
emphasis: {
itemStyle: {
color: 'inherit'
}
}
}
]
};
上面代码效果图如下:

这里可以通过修改 阴影指示器 的 z 属性配置, 来设置 指示器 的层级, 使 阴影指示器 的 层级 低于图表即可, 修改代码如下:
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
// 开启坐标指示器
axisPointer: {
+ z: -1,
show: true,
type: 'shadow',
+ label: { show: false }, // 将 label 隐藏了, 这里的 label 样式会有点问题
}
},
yAxis: { type: 'value' },
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
// 设置高亮时颜色保存一致
emphasis: {
itemStyle: {
color: 'inherit'
}
}
}
]
};
调整后效果如下:

二、如何让坐标轴指示器不触发图表的「高亮」「tooltip」?
下面代码中, 开启了 tooltip、阴影指示器 同时设置了高亮颜色为 red, 那么当我们触发 阴影指示器 (鼠标没有 hover 图表上)的情况下, 依然会触发图表高亮、以及 tooltip
option = {
tooltip: { trigger: 'item' },
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
// 开启坐标指示器
axisPointer: {
z: -1,
show: true,
type: 'shadow',
label: { show: false }, // 将 label 隐藏了, 这里的 label 样式会有点问
}
},
yAxis: { type: 'value' },
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
emphasis: {
itemStyle: {
color: 'red'
}
}
}
]
};
上面代码效果图如下:

上图可以发现, 我们鼠标 hover 在图表外面, 依然触发了 tooltip, 要知道配置中 tooltip.trigger 可是 item; 这种情况下我们可以通过设置 axisPointer.triggerTooltip 为 false, 让 指示器 不触发 tooltip
option = {
tooltip: { trigger: 'item' },
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
// 开启坐标指示器
axisPointer: {
z: -1,
show: true,
type: 'shadow',
+ triggerTooltip: false,
label: { show: false }, // 将 label 隐藏了, 这里的 label 样式会有点问
}
},
yAxis: { type: 'value' },
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
emphasis: {
itemStyle: {
color: 'red'
}
}
}
]
};
上面代码效果图如下: 在图表之外触发 指示器 不会再显示 tooltip 了, 只有 hover 到图表上面才会触发 tooltip

同样的, 如果在触发 指示器 时不想触发高亮, 我们又该怎么处理呢? 这里处理起来就相对麻烦点, 整体思路: 创建 2 套相同的坐标体系, 一个作为 series 的坐标轴, 一个则隐藏用于创建 坐标轴指示器, 这样 坐标轴指示器就 就不会触发 series 高亮了, 因为它们不是处于同一个坐标系
+ const xAxisBase = {
+ type: 'category',
+ data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
+ }
option = {
tooltip: { trigger: 'item' },
+ xAxis: [
+ {...xAxisBase},
+ // 隐藏坐标系, 用于设置坐标指示器
+ {
+ ...xAxisBase,
+ show: false,
+ axisPointer: {
+ z: -1,
+ show: true,
+ type: 'shadow',
+ label: { show: false }, // 将 label 隐藏了, 这里的 label 样式会有点问
+ }
+ },
+ ],
+ yAxis: [
+ { type: 'value' },
+ { type: 'value' },
+ ],
series: [
{
+ // 设置 serie 使用的是那个坐标系
+ xAxisIndex: 0,
+ yAxisIndex: 0,
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
emphasis: {
itemStyle: {
color: 'red'
}
}
}
]
};
上面代码效果图如下: 通过设置 2 个坐标系, serie 和 指示器 处于不同 坐标系, 它们之间就不会产生影响

三、圆环饼图鼠标悬停时显示文本
很常见的一个饼图交互方式, 官网也有完整的一个 DEMO, 思路就是: 默认情况下隐藏 饼图 的 label 在鼠标 hover 时会触发 饼图 的高亮, 这时将 label 显示出来即可
option = {
series: [
{
type: 'pie',
radius: ['40%', '70%'],
// 默认隐藏
label: {
show: false,
position: 'center'
},
// 高亮则显示
emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: 'bold'
}
},
data: [
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
]
}
]
};
上面代码效果图如下:

四、 主动触发 Tooltip
如下代码, 通过 Echarts 实例中 dispatchAction 方法, 来触发 action, 其中 tyep 表示 action 类型, seriesIndex dataIndex 分别表示要触发哪个系列、哪条数据的 action, 代码中通过 setTimeout 实现了 tooltip 的轮播
// 轮播器: 轮播每条数据的 tooltip
const carousel = () => {
// 1. 计算器
carousel.total = (carousel.total || 0) + 1
// 2. 手动显示 tooltip
myChart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: carousel.total % 4,
});
// 3. 清除定时器
clearTimeout(carousel.timer)
// 4. 重新启动定时器
carousel.timer = setTimeout(carousel, 1000);
}
// 执行轮播
carousel()
option = {
tooltip: {
trigger: 'item'
},
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'],
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
data: [
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
]
}
]
};
上面代码效果图如下:

同时我们也可以通过 dispatchAction 来取消 tooltip
// 取消所有高亮设置
echart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
seriesName: '告警处置占比',
});
关于更多 dispatchAction 的使用细节, 请参考 官网
五、为坐标轴绑定事件
在实际项目中, 我们可能需要点击 坐标轴 的 label 跳转到新页面, 这时我们可能会为 Echarts 实例绑定 click 事件, 但事实上会发现点击 坐标轴 的 label 是无法触发 click 事件的, 如下代码: 为 为 Echarts 实例绑定了 click 事件, 会发现点击事件并不能生效
myChart.on('click', params => {
if(params.targetType === 'axisLabel'){
alert(`点击 label: ${params.value}`)
}
})
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value',
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}
]
};
那么我们是不是可以认为 Echarts 是无法为坐标轴绑定事件呢? 其实不是的, 坐标轴也是能够触发事件的, 只是默认是关闭的, 我们需要通过 yAxis.triggerEvent 配置来启用它
myChart.on('click', params => {
if(params.targetType === 'axisLabel'){
alert(`点击 label: ${params.value}`)
}
})
option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
+ triggerEvent: true,
type: 'value',
},
series: [
{
data: [150, 230, 224, 218, 135, 147, 260],
type: 'line'
}
]
};
六、坐标轴 label 超出显示省略号「hover」 展示全部信息
这里我们需要完成两个功能: 第一个就是需要针对长字符串进行一个截取, 字符串截取方法如下: 方法的主要功能就是针对字符串、以及给定的长度, 对字符串进行处理, 如果字符串过长则会进行截取, 并显示 ...
/**
* 字符串截取
* @param {String} str 源字符串
* @param {Number} len 截取长度, 双字节字符按 2 个长度进行计算
* @param {String} [ellipsis='...'] 尾缀
* @returns 截取后的字符串 + 尾缀
*/
const sliceStr = ({
str,
len,
padStr,
ellipsis = '...',
}) => {
// 1. 空值处理
if (!str || !len) { return '' }
// 2. 计算字符串长度: 双字节按 2 个长度计算
const strLen = str.replace(/[^\x00-\xff]/gi, 'aa').length
// 3. 判断字符串是否超出指定的长度
if (strLen <= len) {
return padStr ? `${''.padStart(len - strLen, padStr)}${str}` : str
}
// 4. 循环每个字符进行算计
let newStr = ''
let num = ellipsis.length
for (let i = 0, lens = str.length; i < lens; i++) {
num += str.charCodeAt(i) > 255 ? 2 : 1
if (num > len) {
break
} else {
newStr = str.substring(0, i + 1)
}
}
return `${newStr}${ellipsis}`
}
sliceStr({ str: '索非亚皮革制品', len: 7 }) // 索非...
在 Echarts 中使用 sliceStr 来截取 label 长度: 如下代码针对 y 坐标轴的 label 进行了截取
/**
* 字符串截取
* @param {String} str 源字符串
* @param {Number} len 截取长度, 双字节字符按 2 个长度进行计算
* @param {String} [ellipsis='...'] 尾缀
* @returns 截取后的字符串 + 尾缀
*/
const sliceStr = ({
str,
len,
padStr,
ellipsis = '...',
}) => {
// 1. 空值处理
if (!str || !len) { return '' }
// 2. 计算字符串长度: 双字节按 2 个长度计算
const strLen = str.replace(/[^\x00-\xff]/gi, 'aa').length
// 3. 判断字符串是否超出指定的长度
if (strLen <= len) {
return padStr ? `${''.padStart(len - strLen, padStr)}${str}` : str
}
// 4. 循环每个字符进行算计
let newStr = ''
let num = ellipsis.length
for (let i = 0, lens = str.length; i < lens; i++) {
num += str.charCodeAt(i) > 255 ? 2 : 1
if (num > len) {
break
} else {
newStr = str.substring(0, i + 1)
}
}
return `${newStr}${ellipsis}`
}
+ option = {
+ xAxis: {
+ type: 'value',
+ boundaryGap: [0, 0.01]
+ },
+ yAxis: {
+ type: 'category',
+ data: ['前端算法', '即时通讯技术', '前端可视化'],
+ // 关键代码: 针对 y 坐标轴 label 进行格式化
+ axisLabel: {
+ formatter: (value) => sliceStr({ str: value, len: 7 })
+ }
+ },
+ series: [
+ {
+ name: '2012',
+ type: 'bar',
+ data: [19325, 23438, 31000]
+ }
+ ]
+ };
上面代码效果图如下:

接下来我们要做的就是实现: 鼠标 hover 展示, 完整信息, 这里思路其实也很简单:
- 为
Echarts绑定 鼠标移入、移出事件 - 鼠标
hover到坐标轴label上时, 判断展示文本和实际值是否相等, 不等则展示完整信息
/**
* 字符串截取
* @param {String} str 源字符串
* @param {Number} len 截取长度, 双字节字符按 2 个长度进行计算
* @param {String} [ellipsis='...'] 尾缀
* @returns 截取后的字符串 + 尾缀
*/
const sliceStr = ({ str, len, padStr, ellipsis = '...' }) => {
// 1. 空值处理
if (!str || !len) {
return '';
}
// 2. 计算字符串长度: 双字节按 2 个长度计算
const strLen = str.replace(/[^\x00-\xff]/gi, 'aa').length;
// 3. 判断字符串是否超出指定的长度
if (strLen <= len) {
return padStr ? `${''.padStart(len - strLen, padStr)}${str}` : str;
}
// 4. 循环每个字符进行算计
let newStr = '';
let num = ellipsis.length;
for (let i = 0, lens = str.length; i < lens; i++) {
num += str.charCodeAt(i) > 255 ? 2 : 1;
if (num > len) {
break;
} else {
newStr = str.substring(0, i + 1);
}
}
return `${newStr}${ellipsis}`;
};
+ // 添加 dom 用于放置 tip 信息
+ const tip = document.createElement('div')
+ document.body.appendChild(tip)
+
+ // 鼠标移入事件
+ myChart.on('mouseover', ({ event, value, componentType, targetType }) => {
+ const {
+ target,
+ event: { clientX, clientY }
+ } = event;
+
+ // 判断条件: 在 y 轴触发 && 触发类型为坐标轴 label && 展示内容和 value 不一致
+ if (componentType === 'yAxis' && targetType === 'axisLabel' && target?.parent?.style?.text !== value) {
+ // 显示 tip
+ tip.innerHTML = `
+ <div style="
+ position: fixed;
+ top: ${clientY}px;
+ left: ${clientX + 40}px;
+ padding: 5px;
+ border-radius: 4px;
+ background: #f5f5f5;
+ ">
+ ${value}
+ </div>
+ `
+ }
+ });
+
+ // 鼠标移出事件
+ myChart.on('mouseout', ({ event, value, componentType, targetType }) => {
+ // 判断条件: 在 y 轴触发 && 触发类型为坐标轴 label
+ if (componentType === 'yAxis' && targetType === 'axisLabel') {
+ tip.innerHTML = '' // 移除 tip
+ }
+ });
option = {
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
triggerEvent: true,
data: ['前端算法', '即时通讯技术', '前端可视化'],
// 关键代码: 针对 y 坐标轴 label 进行格式化
axisLabel: {
formatter: (value) => sliceStr({ str: value, len: 7 })
}
},
series: [
{
name: '2012',
type: 'bar',
data: [19325, 23438, 31000]
}
]
};
上面代码效果图如下:

七、修改鼠标手势 ✋🏻
在实现各种交互过程中, 我们可能需要修改鼠标的 手势, 这里可以直接通过 Echarts 实例方法 getDom() 找到图表的容器, 然后通过操作 DOM 方式修改 canvas 的样式
const [canvasDom] = myChart.getDom().getElementsByTagName('canvas')
// 设置鼠标手势
if (canvasDom) {
canvasDom.style.cursor = 'default'
}
八、显示 tooltip 时, 不触发高亮状态
下面代码 tooltip 触发模式为 axis, 这种情况下, 触发 Tooltip 时会同时触图表的高亮
option = {
tooltip: {
trigger: 'axis',
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
emphasis: {
itemStyle: {
color: 'red'
}
}
}
]
};
上面代码效果图如下:

那么, 如果想要在触发 tooltip 时不触发 高亮, 在 hover 到图表上再触发 高亮, 我们又该怎么处理呢? 实现思路有:
- 在
toltip.formatter中手动取消掉图表的高亮 - 为
Echarts绑定mousemove事件, 当鼠标hover到图表上, 手动触发高亮
+ // 单独触发高亮
+ myChart.on('mousemove', ({ dataIndex, seriesIndex }) => {
+ myChart.dispatchAction({
+ dataIndex,
+ seriesIndex,
+ type: 'highlight',
+ })
+ })
option = {
tooltip: {
trigger: 'axis',
+ formatter: (args) => {
+ myChart.dispatchAction({
+ type: 'downplay',
+ })
+ return '1111'
+ },
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
emphasis: {
itemStyle: {
color: 'red'
}
}
}
]
};
上面代码效果图如下:

九、简单地图绘制
这里主要是介绍下 Echarts 5.3 版本之后, 如何注册地图组件, 这里之所以要单独进行讲解, 主要是因为 5.3 版本之后为了减少打包的体积, 官方在核心模块中移除了地图数据管理的方法 getMap 和 registerMap, 而目前网上找的案例大多是旧版本的实现方式, 导致很多 DEMO 在新版本都跑不起来, 下面是在 React 上的一个精简的演示代码, 更多细节可以翻看 官方文档
import { MapChart } from 'echarts/charts'
import * as echarts from 'echarts/core'
// 1. 注册地图组件
echarts.use([MapChart])
const render = useCallback(async () => {
// 2. 读取地图数据
const geo = await request({
url: '地图数据',
})
// 3. 注册地图数据: HK 地图名(自定义)、geo 地理数据
echarts.registerMap('HK', geo)
// 4. 设置 options
echarts.setOption({
visualMap: {
min: 800,
max: 50000,
text: ['High', 'Low'],
realtime: false,
calculable: true,
inRange: {
color: ['lightskyblue', 'yellow', 'orangered']
}
},
series: [
{
name: '香港18区人口密度',
type: 'map',
map: 'HK',
label: {
show: true
},
data: [
{ name: '中西区', value: 20057.34 },
{ name: '湾仔', value: 15477.48 },
],
}
]
})
}, [])
- 官方文档: registerMap 和 getMap 方法需要在引入地图组件后才能使用
- 地图数据获取: DataV.GeoAtlas地理小工具系列
十、自定义导出图片
Echarts 内置有 导出图片, 数据视图, 动态类型切换、数据区域缩放、重置 等工具, 可通过 toolbox 进行配置; 但是有个限制就是, 工具箱是集成在图表上的, 而且样式定制起来相对也比较麻烦, 同时实际开发中更多的可能需要我们进行手动导出、批量导出
自定义导出图片其实也简单 Ecahrts 实例中提供了方法 getDataURL(), 可获取到图片的 DataUrl
// 获取图表 DataUrl
const imgDataUrl = .getDataURL({
pixelRatio: 2,
backgroundColor: '#fff',
})
最后可以使用第三方库 file-saver 来导出图片
+ import { saveAs } from 'file-saver'
// 获取图表 DataUrl
const imgDataUrl = .getDataURL({
pixelRatio: 2,
backgroundColor: '#fff',
})
+ // 导出
+ saveAs(imgData, 'export.png')
如果您不想使用第三方库, 可参考下面代码进行导出
/**
* 手动下载
* @param {Object} echarts Echarts 实例
* @param {String} name 下载的图片名称(不带后缀)
* @param {String} [type = png] 下载文件类型 png, jpeg
* @param {Number} [pixelRatio = 2] 导出的图片分辨率比例
* @param {String} [backgroundColor = #fff] 导出图片背景色
* @returns
*/
export const download = (echarts) => ({
name,
type = 'png',
pixelRatio = 2,
backgroundColor = '#fff',
}) => {
// 获取画布图表地址信息
const imgUrl = echarts.getDataURL({
type,
pixelRatio,
backgroundColor,
})
// 如果浏览器支持 msSaveOrOpenBlob 方法(也就是使用IE浏览器的时候)那么调用该方法去下载图片
if (window.navigator.msSaveOrOpenBlob) {
// 1. 截取 base64 的数据内容 (掉前面的描述信息, 类似这样的一段: data:image/png;base64) 并解码为2进制数据
const bstr = atob(imgUrl.split(',')[1])
// 2. 获取解码后的二进制数据的长度, 用于后面创建二进制数据容器
let bstrLength = bstr.length
// 3. 创建一个 Uint8Array 类型的数组以存放二进制数据
const u8arr = new Uint8Array(bstrLength)
// 4. 将二进制数据存入 Uint8Array 类型的数组中
while (bstrLength--) {
u8arr[bstrLength] = bstr.charCodeAt(bstrLength)
}
// 5. 创建 blob 对象
const blob = new Blob([u8arr])
// 6. 调用浏览器的方法, 调起 IE 的下载流程
window.navigator.msSaveOrOpenBlob(blob, `${name}.${type}`)
} else {
// 类 chrome 浏览器创建一个 a 标签并使用 a 标签的 href 属性下载
const aElement = document.createElement('a')
aElement.setAttribute('href', imgUrl)
aElement.setAttribute('download', `${name}.${type}`)
aElement.dispatchEvent(new MouseEvent('click'))
}
}
大家好, 我是墨渊君, 如果您喜欢我的文章可以:
- 关注公众号: 「昆仑虚F2E」获取最新文章。
- GitHub: github.com/MoYuanJun
- 个人网站(昆仑虚, 虽然现在没啥东西): www.kunlunxu.cc