Echarts 图表动态更新实战从零开始教你用 setInterval 定时刷新数据图表实时显示变化曲线和柱状图常见报错解决方案
嘿,朋友!今天咱们来聊聊一个很实用的话题——让 Echarts 图表动起来。
你有没有见过那种数据实时更新的效果?比如股票价格、监控大屏、或者天气变化,曲线和柱状图跟着时间一秒一秒地跳动,特别带感。其实实现起来并没有想象中那么难,核心就在于一个东西:setInterval。
但是!很多小伙伴刚开始用的时候,总是会遇到各种报错,图表要么不刷新,要么闪烁得让人头晕,甚至直接把浏览器卡死。别慌,这篇内容我会带你从最基础的环境搭建开始,一步步把整个逻辑讲清楚,并且把那些容易踩的坑都给你列出来,附带着解决方案。
先搞清楚,为什么需要动态更新?
在写代码之前,咱们先想明白一个事情:Echarts 本身是一个静态图表库。你给它数据,它画图;你不给数据,它不画。
但现实场景里,数据是活的。比如:
- 你做了一个监控大屏,每隔 5 秒需要拉取最新温度数据并更新折线图
- 你做了一个股票看板,希望价格和成交量能实时跳动
- 你做的是一个比赛直播系统,比分和各项数据需要不停刷新
这些场景的共同点就是:数据会随时间变化,图表也需要跟着变化。
这时候,setInterval 就派上用场了。它的作用很简单:每隔一段时间,执行一次函数。
从零开始:最简环境搭建
首先,你需要在本地创建一个 HTML 文件。不用什么复杂的项目框架,一个最简单的 HTML 页面就够用了。
打开你的编辑器(VS Code 之类的都行),新建一个文件,命名为 index.html,然后写下最基础的结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Echarts 动态更新实战</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #1a1a2e;
color: #eee;
font-family: 'Microsoft YaHei', sans-serif;
padding: 20px;
}
h2 {
text-align: center;
margin-bottom: 20px;
color: #00d4ff;
}
#chart-container {
width: 90%;
max-width: 1000px;
height: 500px;
margin: 0 auto;
background: #16213e;
border-radius: 12px;
padding: 15px;
}
.controls {
text-align: center;
margin-top: 15px;
}
button {
padding: 10px 25px;
margin: 0 10px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s;
}
#startBtn {
background: #00d4ff;
color: #1a1a2e;
}
#startBtn:hover {
background: #00b8d9;
}
#stopBtn {
background: #ff6b6b;
color: #fff;
}
#stopBtn:hover {
background: #ee5a5a;
}
.status {
text-align: center;
margin-top: 10px;
color: #888;
font-size: 13px;
}
</style>
</head>
<body>
<h2>📊 Echarts 实时数据动态更新演示</h2>
<div id="chart-container"></div>
<div class="controls">
<button id="startBtn">▶ 开始更新</button>
<button id="stopBtn">⏹ 停止更新</button>
</div>
<div class="status" id="statusText">当前状态:已停止</div>
<!-- 引入 Echarts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script>
// 你的 JavaScript 代码写在这里
</script>
</body>
</html>
先别急着往下跑,我来解释一下这几行代码里有哪些关键细节:
第一,CDN 引入 Echarts。 这里用的是 echarts@5.4.3 的版本,这是目前比较稳定的版本。你如果用更低版本,API 可能有一些差异。
第二,CSS 样式。 我特意给背景用了深色主题,这样图表颜色会更鲜明,看起来也更舒服。#chart-container 这个 div 就是图表要放的地方,我给的高度是 500px,宽度是 90% 且最大不超过 1000px,这样在不同屏幕上都能正常显示。
第三,控制按钮和状态文字。 这两个按钮是用来控制更新开关的,状态文字会告诉你当前是运行中还是已停止。这个交互设计虽然简单,但很重要——它能让你清楚地知道程序在做什么。
核心逻辑:初始化图表 + setInterval 驱动
好,接下来就是真正有意思的部分了。把下面的 JavaScript 代码放到 script 标签里面:
// 第一步:获取图表容器
const chartDom = document.getElementById('chart-container');
const myChart = echarts.init(chartDom);
// 第二步:准备初始数据
let xData = []; // X轴数据(时间)
let yData = []; // Y轴数据(数值)
const maxDataPoints = 20; // 最多显示多少个数据点
// 生成初始数据(为了图表一开始不空)
for (let i = 0; i < maxDataPoints; i++) {
xData.push(formatTime(new Date(Date.now() - (maxDataPoints - i) * 1000)));
yData.push(Math.floor(Math.random() * 100) + 20);
}
// 辅助函数:格式化时间
function formatTime(date) {
const h = String(date.getHours()).padStart(2, '0');
const m = String(date.getMinutes()).padStart(2, '0');
const s = String(date.getSeconds()).padStart(2, '0');
return `${h}:${m}:${s}`;
}
// 第三步:配置图表选项
const option = {
backgroundColor: 'transparent',
title: {
text: '实时温度变化曲线',
left: 'center',
textStyle: { color: '#00d4ff', fontSize: 18 }
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(22, 33, 62, 0.9)',
borderColor: '#00d4ff',
textStyle: { color: '#eee' }
},
legend: {
data: ['温度(°C)'],
top: 30,
textStyle: { color: '#aaa' }
},
grid: {
left: '8%',
right: '5%',
top: '20%',
bottom: '10%'
},
xAxis: {
type: 'category',
data: xData,
axisLine: { lineStyle: { color: '#555' } },
axisLabel: { color: '#aaa', fontSize: 11 }
},
yAxis: {
type: 'value',
name: '温度(°C)',
nameTextStyle: { color: '#aaa' },
axisLine: { lineStyle: { color: '#555' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#333' } }
},
series: [{
name: '温度(°C)',
type: 'line',
data: yData,
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: { color: '#00d4ff', width: 2 },
itemStyle: { color: '#00d4ff' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(0, 212, 255, 0.3)' },
{ offset: 1, color: 'rgba(0, 212, 255, 0.02)' }
])
}
}],
animation: false // 关键点:关闭动画,避免闪烁
};
// 第四步:设置图表
myChart.setOption(option);
// 第五步:定义定时更新函数
let timer = null; // 用来存 timer 的引用,后面停止的时候需要它
function updateData() {
const now = new Date();
const timeStr = formatTime(now);
// 新数据推入
xData.push(timeStr);
yData.push(Math.floor(Math.random() * 80) + 20);
// 保持数据点数量不超过上限
if (xData.length > maxDataPoints) {
xData.shift(); // 移除最老的
yData.shift(); // 移除最老的
}
// 更新图表
myChart.setOption({
xAxis: { data: xData },
series: [{ data: yData }]
});
// 更新状态文字
document.getElementById('statusText').textContent =
`当前状态:运行中 | 最新数据点:${xData.length} | 最后更新:${timeStr}`;
}
// 第六步:绑定按钮事件
document.getElementById('startBtn').addEventListener('click', function() {
if (timer !== null) return; // 防止重复点击
timer = setInterval(updateData, 1000); // 每秒更新一次
this.disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('statusText').textContent = '当前状态:正在更新...';
});
document.getElementById('stopBtn').addEventListener('click', function() {
if (timer === null) return;
clearInterval(timer);
timer = null;
this.disabled = true;
document.getElementById('startBtn').disabled = false;
document.getElementById('statusText').textContent = '当前状态:已停止';
});
// 第七步:响应窗口大小变化
window.addEventListener('resize', function() {
myChart.resize();
});
代码比较长,但别担心,我逐段给你拆解,保证你能看懂。
第一段:初始化图表
const chartDom = document.getElementById('chart-container');
const myChart = echarts.init(chartDom);
这两行是 Echarts 的标准起手式。echarts.init 会在你指定的 DOM 元素上初始化一个图表实例。注意,chartDom 是那个 div 的引用,图表就会画在这个 div 里面。
第二段:准备数据
let xData = [];
let yData = [];
const maxDataPoints = 20;
这里定义了两个数组,xData 用来存时间(X轴),yData 用来存数值(Y轴)。maxDataPoints 控制最多显示多少个数据点,这里是 20 个。为什么要控制数量?因为如果数据点无限增长,图表会越来越密集,最后看起来就像一坨黑线,而且浏览器也会越来越卡。
下面那段 for 循环是为了让图表一开始就有数据,不会显示成一条空白的线。Math.random() * 100 + 20 是生成 20 到 120 之间的随机数,模拟温度数据。
第三段:配置 option
这部分是 Echarts 最核心的配置。我列一下几个关键点:
animation: false— 这个特别重要,我放在最后讲,但你提前知道有这回事。smooth: true— 让折线变得平滑,不是那种折来折去的折线。areaStyle— 给折线下方加了渐变色填充,视觉效果更好。tooltip— 鼠标悬停时显示详细数据,这个交互体验很关键。grid— 控制图表边距,防止标签被截断。
第四段:核心更新逻辑
function updateData() {
// 生成新数据
xData.push(formatTime(new Date()));
yData.push(Math.floor(Math.random() * 80) + 20);
// 保持数据点数量
if (xData.length > maxDataPoints) {
xData.shift();
yData.shift();
}
// 更新图表
myChart.setOption({
xAxis: { data: xData },
series: [{ data: yData }]
});
}
这个函数就是整个动态更新的核心。它做的事情很简单:
- 往
xData和yData末尾添加新数据 - 如果数据点超过了
maxDataPoints,就把最老的那个移除(用shift()方法) - 调用
myChart.setOption()更新图表
注意:这里我只传了 xAxis.data 和 series.data,没有把所有 option 都重写一遍。setOption 会合并你传入的配置和已有的配置,不会覆盖没传的部分。这个特性非常实用。
第五段:控制按钮
timer = setInterval(updateData, 1000);
这行代码是真正的定时刷新。setInterval 的第一个参数是要执行的函数,第二个参数是间隔时间(毫秒)。这里设置的是 1000 毫秒,也就是每秒刷新一次。
timer 变量用来存这个定时器的引用,后面停止的时候需要用它来调用 clearInterval(timer)。
柱状图的动态更新
搞定了折线图,柱状图其实更简单。你只需要把 type 从 'line' 改成 'bar',再稍微调整一下样式就行。
下面是一个完整的柱状图动态更新示例,你可以把之前的 index.html 里的 script 部分替换掉,或者直接新建一个页面:
// 柱状图动态更新完整代码
const chartDom = document.getElementById('chart-container');
const myChart = echarts.init(chartDom);
let xData = [];
let yDataA = []; // 产品A销量
let yDataB = []; // 产品B销量
const maxDataPoints = 12;
// 初始化数据
for (let i = 0; i < maxDataPoints; i++) {
xData.push(formatTime(new Date(Date.now() - (maxDataPoints - i) * 2000)));
yDataA.push(Math.floor(Math.random() * 200) + 50);
yDataB.push(Math.floor(Math.random() * 150) + 30);
}
function formatTime(date) {
const h = String(date.getHours()).padStart(2, '0');
const m = String(date.getMinutes()).padStart(2, '0');
const s = String(date.getSeconds()).padStart(2, '0');
return `${h}:${m}:${s}`;
}
const option = {
backgroundColor: 'transparent',
title: {
text: '实时销量对比',
left: 'center',
textStyle: { color: '#00d4ff', fontSize: 18 }
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(22, 33, 62, 0.9)',
borderColor: '#00d4ff',
textStyle: { color: '#eee' }
},
legend: {
data: ['产品A', '产品B'],
top: 30,
textStyle: { color: '#aaa' }
},
grid: {
left: '5%',
right: '5%',
top: '18%',
bottom: '10%'
},
xAxis: {
type: 'category',
data: xData,
axisLine: { lineStyle: { color: '#555' } },
axisLabel: { color: '#aaa', fontSize: 10, rotate: 0 }
},
yAxis: {
type: 'value',
name: '销量',
nameTextStyle: { color: '#aaa' },
axisLine: { lineStyle: { color: '#555' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#333' } }
},
series: [
{
name: '产品A',
type: 'bar',
data: yDataA,
barWidth: '30%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#00d4ff' },
{ offset: 1, color: '#0077b6' }
])
}
},
{
name: '产品B',
type: 'bar',
data: yDataB,
barWidth: '30%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#ff6b6b' },
{ offset: 1, color: '#c0392b' }
])
}
}
],
animation: false
};
myChart.setOption(option);
let timer = null;
function updateData() {
const now = new Date();
const timeStr = formatTime(now);
xData.push(timeStr);
yDataA.push(Math.floor(Math.random() * 200) + 50);
yDataB.push(Math.floor(Math.random() * 150) + 30);
if (xData.length > maxDataPoints) {
xData.shift();
yDataA.shift();
yDataB.shift();
}
myChart.setOption({
xAxis: { data: xData },
series: [
{ data: yDataA },
{ data: yDataB }
]
});
document.getElementById('statusText').textContent =
`当前状态:运行中 | 最新数据点:${xData.length}`;
}
document.getElementById('startBtn').addEventListener('click', function() {
if (timer !== null) return;
timer = setInterval(updateData, 1000);
this.disabled = true;
document.getElementById('stopBtn').disabled = false;
});
document.getElementById('stopBtn').addEventListener('click', function() {
if (timer === null) return;
clearInterval(timer);
timer = null;
this.disabled = true;
document.getElementById('startBtn').disabled = false;
document.getElementById('statusText').textContent = '当前状态:已停止';
});
window.addEventListener('resize', function() {
myChart.resize();
});
柱状图和折线图的区别主要在于:
series里可以有多个系列(产品A 和产品B)- 用
barWidth控制柱子的宽度 - 每个柱子的颜色可以用
LinearGradient做渐变效果
那些容易踩的坑:常见报错及解决方案
这部分是最实用的,我根据自己踩过的坑和常见的问题,给你整理了几类典型报错:
问题一:图表闪烁 / 跳动
这是最常见的问题。你会发现图表在更新的时候,数据点会”跳”一下,很不自然。
原因分析:
Echarts 默认有动画效果。每次调用 setOption,Echarts 都会对变化的数据进行动画过渡。对于实时数据来说,这个动画效果反而会造成视觉上的抖动。
解决方案:
在 option 里加一行:
animation: false
这行代码的意思是关闭动画效果。数据更新时直接渲染,不会有过渡动画,图表就不会闪烁了。
如果你一定要保留动画效果,可以用这个方案——在更新数据前,先缓存当前的 series 配置,更新时只修改 data,Echarts 会尽量保持其他配置不变:
// 缓存初始 series 配置
const baseSeries = option.series;
function updateData() {
// ... 更新数据逻辑 ...
myChart.setOption({
xAxis: { data: xData },
series: baseSeries.map(s => ({ ...s, data: /* 对应的新数据 */ }))
});
}
问题二:内存泄漏,浏览器越来越卡
如果你运行一段时间后,发现浏览器开始卡顿,甚至标签页直接卡死,那很可能是内存泄漏了。
原因分析:
最常见的原因是定时器没有被正确清除。如果你的代码里有这样的问题:
// 错误示例
function startTimer() {
timer = setInterval(updateData, 1000);
}
// 点击开始按钮时,没有判断 timer 是否已经存在
// 结果点一次,就多一个定时器,点十次就有十个定时器在跑
document.getElementById('startBtn').addEventListener('click', startTimer);
每次点击”开始”按钮,都会创建一个新定时器,旧的定时器还在跑,新的定时器也在跑。数据更新的速度会指数级增加,浏览器内存也被迅速占满。
解决方案:
在创建定时器之前,先检查是否已有定时器在运行:
// 正确示例
document.getElementById('startBtn').addEventListener('click', function() {
if (timer !== null) {
return; // 已经有定时器在跑了,直接返回
}
timer = setInterval(updateData, 1000);
this.disabled = true;
});
document.getElementById('stopBtn').addEventListener('click', function() {
if (timer === null) {
return;
}
clearInterval(timer);
timer = null;
this.disabled = true;
document.getElementById('startBtn').disabled = false;
});
另外,还要确保在页面关闭时清理定时器:
// 页面卸载时清除定时器
window.addEventListener('beforeunload', function() {
if (timer !== null) {
clearInterval(timer);
}
myChart.dispose(); // 销毁 Echarts 实例,释放内存
});
问题三:setInterval 精度问题导致数据不准确
setInterval 并不精确。你设置了 1000ms,但实际上每次执行的时间间隔可能会有几毫秒的偏差。而且,如果你的 updateData 函数执行时间比较长(比如数据量大),实际间隔会比你设置的更长。
解决方案:
如果你需要更精确的时间控制,可以用递归的 setTimeout 来代替 setInterval:
function scheduleUpdate() {
updateData();
// 计算下次执行的延迟
const now = Date.now();
const nextTime = now + 1000 - (now % 1000); // 对齐到秒边界
const delay = nextTime - Date.now();
timer = setTimeout(scheduleUpdate, delay);
}
// 开始
timer = setTimeout(scheduleUpdate, 1000);
// 停止
clearTimeout(timer);
这种方法的好处是:
- 每次执行完后才安排下一次,不会因为函数执行时间长而累积误差
- 可以对齐到秒边界,时间更精确
问题四:图表在窗口 resize 后变形
这个错误表现为:浏览器窗口大小改变后,图表没有跟着调整大小,变得很窄或很宽。
原因分析:
Echarts 实例初始化时会根据容器的大小来确定图表的尺寸。但如果窗口大小改变了,Echarts 不会自动调整。
解决方案:
监听窗口的 resize 事件,调用 myChart.resize():
window.addEventListener('resize', function() {
myChart.resize();
});
这是最基础的解决方案。如果你做了更复杂的布局(比如侧边栏展开收起),可以考虑用防抖来优化:
let resizeTimer = null;
window.addEventListener('resize', function() {
if (resizeTimer) clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
myChart.resize();
}, 200); // 200ms 后执行,避免频繁 resize
});
问题五:setOption 参数错误
有时候你调用 myChart.setOption() 时,会报类似这样的错误:
TypeError: Cannot read properties of undefined (reading 'data')
原因分析:
通常是因为你传入的 series 配置结构和初始的 option.series 不匹配。比如,你初始配置了两个 series(产品A 和产品B),但在更新时只传了一个:
// 错误示例
myChart.setOption({
series: [{ data: yData }] // 只有一个 series,但初始有两个
});
Echarts 在合并配置时,会根据 series 的索引来匹配。如果数量不一致,可能会出现问题。
解决方案:
确保传入的 series 数量和初始配置一致:
// 正确示例
myChart.setOption({
series: [
{ data: yDataA },
{ data: yDataB }
]
});
或者,只更新你需要改变的参数,不要传整个 series:
// 正确示例 - 只更新数据
myChart.setOption({
xAxis: { data: xData },
series: [{ data: yDataA }, { data: yDataB }]
});
问题六:第一次渲染时图表不显示
这个错误比较隐蔽。你代码写完了,按钮也绑好了,但页面加载后图表区域是一片空白。
原因分析:
最常见的原因有两个:
- 容器高度为 0 — Echarts 需要一个有实际高度的容器。如果你的
#chart-container没有设置高度,或者父元素没有高度,图表就画不出来。 - CDN 加载失败 — 网络问题导致 Echarts 库没有加载成功,
echarts对象是 undefined。
解决方案:
检查容器的高度:
#chart-container {
width: 90%;
height: 500px; /* 必须有高度 */
margin: 0 auto;
}
检查 Echarts 是否加载成功:
if (typeof echarts === 'undefined') {
console.error('Echarts 未加载,请检查网络或 CDN 地址');
} else {
const myChart = echarts.init(chartDom);
// ... 继续你的代码
}
问题七:数据更新但图表不刷新
你明明调用了 setOption,数据数组也在变化,但图表就是不动。
原因分析:
这通常是因为你传入的 setOption 参数是空对象或者没有实际变化:
// 错误示例 - 没有实际数据变化
myChart.setOption({});
// 或者 - 数据虽然变了,但引用没变
let data = [1, 2, 3];
myChart.setOption({ series: [{ data: data }] });
// 后面你改了 data 数组的内容,但 setOption 用的还是同一个引用
// Echarts 可能不会重新渲染
解决方案:
确保每次调用 setOption 时,传入的都是最新的数据:
function updateData() {
// ... 生成新数据 ...
myChart.setOption({
xAxis: { data: [...xData] }, // 用展开运算符创建新数组
series: [{ data: [...yData] }]
});
}
用 [...xData] 创建新数组,可以确保 Echarts 检测到数据确实变了。
问题八:Firefox / Safari 中定时器不生效
你在中国 Chrome 上测试没问题,但换到 Safari 或 Firefox,图表就不更新了。
原因分析:
现代浏览器(尤其是 Safari)有后台标签页节流机制。当你的网页标签不在前台时,setInterval 的触发频率会被降低到最低 1 分钟一次,甚至完全停止。这是浏览器为了节省电量和资源做的优化。
解决方案:
这不是代码错误,而是浏览器行为。如果你需要在后台也保持更新,可以考虑以下几种方案:
方案一:用 Web Workers(适合复杂场景)
// worker.js(单独的文件)
self.onmessage = function(e) {
const data = e.data;
// 处理数据...
self.postMessage({ xData: data.xData, yData: data.yData });
};
方案二:接受浏览器节流,但在可见性变化时补偿更新
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible' && timer !== null) {
// 标签页重新可见时,立即更新一次
updateData();
}
});
方案三:用 requestAnimationFrame 代替 setInterval
let rafId = null;
function animate() {
updateData();
rafId = requestAnimationFrame(animate);
}
function startAnimation() {
rafId = requestAnimationFrame(animate);
}
function stopAnimation() {
if (rafId !== null) {
cancelAnimationFrame(rafId);
rafId = null;
}
}
requestAnimationFrame 会根据屏幕刷新率来更新,通常比 setInterval 更平滑,而且在前台时不会被节流。
完整项目:数据从 API 动态获取
上面的例子都是用随机数模拟数据。但在实际项目中,数据通常是从服务器 API 获取的。下面是一个完整的实战示例,展示如何从接口获取数据并动态更新图表:
// 从 API 获取数据并动态更新图表
const chartDom = document.getElementById('chart-container');
const myChart = echarts.init(chartDom);
let xData = [];
let yData = [];
const maxDataPoints = 30;
let timer = null;
// 模拟 API 请求(实际项目中替换成真实的 fetch 或 axios 请求)
function fetchDataFromAPI() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
time: formatTime(new Date()),
value: Math.floor(Math.random() * 100) + 20
});
}, 100);
});
}
function formatTime(date) {
const h = String(date.getHours()).padStart(2, '0');
const m = String(date.getMinutes()).padStart(2, '0');
const s = String(date.getSeconds()).padStart(2, '0');
return `${h}:${m}:${s}`;
}
const option = {
backgroundColor: 'transparent',
title: {
text: '实时 API 数据监控',
left: 'center',
textStyle: { color: '#00d4ff', fontSize: 18 }
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(22, 33, 62, 0.9)',
borderColor: '#00d4ff',
textStyle: { color: '#eee' }
},
grid: {
left: '5%',
right: '5%',
top: '15%',
bottom: '10%'
},
xAxis: {
type: 'category',
data: xData,
axisLine: { lineStyle: { color: '#555' } },
axisLabel: { color: '#aaa' }
},
yAxis: {
type: 'value',
name: '数值',
nameTextStyle: { color: '#aaa' },
axisLine: { lineStyle: { color: '#555' } },
axisLabel: { color: '#aaa' },
splitLine: { lineStyle: { color: '#333' } }
},
series: [{
name: '数据',
type: 'line',
data: yData,
smooth: true,
symbol: 'circle',
symbolSize: 5,
lineStyle: { color: '#00d4ff', width: 2 },
itemStyle: { color: '#00d4ff' },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(0, 212, 255, 0.3)' },
{ offset: 1, color: 'rgba(0, 212, 255, 0.02)' }
])
}
}],
animation: false
};
myChart.setOption(option);
// 异步更新函数
async function updateData() {
try {
const data = await fetchDataFromAPI();
xData.push(data.time);
yData.push(data.value);
if (xData.length > maxDataPoints) {
xData.shift();
yData.shift();
}
myChart.setOption({
xAxis: { data: xData },
series: [{ data: yData }]
});
document.getElementById('statusText').textContent =
`当前状态:运行中 | 最后更新:${data.time}`;
} catch (error) {
console.error('数据获取失败:', error);
document.getElementById('statusText').textContent =
'当前状态:获取数据失败,请检查网络';
}
}
// 开始更新
function startUpdate() {
if (timer !== null) return;
timer = setInterval(updateData, 2000); // 每 2 秒请求一次
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
}
// 停止更新
function stopUpdate() {
if (timer === null) return;
clearInterval(timer);
timer = null;
document.getElementById('stopBtn').disabled = true;
document.getElementById('startBtn').disabled = false;
document.getElementById('statusText').textContent = '当前状态:已停止';
}
document.getElementById('startBtn').addEventListener('click', startUpdate);
document.getElementById('stopBtn').addEventListener('click', stopUpdate);
// 页面关闭时清理
window.addEventListener('beforeunload', function() {
if (timer !== null) {
clearInterval(timer);
}
myChart.dispose();
});
window.addEventListener('resize', function() {
myChart.resize();
});
这个示例展示了真实项目中的典型用法:
- 异步数据获取 — 用
async/await处理 API 请求 - 错误处理 — 用
try/catch捕获请求失败的情况 - 状态反馈 — 让用户知道当前是成功还是失败
- 资源清理 — 页面关闭时清理定时器和 Echarts 实例
进阶:多图表联动更新
如果你的页面上有多个图表,需要同步更新,那就要注意一下定时器的问题。下面是一个多图表联动的示例:
// 多图表联动更新
const chart1 = echarts.init(document.getElementById('chart1'));
const chart2 = echarts.init(document.getElementById('chart2'));
let xData = [];
let yDataA = [];
let yDataB = [];
const maxDataPoints = 20;
const option1 = {
backgroundColor: 'transparent',
title: { text: '温度变化', left: 'center', textStyle: { color: '#00d4ff' } },
xAxis: { type: 'category', data: xData, axisLabel: { color: '#aaa' } },
yAxis: { type: 'value', name: '温度', nameTextStyle: { color: '#aaa' } },
series: [{
name: '温度',
type: 'line',
data: yDataA,
smooth: true,
lineStyle: { color: '#00d4ff' },
itemStyle: { color: '#00d4ff' }
}],
animation: false
};
const option2 = {
backgroundColor: 'transparent',
title: { text: '湿度变化', left: 'center', textStyle: { color: '#ff6b6b' } },
xAxis: { type: 'category', data: xData, axisLabel: { color: '#aaa' } },
yAxis: { type: 'value', name: '湿度', nameTextStyle: { color: '#aaa' } },
series: [{
name: '湿度',
type: 'bar',
data: yDataB,
itemStyle: { color: '#ff6b6b' }
}],
animation: false
};
chart1.setOption(option1);
chart2.setOption(option2);
let timer = null;
function updateData() {
const now = new Date();
const timeStr = formatTime(now);
xData.push(timeStr);
yDataA.push(Math.floor(Math.random() * 50) + 20);
yDataB.push(Math.floor(Math.random() * 40) + 30);
if (xData.length > maxDataPoints) {
xData.shift();
yDataA.shift();
yDataB.shift();
}
// 同时更新两个图表
chart1.setOption({
xAxis: { data: xData },
series: [{ data: yDataA }]
});
chart2.setOption({
xAxis: { data: xData },
series: [{ data: yDataB }]
});
}
function formatTime(date) {
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
}
document.getElementById('startBtn').addEventListener('click', function() {
if (timer !== null) return;
timer = setInterval(updateData, 1000);
this.disabled = true;
document.getElementById('stopBtn').disabled = false;
});
document.getElementById('stopBtn').addEventListener('click', function() {
if (timer === null) return;
clearInterval(timer);
timer = null;
this.disabled = true;
document.getElementById('startBtn').disabled = false;
});
// 响应式调整
window.addEventListener('resize', function() {
chart1.resize();
chart2.resize();
});
多图表联动的核心思路是:一个定时器,多个图表同时更新。这样保证了所有图表的数据时间是同步的,不会出现时间差。
性能优化建议
如果你的项目对性能有较高要求,以下是一些优化建议:
1. 减少数据点数量
数据点越多,渲染压力越大。根据你的实际需求,合理设置 maxDataPoints。一般来说,20-50 个数据点就够看了,再多了图表也会变得很密,看不清。
2. 关闭不必要的动画
animation: false
动画虽然好看,但在实时数据场景中,它会造成额外的渲染负担。关闭动画可以显著提升性能。
3. 用 requestAnimationFrame 代替 setInterval
let lastTime = Date.now();
const interval = 1000;
function tick() {
const now = Date.now();
if (now - lastTime >= interval) {
updateData();
lastTime = now;
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
这种方式避免了 setInterval 的累积误差,而且与浏览器的刷新率同步,渲染更流畅。
4. 大数据量时用 Canvas 渲染
Echarts 默认用 Canvas 渲染,但如果你数据量特别大(比如每秒几百个点),可以考虑开启 WebGL 渲染:
const myChart = echarts.init(chartDom, null, {
renderer: 'canvas',
devicePixelRatio: 2
});
5. 按需更新
不是每次都需要更新整个图表。如果只有 Y 轴数据变了,X 轴没变,那就只更新 series 的 data:
myChart.setOption({
series: [{ data: yData }]
});
这样比更新整个 option 要快得多。
总结一下
今天我们从最基础的环境搭建开始,一步步介绍了如何用 setInterval 让 Echarts 图表动态更新。我们讲了:
- 折线图的动态更新
- 柱状图的动态更新
- 从 API 获取数据的完整流程
- 多图表联动更新
- 八大常见报错及解决方案
- 性能优化建议
最关键的一点是:动态更新的核心就是 setInterval + setOption。setInterval 负责定时触发,setOption 负责更新图表数据。
另外,别忘了处理那些容易踩的坑:
- 定时器要记得清除,防止内存泄漏
- 容器高度要给够,否则图表不显示
animation: false可以避免闪烁- 数据更新时创建新数组,确保 Echarts 能检测到变化
- 窗口 resize 时要调用
myChart.resize()
希望这篇内容能帮你搞定 Echarts 动态更新的问题。如果你在实战中遇到了其他报错,欢迎随时来问,我们一起解决!
