边
在本章节中主要介绍边相关的知识,通过阅读,你可以了解到
- 添加边的方法
- 如何配置边的形状
- 如何在边上增加箭头
- 如何自定义边
- 如何通过 API 修改边
添加边
节点和边都有共同的基类 Cell,除了从 Cell 继承属性外,还支持以下选项。
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| source | TerminalData | - | 源节点或起始点。 |
| target | TerminalData | - | 目标节点或目标点。 |
| vertices | Point.PointLike[] | - | 路径点。 |
| router | RouterData | - | 路由。 |
| connector | ConnectorData | - | 连接器。 |
| labels | Label[] | - | 标签。 |
| defaultLabel | Label | 默认标签 | 默认标签。 |
graph.addEdge({
shape: 'edge',
source: 'node1',
target: 'node2',
})
配置边
下面分别看下上面的配置如何使用。
source/target
边的源和目标节点(点)。
graph.addEdge({
source: rect1, // 源节点
target: rect2, // 目标节点
})
graph.addEdge({
source: 'rect1', // 源节点 ID
target: 'rect2', // 目标节点 ID
})
graph.addEdge({
source: { cell: rect1, port: 'out-port-1' }, // 源节点和连接桩 ID
target: { cell: 'rect2', port: 'in-port-1' }, // 目标节点 ID 和连接桩 ID
})
graph.addEdge({
source: 'rect1', // 源节点 ID
target: { x: 100, y: 120 }, // 目标点
})
vertices
路径点。边从起始点开始,按顺序经过路径点,最后到达终止点。
graph.addEdge({
source: rect1,
target: rect2,
vertices: [
{ x: 100, y: 200 },
{ x: 300, y: 120 },
],
})
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
Graph.registerNode(
'custom-node',
{
inherit: 'rect', // 继承于 rect 节点
width: 100,
height: 40,
markup: [
{
tagName: 'rect', // 标签名称
selector: 'body', // 选择器
},
{
tagName: 'image',
selector: 'img',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
img: {
'xlink:href':
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
width: 16,
height: 16,
x: 12,
y: 12,
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
const source = graph.addNode({
shape: 'custom-node', // 可以直接使用上面注册过的 shape
x: 40,
y: 40,
label: 'hello',
})
const target = graph.addNode({
shape: 'custom-node',
x: 300,
y: 220,
label: 'world',
})
graph.addEdge({
source,
target,
attrs: {
line: {
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
vertices: [
{ x: 100, y: 200 },
{ x: 300, y: 120 },
],
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="vertices-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.vertices-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 320px;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}router
路由 router 将对 vertices 进一步处理,并在必要时添加额外的点,然后返回处理后的点。例如,经过 orth 路由处理后,边的每一条链接线段都是水平或垂直的。
graph.addEdge({
source: rect1,
target: rect2,
vertices: [
{ x: 100, y: 200 },
{ x: 300, y: 120 },
],
// 如果没有 args 参数,可以简写为 router: 'orth'
router: {
name: 'orth',
args: {},
},
})
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
Graph.registerNode(
'custom-node',
{
inherit: 'rect', // 继承于 rect 节点
width: 100,
height: 40,
markup: [
{
tagName: 'rect', // 标签名称
selector: 'body', // 选择器
},
{
tagName: 'image',
selector: 'img',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
img: {
'xlink:href':
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
width: 16,
height: 16,
x: 12,
y: 12,
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
const source = graph.addNode({
shape: 'custom-node', // 可以直接使用上面注册过的 shape
x: 40,
y: 40,
label: 'hello',
})
const target = graph.addNode({
shape: 'custom-node',
x: 300,
y: 220,
label: 'world',
})
graph.addEdge({
source,
target,
attrs: {
line: {
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
vertices: [
{ x: 100, y: 200 },
{ x: 300, y: 120 },
],
router: 'orth',
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="router-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.router-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 320px;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}X6 默认提供了以下几种路由,点击下面的链接查看每种路由的使用方式。
另外,我们也可以注册自定义路由,详情请参考自定义路由教程。
connector
连接器 connector 将路由 router 返回的点加工成渲染边所需要的 pathData。例如,rounded 连接器将连线之间的倒角处理为圆弧倒角。
graph.addEdge({
source: rect1,
target: rect2,
vertices: [
{ x: 100, y: 200 },
{ x: 300, y: 120 },
],
router: 'orth',
// 如果没有 args 参数,可以简写为 connector: 'rounded'
connector: {
name: 'rounded',
args: {},
},
})
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
Graph.registerNode(
'custom-node',
{
inherit: 'rect', // 继承于 rect 节点
width: 100,
height: 40,
markup: [
{
tagName: 'rect', // 标签名称
selector: 'body', // 选择器
},
{
tagName: 'image',
selector: 'img',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
img: {
'xlink:href':
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
width: 16,
height: 16,
x: 12,
y: 12,
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
const source = graph.addNode({
shape: 'custom-node', // 可以直接使用上面注册过的 shape
x: 40,
y: 40,
label: 'hello',
})
const target = graph.addNode({
shape: 'custom-node',
x: 300,
y: 220,
label: 'world',
})
graph.addEdge({
source,
target,
attrs: {
line: {
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
vertices: [
{ x: 100, y: 200 },
{ x: 300, y: 120 },
],
router: 'orth',
connector: 'rounded',
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="connector-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.connector-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 320px;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}X6 默认提供了以下几种连接器,点击下面的链接查看每种连接器的使用方式。
另外,我们也可以注册自定义连接器,详情请参考自定义连接器。
labels
用于设置标签文本、位置、样式等。通过数组形式支持多标签,labels 指定的每一项都将与 defaultLabel 进行 merge 后使用。
const edge = graph.addEdge({
source: rect1,
target: rect2,
labels: [
{
attrs: {
label: {
text: 'edge',
},
},
},
],
})
// 或
const edge = graph.addEdge({
source: rect1,
target: rect2,
labels: ['edge'], // 通过 labels 可以设置多个标签,当只设置标签文本是可以简化为此写法
})
// 或
const edge = graph.addEdge({
source: rect1,
target: rect2,
label: 'edge', // 通过 label 设置单个标签,当只设置标签文本时可以简化为此写法
})
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
Graph.registerNode(
'custom-node',
{
inherit: 'rect', // 继承于 rect 节点
width: 100,
height: 40,
markup: [
{
tagName: 'rect', // 标签名称
selector: 'body', // 选择器
},
{
tagName: 'image',
selector: 'img',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
img: {
'xlink:href':
'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
width: 16,
height: 16,
x: 12,
y: 12,
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
const source = graph.addNode({
shape: 'custom-node',
x: 40,
y: 40,
label: 'hello',
})
const target = graph.addNode({
shape: 'custom-node',
x: 300,
y: 220,
label: 'world',
})
graph.addEdge({
source,
target,
attrs: {
line: {
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
labels: [
{
attrs: {
label: {
text: '40%',
stroke: '#aaa',
},
},
position: 0.4,
},
{
attrs: {
label: {
text: '60%',
stroke: '#aaa',
},
},
position: 0.6,
},
],
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="labels-app ">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.labels-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 320px;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}除了设置文本,还可以使用 Label 在边上创建一些复杂图形,我们在 API 中会详细介绍。
defaultLabel
默认标签。默认标签可以简化标签配置项,labels 指定的每一项都将与 defaultLabel 进行 merge 后使用。
使用箭头
我们定义了 sourceMarker 和 targetMarker 两个特殊属性来为边定制起始和终止箭头。例如,对 Shape.Edge 我们可以通过 line 选择器来指定起始和终止箭头。
内置箭头
X6 提供了以下几种内置箭头,使用时只需要指定箭头名和参数(可省略)即可。
graph.addEdge({
shape: 'edge',
source: [100, 100],
target: [500, 500],
attrs: {
line: {
sourceMarker: 'block', // 实心箭头
targetMarker: {
name: 'ellipse', // 椭圆
rx: 10, // 椭圆箭头的 x 半径
ry: 6, // 椭圆箭头的 y 半径
},
},
},
})
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
const markers = [
'block',
'classic',
'diamond',
'circle',
'circlePlus',
'ellipse',
'cross',
'async',
]
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
markers.forEach((marker, i) => {
graph.addEdge({
sourcePoint: [120, 20 + i * 40],
targetPoint: [400, 20 + i * 40],
label: marker,
attrs: {
line: {
sourceMarker: marker,
targetMarker: marker,
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
})
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="native-marker-app ">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.native-marker-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 320px;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}提示
X6 中边默认自带 classic 箭头,如果要去掉,可以将 targetMarker 设置为 null。
自定义箭头
我们也可以通过 tagName 指定的 SVG 元素来渲染箭头,例如下面我们使用 <path> 元素来渲染箭头,箭头默认继承边的填充色 fill 和边框色 stroke。
graph.addEdge({
shape: 'edge',
source: [100, 100],
target: [500, 500],
attrs: {
line: {
sourceMarker: {
tagName: 'path',
d: 'M 20 -10 0 0 20 10 Z',
},
targetMarker: {
tagName: 'path',
fill: 'yellow', // 使用自定义填充色
stroke: 'green', // 使用自定义边框色
strokeWidth: 2,
d: 'M 20 -10 0 0 20 10 Z',
},
},
},
})
提示
我们的起始箭头和终止箭头使用了相同的 d 属性,这是因为我们会自动计算箭头方向,简单来说,我们在定义箭头时,只需要定义一个向左指向坐标原点的箭头即可。
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
graph.addEdge({
source: [100, 140],
target: [400, 140],
label: 'custom-marker',
attrs: {
line: {
sourceMarker: {
tagName: 'path',
d: 'M 20 -10 0 0 20 10 Z',
},
targetMarker: {
tagName: 'path',
stroke: '#D94111',
strokeWidth: 2,
fill: '#90C54C',
d: 'M 20 -10 0 0 20 10 Z',
},
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="custom-marker-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.custom-marker-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 320px;
margin-right: 8px;
margin-left: 8px;
box-shadow: 0 0 10px 1px #e9e9e9;
}
}更多箭头的案例和定制技巧请参考 API。
定制边
和节点一样,我们可以通过 markup 和 attrs 来定制边的形状和样式,也可以注册自定义边来达到复用效果。X6 默认的边 Shape.Edge 中定义了 line(代表 path 元素)和 wrap(代表透明的 path 元素,用于响应交互)两个选择器。我们在创建边时可以像下面这样定义边的样式。
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph } from '@antv/x6'
Graph.registerEdge('double-edge', {
inherit: 'edge',
markup: [
{
tagName: 'path',
selector: 'outline',
attrs: {
fill: 'none',
},
},
{
tagName: 'path',
selector: 'line',
attrs: {
fill: 'none',
cursor: 'pointer',
},
},
],
attrs: {
line: {
connection: true,
stroke: '#dddddd',
strokeWidth: 4,
strokeLinejoin: 'round',
targetMarker: {
tagName: 'path',
stroke: '#000000',
d: 'M 10 -3 10 -10 -2 0 10 10 10 3',
},
},
outline: {
connection: true,
stroke: '#000000',
strokeWidth: 6,
strokeLinejoin: 'round',
},
},
})
Graph.registerEdge('shadow-edge', {
inherit: 'edge',
markup: [
{
tagName: 'path',
selector: 'shadow',
attrs: {
fill: 'none',
},
},
{
tagName: 'path',
selector: 'line',
attrs: {
fill: 'none',
cursor: 'pointer',
},
},
],
attrs: {
line: {
connection: true,
stroke: '#dddddd',
strokeWidth: 20,
strokeLinejoin: 'round',
targetMarker: {
name: 'path',
stroke: 'none',
d: 'M 0 -10 -10 0 0 10 z',
offsetX: -5,
},
sourceMarker: {
name: 'path',
stroke: 'none',
d: 'M -10 -10 0 0 -10 10 0 10 0 -10 z',
offsetX: -5,
},
},
shadow: {
connection: true,
refX: 3,
refY: 6,
stroke: '#000000',
strokeOpacity: 0.2,
strokeWidth: 20,
strokeLinejoin: 'round',
targetMarker: {
name: 'path',
d: 'M 0 -10 -10 0 0 10 z',
stroke: 'none',
offsetX: -5,
},
sourceMarker: {
name: 'path',
stroke: 'none',
d: 'M -10 -10 0 0 -10 10 0 10 0 -10 z',
offsetX: -5,
},
},
},
})
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
graph.addEdge({
shape: 'double-edge',
source: [100, 100],
target: [400, 100],
})
graph.addEdge({
shape: 'shadow-edge',
source: [100, 180],
target: [400, 180],
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="registry-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.registry-app {
display: flex;
width: 100%;
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
}修改边
和节点类似,在渲染完成之后,我们还可以通过 API 修改边的所有属性。我们会常用到下面两个方法:
下面我们看一下 X6 默认提供的边的 prop。
const edge = graph.addEdge({
source: [200, 140],
target: [500, 140],
label: 'edge',
})
console.log(edge.prop())
// 输出结果
{
"shape": "edge",
"attrs": {
"lines": {
"connection": true,
"strokeLinejoin": "round"
},
"wrap": {
"strokeWidth": 10
},
"line": {
"stroke": "#333",
"strokeWidth": 2,
"targetMarker": "classic"
}
},
"id": "9d5e4f54-1ed3-429e-8d8c-a1526cff2cd8",
"source": {
"x": 200,
"y": 140
},
"target": {
"x": 500,
"y": 140
},
"labels": [{
"attrs": {
"label": {
"text": "edge"
}
}
}],
"zIndex": 1
}
从上面结果可以看到,prop 是处理后的一份新的配置,它的值可以通过方法进行更新,更新之后,边会立即刷新到最新状态。为了更快捷的修改边的 attrs,X6 提供了 attr 方法。
edge.prop('target', { x: 300, y: 300 }) // 修改终点
edge.attr('line/stroke', '#ccc') // 修改边颜色,等价于 edge.prop('attrs/line/stroke', '#ccc')
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph, Color } from '@antv/x6'
import { Button } from 'antd'
const commands = [
{
key: 'prop',
label: 'prop',
},
{
key: 'attr',
label: 'attr',
},
]
export default class Example extends React.Component {
private container: HTMLDivElement
private graph: Graph
componentDidMount() {
const graph = new Graph({
container: this.container,
background: {
color: '#F2F7FA',
},
})
const source = graph.addNode({
shape: 'rect',
x: 40,
y: 100,
width: 100,
height: 40,
label: 'hello',
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
},
})
const target = graph.addNode({
shape: 'rect',
x: 340,
y: 100,
width: 100,
height: 40,
label: 'hello',
attrs: {
body: {
stroke: '#8f8f8f',
strokeWidth: 1,
fill: '#fff',
rx: 6,
ry: 6,
},
},
})
graph.addEdge({
source,
target,
attrs: {
line: {
stroke: '#8f8f8f',
strokeWidth: 1,
},
},
})
this.graph = graph
}
change = (command: string) => {
const edges = this.graph.getEdges()
switch (command) {
case 'prop':
edges.forEach((edge) => {
const x = Math.floor(Math.random() * 600)
const y = Math.floor(Math.random() * 200)
edge.prop('vertices', [[x, y]])
})
break
case 'attr':
edges.forEach((edge) => {
const color = Color.randomHex()
edge.attr('line/stroke', color)
})
break
default:
break
}
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="prop-app">
<div className="app-btns">
<Button.Group>
{commands.map((item) => (
<Button onClick={() => this.change(item.key)} key={item.key}>
{item.label}
</Button>
))}
</Button.Group>
</div>
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}
const root = createRoot(document.getElementById('container')!)
root.render(<Example />)
window.addEventListener('pagehide', () => root.unmount(), { once: true })
.prop-app {
border: 1px solid #f0f0f0;
padding: 16px 8px;
font-family: sans-serif;
.app-btns {
bottom: 0;
margin: 0;
padding: 0 8px;
}
.app-content {
width: 100%;
height: 240px;
margin: 0;
margin-top: 16px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
}