1. 准备开始:ECharts能做什么,以及为什么要选它
先说结论:ECharts是目前国内开发圈里上手最快、生态最成熟的开源可视化库之一,底层基于Canvas(也支持SVG渲染),从简单的折线图、柱状图、饼图,到企业级数据可视化大屏里常见的地图、桑基图、热力图,基本都能拿它搞定。它的核心思路也很直白:你只需要给一个DOM容器,再喂一份option配置对象,剩下的绘制、坐标轴计算、动画、图例交互统统交给它处理。
我第一次接触ECharts,是因为当时要做一个销售数据看板。本来打算用FusionCharts这类商业库,但看了看授权费用,还是决定用ECharts。后来真香了,因为它免费、文档全、社区活跃,而且从官网的示例库里几乎能复制出你想要的一切图表。哪怕你是纯后端转前端,只要会写几行JavaScript,花一个下午也能把柱状图、折线图、饼图这三件套跑起来。
这篇文章我打算从一个比较“实在”的角度来写,不讲玄乎的理论,直接带你看懂ECharts的配置结构和常用图表用法,再结合我实际工作中踩过的坑,把那些“文档没写明白,但是不解决就白屏”的地方给你补齐。适合刚接触数据可视化的人,也适合做前端但没细看过ECharts配置项的同学作为速查手册。
2. 三步跑通第一个ECharts图表
2.1 环境准备:CDN引入和npm安装怎么选
ECharts的引入方式主要有两种。如果只是做个Demo、写个静态页面,或者临时在CodePen里测试,直接用CDN引入最快。到官网找到最新版本的CDN链接,在HTML里加一行script标签就行:
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>这里有个小建议:CDN链接里的@5建议锁定大版本,别不加版本号直接引用echarts/dist/echarts.min.js,因为ECharts每年都会发大版本,如果默认指向最新版,过段时间相同代码可能因为API变动突然报错。我自己就遇到过项目里没什么改动,结果图表样式全变了,后来排查半天才发现是CDN版本漂移。
如果是在工程化项目里用,比如Vue3、React或者原生Webpack项目,那更推荐npm安装:
npm install echarts --save然后在你需要用的模块里按需引入:
import * as echarts from 'echarts'; // 或者只引入核心模块 + 需要的图表类型,减小打包体积: // import * as echarts from 'echarts/core';如果你对包体积敏感,可以用echarts/core配合按需注册的方式,只引入柱状图BarChart、折线图LineChart、饼图PieChart这些具体图表,再加上需要的组件。这个方案适合企业级数据可视化项目,首屏加载速度能快不少。不过入门阶段,直接全局引入就够了,毕竟省事,等后面项目做大了再考虑按需压缩。
2.2 写一个最基础的柱状图,吃透option配置结构
很多教程上来就贴一堆配置代码,新手根本看不明白。这里我换个方式,把第一步拆成三个动作:准备容器、初始化实例、设置配置。
首先,页面里要有一个带宽高的DOM容器。ECharts必须在有尺寸的元素里渲染,如果容器没有高度,你会看到一片空白,这是新手最常踩的坑之一:
<div id="demoChart" style="width: 600px; height: 400px;"></div>然后写初始化代码:
const dom = document.getElementById('demoChart'); const myChart = echarts.init(dom);接下来是最核心的部分,写option并设置:
const option = { tooltip: {}, xAxis: { data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] }, yAxis: {}, series: [ { name: '访问量', type: 'bar', data: [120, 200, 150, 80, 70, 110, 130] } ] }; myChart.setOption(option);跑起来之后,你应该能看到一个最简单的、带x轴分类和y轴数值的柱状图。这里面series是最核心的字段,一个图表可以有多个系列,比如柱状图和折线图叠在一起展示多组数据。type决定了图表类型,是柱子还是折线还是扇形,就靠它来切换。xAxis和yAxis分别是两个坐标轴的配置,饼图里这两个字段可以不写,但柱状图、折线图基本离不开它们。
3. 核心细节:坐标轴、图例、提示框的配置技巧
3.1 x轴刻度密密麻麻看不清?试试interval和rotate
柱状图写完后,很多人第一件事就是折腾x轴刻度。网上经常有人搜“echarts折线图x轴刻度”,其实就是x轴的标签显示问题。当数据量很大时,比如一年365天,x轴默认会把所有标签都堆上去,结果字和字叠在一起,完全没法看。
解决办法主要有三个方向。第一个是让ECharts自动跳着显示,也就是默认行为,它自己会计算每两个标签之间隔几个刻度。第二个是手动指定间隔,比如每7天显示一个标签:
xAxis: { type: 'category', data: dateList, axisLabel: { interval: 6, // 0表示全显示,n表示每隔n个刻度显示一个 rotate: 30 // 文字旋转30度,适合长文本 } }第三个是用formatter回调动态处理标签内容,比如只显示月份、或者在某些特殊日期加样式。我个人的习惯是,如果是时间序列数据,x轴用type: 'time'会比type: 'category'聪明很多,它能自动根据数据范围决定标签密度,而且缩放时还会跟着变密度,体验好很多。
还有一点容易被忽视:当标签旋转后,要适当增大图表的上边距或下边距,否则文字会被截断。对应配置是grid里的top、bottom、left、right四个值:
grid: { top: 30, bottom: 40, left: 50, right: 20, containLabel: false }containLabel是个很关键的开关。默认坐标轴标签在grid外,如果grid的left设置小了,y轴数字会被截掉;containLabel设为true后,grid会包含坐标轴刻度文字,布局时更不容易出界。
3.2 tooltip提示框不换行?formatter帮你解决
“echarts tooltip自动换行”这个需求是真的高频。默认情况下tooltip里的内容是一行拼接出来的,如果条目太多或者文字太长,体验很糟糕。好在ECharts给了formatter这么一个入口,既可以写字符串模板,也可以写函数。
用字符串模板时,换行用<br/>:
tooltip: { trigger: 'axis', formatter: '日期:{b}<br/>访问量:{c}' }用函数时更灵活,可以动态拼出带格式的内容。比如我想显示多组数据,每组一行,并且数值后面带上单位,可以这么写:
tooltip: { trigger: 'axis', formatter: function (params) { let res = params[0].axisValue + '<br/>'; params.forEach(function (item) { res += item.seriesName + ':' + item.value + ' 人次<br/>'; }); return res; } }这里params在trigger: 'axis'的折线图、柱状图里会是一个数组,包含同一axisValue下的所有系列数据;而在trigger: 'item'的饼图、地图里则是一个对象。这个区别很重要,我见过有人把params当数组遍历,结果在饼图上报错,就是没搞清trigger的差异。
3.3 柱状图柱子宽度不够,或者想用自定义图片当柱子
默认的柱子宽度有时候会显得很“瘦”,尤其是在x轴分类很多的情况下。直接在series里设置barWidth是最简单的办法:
series: [ { type: 'bar', barWidth: 30, data: [120, 200, 150, 80, 70, 110, 130] } ]如果有多组柱子并排,barWidth会显得僵硬,这时更推荐用barMaxWidth限制最大宽度,或者用百分比比如barWidth: '50%',这样它会根据容器宽度和分类数量自动计算。
还有人问“echarts柱状图柱子可以用自定义图片显示不”,答案是能。有两种玩法。一种是用series的itemStyle给柱体填充图片纹理:
itemStyle: { color: { image: img, // 需要是Image对象或canvas元素 repeat: 'repeat' } }另一种是用graphic组件放置图片标志,或者用markPoint在柱子顶部打图片标记。我实际项目中用过图片纹理的方式,实现了一个“以天为单位”的日历效果,每根柱子是一张缩略图,效果挺惊艳的。要注意的是,image字段一般需要传入一个已经加载完成的Image对象,如果直接传路径字符串在某些场景下会失效,建议用new Image()配合onload回调处理。
4. 折线图、饼图的进阶玩法
4.1 折线图加平滑曲线和渐变面积,样式瞬间提升
折线图是数据可视化里最常用的趋势图,ECharts做折线图的核心是type: 'line'。如果你觉得默认的直角折线太生硬,加一行smooth: true就会变成平滑曲线,观感立刻不一样。
想要更“高级”一点,可以给折线加渐变面积。做法是在series里配置areaStyle,并用echarts.graphic.LinearGradient做渐变色:
series: [ { name: '访问量', type: 'line', smooth: true, data: [120, 200, 150, 80, 70, 110, 130], areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(24, 144, 255, 0.6)' }, { offset: 1, color: 'rgba(24, 144, 255, 0.05)' } ]) } } ]这段渐变的含义是从上到下(参数0,0,0,1表示从y轴顶端到底端)由深到浅,视觉上很轻盈。企业级数据可视化大屏里的潮流折线图,基本都是这个套路。
另外折线图经常需要对比“今年和去年”的数据,这时就要用到双y轴。左边显示绝对值,右边显示同比百分比之类的,配置上要给两个系列分别指定yAxisIndex:
yAxis: [{ type: 'value', name: '访问量' }, { type: 'value', name: '同比变化', splitLine: { show: false } }], series: [ { type: 'line', yAxisIndex: 0, data: [120, 200, 150, 80, 70, 110, 130] }, { type: 'line', yAxisIndex: 1, data: [12.5, -3.2, 8.7, -2.4, -11.3, 6.1, -8.2] } ]注意右侧y轴要关掉splitLine,不然两条轴的水平网格线重叠,画面会很乱。
4.2 饼图labelLine小圆点偏移并不复杂,弄清布局原理就行
饼图是另一个高频场景。基本配置很简单:
series: [ { type: 'pie', radius: ['40%', '70%'], data: [ { value: 1048, name: '搜索引擎' }, { value: 735, name: '直接访问' }, { value: 580, name: '邮件营销' } ] } ]radius写成数组就是环形图,第一个值是内径,第二个值是外径;只写一个数字就是实心饼图。如果想让各类别高度不一致,可以用roseType: 'radius',就变成了南丁格尔玫瑰图,颜值很高,适合展示排名类数据。
关于热搜里提到的“饼图labelLine末尾小圆点偏移”,我猜测大部分人在做饼图时遇到的问题是数据标签和引线重叠,或者小圆点位置不对。这通常是因为饼图的空间不够,或者标签开启后labelLine.length和length2数值不合适。调法如下:
label: { show: true, alignTo: 'edge', formatter: '{b}\n{d}%' }, labelLine: { length: 20, length2: 30, smooth: true }alignTo: 'edge'是个好用的配置,它能让标签统一靠边对齐,配合edgeDistance控制离饼图的距离,扇区多的时候能极大避免文字交叉。如果还是偏移严重,那往往是饼图中心位置被挤压了,可以把center: ['50%', '50%']调一调,或者把radius稍微缩小一点。ECharts 5里还有个labelLayout回调,可以自定义标签碰撞后的位置微调,不过入门阶段,先掌握alignTo和长度调整就够用了。
5. 数据交互:从静态数据到异步请求
5.1 用ajax拉取后端数据,注意setOption的合并逻辑
实际项目中,数据几乎不可能写死在代码里,而是要从后端接口拉取。以前用jQuery时代习惯写$.ajax,现在原生fetch更方便。常见的场景是:页面加载时请求接口,拿到数组后更新图表。
fetch('/api/trend') .then(function (res) { return res.json(); }) .then(function (data) { myChart.setOption({ xAxis: { data: data.dates }, series: [{ data: data.values }] }); });这里有个ECharts的重要特性:setOption默认是“合并”模式,也就是说你传入的option会和之前的配置合并,没传的部分保持不变。这个特性在动态更新数据时很好用,比如只想改series数据,其他配置不动,就可以像上面这样只传xAxis和series。
但如果后端返回的数据结构变化很大,或者你想完全重置图表,就需要在setOption时加第二个参数:
myChart.setOption(option, true);true代表notMerge,意思是放弃旧的option,换成全新的。这个参数在切换图表类型、清空数据时会很有用。我记得有个项目里,切换时间段后tab标签和单位都变了,但图表残留了旧的x轴刻度,折腾了很久才发现是没有传notMerge。
5.2 监听事件做图表联动
ECharts的事件机制也很强大,最常见的需求是点击柱子看到明细数据。监听事件用的是on方法:
myChart.on('click', function (params) { console.log(params.name, params.value); // 这里可以打开弹窗、跳转页面,或者联动另一个图表 });params里会带上当前点击的图表系列、数据项、名称、value等信息。除了click,还有mouseover、mouseout、legendselectchanged等事件。比如实现两个图表联动:点击左边柱状图的某个分类,右边饼图就展示该分类的组成明细。这种玩法在企业级数据可视化大屏里很常见,代码也不复杂,核心就是在一个图表的click事件里setOption另一个图表的数据。
还有一个小技巧:某些场景下需要模拟触发事件,比如默认选中某个扇区触发联动,可以用myChart.dispatchAction:
myChart.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: 2 });这类“动作”API是ECharts的一大特色,不光能高亮,还能控制tooltip显示、图例选中、数据区域缩放等。
6. 进阶场景:中国地图、大屏适配和Vue3踩坑
6.1 ECharts中国地图怎么做?核心是GeoJSON
热搜里“echarts中国地图”排得很靠前,确实很多人第二步就想在地图上画数据。ECharts本身不内置中国地图的GeoJSON数据,需要自己注册。最常用的方式是从china.js或公开的地图GeoJSON仓库里拿到中国各省份的坐标边界数据。
在ECharts里注册地图数据的写法是这样的:
import chinaGeoJson from '@/assets/china.json'; echarts.registerMap('china', chinaGeoJson); const option = { geo: { map: 'china', roam: false, itemStyle: { areaColor: '#f5f5f5' } }, series: [ { type: 'map', map: 'china', data: [ { name: '北京', value: 100 }, { name: '上海', value: 200 } ], label: { show: false } } ] };这里name要和GeoJSON里的properties.name字段完全一致,比如“北京”不能写成“北京市”,否则地图上对应的区域不会渲染数据。处理这种问题的方法是把GeoJSON数据打印出来,直接看properties.name到底叫什么。
地图的价值在于分布展示,比如全国门店数量、各省销售额占比。配合visualMapPiecewise或visualMapContinuous,就能把数值映射成不同颜色,实现“颜色深浅表示大小”的效果,这是大屏里最经典的地图玩法。
6.2 数据可视化大屏怎么适配不同屏幕?
做数据可视化大屏,最麻烦的事适配。大屏的常见尺寸是1920x1080或2560x1440,但用户浏览器窗口可能千奇百怪。我的做法是两套方案组合使用。
第一套方案是外层容器做自适应缩放。用一个wrapper div固定设计稿宽高,然后通过transform的scale整体缩放,按当前窗口和设计稿的比例缩放:
const scaleX = window.innerWidth / 1920; const scaleY = window.innerHeight / 1080; wrapper.style.transform = `scale(${scaleX}, ${scaleY})`;这种方案对所有ECharts图表都一视同仁,缩放过程中canvas会跟着变。缺点是非等比缩放时图表会被拉变形,所以一般用Math.min(scaleX, scaleY)同时保持比例,居中展示。
第二套方案是图表容器用百分比宽度,配合ECharts的resize事件。监听窗口变化:
window.addEventListener('resize', function () { myChart.resize(); });如果有多个图表实例,建议把它们放进一个数组统一循环resize,不然每个都要手动写一遍。
关于“pxtorem对echarts没起到效果 vue3”这个热搜,我想补充一下。现在很多项目用postcss-pxtorem做移动端适配,这种方案是把px转成rem,但ECharts渲染在canvas上,它内部的字体、图形大小都是不受CSS影响的,所以转rem对canvas内容无效。如果大屏需要响应式,千万不要只依赖pxtorem。正确做法是:
- 入口容器用vw/vh适配,或者JS计算scale;
- ECharts内部字体指定用px即可,因为canvas是位图,CSS的rem不会影响它;
- 如果是大屏需要文字跟着缩放,就要在
textStyle里显式设置随resize计算后的fontSize。
6.3 Vue3中使用ECharts:ref容器与生命周期销毁
Vue3里用ECharts,有几个坑需要提前避。
第一个坑是容器还没挂载就初始化。在onMounted里初始化才能确保DOM已经渲染完成;或者用ref拿到DOM节点。写一个标准的组合式API用法:
<template> <div ref="chartRef" style="width: 100%; height: 400px;"></div> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue'; import * as echarts from 'echarts'; const chartRef = ref(null); let myChart = null; onMounted(() => { myChart = echarts.init(chartRef.value); myChart.setOption({ // option配置 }); }); onBeforeUnmount(() => { if (myChart) { myChart.dispose(); myChart = null; } }); </script>第二个坑是响应式数据变化时更新图表。Vue3的响应式系统有时候会让你忘记去手动setOption,其实ECharts实例不会自动感知数据变化,你必须监听数据并手动更新:
watch( () => props.data, (newData) => { myChart.setOption({ series: [{ data: newData }] }); }, { deep: true } );第三个坑是dispose。如果不销毁实例,路由切换后旧图表还在监听事件、占着内存,次数多了页面会卡。Vue3的onBeforeUnmount里调用echarts.dispose()是标配动作。
7. 常见问题与排查技巧实录
我从自己和他人的实践里,把最高频的问题列成一张速查表,方便你遇到问题时直接对号入座。
| 现象 | 常见原因 | 解决办法 |
|---|---|---|
| 打开页面空白,无任何图形 | 容器高度为0或未初始化 | 检查div是否有height;确认echarts.init执行在DOM渲染后 |
| 图表不更新数据 | 忘了调用setOption,或数据是深层嵌套 | 数据变化后手动setOption,必要时deep: true监听 |
| tooltip内容不换行 | formatter里用了\n而不是<br/> | 字符串模板用<br/>,函数用'<br/>'拼接 |
| x轴文字被截断/重叠 | 分类太多,标签太密 | 用interval控制显示间隔,rotate旋转,或改用time轴 |
| 旧图表残留 | setOption默认合并 | 传第二个参数true,强制notMerge |
| 图表不随窗口变化 | 没有监听resize或实例未调用resize | 统一监听window.resize,调用每个实例的resize() |
| 地图上部分区域无颜色 | name和GeoJSON里的名字不一致 | 打印GeoJSON,核对properties.name |
| canvas字体不受px转rem影响 | 样式转换对canvas无效 | 用含canvas内部的textStyle手动设大小,配合JS动态计算 |
补充几个我踩过比较深的坑。
一个是初始化时的echarts.init如果放在不可见的tab或弹窗里,图表可能宽度为0,显示不出来。这时候需要在弹窗打开后调用myChart.resize(),而不是初始化后立即调用,因为DOM可能还没有正常的布局宽度。
另一个是折线图x轴如果设为type: 'time',数据要传[时间戳, 值]的二维数组,不然时间解析会出问题。我之前传过字符串日期,结果x轴显示出的刻度乱掉了,就是因为没转成时间戳。
还有关于柱状图3D的搜索。ECharts本身支持3D效果,但需要在官方扩展中引入echarts-gl。比如用bar3D画三维柱子,或者在map基础上叠加3D柱状图。这类玩法在大屏上很抓眼球,但性能开销比普通图表大,移动端慎用。如果你需要3D效果,建议单独学习echarts-gl的文档,不要和基础图表混在一起写。
8. 写在最后:我个人用ECharts的一点体会
如果你刚接触ECharts,我的建议是别急着背配置项,先把series、xAxis、yAxis、tooltip、legend这几个核心概念吃透,再跑通一个柱状图和折线图,之后大部分图表都能靠“抄示例、改数据”完成了。遇到不会的效果,先去官网示例库搜,再不行去社区搜,这比我见过的任何速成教程都靠谱。
最后再分享一个小技巧:ECharts的option本质上就是一棵配置树,调试时可以把它打印到控制台看结构,也可以把新旧配置做对比找差异。我在复杂项目里全靠这个定位问题,比瞎猜快得多。希望这篇教程能帮你省下一点踩坑时间。