尧图精选

用ECharts graph绘制流程图:从节点布局到动态数据联动实战

🕒 发布时间:2026/10/1 6:28:20 📁 来源:尧图网络
1. 为什么我会用echarts来画流程图先说个真实经历。大概一年前我接到一个内部数据平台的需求要在用户管理模块里展示完整的注册、登录、权限校验链路还要跟后端返回的实时数据联动比如每个环节当前的请求量、失败率、平均耗时。当时团队里有人提议用bpmn.js有人建议上antv X6还有人嫌麻烦说干脆用visio截图扔上去。我琢磨了一下最后选了echarts的graph系列。这个选择当时被同事质疑过毕竟echarts在大多数人印象里就是画柱状图、折线图、饼图的跟流程图八竿子打不着。但我的理由其实很现实项目里已经引入了echarts数据可视化的部分大量依赖它我不希望为了一个流程图再额外引入一套几百KB的渲染引擎而且需求里明确要求节点颜色、大小、文字内容要随着接口数据动态变化甚至点击某个节点要联动旁边的统计面板。这种“数据驱动展示型流程图”的场景echarts的graph系列反而比专业流程编辑工具更顺手。这里要先说清楚一件事echarts画的流程图和bpmn.js画的流程图本质上不是同一个东西。bpmn.js的核心价值在于流程定义的编辑、校验、序列化它面向的是“需要保存成标准BPMN模型”的业务流程设计场景比如OA审批流、工作流引擎配置。而echarts graph系列面向的是“把流程结构可视化展示出来”的场景它不做流程合法性校验也不负责生成可执行的流程定义文件但它胜在灵活、轻量、跟数据绑定极其自然。所以如果你接到的需求是“做一个流程图给领导看”“把系统模块调用关系可视化”“根据日志数据实时渲染调用链”那echarts完全够用而且体验不差。如果需求是“用户要拖拖拽拽自己设计流程还要保存成XML”那请直接选bpmn.js或logicflow别用echarts硬扛。这个边界想清楚了后面的技术选型才不会跑偏。2. 动手前的知识铺垫graph系列的核心概念在用echarts画流程图之前有几个基础概念必须先吃透。不然你很可能跟我第一次一样数据填进去图就是出不来或者出来了一团乱麻。2.1 节点、边和图结构echarts的graph类型关系图由两部分组成nodes数组和links数组你也可以写成edges。nodes里的每个对象代表图中的一个节点比如一个流程步骤、一个系统模块、一个人links里的每个对象代表一条连接关系通常包含source和target两个字段分别指向节点的id或下标。拿用户管理模块举例nodes可能是这样的结构const nodes [ { id: start, name: 开始, symbolSize: 60 }, { id: login, name: 登录, symbolSize: 60 }, { id: checkRole, name: 角色校验, symbolSize: 60 }, { id: adminPanel, name: 管理员面板, symbolSize: 60 } ]; const links [ { source: start, target: login }, { source: login, target: checkRole }, { source: checkRole, target: adminPanel } ];只要给option里配一个type: graph的series把这两个数组填进去就能渲染出最简单的流程图。但注意这只能得到最基本的效果节点按默认布局排布、连线是直线、没有箭头、没有各种流程形状。要做到像模像样的流程图还需要处理布局、造型和交互。2.2 理解布局layout属性是流程图成败的关键graph系列里有一个让人又爱又恨的属性layout。它有三种取值none、force和circular。第一次尝试用echarts画流程图的人十个有九个会栽在force上。力引导布局会让所有节点像带电粒子一样互相排斥同时连线又像弹簧一样把节点拉近整个图会不断抖动、自动寻找平衡位置。听起来很智能对吧但对于流程图这基本是灾难。你想表达“步骤A到步骤B再到步骤C”的严格顺序力引导布局却可能把C甩到A旁边整个流程的阅读顺序全部乱掉。所以我的建议很直接画流程图一律使用layout: none然后手动指定每个节点的x和y坐标。坐标系的原点在echarts图表的中心向右是x正方向向下是y正方向。虽然手动布局需要自己计算位置但这些付出是值得的——只有你能完全掌控节点的位置才能保证流程图的清晰性和可读性。除非你只是画一个简单的环形调用图或者节点之间没有明显的先后次序那circular布局倒是可以用一下把节点排成一个圈。2.3 流程图形状怎么做玩转symbol流程图的视觉灵魂在于各种形状圆角矩形表示起止、矩形表示处理步骤、菱形表示判断、平行四边形表示输入输出。但echarts的graph节点默认是圆形怎么折腾成流程图的样子答案是利用symbol属性。它支持内置的形状circle、rect、roundRect、triangle、diamond等也支持自绘SVG path或dataURI。做流程图最有用的是两个方向第一用内置形状。symbol: roundRect可以得到圆角矩形适合做开始/结束节点symbol: diamond可以得到菱形适合做判断节点。这几个内置形状虽然无法严格区分矩形和平行四边形但只要颜色、文字标注到位效果完全够用。第二用SVG path自定义形状。如果你想做得更专业比如结束节点用双圆线框或者处理节点要带阴影立体感就需要自绘symbol。比如我要画一个输入/输出感的平行四边形可以这样写symbol: path://M30,0 L70,0 L40,40 L0,40 Z这个path字符串的写法其实就是SVG的路径语法M移动到起点L画直线到指定坐标Z闭合路径。用这种方式你可以画出任何形状。要注意的是symbol默认的原点在图形左上角所以如果你想精确对齐位置可以通过symbolOffset微调。2.4 连线和箭头流程走向的表达有了节点和形状还需要箭头来指明流程方向。echarts graph的边默认是纯直线不带箭头。要加箭头需要配置links: [{ source: login, target: checkRole, lineStyle: { color: #333, width: 2, type: solid }, symbol: [none, arrow], // 数组第二项表示在target端加箭头 symbolSize: 10 }]这里symbol可以设成一个数组第一个值代表source端的箭头样式第二个值代表target端的箭头样式。通常我们在流程终点加箭头所以写[none, arrow]。此外如果你觉得直角折线更像流程图很多标准流程图画法确实是横平竖直的echarts graph本身不支持自动生成正交折线但你可以通过给links设置不同段的控制点和拐点来实现或者用一条折线polyline加多个点模拟。不过坦白说我在实际项目里很少折腾直角折线曲线其实更优雅阅读顺序感也更自然。只要箭头清楚别人不会纠结你是直角还是圆角。3. 核心实战从零开始做一个登录注册流程图理论铺垫得差不多了下面我带你完整走一遍实操。我的场景是“用户登录与权限分配流程”包含开始、输入账号密码、判断格式、请求后端、角色校验、管理员/普通用户分支、结束等多个节点。我们一步步把代码写出来。3.1 定义流程图结构和数据首先确定整个流程包含哪些步骤以及它们之间的连线关系。流程大致是这样开始 - 输入账号密码 - 格式校验判断 - 是则请求登录接口 - 后端返回角色 - 管理员分支/普通用户分支 - 结束这个流程里有判断节点有分支足够说明大部分问题。先把节点和边的数据定义好const nodes [ { id: start, name: 开始, x: 0, y: -240, symbol: roundRect }, { id: input, name: 输入账号密码, x: 0, y: -140, symbol: rect }, { id: checkFormat, name: 格式是否正确, x: 0, y: -40, symbol: diamond }, { id: request, name: 请求登录接口, x: 0, y: 60, symbol: rect }, { id: getRole, name: 解析角色信息, x: 0, y: 160, symbol: rect }, { id: isAdmin, name: 是否管理员, x: 0, y: 260, symbol: diamond }, { id: adminPanel, name: 管理员面板, x: -160, y: 360, symbol: rect }, { id: userPanel, name: 普通用户面板, x: 160, y: 360, symbol: rect }, { id: end, name: 结束, x: 0, y: 460, symbol: roundRect } ];这里我用x/y直接指定了每个节点的坐标整体呈纵向自上而下的布局判断节点出分支时左右分开。需要注意的是x:0并不是图表的最左边而是水平方向的中点所以负值表示靠左正值靠右。在实际项目中建议先画出流程草图标出每个节点的大致位置再转换成坐标值这样不容易乱。3.2 配置连线与逻辑分支边的定义同样直接const links [ { source: start, target: input }, { source: input, target: checkFormat }, { source: checkFormat, target: request, label: { show: true, formatter: 是 } }, { source: checkFormat, target: start, label: { show: true, formatter: 否 }, lineStyle: { type: dashed } }, { source: request, target: getRole }, { source: getRole, target: isAdmin }, { source: isAdmin, target: adminPanel, label: { show: true, formatter: 是 } }, { source: isAdmin, target: userPanel, label: { show: true, formatter: 否 } }, { source: adminPanel, target: end }, { source: userPanel, target: end } ];这里有两个细节值得展开说。第一个是判断分支的标签。格式不正确时我让连线回到“开始”节点用虚线表示并加上label显示“否”格式正确时用实线进到下一步。这样看的人一眼就能理解条件分支的语义。第二个是标签的显示。graph的边默认是不显示文字的需要单独配置label.show: true然后用formatter指定文字内容。如果分支多你还得考虑文字会不会重叠比如判断框出来的“是”和“否”方向要错开。简单办法是给label再加一个position: middle然后配合边的曲线偏移把标签推离连线中心。3.3 组装完整的series与option核心series这样写const series { type: graph, layout: none, data: nodes, links: links, roam: true, label: { show: true, position: inside, fontSize: 12, color: #333, formatter: function(params) { return params.name; } }, lineStyle: { color: #999, width: 2, curve: 0.2, type: solid }, emphasis: { focus: adjacency, lineStyle: { width: 4, color: #5470c6 } } };把series填进option再myChart.setOption(option)就能看到一张基础流程图了。这里我建议把roam设为true允许用户缩放和平移。流程图如果节点很多超出屏幕是常事没有roam的话用户只能干瞪眼。focus: adjacency这个配置也很关键。它实现了“鼠标悬停某个节点时只高亮与该节点直接相连的边和节点其他元素变暗”的效果。在复杂流程里这个交互能帮你快速看清某一步的前后依赖关系体验提升非常明显。3.4 代码层面的完整示例如果你只想要一个能直接用起来的最小demo可以参考下面这段代码const chartDom document.getElementById(main); const myChart echarts.init(chartDom); const nodes [ { id: start, name: 开始, x: 0, y: -240, symbol: roundRect }, { id: input, name: 输入账号密码, x: 0, y: -140, symbol: rect }, { id: checkFormat, name: 格式是否正确, x: 0, y: -40, symbol: diamond }, { id: request, name: 请求登录接口, x: 0, y: 60, symbol: rect }, { id: getRole, name: 解析角色信息, x: 0, y: 160, symbol: rect }, { id: isAdmin, name: 是否管理员, x: 0, y: 260, symbol: diamond }, { id: adminPanel, name: 管理员面板, x: -160, y: 360, symbol: rect }, { id: userPanel, name: 普通用户面板, x: 160, y: 360, symbol: rect }, { id: end, name: 结束, x: 0, y: 460, symbol: roundRect } ]; const links [ { source: start, target: input }, { source: input, target: checkFormat }, { source: checkFormat, target: request, label: { show: true, formatter: 是 } }, { source: checkFormat, target: start, label: { show: true, formatter: 否 }, lineStyle: { type: dashed } }, { source: request, target: getRole }, { source: getRole, target: isAdmin }, { source: isAdmin, target: adminPanel, label: { show: true, formatter: 是 } }, { source: isAdmin, target: userPanel, label: { show: true, formatter: 否 } }, { source: adminPanel, target: end }, { source: userPanel, target: end } ]; const option { tooltip: {}, series: [{ type: graph, layout: none, data: nodes, links: links, roam: true, label: { show: true, position: inside, fontSize: 12, color: #333 }, lineStyle: { color: #999, width: 2, curve: 0.2 }, emphasis: { focus: adjacency, lineStyle: { width: 4, color: #5470c6 } } }] }; myChart.setOption(option);这套代码我实际跑过直接粘到HTML里配合echarts.min.js就能看到效果。跑起来之后你基本就有了一个可以交互、可以缩放、节点造型区分明显的流程图。4. 进阶美化与实践技巧说实话第一步能画出来只是完成了20%。一个合格的流程图表现在还缺少很多细节tooltip友好提示、自动换行、颜色分级、节点尺寸自适应、复杂分支的路径规划。这一节我挑几个高频问题重点讲。4.1 tooltip自动换行的两种解法echarts的tooltip默认在一行内展示所有内容如果节点描述太长很容易撑爆弹层或者显示不全。网上刷到很多人在问“echarts tooltip自动换行”这里给出两个常用解法。第一种在formatter回调里手动拼接换行标签。tooltip的formatter支持HTML字符串所以你可以这么做tooltip: { formatter: function(params) { return div stylemax-width:220px; divb params.name /b/div div stylefont-size:12px;color:#888;节点ID params.data.id /div div styleword-break:break-all;描述这里是很长的描述文字用于说明该流程节点的具体职责与注意事项。/div /div; } }这样通过max-width和word-break:break-all长文本就能自动折行。注意在formatter里不要用\n换行直接拼HTML标签更可靠。第二种如果不想手动拼HTML可以给tooltip设置confine: true加上extraCssText来限制宽度。但实测下来处理复杂内容时自己拼formatter最灵活后面的加粗、变色、字段过滤都能按需定制。4.2 让节点颜色与数据联动流程图的另一个常用场景是根据指标动态染色。比如管理员面板的请求量超过阈值就变红正常是绿色。这个需求实现起来非常简单因为nodes的数据本身就是普通的JS对象页面可以随时更新它们。常见的做法是维护一个状态map然后在每次渲染时把状态字段合并进node数据const statusMap { adminPanel: error, userPanel: success, request: running }; const colorMap { success: #52c41a, error: #ff4d4f, running: #1890ff }; nodes.forEach(node { const status statusMap[node.id]; if (status) { node.itemStyle { color: colorMap[status], shadowColor: colorMap[status], shadowBlur: 10 }; } });刷新方式就是我之前讲的那两行myChart.setOption({ series: [{ data: nodes }] })。这种联动在监控大屏场景里特别实用。整个流程的链路健康状态一眼就能看出来哪个节点挂了、哪个节点有延迟通过颜色就一目了然。4.3 节点拖拽与布局微调graph系列默认在layout: none时节点是不允许拖拽的。但是 echarts 提供了graphDraggable配置项让节点可以被拖动series: [{ type: graph, draggable: true }]拖动节点之后节点位置会保留在你拖到的位置。如果你希望位置变化后能持久化到后端可以监听georoam对应的事件或者监听graphRoam事件。有一个坑需要注意如果用户拖乱了布局你通过setOption重新传了新的data即使坐标没变拖拽可能被重置。解决办法是先myChart.clear()再重新setOption或者在你的data里明确记录x/y每次刷新都主动写回坐标。4.4 复杂分支怎么不交叉流程图节点多了以后最容易出现的问题就是连线交叉。比如两个分支从同一个判断节点出来经过几步后又汇聚到同一个节点画出来很可能变成“X”形交叉观感很差。我的经验有两个土办法。第一调整分支节点的坐标使两条路径在空间上错开。比如左边的分支先向下走再向右汇合右边的分支先向下走再向左汇合形成一个“括弧”的形状。这需要你在设计坐标时就规划好每条边尽量走“C”形或“S”形路径。第二利用边的curve属性。给不同的边设置正负不同的曲率可以让它们在视觉上分离// 同一条路径上的分支一条左弯一条右弯 const links [ { source: isAdmin, target: adminPanel, lineStyle: { curve: 0.3 } }, { source: isAdmin, target: userPanel, lineStyle: { curve: -0.3 } } ];这样两条线会明显分开不会重合纠缠。值越大弯曲越厉害具体数值需要根据节点距离微调。4.5 节点尺寸自适应流程图要放到大屏或者PC端不同屏幕尺寸下节点大小最好能自适应。不建议单纯写死symbolSize而是基于图表宽度算一个比例。可以在窗口resize时重新计算function getAdaptiveSize() { const width chartDom.clientWidth; return Math.max(40, Math.min(80, width / 20)); } window.addEventListener(resize, () { const size getAdaptiveSize(); myChart.setOption({ series: [{ data: nodes.map(node ({ ...node, symbolSize: node.symbolSize || size })) }] }); myChart.resize(); });当然如果只是内部展示固定symbolSize也没问题。我建议至少保留roam: true让用户在需要的时候自己放大局部看清楚。5. 常见问题排查实战笔记最后把我踩过的坑和读者常见的问题整理成一个速查表方便你复用。问题原因解决方案图表空白控制台无报错没有指定layout, 或者data/links为空数组检查series.data和links是否为空layout显式指定为none连线出现在节点下层/被挡住echarts渲染层级问题给node设置z: 10给lineStyle设置z: 2或者通过emphasis提升hover层级自定义symbol显示成大色块path字符串写错或坐标系范围不对用path://前缀确保SVG path的坐标在合理范围内可以先测一个纯矩形path箭头不见了没有配置symbol或symbolSizelinks里配置symbol: [none, arrow],symbolSize: 10拖拽后节点错位拖拽改变了内部坐标用myChart.clear()或重新写入x/y监听事件记录最终坐标tooltip不换行formatter没有设置宽度约束用formatter返回HTML字符串并设置max-widthfocus adjacency无效果emphasis配置层级不对确认配置在series的emphasis.focus而不是legend里标签显示不全或重叠标签默认在节点外部设置label.position: inside或调整label.padding分支文字用position: right/left分别设定再补充一个我踩过比较深的坑用setOption更新graph数据时如果新旧数据的id对不上echarts会重新计算布局导致节点位置跳动。尤其是我用layout: none并且手动指定坐标时id必须保持稳定。如果id变化了图表会认为来了新节点之前的坐标信息就丢了。所以生产环境里我一般会约定id就是数据库里的主键绝不用数组下标当id。关于缩放后文字模糊目前没有非常完美的方案。echarts的canvas渲染在浏览器缩放时会出现发虚的情况。缓解办法是监听窗口尺寸变化调用chart.resize()强制重绘。如果对清晰度要求极高可以尝试SVG渲染const myChart echarts.init(chartDom, null, { renderer: svg });SVG渲染模式下文字清晰度会好不少但节点数量超过几百个时性能会明显下降。你需要在清晰度和性能之间做取舍我一般推荐展示型流程图用SVG数据监控型的大规模图用canvas。6. 一些踩坑后的真实体会做完整套流程后我再回头说点实在的。用echarts画流程图最大的优势不是它有多专业而是它能让流程跟数据“活”在一起。传统的流程编辑工具画出来的图是静态的节点状态、颜色、大小都必须手动改echarts里这些全是数据驱动后端接口一刷新图自己就变了。对于做数据可视化大屏、运维监控、业务链路追踪这类项目这是不可替代的优势。但我也想说清楚它的边界。如果你要做一个让用户自己拖拽、连线、编辑并且保存流程定义的工具别用echarts硬扛。我见过有些团队为了省依赖非要用graph模拟节点编辑最后边拖拽边计算坐标写了几百行代码还是不够流畅体验跟专业编辑器差很多。选型这件事适合才是王道。最后分享一个小技巧如果你希望流程图的连线看起来更“柔”不要在lineStyle里把curve调太大0.15到0.3之间是比较舒服的区间。调太大线会弯成一个大圈反而影响阅读。另外判断节点出来的“是/否”标签建议把标签放在靠近目标节点的一端而不是线的正中间可以用label.position: right或者label.position: bottom配合offset微调这样视觉上更清晰读者看分支时不需要费力找文字。流程图的本质是把复杂逻辑变简单画图只是手段让人一眼看懂才是目的。希望这篇分享能帮你少走一些弯路。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →