ECharts瀑布图实战:透明底柱堆叠实现与避坑指南

发布时间:2026/11/3 18:07:00

ECharts瀑布图实战:透明底柱堆叠实现与避坑指南 瀑布图这个图表类型我在数据可视化项目里用得不算最多但每次用到都是关键场景——财务的利润拆解、用户的增长归因、库存的出入库流水这些需要看清楚每一步怎么从起点走到终点的需求普通柱状图根本表达不了。ECharts 官方示例库里没有现成的瀑布图类型很多人第一次做的时候会卡在怎么让柱子悬空这个点上。这篇就把我用 ECharts 绘制瀑布图的完整思路拆开讲从数据结构的转换、透明底柱的实现到颜色语义、标签对齐、负值处理这些细节全部落到可以直接复现的程度。不管你是刚接触 ECharts 的新手还是做过不少图表但没碰过瀑布图的老手看完都能直接拿去改改就用。1. 瀑布图到底解决什么问题为什么值得单独做1.1 从结果到过程的表达缺口常规柱状图擅长表达每个类别的值是多少但它天然丢失了一件事这些值之间是怎么累加、抵消、传导的。举个财务场景一个季度从期初利润 120 万走到期末利润 180 万中间经历了收入增长 80 万、成本上升 -50 万、税费 -20 万、其他收益 50 万。你把这五个数字画成普通柱状图读者看到的是五根独立的柱子得自己在脑子里做加减法才能还原出120 怎么变成 180的过程。而瀑布图的做法是第一根柱子从 0 立到 120后面每一根柱子都从前一根的顶部接着往上或往下长最后一根落到 180。整个过程像瀑布一样层层跌落或抬升一眼就能看出哪一步贡献最大、哪一步在拖后腿。这个表达能力的差异决定了瀑布图在归因分析类场景里几乎不可替代。我见过的典型用法包括营收的同比变化拆解哪些产品线拉高、哪些拉低、用户留存的漏斗式衰减、现金流的进出流水、项目预算的执行偏差分析。这些场景的共同特征是有一个起点和一个终点中间是一串有正有负的变化量只要符合这个特征瀑布图就是最合适的表达形式。1.2 ECharts 没有瀑布图类型这不是缺陷很多人第一次查 ECharts 文档发现 series 里没有 waterfall 这个 type会觉得是不是版本不够新。其实不是ECharts 的设计哲学是提供基础图形原语bar、line、scatter 等复杂图表通过组合和配置来实现。瀑布图的本质就是一组堆叠柱状图只不过其中一根是透明的底座用来把可见的柱子顶到正确的高度。理解了这一点实现路径就清晰了用两个 series 做堆叠一个负责撑高度但不可见一个负责显示实际色块。这种用基础图形拼复杂图表的思路其实是 ECharts 最值得掌握的能力之一。你学会了瀑布图的做法同样的堆叠透明底柱技巧可以迁移到甘特图、区间柱状图、漏斗图的自定义版本上。所以这篇不只是教一个图表更是讲清楚一类组合思路。1.3 适合谁来读这篇如果你手头正好有一个起点-变化-终点的数据要展示直接抄本文的配置就能跑如果你对 ECharts 的 series 堆叠机制还不熟本文会把 stack 的工作原理讲透如果你已经能做瀑布图但总在颜色、标签、负值这些细节上翻车第 4 节的排查表应该能帮到你。下面从设计思路开始一层层往下拆。2. 整体设计思路透明底柱 可见柱的堆叠组合2.1 核心原理把悬空翻译成堆叠瀑布图里那些悬在半空的柱子在 ECharts 里并不存在真正的悬空概念——柱状图的柱子永远是从坐标轴基线通常是 0开始画的。那怎么让一根柱子看起来是从 120 的高度开始、长到 200 呢答案是堆叠先画一根从 0 到 120 的透明柱子再在它上面堆一根从 120 到 200 的可见柱子。因为两根柱子 stack 相同ECharts 会把它们累加第二根的起点自动落在第一根的顶部。透明柱子看不见视觉上就只剩下那根从 120 到 200 的可见部分悬空效果就出来了。这个转换是理解整个实现的关键。你可以把它想成搭积木底下垫一块看不见的砖上面放一块看得见的砖最终看到的高度是两块砖叠起来的总高但视觉焦点只在上面那块。所以每一根可见柱子的起点高度和高度值这两个信息必须提前算好分别喂给透明 series 和可见 series。2.2 数据结构的设计原始数据要经过一次转换原始业务数据通常长这样一个标签加一个变化量比如{name: 收入增长, value: 80}。但 ECharts 需要的是两组数值透明底柱的高度起点和可见柱的高度变化量的绝对值。所以中间要做一次遍历转换维护一个当前累计高度的游标每处理一项就更新游标。这个转换逻辑是整个瀑布图的大脑写错了后面全错第 3 节会给出完整代码。这里有个容易忽略的点可见柱的高度要用变化量的绝对值因为柱子的长度不能是负数方向向上还是向下由起点和终点的相对位置决定而不是由数值正负直接决定。比如成本上升 -50可见柱高度是 50但它应该从当前高度往下长也就是它的起点是当前高度 - 50终点是当前高度。这个符号处理是新手最容易翻车的地方我在 4.1 节专门列了排查方法。2.3 为什么不用自定义系列custom series实现ECharts 的 custom series 确实可以画出任意形状理论上也能做瀑布图而且不用算透明底柱。但我实测下来不推荐原因有三一是 custom series 的 renderItem 函数写起来复杂坐标换算、图形绘制都要手动处理调试成本高二是它丢失了 bar 系列自带的很多能力比如 tooltip 的自动触发、label 的自动定位、emphasis 高亮效果这些都要自己重写三是可维护性差团队里其他人接手时看不懂。用堆叠 bar 的方案配置全是声明式的改颜色改标签都是改配置项维护成本低得多。除非你有非常特殊的视觉需求比如柱子要做成箭头形状否则堆叠方案是更务实的选择。2.4 颜色语义的设计原则瀑布图的颜色不是随便配的它承载语义增加项、减少项、汇总项起点和终点通常要用三种不同的颜色区分。我的习惯是增加用绿色系、减少用红色系、汇总用中性色深灰或品牌主色。这里要注意色盲友好性纯红纯绿对红绿色盲不友好可以用偏蓝的绿和偏橙的红来替代。另外汇总项的柱子通常从 0 开始画不悬空视觉上作为锚点让读者知道这是绝对值而不是变化量。这个区分很重要否则读者会误以为起点也是一个变化量。3. 完整实操从数据转换到图表配置3.1 第一步原始数据与累计游标转换先定义原始数据每项包含名称、变化值、类型increase / decrease / total。转换时维护一个current变量表示当前累计高度遍历每一项如果是 total 类型透明底柱设为 0可见柱设为该值本身并把 current 重置为该值如果是普通变化项透明底柱设为min(current, current value)可见柱设为Math.abs(value)然后current value。这里用 min 是为了处理负值——当变化量为负时起点应该是变化后的较低位置柱子从那里往上长到原来的高度。const rawData [ { name: 期初利润, value: 120, type: total }, { name: 收入增长, value: 80, type: increase }, { name: 成本上升, value: -50, type: decrease }, { name: 税费支出, value: -20, type: decrease }, { name: 其他收益, value: 50, type: increase }, { name: 期末利润, value: 180, type: total } ]; let current 0; const baseData []; // 透明底柱 const visibleData []; // 可见柱 rawData.forEach(item { if (item.type total) { baseData.push(0); visibleData.push(item.value); current item.value; } else { const start Math.min(current, current item.value); baseData.push(start); visibleData.push(Math.abs(item.value)); current item.value; } });跑完这段baseData 是[0, 120, 150, 130, 110, 0]visibleData 是[120, 80, 50, 20, 50, 180]。你可以手动验算一下收入增长从 120 涨到 200底柱 120、可见 80加起来顶部在 200对成本上升从 200 降到 150底柱取 min(200,150)150、可见 50柱子从 150 长到 200视觉上是从 200 往下掉 50 的效果对。这个验算习惯建议保留数据转换错了图表再漂亮也是错的。3.2 第二步双 series 堆叠配置有了两组数据配置两个 bar seriesstack 设为同一个值比如 waterfall第一个 series 的 itemStyle 颜色设为透明第二个 series 按类型给颜色。关键配置项barGap: -100%让两个 series 的柱子完全重叠否则会并排显示silent: true让透明 series 不响应鼠标事件否则 tooltip 会触发在透明柱上。option { tooltip: { trigger: axis, axisPointer: { type: shadow }, formatter: params { const visible params.find(p p.seriesName visible); const raw rawData[visible.dataIndex]; const sign raw.value 0 ? : ; return ${raw.name}br/变化量: ${sign}${raw.value}; } }, xAxis: { type: category, data: rawData.map(d d.name), axisLabel: { interval: 0, rotate: 0 } }, yAxis: { type: value }, series: [ { name: base, type: bar, stack: waterfall, itemStyle: { color: transparent }, emphasis: { itemStyle: { color: transparent } }, silent: true, data: baseData }, { name: visible, type: bar, stack: waterfall, barGap: -100%, data: visibleData.map((v, i) ({ value: v, itemStyle: { color: getColor(rawData[i].type) } })), label: { show: true, position: top, formatter: params { const raw rawData[params.dataIndex]; if (raw.type total) return raw.value; return (raw.value 0 ? : ) raw.value; } } } ] };getColor函数按类型返回颜色比如 increase 返回#4caf50、decrease 返回#f44336、total 返回#37474f。注意 label 的 formatter 里要区分 total 和变化项total 显示绝对值不带正负号变化项带正负号这样读者一眼能分清哪些是汇总、哪些是增减。3.3 第三步负值柱子的标签位置修正上面配置有个细节问题当变化量为负时可见柱是从下往上画的因为底柱在较低位置label 的 position: top 会显示在柱子顶部也就是变化前的那个高度视觉上会让人误以为标签对应的是变化后的值。更合理的做法是负值柱的标签显示在底部position: bottom或者统一用 inside 放在柱子内部。我一般用动态 position正值 top、负值 bottom通过 label 的 formatter 无法改 position需要在 data 的每一项里单独设置 label.position。data: visibleData.map((v, i) { const raw rawData[i]; return { value: v, itemStyle: { color: getColor(raw.type) }, label: { position: raw.type total ? top : (raw.value 0 ? top : bottom) } }; })这样负值柱的标签就贴在柱子下沿读者看到的是从这里往下掉了多少语义更准确。这个细节很多教程会漏掉但实际做出来如果负值标签位置不对整个图的可读性会打折扣。3.4 第四步连接线的补充可选增强有些瀑布图会在相邻柱子之间画虚线连接帮助读者追踪高度变化。ECharts 没有内置这个能力但可以用 markLine 或者额外的 line series 模拟。我的做法是用一个 line series数据取每根柱子的终点高度即 baseData[i] visibleData[i] 对于正值或 baseData[i] 对于负值设置symbol: none、lineStyle: { type: dashed, width: 1 }、silent: true让它作为背景装饰。不过要注意 line series 默认会连成一条折线而瀑布图的连接线应该是阶梯状的所以更精确的做法是用 custom series 画阶梯线或者干脆省略——我实测下来柱子本身已经足够表达变化连接线属于锦上添花数据项多的时候反而显得乱建议按需添加。4. 常见问题排查与避坑实录4.1 柱子位置全错九成是累计游标算错了这是最高频的问题。表现是柱子悬空位置不对、有的柱子跑到负值区、或者柱子之间出现不该有的间隙。排查顺序先打印 baseData 和 visibleData手动验算前两三项再检查 total 类型是否正确重置了 current最后检查负值项的 min 逻辑有没有写反写成 max 就全错了。我踩过的一个坑是在 total 项之后忘记重置 current导致后续变化项基于错误的起点计算整张图从第二段开始就歪了。建议在转换函数里加一行断言比如console.assert(Math.abs(current - expectedEnd) 0.01)开发阶段能快速定位。4.2 柱子并排显示而不是重叠如果两个 series 的柱子左右并排说明 barGap 没设或者设错了。堆叠的两个 series 默认会并排必须给第二个 series 设barGap: -100%才能完全重叠。注意这个属性要设在可见柱那个 series 上设在透明柱上无效。另外如果图表里还有第三个非堆叠 seriesbarGap 的作用范围会受影响需要单独调 barCategoryGap。4.3 tooltip 显示的是透明柱的数据默认情况下 tooltip 的 trigger: axis 会把同一类目下所有 series 的数据都列出来包括那个透明的 base series读者会看到一行莫名其妙的base: 120。解决办法有两个一是给透明 series 设tooltip: { show: false }二是在 tooltip 的 formatter 里过滤掉 seriesName 为 base 的项。我倾向后者因为 formatter 里本来就要做格式化顺手过滤更干净。4.4 负值柱子的视觉方向反了前面提过负值柱的可见部分是从下往上画的如果底柱的 min 逻辑写错柱子会从错误的位置往上长视觉上变成增加而不是减少。判断方法负值柱的顶部应该等于变化前的高度底部等于变化后的高度。如果发现负值柱的顶部比前一根柱子的顶部还高基本可以确定 min/max 用反了。这个错误在数据里正负混杂时特别隐蔽建议用一组包含连续负值的数据专门测一遍。4.5 数据项很多时 x 轴标签重叠瀑布图的数据项一多超过 8 项x 轴标签就会挤在一起。常规做法是axisLabel: { rotate: 45 }旋转但旋转后长标签还是会重叠。更彻底的办法是缩短标签文案用简称或编号或者改用横向瀑布图把 xAxis 和 yAxis 对调柱子水平排列横向布局对长标签更友好。如果业务上必须保留完整标签可以考虑axisLabel: { interval: 0, formatter: 截断函数 }配合 tooltip 显示全称。4.6 常见问题速查表现象最可能原因解决方向柱子悬空位置错乱累计游标未正确更新或 total 未重置打印 baseData 逐项验算柱子并排不重叠缺少 barGap: -100%在可见柱 series 上补该配置tooltip 多出透明柱数据透明 series 未屏蔽formatter 过滤或 tooltip.show: false负值柱方向反了min/max 逻辑写反底柱取 min(current, currentvalue)x 轴标签重叠数据项过多旋转、缩短文案或改横向布局柱子颜色不生效itemStyle 设在 series 层被 data 层覆盖检查 data 项里的 itemStyle 优先级5. 几个让图表更专业的细节打磨5.1 汇总柱的视觉强化起点和终点这两根汇总柱我通常会让它们从 0 开始画不悬空并且颜色用深色、宽度略宽通过 barWidth 单独设置视觉上作为锚点。这样读者一眼能区分绝对值和变化量。实现上total 项的 baseData 设为 0 即可宽度可以在 data 项里单独设barWidth。另外汇总柱的 label 建议加粗用fontWeight: bold进一步强化区分。5.2 零线的重要性瀑布图里 0 这条线是重要的参考基准尤其是当数据跨越正负时。建议给 yAxis 加axisLine: { show: true }或者用 markLine 画一条 0 线颜色用浅灰避免和柱子抢视觉。如果所有数据都是正值零线可以弱化一旦有负值零线必须清晰否则读者分不清柱子是在 0 以上还是以下。5.3 动画与交互的取舍ECharts 默认的柱状图动画是从 0 长出来但瀑布图的柱子是堆叠的动画过程中透明柱和可见柱的生长节奏如果不一致会出现柱子跳一下的观感。我的处理是给两个 series 设相同的animationDuration和animationEasing让它们同步生长。另外如果数据项多建议关掉 animation 或者缩短时长否则用户要等动画播完才能看清全貌。交互上emphasis 高亮可以保留但透明 series 要设emphasis: { disabled: true }避免鼠标移上去时透明柱突然显形。5.4 响应式与容器适配瀑布图对宽度比较敏感数据项多的时候在窄屏上会挤。建议给容器设min-width或者用 ECharts 的media配置做响应式窄屏时自动切换成横向布局、缩短标签、减小 barWidth。我一般会在 resize 事件里根据容器宽度动态调整axisLabel.rotate和barWidth比纯 CSS 媒体查询更精确因为 ECharts 的布局是 canvas 内部计算的CSS 管不到。5.5 数据精度与标签格式财务类数据经常有小数label 直接显示原始值会很长。建议在 formatter 里做格式化比如保留一位小数、加千分位分隔符、大数字用万作单位。注意格式化只影响显示不影响数据本身tooltip 里可以显示更精确的值。另外正负号的处理要统一增加项显示 减少项显示 -数值本身带负号汇总项不带符号。这个一致性看似小事但读者对符号很敏感不统一会显得不专业。6. 从瀑布图延伸出去的组合思路6.1 堆叠透明底柱这套技巧还能用在哪学会了透明底柱 可见柱的组合你会发现它能解决一类问题任何需要让柱子从非零位置开始的图表。比如甘特图任务条从开始时间的位置起画、区间柱状图显示一个范围而不是单值、进度对比图实际值叠加在目标值之上。这些图表的实现套路和瀑布图一模一样只是数据转换逻辑不同。所以这篇的价值不止于瀑布图本身更在于让你掌握一种 ECharts 的组合思维。6.2 瀑布图与折线图的叠加有时候业务方希望在瀑布图上叠加一条累计趋势线展示每一步之后的累计值。实现上就是在双 bar series 之外再加一个 line series数据取每根柱子的终点高度baseData[i] visibleData[i] 对正值baseData[i] 对负值注意 line series 不要参与 stack。这样读者既能看到单步变化柱子又能看到整体趋势折线。不过这种叠加会让图表信息密度变高建议只在数据项较少6 项以内时使用。6.3 动态数据下的重绘策略如果瀑布图的数据是动态更新的比如实时流水每次更新都要重新跑一遍数据转换然后 setOption。注意 setOption 默认是合并模式如果数据项数量变了比如新增了一项旧的 series 数据可能残留建议用setOption(option, true)做全量替换或者用notMerge: true。另外频繁更新时建议关掉动画避免视觉抖动。我在实际项目里做瀑布图最大的体会是数据转换那一步的严谨程度直接决定了后面所有工作的价值。图表配置再花哨如果 baseData 算错了一位整张图就是误导。所以每次做完我都会拿原始数据手动验算前两项和最后一项确认起点、终点、累计值都对得上再交付。另外颜色语义一定要在团队内统一增加绿、减少红、汇总深色这个约定一旦定下来就别随意改否则同一份报告里不同图表颜色含义不一致读者会困惑。最后一个小建议瀑布图的数据项控制在 5 到 8 项之间最舒服超过 10 项就该考虑拆分成多张图或者换一种表达方式了信息过载的瀑布图反而失去了它一眼看清过程的优势。
延伸阅读

更多相关文章

2026/11/3 18:07:00

LS-Reader与C++读取d3plot结果数据:原理、实现与避坑指南

简介:面向LS-DYNA仿真分析人员,这份压缩包聚焦于利用LS-Reader与C对d3plot结果文件进行数据提取与后处理,适用于水下爆炸、碰撞冲击等场景的深入分析。资源共18个文件,整体仅2.76MB,包含C源码(.cpp与.h文件…

2026/11/3 18:07:00

51单片机驱动MPU6050实现稳定角度输出

简介:本资源是一套基于STC89C52单片机实现MPU6050姿态解算的完整嵌入式开发工程,面向单片机初学者与嵌入式硬件实践者,解决51平台下惯性传感器数据融合与实时角度输出的核心问题。工程采用互补滤波算法融合加速度计与陀螺仪数据,稳…

2026/11/3 18:07:00

AI Agent场景下水印干扰:从概率分布到工具调用的全面解析

水印这东西,圈里聊得挺多,但大部分人关注点都落在"怎么给文本打标""怎么稳定检出"。我最近反复琢磨的是另一个问题:当水印不再是孤立文本的特征,而是混进AI Agent每分每秒的思考、规划和行动里,到…

2026/11/3 18:57:13

UVa 792 Program Modules

题目描述 给定一个程序中的所有函数及其直接调用关系,定义关系 A→BA \rightarrow BA→B 表示函数 AAA 直接调用 BBB,A⇒BA \Rightarrow BA⇒B 表示 AAA 可传递地调用 BBB(直接或间接)。已知 →\rightarrow→ 是 ⇒\Rightarrow⇒ …

2026/11/3 18:57:13

UVa 771 Flying Stars

题目描述 给定若干机场以及每日循环的航班信息。每个机场有固定的时区偏移和登机 / 转机耗时。从起点机场的某个本地时刻出发,需要经过一系列航班抵达终点机场。要求计算从出发时刻到抵达终点时刻的总旅行时间(以天、小时、分钟表示)&#xf…

2026/11/3 18:57:13

政务文稿场景中,AI辅助工具能做什么、不能做什么

组织人事和政务文稿工作有几个共同点:材料多、标准严、容错低、重复性工作占比高。AI辅助工具确实可以分担一部分工作,但它更适合做初筛、初稿和整理,不适合直接替代最终审核。一、文档校对:适合做规则检查,不适合做最…

2026/11/3 18:57:13

UVa 773 The JustaPox Language

题目描述 JustaPox\texttt{JustaPox}JustaPox 语言只处理整数数组,声明数组有两种方式: 显式指定大小:A is [1] 表示 A 是长度为 111 的数组,其值可以是任意一个长度为 111 的整数序列。通过包含已声明的数组:C is A B…

2026/11/3 18:52:12

LG发布2360亿参数K-EXAONE大模型:多语言+256K长上下文

LG发布2360亿参数K-EXAONE大模型:多语言256K长上下文 【免费下载链接】K-EXAONE-236B-A23B 项目地址: https://ai.gitcode.com/hf_mirrors/LGAI-EXAONE/K-EXAONE-236B-A23B LG电子旗下AI研究机构LG AI Research正式推出参数量达2360亿的大语言模型K-EXAONE-…

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 1:04:48

STM32实战:基于MAX14431数字隔离器的SPI隔离通信方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 1:04:48

工业SPI隔离通信设计:MAX14431BAEE+与STM32L4A6RG实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 1:04:48

STM32+NRF24L01无线抢答器:硬件设计、通信协议与低功耗实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑