setOption 更新机制
ECharts 配置更新的核心 API 深度解析与性能优化
📖 概述
setOption 是 ECharts 最核心的 API,用于设置和更新图表配置。理解其工作原理对于性能优化、避免 bug 至关重要。
核心特性:
- ✅ 自动合并配置
- ✅ 增量更新支持
- ✅ 异步数据更新
- ✅ 动画过渡控制
- ✅ 批量更新优化
🔍 核心概念
1. setOption API 签名
typescript
chart.setOption(
option: Object, // 配置对象
opts?: {
notMerge?: boolean, // 是否不合并配置(默认 false)
lazyUpdate?: boolean, // 是否延迟更新(默认 false)
silent?: boolean, // 是否不触发事件(默认 false)
replaceMerge?: string | string[] // 替换式合并的组件
}
): void1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
2. 配置合并策略
默认合并(浅合并)
javascript
// 第一次设置
chart.setOption({
title: { text: '标题' },
series: [{ type: 'bar', data: [1, 2, 3] }]
});
// 第二次设置 - 自动合并
chart.setOption({
title: { subtext: '副标题' } // 只添加副标题
});
// 结果:
// {
// title: { text: '标题', subtext: '副标题' },
// series: [{ type: 'bar', data: [1, 2, 3] }]
// }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
合并规则:
- 对象类型: 递归合并
- 数组类型: 按索引合并
- 基本类型: 直接覆盖
数组合并详解
javascript
// 初始配置
chart.setOption({
series: [
{ name: '系列1', data: [1, 2, 3] },
{ name: '系列2', data: [4, 5, 6] }
]
});
// 更新第一个系列的数据
chart.setOption({
series: [
{ data: [10, 20, 30] } // 按索引合并到 series[0]
]
});
// 结果:
// series[0]: { name: '系列1', data: [10, 20, 30] }
// series[1]: { name: '系列2', data: [4, 5, 6] } 保持不变1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
注意: 数组按索引合并,不是按 name 匹配!
完全替换(notMerge)
javascript
// 完全替换配置
chart.setOption(newOption, { notMerge: true });
// 等价于
chart.clear();
chart.setOption(newOption);1
2
3
4
5
6
2
3
4
5
6
使用场景:
- 切换图表类型(bar → line)
- 完全不同的数据结构
- 重置所有配置
替换式合并(replaceMerge)
ECharts 5+ 新增特性:
javascript
// 替换 series 数组,但保留其他配置
chart.setOption({
series: [
{ type: 'line', data: newData }
]
}, {
replaceMerge: ['series'] // 只替换 series
});
// 结果: title、legend 等保持不变,只有 series 被替换1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
3. 更新流程
详细步骤:
- 配置验证: 检查配置合法性
- 配置标准化: 转换为内部格式
- 配置合并: 与现有配置合并
- 差异检测: 找出变化的组件
- 组件更新: 只更新变化的部分
- 动画处理: 计算动画过渡
- 重绘渲染: 绘制到 Canvas/SVG
💡 使用场景
场景 1: 增量更新数据(最常见)
javascript
// 初始设置
chart.setOption({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [10, 20, 30] }]
});
// 只更新数据(高效!)
function updateData(newData) {
chart.setOption({
series: [{ data: newData }]
});
}
// 每秒更新
setInterval(() => {
updateData([15, 25, 35]);
}, 1000);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
优势:
- 只更新数据,不重建整个图表
- 自动触发动画过渡
- 性能优异
场景 2: 动态添加/删除系列
javascript
// 初始只有一个系列
chart.setOption({
legend: { data: ['2023年'] },
series: [{
name: '2023年',
type: 'bar',
data: data2023
}]
});
// 添加第二个系列
chart.setOption({
legend: { data: ['2023年', '2024年'] },
series: [
{ name: '2023年', data: data2023 }, // 保持
{ name: '2024年', data: data2024 } // 新增
]
});
// 删除系列(设置为空数组)
chart.setOption({
legend: { data: ['2023年'] },
series: [
{ name: '2023年', data: data2023 }
]
});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
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
场景 3: 异步数据加载
javascript
async function loadChartData() {
// 显示加载动画
chart.showLoading({
text: '加载中...',
color: '#5470c6',
textColor: '#333',
maskColor: 'rgba(255, 255, 255, 0.8)'
});
try {
// 模拟 API 请求
const response = await fetch('/api/chart-data');
const data = await response.json();
// 隐藏加载动画
chart.hideLoading();
// 设置数据
chart.setOption({
xAxis: { data: data.categories },
series: [{ data: data.values }]
});
} catch (error) {
chart.hideLoading();
console.error('加载失败:', error);
// 显示错误提示
chart.setOption({
title: { text: '数据加载失败', textStyle: { color: 'red' } }
});
}
}
// 调用
loadChartData();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
29
30
31
32
33
34
35
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
29
30
31
32
33
34
35
场景 4: 批量更新优化
javascript
// ❌ 错误: 多次调用导致多次重绘
for (let i = 0; i < 10; i++) {
chart.setOption({
series: [{ data: dataArray[i] }]
});
}
// 结果: 重绘 10 次!
// ✅ 正确: 合并后一次性更新
const allSeries = dataArray.map((data, index) => ({
name: `系列${index + 1}`,
data
}));
chart.setOption({
series: allSeries
});
// 结果: 只重绘 1 次!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
性能提升: 减少 90% 的重绘次数
场景 5: 条件更新
javascript
// 根据条件决定是否更新
function conditionalUpdate(shouldUpdateTitle, newTitle) {
const updates = {};
if (shouldUpdateTitle) {
updates.title = { text: newTitle };
}
// 只在有变化时才调用 setOption
if (Object.keys(updates).length > 0) {
chart.setOption(updates);
}
}
// 使用
conditionalUpdate(true, '新标题'); // 会更新
conditionalUpdate(false, ''); // 不会更新1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
🔧 高级用法
1. lazyUpdate 延迟更新
javascript
// 正常更新 - 立即重绘
chart.setOption({
series: [{ data: newData }]
});
// 延迟更新 - 不立即重绘
chart.setOption({
series: [{ data: newData }]
}, { lazyUpdate: true });
// ... 可以连续多次 setOption ...
chart.setOption({
title: { text: '新标题' }
}, { lazyUpdate: true });
// 最后一次触发重绘
chart.setOption({}); // 空配置触发重绘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
使用场景:
- 批量更新多个配置项
- 减少重绘次数
- 提升性能
性能对比:
不使用 lazyUpdate: 3次 setOption → 3次重绘
使用 lazyUpdate: 3次 setOption → 1次重绘
性能提升: 66%1
2
3
2
3
2. silent 静默更新
javascript
// 正常更新 - 触发事件
chart.setOption({
series: [{ data: newData }]
});
// 会触发 'finished' 事件
// 静默更新 - 不触发事件
chart.setOption({
series: [{ data: newData }]
}, { silent: true });
// 不会触发任何事件1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
使用场景:
- 避免事件循环
- 后台数据同步
- 性能敏感场景
3. 动画控制
javascript
// 关闭动画(大数据量时推荐)
chart.setOption({
series: [{
data: hugeData,
animation: false // 关闭动画
}]
});
// 自定义动画
chart.setOption({
series: [{
data: newData,
animationDuration: 2000, // 动画时长 2秒
animationEasing: 'elasticOut', // 缓动函数
animationDelay: 100 // 延迟 100ms 开始
}]
});
// 渐进式动画(大数据量)
chart.setOption({
progressive: 1000, // 每帧渲染1000个元素
progressiveThreshold: 5000, // 超过5000启用
series: [{ data: largeData }]
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
📝 代码示例
示例 1: 实时数据监控系统
javascript
class RealtimeMonitor {
constructor(dom, maxPoints = 100) {
this.chart = echarts.init(dom);
this.maxPoints = maxPoints;
this.data = [];
this.init();
}
init() {
this.chart.setOption({
title: { text: '实时监控' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'time',
splitLine: { show: false }
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
splitLine: { show: true, lineStyle: { type: 'dashed' } }
},
series: [{
name: '监控值',
type: 'line',
showSymbol: false,
smooth: true,
lineStyle: { width: 2, color: '#ee6666' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(238, 102, 102, 0.3)' },
{ offset: 1, color: 'rgba(238, 102, 102, 0.05)' }
])
},
data: this.data
}]
});
// 启动数据采集
this.startCollecting();
}
startCollecting() {
setInterval(() => {
this.addPoint();
}, 1000);
}
addPoint() {
const now = Date.now();
const value = Math.random() * 100;
this.data.push([now, value]);
// 保持数据长度
if (this.data.length > this.maxPoints) {
this.data.shift();
}
// 高效更新: 只更新数据
this.chart.setOption({
series: [{ data: this.data }]
}, {
lazyUpdate: false, // 立即更新
silent: true // 不触发事件
});
}
dispose() {
this.chart.dispose();
}
}
// 使用
const monitor = new RealtimeMonitor(document.getElementById('chart'), 200);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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
示例 2: 动态主题切换
javascript
class ThemeSwitcher {
constructor(dom, initialTheme = 'light') {
this.dom = dom;
this.currentTheme = initialTheme;
this.baseOption = null; // 保存基础配置
this.init();
}
init() {
this.chart = echarts.init(this.dom, this.currentTheme);
// 保存基础配置
this.baseOption = {
title: { text: '主题切换示例' },
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [10, 20, 30] }]
};
this.chart.setOption(this.baseOption);
}
switchTheme(theme) {
if (theme === this.currentTheme) return;
// 销毁旧实例
this.chart.dispose();
// 创建新实例
this.currentTheme = theme;
this.chart = echarts.init(this.dom, theme);
// 重新应用配置(使用 notMerge)
this.chart.setOption(this.baseOption, { notMerge: true });
}
toggleTheme() {
const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
this.switchTheme(newTheme);
}
}
// 使用
const switcher = new ThemeSwitcher(document.getElementById('chart'));
// 绑定按钮
document.getElementById('toggle-btn').addEventListener('click', () => {
switcher.toggleTheme();
});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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
示例 3: 级联更新(多图表联动)
javascript
class LinkedCharts {
constructor(chartDoms) {
this.charts = chartDoms.map(dom => echarts.init(dom));
this.linkedIndex = null;
this.init();
}
init() {
// 为每个图表设置配置
this.charts.forEach((chart, index) => {
chart.setOption({
title: { text: `图表 ${index + 1}` },
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: categories },
yAxis: { type: 'value' },
series: [{
type: 'bar',
data: this.generateData(),
emphasis: {
itemStyle: { color: '#ee6666' }
}
}]
});
// 监听高亮事件
chart.on('highlight', (params) => {
this.onHighlight(index, params.dataIndex);
});
chart.on('downplay', () => {
this.onDownplay();
});
});
}
onHighlight(sourceIndex, dataIndex) {
this.linkedIndex = dataIndex;
// 更新其他图表
this.charts.forEach((chart, index) => {
if (index !== sourceIndex) {
chart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: dataIndex
});
}
});
}
onDownplay() {
this.linkedIndex = null;
// 取消所有图表的高亮
this.charts.forEach(chart => {
chart.dispatchAction({
type: 'downplay',
seriesIndex: 0
});
});
}
generateData() {
return Array.from({ length: 10 }, () => Math.floor(Math.random() * 100));
}
dispose() {
this.charts.forEach(chart => chart.dispose());
}
}
// 使用
const linkedCharts = new LinkedCharts([
document.getElementById('chart1'),
document.getElementById('chart2'),
document.getElementById('chart3')
]);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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
⚠️ 常见问题
Q1: 配置不生效?
排查步骤:
javascript
// 1. 检查配置路径
console.log(option.series); // 是否是数组?
// 2. 检查是否需要 notMerge
chart.setOption(newOption, { notMerge: true });
// 3. 检查是否有拼写错误
// ❌ 错误
{ serise: [...] } // 拼写错误
// ✅ 正确
{ series: [...] }
// 4. 检查数据类型
// ❌ 错误
{ data: "10,20,30" } // 字符串
// ✅ 正确
{ data: [10, 20, 30] } // 数组1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Q2: 内存泄漏?
原因: 忘记清理旧实例
javascript
// ❌ 错误: 直接覆盖引用
let chart = echarts.init(dom1);
chart = echarts.init(dom2); // dom1 的实例未销毁!
// ✅ 正确: 先销毁再创建
if (chart) {
chart.dispose();
}
chart = echarts.init(dom2);1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Q3: 动画不流畅?
解决方案:
javascript
// 1. 关闭不必要的动画
chart.setOption({
series: [{
data: largeData,
animation: false // 大数据关闭动画
}]
});
// 2. 调整动画时长
chart.setOption({
series: [{
animationDuration: 500, // 缩短动画时长
animationEasing: 'linear' // 使用简单缓动
}]
});
// 3. 使用渐进式动画
chart.setOption({
progressive: 1000,
progressiveThreshold: 5000
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Q4: 如何判断更新是否完成?
javascript
// 方法1: 监听 finished 事件
chart.on('finished', () => {
console.log('渲染完成');
});
// 方法2: 使用 Promise(需要封装)
function setOptionAsync(option) {
return new Promise(resolve => {
chart.setOption(option);
chart.once('finished', resolve);
});
}
// 使用
await setOptionAsync(option);
console.log('更新完成');1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
🎯 最佳实践
1. 更新策略选择
javascript
// 场景1: 只更新数据
chart.setOption({
series: [{ data: newData }]
});
// 场景2: 更新多个配置项
chart.setOption({
title: { text: '新标题' },
series: [{ data: newData }]
});
// 场景3: 完全替换
chart.setOption(newOption, { notMerge: true });
// 场景4: 批量更新
chart.setOption(update1, { lazyUpdate: true });
chart.setOption(update2, { lazyUpdate: true });
chart.setOption({}); // 触发重绘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2. 性能优化清单
javascript
// ✅ 大数据量优化
if (data.length > 10000) {
chart.setOption({
series: [{
data,
animation: false,
large: true,
progressive: 5000
}]
}, { silent: true }); // 静默更新
}
// ✅ 频繁更新优化
chart.setOption({
series: [{ data: newData }]
}, {
lazyUpdate: false, // 立即更新
silent: true // 不触发事件
});
// ✅ 批量操作优化
updates.forEach(update => {
chart.setOption(update, { lazyUpdate: true });
});
chart.setOption({}); // 统一重绘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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
3. 错误处理
javascript
try {
chart.setOption(option);
} catch (error) {
console.error('setOption 失败:', error);
// 降级处理
chart.clear();
chart.setOption(fallbackOption);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
📊 性能指标
| 操作类型 | 耗时 | 建议 |
|---|---|---|
| 小数据更新(<1K) | < 5ms | 无限制 |
| 中等数据更新(1K-10K) | 5-20ms | 正常 |
| 大数据更新(10K-100K) | 20-100ms | 关闭动画 |
| 超大数据更新(>100K) | 100-500ms | large模式+渐进式 |
| notMerge 全量替换 | 50-200ms | 谨慎使用 |
🔗 相关链接
最后更新: 2026-04-23
难度等级: ⭐⭐⭐
预计阅读时间: 22 分钟
示例演示
{
"title": {
"text": "标题"
},
"series": [
{
"type": "bar",
"data": [
1,
2,
3
]
}
]
}示例演示
{
"series": [
{
"name": "系列1",
"data": [
1,
2,
3
]
},
{
"name": "系列2",
"data": [
4,
5,
6
]
}
]
}示例演示
{
"xAxis": {
"type": "category",
"data": [
"A",
"B",
"C"
]
},
"yAxis": {
"type": "value"
},
"series": [
{
"type": "bar",
"data": [
10,
20,
30
]
}
]
}