一、ECharts 是什么
ECharts 是百度开源(现由 Apache 基金会管理)的 JavaScript 可视化图表库,提供丰富的图表类型和强大的交互能力,适配 PC 和移动端。
与其他图表库对比
| 特性 | ECharts | Chart.js | D3.js | Highcharts |
|---|
| 上手难度 | 低 | 低 | 高 | 低 |
| 图表类型 | 50+ 种 | 8 种 | 任意(需自行组合) | 30+ 种 |
| 大数据量 | 优秀(WebGL 加速) | 一般 | 优秀 | 一般 |
| 交互 | 内置丰富 | 基础 | 需自行实现 | 内置 |
| 中国地图 | ✅ 内置 | ❌ | ❌ | ❌ |
| 商业许可 | Apache 2.0(免费) | MIT(免费) | BSD(免费) | 付费 |
| 移动端 | 触摸交互优化 | 支持 | 需自行优化 | 支持 |
二、快速开始
安装
CDN
1
| <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
渲染第一个图表
1
| <div id="chart" style="width: 600px; height: 400px;"></div>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import * as echarts from 'echarts';
const chart = echarts.init(document.getElementById('chart'));
const option = { title: { text: '销售额趋势' }, tooltip: {}, xAxis: { data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], }, yAxis: {}, series: [{ name: '销售额', type: 'line', data: [120, 200, 150, 80, 70, 110, 130], }], };
chart.setOption(option);
|
三、核心概念
实例(Instance)
echarts.init(dom, theme?, opts?) 创建实例。一个 DOM 节点只能创建一个实例。
1 2 3 4 5
| const chart = echarts.init( document.getElementById('chart'), 'dark', { renderer: 'canvas' } );
|
option(配置项)
所有图表配置集中在一个 option 对象中。核心结构:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const option = { title: {}, tooltip: {}, legend: {}, grid: {}, xAxis: {}, yAxis: {}, series: [], color: [], toolbox: {}, dataZoom: [], visualMap: {}, };
|
setOption 是增量更新的——ECharts 会自动 diff 新旧 option,只更新变化部分:
1 2 3 4 5
| chart.setOption({ series: [{ data: [1, 2, 3] }] });
chart.setOption({ series: [{ data: [4, 5, 6] }] });
|
系列(Series)
series 是 option 的核心,每个系列对应一种图表类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| series: [ { name: '销售额', type: 'line', data: [120, 200], smooth: true, areaStyle: {}, }, { name: '利润', type: 'bar', data: [30, 50], }, ]
|
四、常用图表类型
折线图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| option = { xAxis: { type: 'category', data: ['Jan', 'Feb', 'Mar'] }, yAxis: { type: 'value' }, series: [{ type: 'line', data: [100, 200, 150], smooth: true, areaStyle: { opacity: 0.3 }, markLine: { data: [{ type: 'average', name: '平均值' }] }, markPoint: { data: [ { type: 'max', name: '最大值' }, { type: 'min', name: '最小值' }, ], }, }], };
|
柱状图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| option = { xAxis: { data: ['A', 'B', 'C'] }, yAxis: {}, series: [{ type: 'bar', data: [30, 80, 45], itemStyle: { borderRadius: [4, 4, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#409eff' }, { offset: 1, color: '#79bbff' }, ]), }, }], };
|
多系列柱状图(分组)
1 2 3 4 5 6 7 8
| option = { xAxis: { data: ['A', 'B', 'C'] }, yAxis: {}, series: [ { name: '2023', type: 'bar', data: [30, 80, 45] }, { name: '2024', type: 'bar', data: [40, 90, 55] }, ], };
|
堆叠柱状图
1 2 3 4 5 6 7 8
| option = { xAxis: { data: ['A', 'B', 'C'] }, yAxis: {}, series: [ { name: '基础', type: 'bar', stack: 'total', data: [30, 40, 50] }, { name: '高级', type: 'bar', stack: 'total', data: [20, 30, 25] }, ], };
|
饼图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| option = { tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, series: [{ type: 'pie', radius: ['40%', '70%'], center: ['50%', '50%'], data: [ { value: 1048, name: '搜索引擎' }, { value: 735, name: '直接访问' }, { value: 580, name: '邮件营销' }, { value: 484, name: '联盟广告' }, { value: 300, name: '视频广告' }, ], roseType: 'area', label: { formatter: '{b}\n{d}%' }, emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,.3)' }, }, }], };
|
散点图
1 2 3 4 5 6 7 8 9 10 11 12
| option = { xAxis: {}, yAxis: {}, series: [{ type: 'scatter', data: [ [10, 20], [15, 35], [20, 30], [25, 45], [30, 40], [35, 55], [40, 50], [45, 65], [50, 60], ], symbolSize: (val) => val[1] / 5, }], };
|
雷达图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| option = { radar: { indicator: [ { name: '销售', max: 100 }, { name: '管理', max: 100 }, { name: '技术', max: 100 }, { name: '客服', max: 100 }, { name: '研发', max: 100 }, ], }, series: [{ type: 'radar', data: [{ value: [90, 70, 80, 60, 95], name: '能力评估' }], }], };
|
五、交互与事件
图表事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| chart.on('click', (params) => { console.log(params.name); console.log(params.value); console.log(params.seriesName); });
chart.on('mouseover', (params) => {});
chart.on('globalout', () => {});
chart.on('click', (params) => { if (params.componentType === 'series') { fetch(`/api/detail/${params.name}`).then(res => { chart.setOption({ series: [{ data: res.data }] }); }); } });
|
图表的联动
多个图表实例可以通过事件实现联动:
1 2 3 4 5 6 7 8 9 10
| const chart1 = echarts.init(dom1); const chart2 = echarts.init(dom2);
chart1.on('click', (params) => { chart2.dispatchAction({ type: 'highlight', seriesIndex: 0, dataIndex: params.dataIndex, }); });
|
工具栏
1 2 3 4 5 6 7 8 9 10 11 12 13
| option = { toolbox: { feature: { saveAsImage: {}, dataView: {}, restore: {}, dataZoom: {}, magicType: { type: ['line', 'bar', 'stack'], }, }, }, };
|
六、响应式与自适应
容器尺寸变化
1 2 3
| window.addEventListener('resize', () => { chart.resize(); });
|
ResizeObserver(更精准)
1 2
| const observer = new ResizeObserver(() => chart.resize()); observer.observe(container);
|
配置百分比
ECharts 支持百分比值,会按容器实际尺寸自动计算:
1 2 3 4
| option = { series: [{ center: ['50%', '50%'], radius: ['30%', '50%'] }], grid: { left: '10%', right: '10%', top: 60, bottom: 40 }, };
|
七、数据更新
全量替换
1
| chart.setOption({ series: [{ data: newData }] });
|
追加数据(实时图表)
1 2 3 4 5
| setInterval(() => { chart.setOption({ series: [{ data: getLatestData() }], }); }, 1000);
|
动态添加系列
1 2 3 4 5 6
| chart.setOption({ series: [ { name: '原有', type: 'line', data: [1, 2, 3] }, { name: '新增', type: 'line', data: [4, 5, 6] }, ], });
|
加载动画
1 2 3 4 5 6 7 8
| chart.showLoading({ text: '加载中...', maskColor: 'rgba(255,255,255,.8)', });
chart.hideLoading(); chart.setOption(option);
|
八、主题与样式
内置主题
1
| const chart = echarts.init(dom, 'dark');
|
自定义颜色
1 2 3 4
| option = { color: ['#409eff', '#67c23a', '#e6a23c', '#f56c6c', '#909399'], };
|
注册主题
1 2 3 4 5 6 7
| echarts.registerTheme('myTheme', { backgroundColor: '#f5f5f5', color: ['#409eff', '#67c23a', '#e6a23c'], textStyle: { fontFamily: 'Microsoft YaHei' }, });
const chart = echarts.init(dom, 'myTheme');
|
九、大数据量优化
当数据量超过千条时,ECharts 提供的优化手段直接影响图表是否能流畅交互。优化的核心思路是减少渲染开销和降低重绘频率。
9.1 采样(Sampling)
采样是在数据量极大(数万到百万级)时,在保持数据趋势的前提下减少绘图点数的策略。ECharts 在折线图中内置了多种采样算法。
1 2 3 4 5 6 7
| option = { series: [{ type: 'line', data: largeDataSet, sampling: 'lttb', }], };
|
各采样算法对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| │ 算法 │ 原理 │ 视觉保留 | 性能 | 适用场景 │ │────────────┼───────────────────────────────┼─────────┼──────┼─────────────────────│ │ lttb │ Largest Triangle Three Buckets│ ★★★★★ │ ★★★★ │ 通用推荐,保留峰值趋势 │ │ │ 将数据分段,每段中取面积最大的 │ │ │ │ │ │ 三角形中间点,保留转折特征 │ │ │ │ ├────────────┼───────────────────────────────┼─────────┼──────┼─────────────────────┤ │ average │ 每段取平均值 │ ★★★ │ ★★★★★ │ 数据波动不大时使用 │ ├────────────┼───────────────────────────────┼─────────┼──────┼─────────────────────┤ │ max │ 每段取最大值 │ ★★ │ ★★★★★ │ 关注上限的场景(监控)│ ├────────────┼───────────────────────────────┼─────────┼──────┼─────────────────────┤ │ min │ 每段取最小值 │ ★★ │ ★★★★★ │ 关注下限的场景(监控)│ ├────────────┼───────────────────────────────┼─────────┼──────┼─────────────────────┤ │ sum │ 每段求和(仅堆叠图使用) │ ★★ │ ★★★★★ │ 堆叠面积图 │ └────────────┴───────────────────────────────┴─────────┴──────┴─────────────────────┘
|
LTTB 算法原理(是折线图默认推荐算法):
1 2 3 4 5 6
| 1. 将数据按比例等分为 n 段(段数 = 图表像素宽度) 2. 从左到右,每段中选一个"代表点" 3. 选择标准:该点与前一段选中的点和后一段中点构成的三角形面积最大 4. 面积最大 → 说明该点偏离趋势最远 → 保留了趋势转折特征
结果是:折线的视觉形状几乎不变,但绘制点数从 10 万锐减到几百。
|
9.2 渐进渲染(Progressive)
渐进渲染用于数据量非常大(散点图超过数万点)时,将渲染分帧完成,避免主线程长时间阻塞导致页面卡死。
1 2 3 4 5 6 7 8
| option = { series: [{ type: 'scatter', data: hugeDataSet, progressive: 500, progressiveThreshold: 3000, }], };
|
渐进渲染的工作原理
1 2 3 4 5 6 7
| 关闭渐进渲染(数据量大时): 数据 → 一次性全部绘制 → 主线程阻塞 5 秒 → 用户看到图表 ↑ [5 秒内页面无响应,无法滚动/点击]
开启渐进渲染: 数据 → 绘制前 500 个 → 交给下一帧 → 再绘 500 个 → ... ↑ [页面始终可操作,图表逐步显示完整]
|
关键参数说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| option = { series: [{ progressive: 500,
progressiveThreshold: 3000,
progressiveChunkMode: 'mod', }], };
|
注意:渐进渲染开启后,如果用户启用了 tooltip 的 trigger: 'axis',ECharts 需要遍历所有数据点来计算哪个点在轴附近——大数据量时可能会卡。解决方案:
1 2 3 4
| tooltip: { trigger: 'item', }
|
9.3 关闭动画
动画在数据量小时提升体验,在大数据量时却会拖慢首次渲染速度。
1 2 3 4 5 6 7 8 9 10
| option = { animation: false, };
option = { animationDuration: 200, animationEasing: 'linear', };
|
9.4 dataZoom 性能优化
dataZoom 是实现大数据”窗口浏览”的最有效手段——只渲染可视区域的数据点。
1 2 3 4 5 6 7 8 9 10 11 12
| option = { dataZoom: [{ type: 'inside', start: 0, end: 10, }, { type: 'slider', start: 0, end: 10, }], };
|
使用 dataZoom 后,ECharts 内部只渲染 start 到 end 范围内的数据点。配合采样使用时,采样也是在裁剪后的子集上进行。
9.5 其他大数据量策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const chart = echarts.init(dom, null, { renderer: 'svg' });
chart.setOption({ animation: false, tooltip: { show: false }, legend: { show: false }, });
const chart = echarts.init(dom); chart.setOption({ series: [{ type: 'scatter' }], });
chart.appendData({ seriesIndex: 0, data: batchData, });
|
9.6 性能对比参考
1 2 3 4 5 6 7 8 9 10
| 测试数据:10 万条散点图数据
│ 优化策略 │ 渲染耗时 │ 交互帧率 │ 首屏可见 │ │───────────────────────│───────────│───────────│────────────────│ │ 无优化 │ 4200ms │ 5fps │ 等待全部渲染 │ │ 渐进渲染(progressive)│ 1800ms │ 30fps │ 逐步显示 │ │ 采样(lttb) │ 120ms │ 55fps │ 立刻显示 │ │ dataZoom + 采样 │ 80ms │ 58fps │ 只渲染窗口区域 │ │ SVG 渲染器 │ 3000ms │ 45fps │ 需更长时间渲染 │ │ appendData 增量追加 │ 每批 50ms │ 60fps │ 立刻显示部分 │
|
9.7 大数据量场景选型建议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 数据量 < 1 万: 不需要任何优化,默认配置即可
数据量 1 万 ~ 10 万(折线图): 开启 sampling: 'lttb',配合 dataZoom 数据量 ≤ 3 万时可关闭采样,仅使用 dataZoom
数据量 10 万 ~ 100 万(散点图): 开启 progressive + progressiveThreshold 开启 sampling 使用 dataZoom 限制渲染窗口 考虑用 appendData 分批追加 关闭动画
数据量 > 100 万: 服务端聚合后再传给前端 使用 ECharts GL 的 WebGL 加速渲染 后端做空间索引(预聚合为热力图或网格)
|
十、与框架集成
React
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { useEffect, useRef } from 'react'; import * as echarts from 'echarts';
function Chart({ option, style }) { const container = useRef(null); const instance = useRef(null);
useEffect(() => { instance.current = echarts.init(container.current); return () => instance.current?.dispose(); }, []);
useEffect(() => { instance.current?.setOption(option); }, [option]);
return <div ref={container} style={style} />; }
|
Vue 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div ref="container" :style="{ width, height }"></div> </template>
<script setup> import { ref, onMounted, onUnmounted, watch } from 'vue'; import * as echarts from 'echarts';
const props = defineProps({ option: Object, width: String, height: String }); const container = ref(null); let chart = null;
onMounted(() => { chart = echarts.init(container.value); chart.setOption(props.option); });
watch(() => props.option, (opt) => chart?.setOption(opt), { deep: true });
onUnmounted(() => chart?.dispose()); </script>
|
十一、常见问题
Q1: 图表容器有宽高但图表显示空白
1 2 3
|
setTimeout(() => chart.resize(), 100);
|
Q2: setOption 不生效
Q3: 内存泄漏
Q4: 图表无法导出高清图片
1 2 3
|
const url = chart.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' });
|
Q5: 同一个页面多个图表
每个图表必须有独立的 DOM 容器,每个容器只调用一次 init。多个实例之间数据独立。
十二、推荐学习路径
- 掌握常见图表类型:line、bar、pie、scatter、radar
- 理解 option 结构:title、tooltip、legend、xAxis、yAxis、series
- 学会事件监听和交互(点击下钻、联动)
- 掌握响应式:
resize、百分比布局 - 了解大数据优化:sampling、progressive
- 与 React / Vue 集成,做好 dispose 防止内存泄漏