开始
连线是 X6 中非常重要的一部分,X6 内置了很多实用的连线功能,也提供了优雅的扩展机制 ,这是相比于其他流程图框架占据绝对优势的地方。本文通过实现流程图的连线功能来一步步介绍 X6 的连线机制。
实现
图形定义
X6 中连线分两种形式,代码生成的和用户手动拖拽而成,首先来看怎么通过代码建立连线:
// source 或 target 是坐标点
graph.addEdge({
source: [0, 0],
target: [100, 100]
})
// source 或 target 是节点对象
graph.addEdge({
source: sourceNode,
target: targetNode,
})
// source 或 target 是节点 ID
graph.addEdge({
source: 'sourceId',
target: 'targetId',
})
// source 或 target 是连接桩
graph.addEdge({
source: { cell: 'cellId1', port: 'portId1' },
target: { cell: 'cellId2', port: 'portId2' }
})
如果想通过手动操作来创建连线,需要有两个条件:
- 需要从具有 magnet: true 属性的元素上才能手动拖拽出连线
- 需要在全局 connecting 配置中自定义 createEdge 方法
import { Graph, Shape } from '@antv/x6'
const graph = new Graph({
connecting: {
createEdge() {
return new Shape.Edge()
},
},
})
graph.addNode({
shape: 'rect',
x: 100,
y: 100,
width: 80,
height: 40,
attrs: {
body: {
stroke: 'red',
magnet: true
}
}
})
graph.addNode({
shape: 'rect',
x: 400,
y: 100,
width: 80,
height: 40,
attrs: {
body: {
stroke: 'red',
magnet: true
}
}
})
默认的连线样式并不好看,我们经常需要自己来定义连线的样式,其实连线和图形都是由 markup 和 attrs 来定义的,默认连线的 markup 为:
[
{
tagName: 'path',
selector: 'wrap',
groupSelector: 'lines',
attrs: {
fill: 'none',
cursor: 'pointer',
stroke: 'transparent',
strokeLinecap: 'round',
},
},
{
tagName: 'path',
selector: 'line',
groupSelector: 'lines',
attrs: {
fill: 'none',
pointerEvents: 'none',
},
},
]
其中 line 为实际展示的连线,wrap 是为了方便响应交互的占位元素。一般场景我们不需要去自定义连线的 markup,但是我们经常需要配置 attrs 来定制连线样式:
graph.addEdge({
source: 'sourceId',
target: 'targetId',
attrs: {
line: {
stroke: '#000', // 连线颜色
strokeWidth: 1, // 连线宽度
},
},
})
连线的箭头也是非常容易配置,比如我们想将上面箭头修改为实心箭头,并且显示的修长一点儿:
graph.addEdge({
source: 'sourceId',
target: 'targetId',
attrs: {
line: {
stroke: '#000',
strokeWidth: 1,
targetMarker: {
name: 'block',
width: 12,
height: 8,
}
},
},
})
和节点类似,动态修改连线的属性方式如下:
const edge = graph.addEdge({
source: 'sourceId',
target: 'targetId',
attrs: {
line: {
stroke: '#000',
strokeWidth: 1,
},
},
})
edge.attr('line/stroke', '#ccc')
同样,连线也提供了注册自定义连线的方式来解决重复定义问题:
Graph.registerEdge('custom-edge', {
inherit: 'edge',
attrs: {
line: {
strokeWidth: 1,
stroke: '#5755a1',
targetMarker: {
name: 'path',
d: 'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
}
},
},
})
graph.addEdge({
shape: 'custom-edge',
source: [50, 50],
target: [300, 50],
})
graph.addEdge({
shape: 'custom-edge',
source: [50, 150],
target: [300, 150],
attrs: {
line: {
stroke: '#31d0c6'
}
}
})
路由和连接器
在每一种图场景中,连线形态有所不同,比如流程图和思维导图的连线样式:
流程图的连线是横平竖直的,思维导图的连线是一种树状,X6 提供了路由和连接器两个概念来实现不同形态的连线。路由就是在原有连线基础上增加一些固定点,让连线符合某种规则,比如 orth 路由,增加固定点后,边的每一条线段都是水平或垂直的正交线段。连接器就是将连线所有点经过一定规则加工后,产生一定形状的连线,比如 smooth 连接器,是用三次贝塞尔曲线线连接起点、路由点和终点。
在我们的流程图中,需要横平竖直的连线,并且能避开重合的图形,所以选择 manhattan 路由,然后连线的拐角处需要平滑处理,这里选择 rounded 连接器。路由和连接器都可以在 connecting 配置中全局配置(对所有连线有效),也可以在一条连线中单独配置:
// 全局配置
const graph = new Graph({
connecting: {
router: 'manhattan',
connector: {
name: 'rounded',
args: {
radius: 8,
}
}
}
})
// 单独针对一条边配置
graph.addEdge({
source: 'sourceId',
target: 'targetId',
router: 'manhattan',
connector: {
name: 'rounded',
args: {
radius: 8,
}
}
})
起点和终点
连线的起点和终点是由 NodeAnchor 和 ConnectionPoint 共同确定。
- 起点:从第一个路径点或目标节点的中心(没有路径点时)画一条参考线到源节点的锚点,然后根据 connectionPoint 指定的交点计算方法,计算参考线与图形的交点,该交点就是边的起点。
- 终点:从最后一个路径点或源节点的中心(没有路径点时)画一条参考线到目标节点的锚点,然后根据 connectionPoint 指定的交点计算方法,计算参考线与图形的交点,该交点就是边的终点。
上面的描述可能比较难以理解,可以看下面的例子,图形 1 和 图形 2 的 NodeAnchor 默认在图形中心,ConnectionPoint 默认是 boundary,也就是图形边框,从锚点 1 拉出一条线来连接到锚点 2,这条线与图形边框的交点就是连线的起点和终点。
经常会遇到这种情况,起点和终点在连接桩的中心,那么 NodeAnchor 和 ConnectionPoint 该怎么设置呢?
const graph = new Graph({
// 锚点在连接桩的中心,连接点是锚点本身
connecting: {
anchor: 'center',
connectionPoint: 'anchor',
}
})
工具
工具是渲染在节点/边上的小部件,用于增强节点/边的交互能力,现在我们需要在连线上增加一个线段工具,在边的每条线段的中心渲染一个工具条,可以拖动工具条调整线段两端的路径点的位置。
graph.addEdge({
tools: [
{
name: 'segments',
args: {
attrs: {
fill: '#459CDB',
}
}
}
]
})
最后
连线是 X6 的核心功能之一,通过本文可以了解到连线的基础用法,我们可以完成大部分场景下连线的配置,在一些复杂场景下,还需要在连线上定制标签的需求,可以参考 edge-label。