拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ECharts桑基图从入门到实战:用户路径分析与避坑指南

ECharts桑基图从入门到实战:用户路径分析与避坑指南 做用户行为路径分析的产品同学找我救火那年我被桑基图狠狠上了一课。当时的需求很朴素想看用户从首页进来之后到底是怎么一步步流到下单页面的中间从哪儿跳走的、从哪儿回头的全都要看得见。我第一反应是用漏斗图结果一脚踩空——漏斗图只能呈现单线条的层层转化用户从详情页回退到列表页再逛到店铺页这种横跳路径它根本画不出来。后来切到Echarts的桑基图一条条带流向的粗细带子铺开产品同学当场就懂了。桑基图这个图种说穿了就是解决流量分流与去向这件事的它天生适合能源流向、资金链路、用户路径、物料转化这类有明确流向关系的场景。这篇文章我打算把桑基图从头到尾讲透数据怎么拼、配置怎么调、坑怎么避尤其是入门阶段最容易被卡住的那几个点。不管你是刚接触Echarts、只画过柱状图和饼图的新手还是已经在大屏项目里摸爬滚打过的老手看完应该都能直接上手干活。1. 桑基图到底解决什么业务问题1.1 什么时候你该考虑上桑基图判断一个需求到底适不适合用桑基图我通常只看一条这个数据里是否存在从一个节点流向另一个节点的关系并且同一个节点既可能接收流量、也可能分发流量。用户从首页进入部分去了搜索页、部分去了活动页搜索页的人又有一部分回到首页、一部分进了详情页——这就是典型的流转结构有分叉、有汇合、有回流。漏斗图处理不了分叉和汇合饼图更不行只有桑基图能把这种多对多的流向关系可视化出来。再往具体业务里套一下。电商的转化路径分析从曝光、点击、加购、下单到支付每个环节都可能流失到其他页面用桑基图能一眼看出哪条路径的转化效率最高。供应链和物流领域原料从供应商流向工厂、半成品流向组装线、成品流向仓库和门店中间的量级差异用带宽直接表示采购和库存同事看着就明白。能源、碳排放、资金流向这些领域更是桑基图的主场因为它们本质上就是流量分配问题。反过来说如果你的数据只是每个类目的销量对比或者每个月的收入趋势那千万别硬套桑基图。我见过有人把五个并列的类目做成桑基图结果节点之间连不出有意义的连线图上一堆孤立的色块纯粹属于为了高级而高级。桑基图的门槛不在代码而在数据结构本身是否适配。1.2 桑基图、漏斗图、和弦图的选型边界三种图经常被拿来比较我把它们的选择逻辑整理成一张表你对着自己的数据形态直接套就行。图种适合的数据结构典型场景不适合什么漏斗图单链条、层层递减注册转化、支付转化有分叉、有回流、多入口桑基图多节点、有向流转、可分流可汇合用户路径、资金流向、物料流转节点间无流向关系和弦图双向对称关系、强调两两之间的强度群体交往、地区贸易互流单向流转、量级差异大我个人的判断顺序是先问有没有流向有流向再问是不是单向为主是单向多节点就用桑基图如果数据是两两之间互相流转且节点数量少比如五个城市之间的人口迁徙那和弦图会更优雅。桑基图最强的点是它能承载量的比例带宽粗细直接对应value大小观众不需要看数字就能感知到主次。这一点在做大屏汇报的时候特别吃香领导一眼就知道钱主要花在哪个环节了。还有一个容易被忽略的点桑基图对层级有天然的隐式表达。Echarts会按照数据里的流向关系自动把节点分层排列你不需要手动指定每个节点在第几列。这对于用户路径这种层数不确定的场景省了非常多布局工作量。但代价是一旦数据里出现回路比如A流向BB又流回A整张图就会出问题这个后面讲排查的时候会重点说。2. 桑基图的数据结构与核心配置拆解2.1 nodes与links两个数组撑起全部逻辑桑基图的全部数据就两样东西data节点和links连线。节点是一个对象数组每个对象最少要有name字段连线也是一个对象数组每条线要有source、target、value三个字段。结构看着简单但这里藏着一个新手最容易翻车的坑。// 正确写法source/target 必须和 data 里的 name 完全一致 option { series: [{ type: sankey, data: [ { name: 首页 }, { name: 搜索页 }, { name: 详情页 }, { name: 下单页 } ], links: [ { source: 首页, target: 搜索页, value: 100 }, { source: 首页, target: 详情页, value: 60 }, { source: 搜索页, target: 详情页, value: 70 }, { source: 详情页, target: 下单页, value: 50 } ] }] };source和target引用的是节点的name不是索引也不是id。如果你写的名字和data里差一个字甚至多一个空格这条连线就会直接消失不会报错也不会提示。我第一次用的时候就栽在这上面排查了半小时才发现是首页 多了一个尾随空格。所以我现在的习惯是节点名字统一从一个常量数组里取绝不手写第二遍。下面这段是我常用的写法从业务数据里抽节点名保证连线引用不会对不上。const steps [首页, 搜索页, 详情页, 下单页]; const nodeSet new Set(steps); const data steps.map(name ({ name })); // 构造 links 时做一层校验防止脏数据混进来 const rawLinks [ { source: 首页, target: 搜索页, value: 100 }, { source: 首页, target: 详情页, value: 60 }, { source: 搜索页, target: 详情页, value: 70 }, { source: 详情页, target: 下单页, value: 50 } ]; const links rawLinks.filter( l nodeSet.has(l.source) nodeSet.has(l.target) );多加这一层filter能把上游统计系统里冒出来的脏节点直接拦掉。线上项目里数据来源往往不干净多一点防御性代码比事后救火省事得多。2.2 布局参数nodeAlign、nodeGap、nodeWidth怎么定节点位置和整体观感主要由几个参数控制我把它们的默认值和实际调优经验整理在一起。nodeWidth默认是20指的是节点色块的宽度nodeGap默认是8是同一层相邻节点之间的垂直间距nodeAlign控制节点在水平方向的排布方式可选left、right、justify默认是justify。nodeAlign是这三个里最需要动脑子的。justify会把起点节点尽量靠左、终点节点尽量靠右中间节点按比例对齐适合绝大多数从左到右递进的场景。left是让所有节点都尽量贴左会让图看起来更紧凑但层次感弱一些。right同理贴右。如果你的桑基图是从左往右读的流程我建议保持默认的justify。nodeGap和nodeWidth要配合画布尺寸一起调。大屏上节点多的时候我把nodeGap从8调到4能让更多节点挤进同一列而不重叠如果画布横向空间紧张把nodeWidth从20调到12连线区域会相应变宽流向带子看起来更舒展。有个经验值可以借用单列节点数超过8个的时候nodeGap调到4到6之间视觉上不容易糊成一片。orient参数控制整体方向默认horizontal水平流向设成vertical就变成从下往上的垂直流向。垂直桑基图在移动端窄屏上挺好用因为手机屏幕竖长垂直布局能充分利用纵向空间。不过垂直布局下标签位置要跟着改否则字会叠在带子上。2.3 连线与标签lineStyle、label的常见改法连线样式在lineStyle里配。color最常用的值是source、target或gradient分别表示连线跟着起点节点颜色、终点节点颜色、或者用渐变过渡。我做大屏时基本都用gradient从起点色渐到终点色视觉上更顺也不容易在交叉区域糊成一片。opacity默认是0.2这个值其实很讲究——调高了连线会盖住节点调低了流向感会变弱我一般控制在0.3到0.5之间。curveness控制连线弯曲程度默认0.5。调小会变直调大会更弯。流向复杂、交叉多的时候适当加大curveness能让交叉处的层次更分明减少视觉混淆。标签放在label里配最常改的是position和formatter。节点标签默认显示在节点旁边position: right是贴右侧left贴左侧inside放进色块里。节点名字长的时候我倾向用formatter加个省略处理或者配合tooltip把完整信息放在悬浮里。这里呼应一下热词里的echarts tooltip自动换行桑基图的tooltip如果内容长默认是不换行的需要手动配置extraCssText里的white-space: normal和max-width或者直接在formatter里返回带br/的字符串。这个细节做不好悬浮框会被撑得老长很难看。3. 从零实现一张用户流转桑基图3.1 环境准备与最小可运行框架不管你是用原生JS、jQuery那套老项目还是Vue3、React这种现代框架桑基图的引入方式都一样先把Echarts实例挂到DOM上。我下面给的是最朴实的一条路不依赖任何构建工具方便你直接扔进一个HTML文件里跑起来验证想法。!DOCTYPE html html head meta charsetutf-8 / title桑基图验证/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script /head body !-- 容器必须有明确的高度否则图不会渲染 -- div idsankey stylewidth: 1000px; height: 600px;/div script const chart echarts.init(document.getElementById(sankey)); // option 在下一节给出 /script /body /html这里有个必须强调的点Echarts的容器一定要有明确的高度行内style或者CSS里都行。我见过太多新手把容器写成空div宽度高度都是0图死活不出来然后以为是代码写错了。其实Echarts是往一个固定尺寸的画布里画画布没尺寸它就无从下手。宽度用百分比没问题但高度必须给定具体值这一条在多框架项目里同样适用。如果你在Vue3里用还要注意热词里提到的pxtorem对echarts没起到效果这个经典问题。原因是Echarts渲染用的是canvas像素不是普通DOMpostcss-pxtorem的转换只作用于CSS里的px管不到canvas。解决办法是在Echarts的配置和init尺寸里手动换算或者直接用rem单位算好宽高再传给init。我个人的做法是写一个统一的尺寸换算函数resize的时候重新调一次。3.2 数据准备构造一条真实的转化链路为了让演示贴近真实业务我构造一条从活动页/首页双入口到下单的完整链路中间带回流。数据来源于常见的埋点统计口径节点名我用中文方便你对着改。const nodes [ { name: 活动页 }, { name: 首页 }, { name: 搜索页 }, { name: 详情页 }, { name: 购物车 }, { name: 下单页 } ]; const links [ { source: 活动页, target: 详情页, value: 120 }, { source: 活动页, target: 首页, value: 40 }, { source: 首页, target: 搜索页, value: 90 }, { source: 首页, target: 详情页, value: 60 }, { source: 搜索页, target: 详情页, value: 70 }, { source: 搜索页, target: 首页, value: 20 }, { source: 详情页, target: 购物车, value: 80 }, { source: 详情页, target: 搜索页, value: 15 }, { source: 购物车, target: 下单页, value: 65 }, { source: 购物车, target: 详情页, value: 12 } ];注意搜索页→首页和详情页→搜索页这两条回流线。桑基图允许回流存在只要整体不构成环就行。所谓环是A到B、B又到C、C再回到A这种闭环。回流线本身如果只是往后指Echarts能画出来但节点分层会变得有点乱读者要多花点时间理解。所以我在真实项目里如果回流不是分析重点通常会把它们聚合成一个流失节点让主线更清爽。值的大小也有讲究。桑基图里连线带宽和value成正比同一层所有流出线的value之和等于流入该层的总量这是一种守恒关系。如果你发现某条线特别粗、和旁边细线差了几十倍视觉上会很难看。这种情况下可以考虑把特别小的值合并成其他或者对数化处理但要注意对数化会改变量的真实感知汇报时要说明。3.3 完整配置与逐步调试过程配置我按先能跑、再好看、最后能讲的顺序给。const option { title: { text: 用户流转路径分析, left: left, textStyle: { fontSize: 16, fontWeight: normal } }, tooltip: { trigger: item, triggerOn: mousemove, extraCssText: max-width: 320px; white-space: normal; }, series: [{ type: sankey, left: 5%, right: 15%, top: 50, bottom: 20, nodeWidth: 16, nodeGap: 10, nodeAlign: justify, orient: horizontal, draggable: true, emphasis: { focus: adjacency }, data: nodes, links: links, lineStyle: { color: gradient, opacity: 0.35, curveness: 0.5 }, label: { position: right, fontSize: 12, color: #333 }, levels: [ { depth: 0, itemStyle: { color: #5B8FF9 }, lineStyle: { color: source, opacity: 0.3 } }, { depth: 1, itemStyle: { color: #5AD8A6 }, lineStyle: { color: source, opacity: 0.3 } }, { depth: 2, itemStyle: { color: #F6BD16 }, lineStyle: { color: source, opacity: 0.3 } } ] }] }; chart.setOption(option);levels这一项是让桑基图从能看变好看的关键。它按深度depth给不同层级的节点和连线统一样式不用一个个节点去设颜色。depth 0是最左边那层往后依次递增。三层配色一铺整张图的层次感立刻就出来了。draggable: true默认就是开的意思是节点可以用鼠标拖动。这个功能在调试时特别有用——有时候自动布局的连线交叉难看手动把某个节点上下挪一挪交叉就解开了。但发布到正式环境前我建议产品确认一下要不要保留拖拽因为用户不小心拖乱之后不知道怎么复位。如果不想让用户拖设成false就行。emphasis.focus: adjacency是桑基图体验上的点睛之笔。鼠标悬浮某个节点时只有跟它直接相连的节点和连线会高亮其他的会变暗。做路径分析的时候这个交互能让观众顺着一条链看下去比静态图信息传达效率高得多。调完这几步一张能直接拿去汇报的桑基图就成型了。我一般还会调一下right留白因为最右侧的节点标签容易超出画布把right设成15%左右给标签留位置是个很实用的小习惯。4. 企业级大屏里的桑基图优化4.1 视觉定制与主题适配大屏项目对桑基图的要求跟普通报表完全两回事。报表讲究清晰大屏讲究氛围。深色底是大屏标配节点和连线在深色背景下要重新调色否则蓝色系节点放在深蓝底上几乎看不见。我的做法是准备两套配色浅底用饱和度高的色深底用带发光感的亮色或者自定义色板。节点颜色可以通过每个节点的itemStyle.color单独指定也可以通过levels按层指定。连线如果用gradient颜色会自动取起点和终点的色做过渡深色背景下这个过渡效果很好看。如果想让连线带点发光感可以给lineStyle加shadowBlur和shadowColor虽然Echarts对连线阴影的支持不算特别强但配合深底还是能做出一点科技感。标签在深色背景下记得改成浅色label.color设成#fff或者淡灰否则字会看不见。节点色块里的标签还要注意对比度深色节点配白字、浅色节点配黑字这个不要一刀切。还有预算和资源的考量。桑基图的带子和节点在大屏上通常要画得比较粗视觉效果才饱满所以nodeWidth往往比报表里大我会调到20到30之间。但节点一粗同一列的节点数量就得控制超过10个就要考虑分成多张图展示或者做成可切换的分页。4.2 响应式与大数据量性能处理响应式这块核心就一件事监听容器尺寸变化然后调用chart.resize()。浏览器窗口变化用window.addEventListener(resize, ...)但如果你的桑基图放在一个会伸缩的布局容器里比如侧边栏收起展开window的resize事件不一定触发这时候要用ResizeObserver去监听容器本身。Vue3项目里我通常封装一个指令容器变化就resize不用手动到处加监听。大数据量是桑基图最头疼的问题。节点上百个、连线几百条的时候渲染会明显变慢而且节点挤在一起标签互相重叠基本没法看。我处理过的几个真实案例节点数落在30到50之间效果最好超过60个就要想办法。手段无非几种按value阈值合并小流量节点为其他限制展示层数比如只展示前四层把桑基图和下钻结合先看总览再点击某个节点展开细节。还有一个优化点是关闭不必要的动画。Echarts桑基图默认有初始动画数据量大的时候这个动画会很卡。可以设置animation: false直接关掉或者把animationDuration调短。大屏上如果数据是周期性刷新的动画反而会分散注意力关掉更干脆。另外提醒一句桑基图的数据更新用setOption时如果传入新数据默认会做合并节点位置可能不会重新计算。要保证布局完全刷新可以在setOption里加第二个参数{ notMerge: true }或者更新前先clear()。这个坑我踩过一次表现为新数据进来了但连线还指在旧位置上挺隐蔽的。5. 桑基图常见问题排查实录5.1 数据类问题速查表桑基图的问题八成出在数据上。我把踩过的和帮别人解决过的整理成一张速查表对着症状找原因比盲目翻文档快得多。症状可能原因解决办法图完全空白容器无宽高 / 数据为空给容器明确宽高打印数据看是否为空部分连线不显示source/target和节点name对不上用Set校验统一从常量取节点名控制台报环错误数据里存在A→B→A闭环找到环并打断或把回流聚合成独立节点带宽和数值不成比例value里有0或负数过滤掉非正数的link负数一般无意义节点顺序很奇怪节点名重复导致合并保证每个节点name唯一环的问题值得单独说一句。Echarts对桑基图的数据是有向无环图的要求一旦检测到环它会直接抛错整个图不渲染。所以拿到上游数据的第一件事应该是做一次环检测。我一般用拓扑排序的思路快速验一下不断删掉入度为0的节点如果最后还有节点没被删掉说明存在环。这个校验逻辑写一次能省掉大量线上事故。5.2 渲染与交互问题速查表症状可能原因解决办法标签超出画布被截断right/left留白不足加大series的right值悬浮框被撑得特别长tooltip默认不换行extraCssText里设white-space和max-width节点重叠在一起nodeGap太小或节点太多调大nodeGap或合并小节点拖动后布局乱了没法复位draggable开着但无复位入口提供重置按钮重新setOption深色背景下字看不见label颜色没改label.color改成浅色tooltip换行这个我再补一句实战写法。默认的tooltip是一行到底内容一多就往旁边拉。加上extraCssText: max-width: 320px; white-space: normal; word-break: break-all;之后它会自动折行。如果tooltip内容是自己用formatter拼的也可以直接在字符串里插br/手动控制断行位置这样排版更可控。5.3 我踩过的几个坑第一个坑是节点命名的隐式合并。桑基图里如果两个节点name一样会被当成同一个节点。我有次做多城市对比节点名直接用了北京结果三个北京分属不同路径全被合并成了一个导致流向完全错乱。解决办法是给节点名加前缀区分比如北京-入口、北京-详情显示的时候再用formatter去掉前缀。第二个坑是数据刷新不重排。前面提过的notMerge问题具体表现是新数据进去之后节点还停在老位置连线交叉得很难看。我现在的标准写法是更新数据时带{ notMerge: true }或者干脆clear()之后重新setOption。多这一行能避免很多为什么图看着不对的困惑。第三个坑是关于层级的理解偏差。Echarts会按流向自动分层但分层的判断依据是数据里的连接关系。如果你希望某个节点强制排在特定位置光靠数据可能达不到需要调整数据里的连接关系或者接受它的自动布局。桑基图不像流程图那样可以手动摆放节点这一点在需求评审阶段就要跟产品说清楚免得后期扯皮。第四个坑是value的量级。有次上游给的数据里夹杂了负值表示退货桑基图直接不画那条线。后来我们把退货单独做成一个节点用正数表示退货量才正常显示。桑基图的value必须是正数这个约束记牢设计数据口径的时候就要考虑进去。桑基图这个图种入门看着挺唬人真正卡人的其实就那几处数据的两数组结构、name的严格对应、不能有环、value为正。这四点过了剩下的都是调样式的手感活。我现在做用户路径类需求第一版基本就是上面这套配置跑起来然后花时间在合并小节点和调留白上。如果你也在做大屏或者数据分析类项目桑基图值得专门花半天时间摸熟它在你需要讲清楚流量去了哪儿的时候能顶得上十张表格。
返回列表