X6 连线(Connector)完全指南:从内置连线器到自定义贝塞尔与跳线实现
2026/9/17 5:44:25 网站建设 项目流程

X6 连线(Connector)完全指南:从内置连线器到自定义贝塞尔与跳线实现

【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6

连线(Connector)是 X6 图编辑引擎中决定「边(Edge)如何绘制」的核心概念:路由(Router)负责规划折线路径点,而 Connector 负责把路径点与边的起终点串成最终的 SVG Path 曲线。本文以 site/examples/edge/connector 下的官方示例为骨架,完整讲解 X6 内置的五种连线器(normal、rounded、smooth、jumpover、loop)的配置方式与参数,并深入源码剖析其实现原理;随后带领你通过Graph.registerConnector注册自定义连线器,实现二次贝塞尔方向曲线、随机摇摆线和多条平行光滑曲线三类高阶玩法。读完本文,你将能够为任意场景(流程图、ER 图、电路图、可视化大屏)选择或编写合适的连线器。

前置准备:搭建一个最小 X6 示例页

所有示例都基于同一个最小图实例,先建立一个包含两个矩形节点与一条边的画布:

import { Graph } from '@antv/x6' const graph = new Graph({ container: document.getElementById('container'), grid: true, }) const source = graph.addNode({ x: 120, y: 40, width: 100, height: 40, attrs: { body: { fill: '#f5f5f5', stroke: '#d9d9d9', }, }, }) const target = graph.addNode({ x: 400, y: 260, width: 100, height: 40, attrs: { body: { fill: '#f5f5f5', stroke: '#d9d9d9', }, }, }) graph.addEdge({ source, target, vertices: [ { x: 200, y: 200 }, { x: 380, y: 120 }, ], connector: { name: 'normal' }, attrs: { line: { stroke: '#722ed1', }, }, })

要点说明:

  • vertices是中间经过点,X6 的 Router(默认使用normal路由)会先依据这些点计算整条路径的完整点序列,再交给 Connector 生成 Path;
  • connector统一为{ name: string, args?: object }结构:name指定注册表(registry)中的连线器名称,args传入该连线器的专属参数;
  • 边的外观通过attrs.line控制,示例统一使用紫色#722ed1以便观察曲线形状。

上述完整示例见 site/examples/edge/connector/demo/normal.ts,其余各内置连线器的可运行示例均位于 site/examples/edge/connector/demo 目录,并在 demo/meta.json 中登记了标题与截图。

内置连线器全解

X6 在 src/registry/connector/main.ts 中统一导出了内置连线器:normalroundedsmoothjumpoverloop。下面逐一讲解配置方式与实现原理。

normal:默认直线折线

normal是未显式指定时的默认连线器,把「起点 → 路由点序列 → 终点」依次连接成折线。

connector: { name: 'normal' }

从源码 src/registry/connector/normal.ts 可以看到其实现非常轻量:

export interface NormalConnectorOptions extends ConnectorBaseOptions { split?: boolean | number } export const normal: ConnectorDefinition = ( sourcePoint, targetPoint, routePoints, options = {}, ) => { const points = [sourcePoint, ...routePoints, targetPoint] const polyline = new Polyline(points) const path = new Path(polyline) return options.raw ? path : path.serialize() }

实现要点:

  • 把起点、路由点、终点合并为点数组,交给Polyline构建多段线,再包装成Path输出;
  • 接受split参数(boolean | number,默认false)。从源码结构看,split用于控制最终输出的d属性是否被切分为多条子路径——例如配合边框动画或虚线流动效果时,按线段拆分后逐段设置样式会更容易;传数字则按指定点数拆分;
  • options.rawtrue时返回Path对象(而非序列化字符串),便于调用方继续做二次处理。

rounded:圆角折线

当折线拐角过于尖锐时,使用rounded可以让每个拐点变为圆角,视觉上更柔和:

connector: { name: 'rounded', args: { radius: 20, }, }
  • radius:圆角半径(单位 px),数值越大拐角越圆润;示例 site/examples/edge/connector/demo/rounded.ts 中设为20
  • 底层实现等价于对每个拐角点用半径radius的圆弧替换折线尖角,再以直线段衔接圆弧。适合流程图、状态图等需要「规整但不生硬」的连线风格。

smooth:三次贝塞尔平滑曲线

smooth把所有路径点拟合成一条平滑的三次贝塞尔曲线(Catmull-Rom 样条),彻底消除折线感:

connector: { name: 'smooth', }

完整示例见 site/examples/edge/connector/demo/smooth.ts。其应用场景包括:组织架构图、知识图谱中节点间的关系连线、需要「流动感」的业务链路展示。

jumpover:跳线(桥接线)

在电路图、流程图等场景中,多条连线交叉时会产生歧义。jumpover会在交叉处把上层的连线「拱起」形成跳线,从而表达「跨越而非连接」的语义:

connector: { name: 'jumpover', args: { type: 'gap', }, }

参考示例 site/examples/edge/connector/demo/jumpover.ts,该示例同时演示了两种用法:

  • 创建边时指定:偶数序号的边使用jumpover(并配置type: 'gap',描边色#faad14),奇数序号的边保持默认连线,形成「跳线与普通线共存」的对比;
  • 动态切换连线器:先以默认方式添加一条紫色边,随后调用edge.setConnector('jumpover')在运行时把该边切换为跳线模式。setConnector是 X6 边的实例方法,接收与connector配置相同的{ name, args }结构,适合交互式编辑场景(如用户切换连线风格)。

type参数用于控制跳线拱起的缺口样式,除gap(缺口式跳线)外还支持arc(圆弧式)等其他形态,可根据交叉密集程度选择。

loop:自环连线

当边的起点与终点是同一个节点时,需要绘制自环。loop连线器(源码位于 src/registry/connector/loop.ts)根据节点尺寸与args参数生成环绕节点的环形路径,常用参数包括控制环的大小与方向,是 UML 状态图、自引用关系建模的标配。

自定义连线器:三步注册,随心所欲

内置连线器无法覆盖所有业务形态时,可以通过Graph.registerConnector(name, handler, overwrite?)注册自定义连线器。handler 的签名如下:

type Connector = ( sourcePoint: Point.PointLike, targetPoint: Point.PointLike, routePoints: Point.PointLike[], args: Record<string, any>, ) => string | Path

四个入参的含义:

  • sourcePoint:起点坐标(已经过锚点 anchor 与连接点 connectionPoint 计算,详见下文「交互组合」);
  • targetPoint:终点坐标;
  • routePoints:路由计算后的中间点序列;
  • args:边配置connector.args透传进来的自定义参数。

返回值为 SVG Path 字符串,或返回Path对象(此时注册的第三个参数overwrite不影响返回值类型,Path对象会被自动序列化)。注册后即可在任意边中通过connector: { name: 'myConnector', args: {...} }使用。

二次贝塞尔方向曲线:结合节点朝向

site/examples/edge/connector/demo/curve.ts 展示了「从节点伸出方向感曲线」的完整思路——边会沿着节点的旋转角度(angle)方向出发,用二次贝塞尔曲线连接到目标节点。这在机器人/自动驾驶(ROS)拓扑、有向架构图等需要表达「朝向」的场景非常实用。

核心注册代码:

import { Graph, Path } from '@antv/x6' const degreesToRadians = (degrees) => degrees * (Math.PI / 180) const transCanvasAngleToRos = (angle) => angle - 90 // 依据点及其方向角,向前偏移 distance 得到辅助点 const calcPoint = (p) => { const distance = 10 const theta = degreesToRadians(transCanvasAngleToRos(p.angle)) const x = p.x + distance * Math.cos(theta) const y = p.y + distance * Math.sin(theta) return { x, y } } // 计算两条方向辅助线的交点,作为贝塞尔控制点 const calcIntersectionPoint = (points) => { const [p1, p2] = points const p11 = calcPoint(p1) const p22 = calcPoint(p2) const d1 = (p1.x - p11.x) * (p2.y - p22.y) const d2 = (p1.y - p11.y) * (p2.x - p22.x) const d = d1 - d2 if (d === 0) { return { x: p1.x, y: p1.y } } const u1 = p1.x * p11.y - p1.y * p11.x const u4 = p2.x * p22.y - p2.y * p22.x const u2x = p2.x - p22.x const u3x = p1.x - p11.x const u2y = p2.y - p22.y const u3y = p1.y - p11.y const px = (u1 * u2x - u3x * u4) / d const py = (u1 * u2y - u3y * u4) / d return { x: px, y: py } } Graph.registerConnector( 'curve', (sourcePoint, targetPoint) => { const path = new Path() path.appendSegment(Path.createSegment('M', sourcePoint)) // 基于方向获取控制点 const controlPoint = calcIntersectionPoint([ { ...sourcePoint, angle: graph.getNodesFromPoint(sourcePoint)[0].getAngle(), }, { ...targetPoint, angle: graph.getNodesFromPoint(targetPoint)[0].getAngle(), }, ]) // 基于控制点生成二次贝塞尔曲线 path.appendSegment(path.quadTo(controlPoint, targetPoint)) return path.serialize() }, true, )

实现拆解:

  1. calcPoint把「点坐标 + 方向角」换算为沿方向延伸distance(示例取 10px)的辅助点,其中transCanvasAngleToRos把 Canvas 坐标系角度转换为 ROS 风格角度(减去 90°),使得 0° 指向正上方;
  2. calcIntersectionPoint求解起终点两条方向辅助线的交点,交点在两线平行时退化为起点;
  3. 注册函数内通过graph.getNodesFromPoint(sourcePoint)[0].getAngle()拿到该点所属节点的当前旋转角,作为曲线出发点方向;
  4. 最后用Path.createSegment('M', ...)建起点、path.quadTo(controlPoint, targetPoint)追加二次贝塞尔段并返回序列化字符串。

配套的图配置需要开启节点旋转能力,并固定锚点/连接点,保证方向计算稳定:

const graph = new Graph({ container: document.getElementById('container'), grid: true, connecting: { anchor: 'nodeCenter', connectionPoint: 'anchor', }, }) graph.use( new Transform({ rotating: { enabled: true, }, }), )
  • connecting.anchor: 'nodeCenter':锚点固定在节点中心;
  • connecting.connectionPoint: 'anchor':连接点与锚点重合,避免连接点计算干扰出线方向;
  • Transform插件开启rotating后,节点可通过交互旋转,旋转角即时参与曲线方向计算。

随机摇摆线:为每段路径加入随机抖动

site/examples/edge/connector/demo/wobble.ts 演示了「手绘风格 / 涂鸦感」连线:沿着路径以固定间距采样,并对每个采样点施加随机偏移:

import { Graph, Path, Point } from '@antv/x6' Graph.registerConnector( 'wobble', (sourcePoint, targetPoint, vertices, args) => { const spread = args.spread || 20 const points = [...vertices, targetPoint].map((p) => Point.create(p)) let prev = Point.create(sourcePoint) const path = new Path() path.appendSegment(Path.createSegment('M', prev)) for (let i = 0, n = points.length; i < n; i += 1) { const next = points[i] const distance = prev.distance(next) let d = spread while (d < distance) { const current = prev.clone().move(next, -d) current.translate( Math.floor(7 * Math.random()) - 3, Math.floor(7 * Math.random()) - 3, ) path.appendSegment(Path.createSegment('L', current)) d += spread } path.appendSegment(Path.createSegment('L', next)) prev = next } return path }, true, )

关键点:

  • args.spread为采样间距(示例边配置中传入spread: 10),间距越小抖动越密集;
  • 每个采样点沿路径方向偏移-d,再叠加[-3, 3]范围的随机平移,形成自然的手绘感;
  • 通过Point的几何方法(clonemovedistancetranslate)完成计算,Path.createSegment('L', ...)追加折线段。

使用方式与内置连线器完全一致:

graph.addEdge({ source, target, vertices: [ { x: 200, y: 200 }, { x: 380, y: 120 }, ], connector: { name: 'wobble', args: { spread: 10, }, }, attrs: { line: { stroke: '#722ed1', }, }, })

这类抖动连线适合脑暴白板、批注演示、儿童教育类产品等需要「非正式感」的界面。

多条平行光滑曲线:利用索引参数做偏移

当两个节点之间存在大量同向连线(如桑基图、批量关系展示)时,直接叠加多条曲线会完全重合。site/examples/edge/connector/demo/multiple-smooth-edges.ts 通过自定义multi-smooth连线器,根据边的序号在垂直方向偏移控制点,生成一组平行且互不遮挡的光滑曲线:

import { Graph, Line, Path, Curve } from '@antv/x6' Graph.registerConnector( 'multi-smooth', ( sourcePoint, targetPoint, routePoints, options: { raw?: boolean; index?: number; total?: number; gap?: number }, ) => { const { index = 0, total = 1, gap = 12 } = options const line = new Line(sourcePoint, targetPoint) const centerIndex = (total - 1) / 2 const dist = index - centerIndex const diff = Math.abs(dist) const factor = diff === 0 ? 1 : diff / dist const vertice = line .pointAtLength(line.length() / 2 + gap * factor * Math.ceil(diff)) .rotate(90, line.getCenter()) const points = [sourcePoint, vertice, targetPoint] const curves = Curve.throughPoints(points) const path = new Path(curves) return options.raw ? path : path.serialize() }, true, )

实现思路:

  • 通过connector.args向注册函数传入该边的index(第几条)与total(总条数),让同一个连线器能感知自身在整组边中的位置;
  • 取起终点连线的中点,向垂直方向偏移gap * factor * ceil(diff),形成随序号递增的错位;gap默认 12,控制平行间距;
  • Curve.throughPoints(points)构造经过三个点(起点、偏移控制点、终点)的光滑曲线,包装为Path返回;
  • options.rawtrue返回Path对象,否则返回序列化字符串。

批量添加边时只需循环传入序号:

const total = 15 for (let i = 0; i < total; i += 1) { graph.addEdge({ source, target, connector: { name: 'multi-smooth', args: { total, index: i, }, }, attrs: { line: { stroke: '#722ed1', strokeWidth: 1, targetMarker: null, }, }, }) }

这里设置strokeWidth: 1让细线更精致,targetMarker: null去掉箭头,适合纯关系展示。

组合机制:Router、Anchor、ConnectionPoint 与 Connector 的分工

要正确使用连线器,需要理解它与相邻机制的分工(它们分别由 src/registry/router、src/registry/node-anchor、src/registry/connection-point、src/registry/connector 四个注册表提供):

  1. Anchor(锚点):确定边在源/目标节点「内部」的挂载位置(如nodeCenter节点中心、orth正交边中点),由connecting.anchor配置;
  2. ConnectionPoint(连接点):在锚点基础上做最终偏移,确定实际出线/入线的坐标(如anchor表示与锚点重合、boundary表示落在节点边界),由connecting.connectionPoint配置;
  3. Router(路由):结合障碍物与vertices计算避开节点后的完整点序列(如manhattan曼哈顿路由、orth正交路由),不关心最终曲线的形状;
  4. Connector(连线器):拿到以上步骤产出的起点、终点与点序列,负责「画」出最终 Path。

因此,相同的verticesconnector配置,配合不同 Router 会得到完全不同的路径形态;而同一 Router 点序列交给normalsmooth也会呈现折线与曲线的差异。官方测试在 src/registry/connector 下针对normalroundedsmoothjumpoverloop均有对应的 spec 用例(如tests/registry/connector/normal.spec.ts、tests/registry/connector/jumpover.spec.ts),可用作理解各连线器输入输出契约的参考。

动态切换与运行时修改

除了建边时指定,X6 边实例还提供运行时 API:

  • edge.setConnector('jumpover'):切换连线器名称;
  • edge.setConnector({ name: 'rounded', args: { radius: 30 } }):带参数切换,修改后路径实时重绘。

这在交互式编辑(工具栏切换连线风格)、动画演示(连线形态变化)等场景非常实用,jumpover.ts 示例末尾正是通过edge.setConnector('jumpover')动态演示了这一能力。

小结与选型建议

连线器形态典型参数适用场景
normal直线折线split通用默认,需逐段样式/动画时开启拆分
rounded圆角折线radius流程图、状态图等规整连线
smooth三次贝塞尔曲线组织架构、图谱关系、流动感展示
jumpover交叉跳线typegap/arc等)电路图、密集连线避免交叉歧义
loop自环环大小/方向UML 状态图、自引用关系
自定义任意 Path自定义args方向曲线、手绘抖动、平行曲线等

实际选型时建议:

  • 需求只是「连起来」:直接用默认normal
  • 需要更精致但仍是结构化连线:rounded+ 合适的radius
  • 表达柔和关系或图谱:smooth
  • 连线密集且有交叉语义:jumpover,并注意合理配置type
  • 业务形态特殊(方向感、手绘风、平行曲线):基于Graph.registerConnector自定义,注册函数内可自由使用 src/geometry 提供的PathPointLineCurve等几何工具。

所有示例代码均可直接运行于任意 X6 项目,核心 API 与内置连线器的完整定义可在 src/registry/connector 目录中进一步查阅。

【免费下载链接】X6🚀 JavaScript diagramming library that uses SVG and HTML for rendering.项目地址: https://gitcode.com/GitHub_Trending/x6/X6

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询