Skip to content
setOption 与数据更新:从异步加载到图表联动
setOption 是 ECharts 实例上最频繁调用的方法。无论首次渲染还是后续更新,最终都是通过它将一个 JavaScript 对象交给图表库。这个方法的行为不仅仅是“设置配置项”这么简单——它内部维护着 option 的合并逻辑,而这套逻辑直接决定了动态更新时哪些配置会变、哪些会被保留、用户的交互状态会不会丢失。
这一节讨论动态数据更新、用户交互、多图表协同相关的能力:合并策略、异步加载、增量数据、事件监听以及通过 dispatchAction 与 connect 实现的联动。option 基础结构、坐标系和交互组件的详细配置已在前面的章节介绍,这里只在与联动触发相关时提及。
setOption 的合并策略
setOption(option, opts?) 接收两个参数。第二个参数 opts 控制合并行为,有三个互斥的字段可用:
notMerge:布尔值,默认false。replaceMerge:字符串数组,指定要整体替换的组件类型,如['series', 'xAxis']。lazyUpdate:推迟渲染到下一帧,与合并策略无关,但经常在批量更新中配合使用。
此外 silent 控制是否触发事件,这里主要关注前两者与默认行为 [1][2]。
默认 merge:局部更新与保留状态
在 notMerge 未开启的情况下,setOption 采用增量合并。新传入的 option 不会直接覆盖旧的,而是递归地与现有配置对比,只更新提供的字段,未提供的保持不变。同类型组件(如 series、xAxis、yAxis)默认按数组索引匹配;给组件加了 id 之后,则按 id 精确匹配 [3][6]。
javascript
// 只更新第一个系列的数据,其余系列和图例、轴全部保留
chart.setOption({
series: [
{
id: 'sales',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
});这里传入的 option 只有 series 数组,且只包含一项。merge 会找到 id 为 'sales' 的那个系列,替换其 data 字段。其它系列、图例的选中状态、dataZoom 的缩放窗口都会继续存在。用户在图例上切换的开关不会被重置,这正是 merge 的核心价值——更新数据不影响交互状态。
需要留意:如果旧 option 中有 3 个系列,按索引传入一个长度为 1 的 series 数组时,只会匹配到第 0 个系列,其余两个系列保持不变。要精确替换整个系列列表,可通过传入 replaceMerge: ['series'],或者确保使用 id 并传入完整的对应关系。
notMerge:清空并重建整个 option
javascript
chart.setOption(option, { notMerge: true });设置 { notMerge: true } 之后,ECharts 会完全丢弃当前实例上的旧 option,用新的 option 重新初始化全部组件。所有用户交互状态——图例的选中、dataZoom 的窗口位置、toolbox 的选项——都会丢失,并且会重新触发初始化动画 [4]。
这种模式适合“整体切换到另一份完全不同的图表”的场景,比如从柱状图切换为饼图,或者要恢复到初始配置。但如果在高频更新中反复使用 notMerge,动画抖动和交互重置会非常明显,不推荐如此使用。
replaceMerge:精准替换指定组件
ECharts 5 引入了 replaceMerge,它比 notMerge 更精细:只整体替换指定的组件列表,其余组件仍然走增量合并 [5]。
javascript
chart.setOption(newOption, { replaceMerge: ['series', 'dataset'] });上面的调用会把旧图表中所有 series 和 dataset 整个替换成 newOption 中的内容,而 legend、tooltip、dataZoom 等其他组件不受影响。常用于数据源结构完全变化,但图表类型不变的情况下——比如数据集从月粒度切换到日粒度,series 对应的 encode 映射也一并更新。
实际效果对比:
| 模式 | series 行为 | 交互状态 | 动画 |
|---|---|---|---|
| 默认 merge | 按 id/index 合并 | 保留 | 更新动画 |
| notMerge | 完全重建 | 丢失 | 初始化动画 |
| replaceMerge: ['series'] | 整体替换 | 其他组件保留 | 取决于涉及组件 |
异步数据加载与 loading 动画
showLoading 与 hideLoading 的使用时机
在数据到达之前,图表区域通常要么是上一份旧数据,要么是一片空白。showLoading() 会在这个区域覆盖一个 loading 动画,表示正在加载。数据拿到后调用 hideLoading() 移除 [7]。
javascript
chart.showLoading();
fetch('/api/sales')
.then(res => res.json())
.then(data => {
chart.setOption({
series: [{ id: 'sales', data }]
});
})
.finally(() => {
chart.hideLoading();
});showLoading() 可以传入配置,比如 chart.showLoading({ text: '正在请求…', color: '#c23531' })。不传参时使用默认样式。
hideLoading 应放在 finally 里,即使请求失败也要隐藏遮罩,否则界面会永远卡在加载状态。
基于 fetch 的数据请求与定时轮询
使用 fetch 与 setInterval 可以搭建最简单的实时数据推送图表。定时轮询适用于后端不提供 WebSocket 的场景(ECharts 官方示例 dynamic-data 演示了这一模式)[8][10]。
javascript
const chart = echarts.init(document.getElementById('main'));
function refresh() {
fetch('/api/live')
.then(res => res.json())
.then(data => {
chart.setOption({
series: [{ id: 'live', data }]
});
});
}
setInterval(refresh, 5000);轮询间隔要根据数据变化频率和接口延迟来权衡。一个常见问题是:如果上一轮请求还没返回,下一轮又发起了,渲染顺序可能混乱。更稳健的做法是使用标志位或通过 AbortController 取消上一次未完成的请求 [8]。
异步更新时的渲染时序
setOption 内部默认同步更新内部 option 并立即触发渲染。如果在同一个事件循环里多次调用 setOption,每次都会重绘一次。lazyUpdate: true 可以把渲染推迟到下一个动画帧,让多次更新合并为一次绘制,减少布局和绘制开销 [11]。
javascript
chart.setOption({ series: [{ id: 'a', data: [1,2,3] }] }, { lazyUpdate: true });
chart.setOption({ series: [{ id: 'b', data: [4,5,6] }] }, { lazyUpdate: true });
// 等到下一帧时,两次更新合并渲染这在批量初始化或一次性更新多个维度时很有用,但与加载动画搭配时要注意:频繁的请求通常更应该考虑请求去重和取消,而不是全靠 lazyUpdate 兜底。
动态修改图表:系列与数据的增删改
动态添加与移除系列
在任意时刻可以通过 setOption 新增系列或删除系列。利用默认 merge,新增系列只需要在 series 数组中加一个新条目(带唯一 id 会更好管理)。如果要移除某个系列,需要整体替换 series 列表,需借助 replaceMerge 或 notMerge 来实现 [12]。
javascript
// 新增一个系列
chart.setOption({
series: [
{ id: 'new_series', type: 'line', data: [10, 20, 30] }
]
});执行后图表会多出一条线,同时原有系列不变。移除 id 为 'new_series' 的系列,可采用:
javascript
chart.setOption({
series: [
// 只保留需要的那几个 id
{ id: 'sales' },
// 'new_series' 不在列表中则会被移除(需要配合 replaceMerge)
]
}, { replaceMerge: ['series'] });默认 merge 不会删系列,因为缺少的索引位置不受影响。因此删除必须用 replaceMerge 或 notMerge。
修改数据点与 dataset 的动态行
更新某个已有系列的数据,只要在 setOption 中传入那个系列的 data:
javascript
chart.setOption({
series: [{ id: 'sales', data: [100, 200, 300] }]
});merge 会把 data 字段替换掉,系列的其他配置(类型、线样式等)保留 [13]。
如果用 dataset 来管理数据,更新数据行会非常简单:dataset.source 整个替换即可,系列通过 encode 去映射列,不需要逐个 series 更新 [14]。
javascript
chart.setOption({
dataset: {
source: [
['product', '2023', '2024'],
['Apples', 43, 55],
['Oranges', 30, 42]
]
}
}, { replaceMerge: ['dataset'] });dataset 替换后,所有关联 series 会自动反映新数据。如果只修改某几行,可以在拿到新数据后在 JS 里拼接新的 source 再整体赋予。
appendData 追加增量数据
对于流式数据(例如监控系统每秒产生一个点),appendData 是更轻量的选择。它直接将数据追加到指定系列末尾,不会像 setOption 那样触发完整的合并和重绘 [15]。
javascript
chart.appendData({
seriesIndex: 0,
data: [Math.random()]
});仅适用于 line、scatter 等可以直接接收一维数据追加的系列。它不改变其他配置,也没有合并步骤,性能更好。但是需要注意,图表的显示数据范围不会自动跟随移动——可能需要配合 dataZoom 或者手动调用 setOption 来调整窗口。
事件系统:监听用户与组件操作
基础事件:click、mouseover、mouseout
chart.on(eventName, handler) 注册事件。支持所有浏览器鼠标事件:click、dblclick、mouseover、mouseout、mousedown、mouseup、contextmenu 等 [16]。
javascript
chart.on('click', function (params) {
console.log('点击了', params.name, '值为', params.data);
});在柱状图、折线图中,点击数据点会触发;在饼图中,点击扇区触发。handler 接收的 params 对象包含被点击项的详细信息。
组件事件:legendselectchanged、datazoom、restore
除数据点事件外,交互组件的行为也会触发事件。常见的有:
legendselectchanged:图例选中状态变化后触发。参数中包含name和selected对象 [19]。datazoom:数据区域缩放窗口变化时触发。参数包含start、end百分比,多 dataZoom 时还有batch[20]。restore:用户点击 toolbox 的还原按钮还原图表后触发 [17]。
javascript
chart.on('datazoom', function (params) {
// params.start, params.end
console.log(`当前窗口:${params.start}% - ${params.end}%`);
});这些事件常常用于多图表状态同步(后文详细展开)。
事件参数的结构与解构
回调收到的 params 对象常用字段如下 [18]:
componentType:'series'、'xAxis'等seriesType:'line'、'bar'等seriesIndex、seriesNamename:数据项名称(类目轴上的标签)dataIndex:在当前系列 data 中的索引data:该数据点的值(可能是数组或数值)color:对应的颜色event:原始浏览器事件对象(鼠标事件时存在)
可以方便地使用解构:
javascript
chart.on('click', ({ seriesName, name, data }) => {
console.log(`${seriesName} - ${name}: ${data}`);
});程序化触发交互:dispatchAction
dispatchAction 的常见动作类型
dispatchAction 允许在代码中模拟用户操作,或者对图表施加某种交互效果。常用 action 类型 [21]:
highlight/downplay:高亮或取消高亮数据项showTip/hideTip:显示或隐藏 tooltiplegendSelect/legendUnSelect/legendToggleSelect:切换图例的选中状态dataZoom:设置数据缩放窗口范围restore:还原图表
javascript
// 高亮第一个系列的第 3 个数据点
chart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: 2
});这些动作结合事件监听后,可以实现复杂的联动效果。
通过 dispatchAction 同步多图表状态
典型场景:图表 A 的 dataZoom 窗口变化后,图表 B 也应缩放到相同的百分比范围。手动同步的做法是监听 A 的 datazoom 事件,再让 B 执行 dataZoom action [22]。
javascript
chartA.on('datazoom', function (params) {
chartB.dispatchAction({
type: 'dataZoom',
start: params.start,
end: params.end
});
});这种方式比全自动的 group 联动更加灵活,比如可以只同步某一个轴的范围,或者同步前进行范围映射。
多图表联动:connect 与 group
group 分组与自动联动
ECharts 提供了 connect 方法,可以自动在多个图表间广播支持的 action。给每个图表实例设置 group 属性,然后调用 echarts.connect('groupName'),同一组的图表在发生图例选择变化、dataZoom 变化时,其他实例会自动同步 [23]。
javascript
const chart1 = echarts.init(dom1);
chart1.group = 'dashboard';
const chart2 = echarts.init(dom2);
chart2.group = 'dashboard';
echarts.connect('dashboard');此后,用户在 chart1 上点击图例切换,chart2 同样类名的系列也会同步切换选中状态。不需要手动写事件监听。用 echarts.disconnect('dashboard') 可以解除联动。
自动联动方便,但也有限制:它同步的是相同 action 的相同参数,比如图例名称必须一致才会跨图表生效。如果两个图表的数据结构差异很大,自动联动就不够用了,这时需要手动事件广播。
事件广播与手动同步
手动同步本质是“监听—转换—dispatch”。例如图表 A 的图例变化后,在图表 B 上做对应的 toggle,但两个图例的 name 不同时,就需要在 handler 里做映射 [24]。
javascript
chartA.on('legendselectchanged', function (params) {
const mappedName = nameMapping[params.name]; // 自定义映射
if (mappedName) {
chartB.dispatchAction({
type: 'legendToggleSelect',
name: mappedName
});
}
});这种方式在数据来源不同但业务上需要保持同步的大屏类应用中很适用。
动态更新注意点
动画冲突与 merge 的影响
每次更新数据,如果未关闭动画,ECharts 会执行数据更新动画。在高频更新场景(如轮询间隔 1 秒),连续的动画会导致视觉混乱且增加渲染开销。通过 animationDurationUpdate: 0 可以关闭更新动画 [25]。
javascript
chart.setOption({
animationDurationUpdate: 0,
series: [{ id: 'live', data: newData }]
});同样,如果使用了 notMerge 或 replaceMerge,可能触发初始化动画,需要根据实际情况关闭 animation 相关配置。
实例销毁前的解绑与 dispose
当图表容器被移除或页面跳转时,应该主动销毁实例并清理资源。chart.dispose() 会销毁实例并释放 DOM/Canvas。在这之前要清除定时器、取消未完成的请求、移除事件监听,避免在已销毁的实例上操作 [26]。
javascript
const intervalId = setInterval(refresh, 5000);
// 在组件卸载或路由离开时:
clearInterval(intervalId);
chart.off('click', clickHandler);
chart.dispose();如果用了第三方框架(React/Vue),在组件卸载钩子里做清理。
高频更新时的防抖与性能控制
即使使用 merge,每秒几十次 setOption 也可能带来卡顿。控制更新频率的方式有几种 [27]:
- 合并数据,在一次 setOption 中更新所有变化,而不是一次更新一个点。
- 使用
lazyUpdate: true合并同帧渲染。 - 选用
appendData追加流式数据,避免重建。 - 对大数据集启用采样
sampling,减少绘制点数。
防抖策略可以放在业务层:在一定时间内只执行最后一次 setOption,或者等到动画帧回调中统一更新。这部分逻辑和具体的 UI 框架结合紧密,本节不再展开。
参考链接
- [1] https://echarts.apache.org/en/api.html#echartsInstance.setOption
- [2] https://echarts.apache.org/handbook/en/concepts/option
- [7] https://echarts.apache.org/en/api.html#echartsInstance.showLoading
- [8] https://fetch.spec.whatwg.org/
- [10] https://echarts.apache.org/examples/en/editor.html?c=dynamic-data
- [14] https://echarts.apache.org/handbook/en/concepts/dataset
- [15] https://echarts.apache.org/en/api.html#echartsInstance.appendData
- [16] https://echarts.apache.org/en/api.html#events
- [18] https://echarts.apache.org/handbook/en/concepts/event
- [21] https://echarts.apache.org/en/api.html#echartsInstance.dispatchAction
- [23] https://echarts.apache.org/en/api.html#echarts.connect
- [25] https://echarts.apache.org/en/option.html#animationDurationUpdate
- [26] https://echarts.apache.org/en/api.html#echartsInstance.dispose
- [27] https://echarts.apache.org/handbook/en/best-practices/performance
