一、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 在数据量超过千条时提供多种优化手段:
采样
1 2 3 4 5 6 7 8 9 10
| option = { series: [{ type: 'line', data: largeDataSet, sampling: 'lttb', }], };
|
渐进渲染
1 2 3 4 5 6 7 8
| option = { series: [{ type: 'scatter', data: hugeDataSet, progressive: 500, progressiveThreshold: 3000, }], };
|
关闭动画
1 2 3
| option = { animation: false, };
|
十、与框架集成
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 防止内存泄漏