
简介本资源是面向数据可视化开发者与前端工程师的 AntV G6 多边缘实战示例包聚焦图可视化中同一对节点间多种关系连接的实现方式帮助读者理解并掌握复杂关系网络的构建思路。包内共 25 个文件以 12 张 png 效果截图、6 个 js 示例脚本、2 个 json 配置、2 个 html 页面及 2 个 md 说明文档为主压缩包约 4.89MB目录按 fine-edge、intro、dist 等模块组织便于对照运行与二次开发。资源围绕自定义 Edge 类型与 EdgeGroup 两种方案展开涵盖边样式定制、分组管理与渲染流程示例代码虽仍在优化中但已提供可复用的基础框架。目前已有 756 人学习下载适合希望提升图可视化项目质量、需要处理社交网络或交通网络等多元关系场景的开发者参考借鉴。1. 多条边叠在一起就糊了antv-g6-multi-edges 到底解决什么做过关系图的人大概率都遇到过这个场景两个节点之间存在两条以上的业务关系比如 A 调用 B 的接口同时 B 又回调 A 的通知画到画布上却只剩一根线另一条要么被完全盖住要么两条线严丝合缝地重叠鼠标点上去根本分不清点中的是哪一条。antv-g6-multi-edges 这个方向讲的就是在 G6 里把「同一对节点之间的多条边」拆开、错开、各自可交互地画出来。它不是一个玄学技巧而是一套围绕边路由、曲率、偏移和自环的工程做法。适合谁适合正在用 G6 做拓扑图、调用链图、血缘图、风控关系图并且已经被重叠边折磨过的前端和可视化工程师。下面我按「先立住原理再动手复现最后讲坑」的顺序把这条路走一遍。2. 先搞懂 G6 的边是怎么画出来的从 edge 配置到多边错开在动手写多边逻辑之前得先弄清楚 G6 画一条边时到底经历了什么。很多人一上来就找现成的多边插件结果发现配置项对不上自己的版本或者错开的方向完全不受控。根子在于没搞明白边的几何是由谁决定的。2.1 一条边的三个关键量起点、终点、控制点G6 里一条边本质上是一条路径最简形式是直线复杂一点是折线或曲线。决定它长什么样的是三个量起点坐标、终点坐标以及中间的控制点。直线没有控制点曲线和折线都有。多边错开的核心思路就是让同一对节点之间的每一条边拿到不同的控制点从而在视觉上分叉。以最常见的二次贝塞尔曲线为例G6 内置的quadratic边类型会用一个控制点来弯曲路径。控制点如果落在起点终点连线的中垂线上边就会朝一侧鼓出去。多条边只要让控制点沿中垂线取不同偏移量就能形成扇形展开的效果。这就是多边错开最朴素的原理不神秘但很多人卡在不知道怎么拿到这个控制点。// 计算两点连线的中垂线方向上的偏移点 function getControlPoint(start, end, curvature) { const midX (start.x end.x) / 2; const midY (start.y end.y) / 2; // 连线方向向量 const dx end.x - start.x; const dy end.y - start.y; const len Math.sqrt(dx * dx dy * dy) || 1; // 垂直方向单位向量用于把控制点推离连线 const nx -dy / len; const ny dx / len; return { x: midX nx * curvature, y: midY ny * curvature, }; }这段代码里curvature就是偏移量正负决定往哪一侧鼓。逻辑说明先求中点再求连线的垂直单位向量最后把中点沿垂直方向推出去。参数说明curvature为 0 时退化成直线绝对值越大弧越夸张一般单条边取 20 到 40 像素比较自然多边场景按边的序号递增。2.2 用 edge 的 type 和 style 控制曲率拿到控制点之后怎么让 G6 用上它常见做法是自定义边或者直接用内置曲线边配合curveOffset之类的样式字段。不同 G6 大版本字段名有差异我一般会先确认自己用的是哪一版再决定是走自定义registerEdge还是走样式配置。下面给一个自定义二次曲线边的骨架思路是把上面算出的控制点塞进路径。import G6 from antv/g6; G6.registerEdge(multi-edge, { // 自定义绘制返回路径 draw(cfg, group) { const start cfg.startPoint; const end cfg.endPoint; // 从数据里取偏移量没传就默认 0 const curvature cfg.curvature || 0; const cp getControlPoint(start, end, curvature); const path [ [M, start.x, start.y], [Q, cp.x, cp.y, end.x, end.y], ]; const shape group.addShape(path, { attrs: { path, stroke: cfg.style?.stroke || #99ADD1, lineWidth: cfg.style?.lineWidth || 1, }, name: edge-path, }); return shape; }, });逻辑说明draw是自定义边的入口cfg里带着这条边的起终点和业务数据。我们用Q指令画二次贝塞尔控制点由curvature决定。参数说明cfg.curvature建议在数据层就算好不要写死在注册函数里否则所有边都会用同一个偏移。stroke和lineWidth从 style 取方便后续做高亮。2.3 给同一对节点的多条边分配不同偏移量原理清楚了接下来是分配策略。同一对节点之间如果有 n 条边最直观的做法是让偏移量关于 0 对称分布比如 n2 时取 30 和 -30n3 时取 -40、0、40。这样视觉上最平衡也不会出现某条边被挤到画布外的情况。// 为一组同源同目标的边分配曲率 function assignCurvature(edges, step 30) { const n edges.length; // 以中点为对称轴向两侧展开 const start -((n - 1) / 2) * step; return edges.map((edge, i) ({ ...edge, curvature: start i * step, })); }逻辑说明start是第一条边的偏移之后每条递增一个step整体关于 0 对称。参数说明step控制相邻两条边的间距太小会重新叠在一起太大弧线会飞出节点范围实测 25 到 40 之间比较稳。注意这里假设边的顺序稳定如果数据是动态增删的最好给每条边一个稳定的业务 id 再排序否则每次重绘偏移都会跳。3. 把多边方案接进真实图数据建模与交互补齐光把线错开还不够真实项目里多边往往还带着标签、箭头、点击高亮这些诉求。这一章讲怎么把多边方案接进一个能用的图里以及交互上要注意什么。3.1 数据层怎么标记「这是一组多边」我一般不会在渲染时才去判断哪些边是同组而是在数据准备阶段就打上标记。常见做法是给边加一个groupKey由源节点 id 和目标节点 id 拼成排序后拼接保证 A→B 和 B→A 能区分开如果业务上需要区分方向的话。// 预处理给边打组标记并分配曲率 function prepareEdges(rawEdges) { const groups {}; rawEdges.forEach((edge) { // 有向图用 source-target无向图排序后再拼 const key ${edge.source}-${edge.target}; if (!groups[key]) groups[key] []; groups[key].push(edge); }); const result []; Object.values(groups).forEach((group) { result.push(...assignCurvature(group)); }); return result; }逻辑说明先按source-target分组再对每组调用上一章的assignCurvature。参数说明如果业务是无向关系把 key 改成两个 id 排序后拼接避免 A→B 和 B→A 被当成两组。这一步放在数据层的好处是渲染逻辑保持干净换布局算法时不用改。3.2 边标签跟着曲线走别让它飘在直线位置多边错开后一个高频翻车点是标签还停在原来直线的中点结果标签和线对不上看起来像贴错了。标签位置要跟着控制点走取曲线上的点而不是起终点中点。// 取二次贝塞尔曲线在 t 处的点t 取 0.5 即中点 function getQuadraticPoint(start, cp, end, t) { const mt 1 - t; return { x: mt * mt * start.x 2 * mt * t * cp.x t * t * end.x, y: mt * mt * start.y 2 * mt * t * cp.y t * t * end.y, }; }逻辑说明这是二次贝塞尔的标准参数方程t从 0 到 1。参数说明标签一般取t0.5但如果多条边标签会互相压可以按序号微调t比如奇数条取 0.45偶数条取 0.55错开一点。注意控制点要用和画线时同一个否则标签还是会偏。3.3 点击命中让每条边都能被单独选中线错开后点击命中通常没问题因为路径分开了。但如果你用的是很细的线或者曲率很小命中区域会很窄。常见做法是给边加一条透明的加粗路径作为命中区或者调大lineAppendWidth。G6.registerEdge(multi-edge, { draw(cfg, group) { // ...前面的路径计算 const hit group.addShape(path, { attrs: { path, stroke: transparent, lineWidth: 12, // 命中区加粗 }, name: edge-hit, }); hit.toBack(); // 放到最底层不挡视觉 return group.addShape(path, { attrs: { path, stroke: cfg.style?.stroke || #99ADD1, lineWidth: 1 }, name: edge-path, }); }, });逻辑说明加一条透明加粗路径专门吃点击事件视觉路径保持细。参数说明lineWidth取 10 到 15 比较合适太大会误触相邻边。toBack保证命中区不盖住视觉线。如果框架支持lineAppendWidth优先用它省一条 shape。4. 避坑与排查多边渲染最容易翻车的五个点这一章是我踩过的坑合集每条按现象、原因、解决来写照着排查能省不少时间。4.1 边还是重叠曲率没生效或全用了同一个值现象明明配了曲率画出来还是叠在一起。原因通常是自定义边没注册成功或者数据里的curvature字段没被读到所有边都取了默认 0。解决先在draw里打印cfg.curvature确认数据透传到了再确认graph初始化时defaultEdge或数据里的type指向了自定义边名。还有一种情况是分组逻辑把本该分开的边分到了不同组各自组内只有一条自然没有偏移。4.2 弧线飞出画布或穿过节点现象曲率调大后边鼓得太远跑到画布外或者弧线穿过了旁边的节点。原因偏移量是固定像素没有考虑节点间距和画布范围。解决把step和节点距离挂钩比如取两节点距离的 10% 到 15% 作为单步偏移同时给曲率设上限超过就截断。布局用 dagre 或 force 时节点位置会变曲率最好在布局完成后重算一次。4.3 标签和线对不上现象线错开了标签还在中间直线上。原因标签位置用了起终点中点没跟控制点走。解决用 3.2 的曲线取点函数控制点必须和画线时一致。如果标签是 G6 内置的labelCfg注意它的定位基准可能是边路径自定义边时要确认框架有没有正确识别你的路径。4.4 动态增删边后偏移乱跳现象新增一条同组边后原来两条边的曲率全变了视觉上跳一下。原因assignCurvature按数组下标分配顺序一变偏移就变。解决给每条边一个稳定 id分配前先按 id 排序或者用「边 id 哈希到固定偏移区间」的方式让每条边的偏移只和自身有关增删不影响其他边。后者更稳但可能出现两条边偏移撞车需要再加一层冲突检测。4.5 自环边和多边混在一起现象节点自己指向自己的边和多边逻辑冲突画出来位置很怪。原因自环的起终点是同一个节点中垂线方向算出来是零向量控制点计算失效。解决自环单独处理不走多边偏移逻辑用固定的环半径和角度画。判断条件就是source target命中就切到自环分支。5. 进阶把多边偏移做成可复用、可验证的模块前面讲的都是单点做法真要在项目里长期用得把它抽成一个可复用模块并且有办法验证它没画错。这一章讲两个具体技巧。第一个技巧是把偏移策略参数化。不要写死step30而是暴露一个配置对象让调用方决定对称展开还是单侧展开、步长多少、是否按边权重调整。比如权重大的边给更大曲率视觉上更突出。下面是一个策略函数的骨架。// 可配置的多边偏移策略 function buildCurvatureStrategy(options {}) { const { step 30, mode symmetric, // symmetric | one-side maxCurvature 120, } options; return function (edges) { const n edges.length; const base mode symmetric ? -((n - 1) / 2) * step : 0; return edges.map((edge, i) { let curvature base i * step; // 截断防止飞出画布 curvature Math.max(-maxCurvature, Math.min(maxCurvature, curvature)); return { ...edge, curvature }; }); }; }逻辑说明工厂函数返回一个策略调用方传入边数组就得到带曲率的边。参数说明mode控制对称还是单侧单侧适合有明确方向的场景比如调用链maxCurvature是安全阀防止极端情况下弧线失控。这个结构的好处是换策略不用改渲染代码。第二个技巧是验证。多边画得对不对肉眼看不完全尤其是边多的时候。我一般会写一个校验函数检查同一组边的曲率是否互不相同、是否都在上限内、标签点是否落在对应曲线上。跑一遍校验比盯着画布看靠谱。// 校验多边数据同组曲率不重复且在范围内 function validateMultiEdges(edges, maxCurvature 120) { const groups {}; edges.forEach((e) { const key ${e.source}-${e.target}; (groups[key] groups[key] || []).push(e.curvature || 0); }); const errors []; Object.entries(groups).forEach(([key, curvatures]) { const unique new Set(curvatures); if (unique.size ! curvatures.length) { errors.push(${key} 存在重复曲率); } curvatures.forEach((c) { if (Math.abs(c) maxCurvature) { errors.push(${key} 曲率 ${c} 超出上限); } }); }); return errors; }逻辑说明按组收集曲率检查重复和越界。参数说明maxCurvature要和策略里的上限保持一致否则校验形同虚设。这个函数可以挂在开发环境的断言里数据一变就跑一次。最后说个我自己的习惯多边这种视觉问题别等画布上出问题才调先在数据层把曲率算好、校验过再交给渲染。我早期图省事在draw里现算结果布局一变、数据一增删偏移全乱排查花的时间比一开始写好策略多得多。把偏移当成数据的一部分而不是渲染的临时产物这条路会顺很多。希望帮到你。本文还有配套的精品资源点击获取