资讯动态

ECharts折线图上下限区域展示:markArea与差值堆叠实战

发布时间:2026/10/4 13:35:17 来源:尧图企业网站定制
做设备监控看板的时候遇到过这样一个需求页面上要同时展示一台机组近一周的负载率走势还要把“安全运行区间”直接画在图上。也就是说超过上线要预警低于下线要告警而正常范围要在背景里一眼就能看出来。项目里正好用的是 ECharts所以我就拿折线图加上下限区域来做效果挺直观后面又反复调整过几次把实现方案、坑点和使用场景都摸得比较透了。这篇文章我会把“echarts 折线图上下限区域展示”这件事从头到尾拆开讲包含两种典型实现路线markArea 区域标注、差值堆叠填充、完整的配置代码、动态数据刷新时要注意的细节以及我在真实项目里踩过的各种问题。无论你是刚接触 ECharts 的新手还是已经在用但没做过这种区间图的同事看这篇都能少走点弯路。1. 先理清楚上下限区域到底该怎么表达1.1 三类典型场景对应的展示逻辑不一样上下限区域这需求看着简单但落到具体业务里表达方式是有区别的。我归纳下来常见的有三类。第一类是固定阈值范围。比如机房温度要求 18℃ 到 27℃CPU 使用率建议控制在 20% 到 80%。这个上下限是固定值不随时间变化画出来就是两条水平线之间的一个色带。用 markArea 直接写 yAxis 坐标区间十几行配置就能搞定。第二类是动态上下限区间。上下限本身也是曲线会随着时间周期性变化。比如生产线不同班次的良率目标不同白天要求 95% 以上夜班 90% 就能接受那上限制线就不是常量而是一条跟着时间变化的折线上下限之间需要始终有填充区域。这时 markArea 就不太方便了因为区域边界必须动态跟着数据走。第三类是异常区间标注。上下限区域只在某些时间段出现比如设备检修时段、业务高峰期需要在折线上额外高亮对应的时间段范围其他时间不显示。这个本质上是把区域绑定到 xAxis 的某个刻度区间。很多同学一上来就问“上下限区域怎么加”其实得先问清楚是上面哪一种。方案选错了后面改起来特别别扭。1.2 两条技术路线markArea 和差值堆叠填充ECharts 里要实现上下限区域我没有找到专门叫“rangeArea”的 series 类型但可以用两种现成的能力组合出来。一条路线是markArea。它是 ECharts 内置的标记区域组件可以挂在折线图 series 下面通过 data 数组指定坐标范围从而在图表区域内画出一个矩形高亮块。因为 markArea 支持 xAxis、yAxis 甚至自定义 coord 坐标所以既能画固定阈值横带也能画某个时间段内的竖条高亮。另一条路线是差值堆叠填充原理比较巧妙。把“下限值”和“上限值减下限值”分别作为两个 series都设置 stack 为同一个 stack 名称并且让这两条线的线条透明、不显示数据点只给它们设置 areaStyle 区域填充。因为堆叠机制会把两个 series 的数值在纵向上相加所以第二个 series 画出来的堆叠面积视觉上正好是从下限制线到上限制线之间的区域。这两条路线的适用场景差异很大。markArea 简单直接适合固定阈值差值堆叠灵活适合上下限本身就是动态曲线的场景。1.3 怎么选从数据形态和维护成本出发我的判断标准主要看上下限数据是什么形态。如果上下限是常量或临时手填的阈值比如业务方在某个版本里要求把预警区间改成 [70, 90]那就用 markArea改起来最快代码也就两行。如果上下限是接口返回的数组每个时间点都有一个对应值比如每个时刻都有 lower 和 upper 字段那就用差值堆叠方案。因为 markArea 虽然也可以把区域分成多个小矩形逐段绘制但数据点一多代码量和心智负担都会变大而且段与段之间容易产生缝隙。差值堆叠方案在视觉上是连续的维护的数据结构也跟折线保持一致。如果场景是多个指标共用一套上下限规则我倾向于把上下限区域做成一个独立的配置函数返回 series 数组片段方便复用。这点在后面的进阶部分会细说。2. 动手前先把数据备好2.1 原始数据通常长什么样无论用哪种方案数据结构都是第一关。我习惯的做法是把原始数据整理成表格思维而不是一上来就写 ECharts option。比如接口返回的原始数据可能是这样[ { time: 2025-06-01 09:00, actual: 62.5, upper: 85, lower: 30 }, { time: 2025-06-01 10:00, actual: 73.1, upper: 85, lower: 30 }, { time: 2025-06-01 11:00, actual: 88.9, upper: 90, lower: 35 } ]这个结构其实已经不错了每一行里同时包括实际值、上限值、下限值。真正写图之前我会先把它们拆成四个平行数组时间列表、实际值数组、上限数组、下限数组。如果你拿到的数据里没有 upper/lower只有一组固定的阈值那就更好办了直接在配置里写常量数组或者单个数值就行。最怕的是数据结构乱七八糟比如上下限被塞在字符串里那就得先做一次数据清洗。2.2 数据预处理的两个关键点第一个关键点是差值计算。如果你打算用差值堆叠方案不能直接把 upper 数组塞给某个 series而是要在数据预处理阶段就算好upper - lower的差值数组。这个计算看起来简单但有一个隐藏问题如果某条数据里 lower 大于 upper差值就是负数堆叠面积会向下延伸图表会出现一个非常奇怪的“负区域”。所以数据清洗阶段就要把这类异常数据处理掉要么修正要么直接过滤并记为一条错误日志。第二个关键点是边界对齐。ECharts 折线图是按数组下标跟 xAxis 数据一一对应的。如果你的时间列表是 10 个点但 actual 数组只有 9 个值、upper 数组有 11 个值图表不会报错但会出现错位。数据点少的时候可能看不太出来点一多整条曲线就乱了。我自己的习惯是统一经过一个 normalize 函数确保所有数组长度一致缺失值用 null 补位。2.3 配色与透明度怎么定上下限区域的配色核心原则是“区域不抢主线”。既然是背景区域优先用透明度低的浅色。比如正常区间用蓝色系rgba(24, 144, 255, 0.15)实际值折线用深橙色#ff7d45。这样前景折线始终是视觉焦点。如果希望区域再明显一点可以叠加一个细边框比如浅蓝到深蓝的渐变色带。还有一个小技巧上下限本身的折线可以设置成浅灰色虚线既能表明边界线位置又不会跟实际数据线混在一起。如果业务上不需要显示上下限的具体走向也可以把这两条线的 lineStyle 设成全透明只留区域填充图表会更干净。3. 方案一markArea 快速圈定上下限3.1 固定阈值区域的完整示例先讲最简单的做法。假设你要展示一条折线并且把 30 到 80 的区间高亮成“正常范围”。const chart echarts.init(document.getElementById(chart)); const option { tooltip: { trigger: axis }, grid: { left: 60, right: 20, top: 60, bottom: 40 }, xAxis: { type: category, boundaryGap: false, data: [10:00, 11:00, 12:00, 13:00, 14:00, 15:00] }, yAxis: { type: value, min: 0, max: 100 }, series: [ { name: 负载率, type: line, data: [45, 62, 78, 55, 68, 72], lineStyle: { width: 3, color: #ff7d45 }, itemStyle: { color: #ff7d45 }, markArea: { silent: true, itemStyle: { color: rgba(24, 144, 255, 0.15), borderColor: rgba(24, 144, 255, 0.4), borderWidth: 1 }, data: [ [ { yAxis: 30 }, { yAxis: 80 } ] ] } } ] }; chart.setOption(option);这里最关键的就是markArea.data里的yAxis: 30和yAxis: 80。ECharts 会从纵坐标 30 画到纵坐标 80并且横向上默认覆盖整个绘图区域。silent: true是让这个区域不响应鼠标事件否则鼠标一移到区域上 tooltip 可能会被挡掉反而影响体验。3.2 把区域绑定到 x 轴时间段的玩法markArea 不仅能画横向阈值带也能画纵向时间段高亮。比如你想把 12:00 到 14:00 这个午间时段单独标记出来就可以这样写markArea: { silent: true, itemStyle: { color: rgba(255, 173, 0, 0.2) }, data: [ [ { xAxis: 12:00 }, { xAxis: 14:00 } ] ] }这里用的是 xAxis 的刻度值也就是 category 类型下对应的分类名称。这种方式很适合用来标注“设备检修时段”“高峰期”“统计口径切换”这类与业务状态相关的时间段。如果你既想标横向阈值带又想标纵向时间段可以把多个[{...}, {...}]一组的数据放进markArea.data数组里每组之间互不影响。3.3 markArea 的局限性和适用边界markArea 虽然方便但有几个很明显的局限。第一它是“静态区域”不是“数据绑定区域”。如果你的上限不是一个固定阈值而是每个时间点都有不同值markArea 就只能笨办法一段一段地人工切分数据一多就非常痛苦而且区域边界不连续视觉效果很差。第二多系列时 markArea 会挂在某个 series 下面。如果这个 series 因为图例操作被用户隐藏了markArea 也会跟着消失。有时候用户只是不想看“负载率”曲线却把上下限区域也搞没了业务上容易产生误解。第三markArea 的区域样式是统一的想做成渐变效果比较麻烦需要自己写 rich text 或者额外处理。如果想要那种从下到上渐变透明的“梦幻区域”还是得靠差值堆叠方案。所以我的结论是markArea 适合快速原型、固定阈值、单图展示但在复杂业务数据看板里我最终几乎都改成了差值堆叠方案。4. 方案二差值堆叠填充实现动态上下限4.1 核心原理为什么是“上限减下限”这块我单独拿出来讲因为很多人第一次看到差值堆叠的写法会一头雾水代码里明明只有一条下限线和一条差值线为什么图上能出现两条边界和一个填充区域。原理其实不复杂。ECharts 的 stack 配置会把相同 stack 名的系列在纵轴方向累加。比如下限值是 30差值系列当前点是 50那么第二个系列在 y 轴上的累加位置就是 80。这就等效于上限制线。而第二个系列自己画的区域从累加起始位置30开始到累加结束位置80之间就是我们需要的高亮区域。用一个小例子来算某时刻实际负载率 62下线 30上线 80预处理得到第一个系列 fallback 值为 30第二个系列 fillArea 值为80 - 30 50。堆叠之后下限线位置30上限线位置30 50 80填充区域覆盖30 到 80实际值折线62正好落在区域中间这个方案看起来多写了一个 dummy series但它把“上下限是动态曲线”的问题彻底解决了。每个时间点的上限是多少差值就是多少区域自然跟着数据走。4.2 完整代码实现下面的代码是完整的动态上下限折线图实现。为了讲清楚我把数据展开写实际项目里用接口返回时只要把对应数组替换掉即可。const times [06-01 09:00, 06-01 10:00, 06-01 11:00, 06-01 12:00]; const actual [62.5, 73.1, 88.9, 58.2]; const upper [85, 85, 90, 80]; const lower [30, 30, 35, 40]; // 预处理计算差值数组 const diff upper.map((value, index) { const low lower[index]; if (low value) { console.warn(第 ${index} 个点下限不小于上限已用下限代替差值); return 0; } return value - low; }); const option { tooltip: { trigger: axis, formatter: function (params) { let result ; params.forEach((item) { if (item.seriesName 上下限差值) { return; } result ${item.marker}${item.seriesName}: ${item.value}br/; }); return result; } }, legend: { data: [实际值, 下限, 上限] }, grid: { left: 60, right: 20, top: 60, bottom: 40 }, xAxis: { type: category, boundaryGap: false, data: times }, yAxis: { type: value, name: % }, series: [ { name: 下限, type: line, stack: range, data: lower, symbol: none, lineStyle: { opacity: 0.3, type: dashed }, areaStyle: { color: rgba(24, 144, 255, 0.05) } }, { name: 上下限差值, type: line, stack: range, data: diff, symbol: none, lineStyle: { opacity: 0 }, areaStyle: { color: rgba(24, 144, 255, 0.2) } }, { name: 实际值, type: line, data: actual, lineStyle: { width: 3, color: #ff7d45 }, itemStyle: { color: #ff7d45 }, symbolSize: 6 } ] };这个配置有几个细节值得注意。第一个细节是 series 的顺序。下限系列必须放在差值系列之前因为堆叠是从第一个 series 开始往上累加的。如果你把差值系列放在前面堆叠位置就会错乱。第二个细节是 tooltip formatter。因为差值系列本质上是辅助系列我不希望在 tooltip 悬浮框里看到“上下限差值: 50”这种业务上无法解释的内容。所以我在 formatter 里把它过滤掉了。第三个细节是 legend。我本来想直接隐藏差值系列的图例但 ECharts 没有“图例隐藏但系列保留”的直接开关其实可以手动控制 selected所以我采取了一个更简单的办法给差值系列设置tooltip: { show: false }然后在 legend 的 data 里不写它。但这里要注意如果差值系列的 name 不在 legend 数组里它默认也不会显示在图例中。所以上面的代码里 legend 只写了实际值、下限、上限三个名字。4.3 动态刷新与数据更新在真实项目里数据不会只加载一次。要么是定时轮询后端接口要么是 WebSocket 推送新点位。ECharts 动态更新有几个容易踩的坑。最稳的做法是重新走一遍数据预处理然后把新的数组一次性 setOption 进去function updateChart(newData) { const times newData.map((item) item.time); const actual newData.map((item) item.actual); const upper newData.map((item) item.upper); const lower newData.map((item) item.lower); const diff upper.map((value, index) { return Math.max(value - lower[index], 0); }); chart.setOption({ xAxis: { data: times }, series: [ { data: lower }, { data: diff }, { data: actual } ] }); }注意这个 setOption 用的是默认的 merge 模式。如果数据结构不变series 数量不变只是 data 变了merge 模式完全够用也不会闪烁。但如果后端把数据结构都变了比如原来返回 3 个 series现在返回 4 个 series我建议直接传第二个参数chart.setOption(option, true);第二个参数 true 表示 notMerge也就是完全用新配置替换旧配置避免旧 series 残留。我遇到过两次奇奇怪怪的“区域重影”排查到最后都是因为没加这个参数旧的区域配置一直在底层没被清掉。5. 实战坑点我在项目里踩过的五个问题5.1 区域填充方向反了数据和视觉对不上第一次用堆叠方案时我把顺序写反了下限系列放到了差值系列后面。结果画出来的区域是从基准线往下延伸的整个图表特别奇怪。后来我才想明白堆叠的方向是从第一个 series 开始累加如果你想画“下限在上、区域向下”的效果就得调整 series 顺序跟数值的正负关系。现在我的习惯是固定把下限系列放在前面差值系列放第二位。写代码前先在纸上把公式列一遍确保下限和差值的加和等于上限再往 option 里填。5.2 tooltip 里冒出了辅助系列的数值辅助系列是给图表渲染用的但默认情况下它也会出现在 tooltip 里。如果业务方只看了一眼发现悬浮框里有个莫名其妙的数值一定会来问。这个问题不算难解决关键是别漏。我的做法是两层保险。第一层在差值系列上写tooltip: { show: false }这样悬浮框默认就不显示它。第二层在 tooltip formatter 里再过滤一次按 seriesName 排除。这样即使后续有人改了 series 的配置也不至于把辅助数据暴露出来。5.3 区域重影旧区域一直不消失这个坑主要出在动态更新场景。早期我在 setOption 时没有传 notMerge并且手动往 series 数组里 push 过一次 markArea 配置。之后每次更新我都只改 datamarkArea 的旧配置就一直留在图表上区域越叠越多到最后整个图都是半透明的色块。后来我统一了更新逻辑要么每次都用完整的新 option 加 notMerge 重置要么就坚持不往 series 里动态增删 markArea。两种方式混用是最容易出问题的。现在我偏向用 notMerge 重置做高频刷新因为代码路径清晰、行为可预期就是每次会重建整个图表性能上注意别在渲染循环里做太重的事就行。5.4 数据量大时页面卡顿一次接口返回 2000 个点同时还有 3 个系列和一个面积区域ECharts 默认渲染也能跑但缩放、拖拽时会明显掉帧。后来我在线上环境实测数据量上万时如果不做任何优化整个页面交互会变得不跟手。我的建议是给折线系列加上数据采样series: [ { type: line, sampling: lttb, data: data } ]lttb是一种保留曲线形态的降采样算法相比简单抽稀它能在数据量很大时尽量保留波峰和波谷。如果业务允许也可以把动画关掉animation: false在实时刷新场景里动画没有意义反而浪费性能。5.5 下限高于上限差值出现负数这是动态上下限方案里最常见的数据异常。比如数据库里某条数据的 lower 写错了比 upper 还大未经过滤直接传给图表差值就是负数。堆叠后上限位置反而比下限低视觉上就会出现一个向下“漏”的异常区域。我的处理策略是在数据预处理阶段做防御性检查。如果upper[i] lower[i]就把该点的差值置为 0并在 console 里打一条 warning。这样图表不会画崩问题点也方便后端排查。如果你要求严格甚至可以把整条数据标记为异常在前端用 different 颜色展示但这就超出本文的范围了。6. 再进一步把图做得更像个正经看板6.1 让上下限边界更清晰线与区域共存有些需求里上下限不仅要有区间色带边界线也要清楚可见。比如业务方希望一眼看出当前值距离上限还有多少差距那上限线就不能完全透明。实现上差值系列负责画区域下限系列负责画区域底部但上限线本身没有独立 series。想让上限线也显示出来有几个变通做法。比较简单的是给差值系列设置一条透明的 lineStyle这样区域是有的但上限边界不明显。如果你想让上限线显示可以再从 diff 数组推导出 upper 数组增加一个辅助系列{ name: 上限, type: line, stack: range-overlay, data: upper, symbol: none, lineStyle: { color: #91caff, type: dashed, opacity: 0.6 }, areaStyle: undefined }但要注意这个“上限”系列不能再跟前面的 range 系列使用同一个 stack否则会把区域叠加逻辑打乱。我这里写的 stack 名故意不同就是为了单独画一条不上色的边界线。如果你觉得加系列太麻烦也可以直接用 markLine 在 series 上画一条动态上限线。markLine 的 data 支持回调函数可以返回每个数据点的 yAxis 坐标不过这种写法要繁琐一些一般用在上限是一个分段常量而不是连续曲线的场景。6.2 增加交互缩放、数据区域选择、图例联动到了真正做看板这一步单纯一个静态图是远远不够的。业务方大概率会要求“能不能看一下最近 3 天”“能不能把某一天的数据拉出来细看”。ECharts 的 dataZoom 组件在这个场景特别实用。如果时间点很多可以在 option 里加上dataZoom: [ { type: inside, start: 0, end: 100 }, { type: slider, height: 20, bottom: 10 } ]type: inside支持鼠标滚轮缩放type: slider在底部显示一个可拖动的滑块条。上下限区域和折线会同步缩放这个交互体验非常自然。加了 dataZoom 之后我之前提到的大数据量性能优化就更有必要了因为缩放过程中 ECharts 会大量重绘。图例联动也值得一提。当 user 在 legend 里点击“实际值”隐藏折线时如果上下限区域还在看起来会有点怪。我在之前的方案里让 limit 系列都在同一个 stack 下图例点击任一辅助系列会导致整块区域显示异常。为了避免这个问题我的做法是不在 legend 里暴露辅助系列把 legend 只留给实际业务指标这样用户不会误操作到区域系列。6.3 和地图、饼图等组件的组合思路如果你在做的是一个完整的数据可视化大屏那上下限区域折线图通常只是其中一块。把折线图和 ECharts 地图、饼图组合在一个页面里是很常见的大屏布局思路。组合时我最关注的是图表实例的管理。每个图表都要有一个独立 DOM 容器并且用echarts.init分别初始化。页面销毁时要调用chart.dispose()否则刷新页面或切换路由时会造成内存泄漏。多个图表之间如果需要进行联动比如地图点击某个省份后折线图只展示该省份的数据那就要在事件回调里再次对折线图setOption。这个场景下我建议把折线图的 series 数据生成封装成一个函数入参是省份 ID输出是完整的 option这样联动逻辑就非常清晰不用在回调里堆一堆数据处理代码。还有一个小经验大屏一般用固定尺寸设计稿但实际屏幕分辨率不一我会在容器 resize 时统一调用每个 chart 的resize()方法并且用防抖限制触发频率不然一拉浏览器窗口整个页面的图表会集体闪烁。最后聊聊我个人的工程习惯做这类区间图做得多了以后我现在反而不太喜欢在业务代码里堆大段 ECharts 配置。我会把“上下限区域折线图”封装成一个通用组件输入只需要三个数组实际值、上限值、下限值和对应的时间轴内部自己完成数据校验、差值计算、辅助系列构建和 tooltip 格式化。这样不同页面要使用相同能力的图表时改动成本极低也不容易因为复制粘贴代码漏掉哪个配置项。再分享一个小技巧调试上下限区域时先用几组极端数据在本地测一遍比如全 0、负值、下限等于上限、单个点缺数据确认图表不会报错或画出奇怪形状。上线前一天我会把这几个用例重新跑一次因为接口数据是不可控的防御性处理做在前端能省下很多夜间告警的麻烦。如果你在实现过程中遇到区域颜色不对、上下限线看不到或者更新数据后残留区域这类问题优先检查 series 顺序、stack 配置和 setOption 的 merge 参数这三板斧能解决大部分怪相。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑