连接桩 Port
链接桩是节点上的固定连接点,很多图应用都有链接桩,并且有些应用还将链接桩分为输入链接桩和输出连接桩。
快速开始
创建节点时我们可以通过 ports
选项来配置链接桩,像下面这样:
const node = new Node({
ports: {
groups: { ... }, // 链接桩组定义
items: [ ... ], // 链接桩
}
})
// 或者
const node = new Node({
ports: [ ... ], // 链接桩
})
创建链接桩
完整的配置项稍微有点复杂,后面将详细介绍。我们先基于系统的默认选项,来快速添加几个链接桩:
graph.addNode({
x: 60,
y: 60,
width: 160,
height: 80,
label: 'Rect With Ports',
ports: [
{ id: 'port1' },
{ id: 'port2' },
{ id: 'port3' },
],
})
左侧的三个圆就是我们添加的链接桩,这里使用了节点的 portMarkup
选项指定的默认的链接桩(如下)。
链接桩 Markup 可以在单个链接桩、链接桩群组和节点的 portMarkup
选项三个位置指定,优先级从高到低。
{
tagName: 'circle',
selector: 'circle',
attrs: {
r: 10,
fill: '#fff',
stroke: '#000',
},
}
知道了链接桩的 DOM 结构,我们就可以来定制链接桩的样式:
graph.addNode({
x: 60,
y: 60,
width: 160,
height: 80,
label: 'Rect With Ports',
ports: [
{
id: 'port1',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
{
id: 'port2',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
{
id: 'port3',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
],
})
值得注意的是,我们给 circle
指定了 magnet: true
这个特殊属性,使链接桩在连线交互时可以被连接上。
上面代码中每个链接桩的样式都一样,显得有点冗长,我们可以通过 group
选项来设置链接桩分组,使该组中的链接桩具有相同的行为和样式。
看下面如何使用链接桩分组来定义链接桩样式:
graph.addNode({
x: 60,
y: 60,
width: 160,
height: 80,
label: 'Rect With Ports',
ports: {
groups: {
group1: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
},
items: [
{
id: 'port1',
group: 'group1', // 指定分组名称
},
{
id: 'port2',
group: 'group1', // 指定分组名称
},
{
id: 'port3',
group: 'group1', // 指定分组名称
},
],
},
})
链接桩标签
另外,还可以为链接桩指定标签文本:
graph.addNode({
x: 60,
y: 60,
width: 160,
height: 80,
label: 'Rect With Ports',
ports: {
groups: {
group1: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
},
items: [
{
id: 'port1',
group: 'group1',
attrs: {
text: { // 标签选择器
text: 'port1', // 标签文本
},
},
},
{
id: 'port2',
group: 'group1',
attrs: {
text: { // 标签选择器
text: 'port2', // 标签文本
},
},
},
{
id: 'port3',
group: 'group1',
attrs: {
text: { // 标签选择器
text: 'port2', // 标签文本
},
},
},
],
},
})
这里使用了节点的 portLabelMarkup
选项指定的默认标签(如下)。
链接桩标签的 Markup 可以在单个链接桩、链接桩群组和节点的 portLabelMarkup
选项三个位置指定,优先级从高到低。
{
tagName: 'text',
selector: 'text',
attrs: {
fill: '#000000',
},
}
连接到链接桩
有了链接桩之后,我们就可以为边指定连接的链接桩:
graph.addEdge({
source: { x: 40, y: 100 },
target: {
cell: rect,
port: 'port1', // 链接桩 ID
},
})
graph.addEdge({
source: { x: 40, y: 100 },
target: {
cell: rect,
port: 'port2', // 链接桩 ID
},
})
graph.addEdge({
source: { x: 40, y: 100 },
target: {
cell: rect,
port: 'port3', // 链接桩 ID
},
})
上面我们快速了解了如何创建和使用链接桩,接下来我们将详细介绍一下 ports
选项中的 items
和 groups
两个配置项。
选项介绍
首先,items
是一个数组 PortMetadata[]
,数组的每一项表示一个链接桩,链接桩支持的选项如下:
interface PortMetadata {
/**
* 链接桩唯一 ID,默认自动生成。
*/
id?: string
/**
* 分组名称,指定分组后将继承分组中的链接桩选项。
*/
group?: string
/**
* 为群组中指定的链接桩布局算法提供参数。
* 我们不能为单个链接桩指定布局算法,但可以为群组中指定的布局算法提供不同的参数。
*/
args?: object
/**
* 链接桩的 DOM 结构定义。指定该选项后将覆盖 `group` 指代的群组提供的默认选项。
*/
markup?: Markup
/**
* 元素的属性样式。指定该选项后将覆盖 `group` 指代的群组提供的默认选项。
*/
attrs?: Attr.CellAttrs
/**
* 链接桩的 DOM 层级,值越大层级越高。指定该选项后将覆盖 `group` 指代的群组提供的默认选项。
*/
zIndex?: number | 'auto'
/**
* 链接桩的标签。指定该选项后将覆盖 `group` 指代的群组提供的默认选项。
*/
label?: {
markup?: Markup // 标签 DOM 结构
position?: { // 标签位置
name: string // 标签位置计算方法的名称
args?: object // 标签位置计算方法的参数
}
}
}
然后,通常我们将具有相同行为和外观的链接桩归为同一组,并通过 groups
选项来设置分组,该选项是一个对象 { [groupName: string]: PortGroupMetadata }
,组名为键,值为每组链接桩的默认选项,支持的选项如下:
interface PortGroupMetadata {
/**
* 链接桩 DOM 结构定义。
*/
markup?: Markup
/**
* 属性和样式。
*/
attrs?: Attr.CellAttrs
/**
* 链接桩的 DOM 层级,值越大层级越高。
*/
zIndex?: number | 'auto'
/**
* 群组中链接桩的布局。
*/
position?:
| [number, number] // 绝对定位
| string // 链接桩布局方法的名称
| { // 链接桩布局方法的名称和参数
name: string
args?: object
}
/**
* 链接桩标签。
*/
label?: {
markup?: Markup
position?: { // 链接桩标签布局
name: string // 布局名称
args?: object // 布局参数
}
}
}
例如:
const node = new Node({
ports: {
group: {
group1: {
markup: {
tagName: 'circle',
selector: 'circle',
attrs: {
r: 10,
fill: '#fff',
stroke: '#000',
},
},
attrs: {
circle: {
r: 6,
},
},
zIndex: 1,
position: {
name: 'top',
args: {},
},
label: {
markup: {
tagName: 'text',
selector: 'text',
attrs: {
fill: '#000',
},
},
},
},
group2: { ... },
group3: { ... },
},
items: [
{ id: 'port1', group: 'group1', ... },
{ id: 'port2', group: 'group1', ... },
{ id: 'port3', group: 'group2', ... },
],
}
})
连接桩和标签位置
上面我们使用默认位置创建了几个连接桩,并为链接桩设置了标签,下面我们来看看如何为链接桩和标签指定位置。
链接桩位置
链接桩布局算法只能通过 groups
中的 position
选项来指定,因为布局算法在计算链接桩位置是需要考虑到群组中的所有连接桩,我们在单个链接桩中可以通过 args
选项来影响该链接桩的布局结果。
我们默认提供了下面几种链接桩布局算法,同时支持自定义链接桩布局算法并注册使用,点击下面的链接可以了解每种布局算法的使用方法。
'absolute'
绝对定位。'left'
矩形节点左侧均匀分布。'right'
矩形节点右侧均匀分布。'top'
矩形节点顶部均匀分布。'bottom'
矩形节点底部均匀分布。'line'
沿指定的线均匀分布。'ellipse'
沿椭圆圆弧分布。'ellipseSpread'
沿椭圆均匀分布。
下面我们就来定制一个具有输入和输出链接桩的节点。
graph.addNode({
x: 60,
y: 60,
width: 180,
height: 60,
label: 'In Ports & Out Ports',
ports: {
groups: {
// 输入链接桩群组定义
in: {
position: 'top',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
// 输出链接桩群组定义
out: {
position: 'bottom',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
},
items: [
{
id: 'port1',
group: 'in',
},
{
id: 'port2',
group: 'in',
},
{
id: 'port3',
group: 'in',
},
{
id: 'port4',
group: 'out',
},
{
id: 'port5',
group: 'out',
},
],
},
})
标签位置
在 groups
的 label.position
选项和节点的 label.position
选项中都可以指定标签的位置。
我们默认提供了下面几种标签位置,也支持自定义标签位置并注册使用,点击下面的链接了解每种标签位置的使用方法。
left
标签位于链接桩左侧。right
标签位于链接桩右侧。top
标签位于链接桩上方。bottom
标签位于链接桩下方。inside
标签位于节点内围(靠近边线的内侧)。outside
标签位于节点外围(靠近边线的外侧)。insideOriented
标签位于节点内围,而且根据所在方位自动调整文本的方向。outsideOriented
标签位于节点外围,而且根据所在方位自动调整文本的方向。radial
标签位于圆形或椭圆形节点的外围。radialOriented
标签位于圆形或椭圆形节点的外围,并使标签文本自动沿圆弧方向旋转。
下面我们为刚刚创建的输入、输出连接桩指定一下标签和标签位置。
graph.addNode({
x: 60,
y: 50,
width: 180,
height: 80,
label: 'In Ports & Out Ports',
ports: {
groups: {
in: {
position: 'top', // 链接桩位置
label: {
position: 'top', // 标签位置
},
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
out: {
position: 'bottom', // 链接桩位置
label: {
position: 'bottom', // 标签位置
},
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
},
items: [
{
id: 'port1',
group: 'in',
attrs: {
text: { text: 'in1' },
},
},
{
id: 'port2',
group: 'in',
attrs: {
text: { text: 'in2' },
},
},
{
id: 'port3',
group: 'in',
attrs: {
text: { text: 'in3' },
},
},
{
id: 'port4',
group: 'out',
attrs: {
text: { text: 'out1' },
},
},
{
id: 'port5',
group: 'out',
attrs: {
text: { text: 'out2' },
},
},
],
},
})
最佳实践
链接桩选项多、配置代码长,推荐的做法是,基于群组将链接桩的通用选项定义为节点的默认选项。例如我们可以定义一个矩形节点,然后为该矩形节点设置预定义的输入和输出链接桩。
Shape.Rect.define({
shape: 'my-rect',
width: 180,
height: 80,
ports: {
groups: {
in: {
position: 'top',
label: {
position: 'top',
},
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
out: {
position: 'bottom',
label: {
position: 'bottom',
},
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
},
},
})
上面我们定义并注册了名为 my-rect
的矩形,并为该矩形指定了输入和输出桩,我们可以这样来使用:
graph.addNode({
x: 60,
y: 50,
shape: 'my-rect',
label: 'In Ports & Out Ports',
ports: [
{
id: 'port1',
group: 'in',
},
{
id: 'port2',
group: 'in',
},
{
id: 'port3',
group: 'in',
},
{
id: 'port4',
group: 'out',
},
{
id: 'port5',
group: 'out',
},
],
})
使用 React 渲染连接桩
除了上面的方式,还可以用 React 组件来渲染连接桩,先看效果:
详细使用见渲染链接桩