---
title: "边"
description: "边"
language: "zh"
canonical: "https://x6.antv.antgroup.com/zh/tutorial/basic/edge/"
version: "3.x"
---

[资料索引](/llms.txt)

> **在本章节中主要介绍边相关的知识，通过阅读，你可以了解到**

- 添加边的方法
- 如何配置边的形状
- 如何在边上增加箭头
- 如何自定义边
- 如何通过 API 修改边


## 添加边

节点和边都有共同的基类 [Cell](/zh/api/model/cell)，除了从 `Cell` 继承属性外，还支持以下选项。

| 属性名       | 类型              | 默认值                                         | 描述              |
|--------------|-------------------|------------------------------------------------|-----------------|
| source       | TerminalData      | -                                              | 源节点或起始点。   |
| target       | TerminalData      | -                                              | 目标节点或目标点。 |
| vertices     | Point.PointLike[] | -                                              | 路径点。           |
| router       | RouterData        | -                                              | 路由。             |
| connector    | ConnectorData     | -                                              | 连接器。           |
| labels       | Label[]           | -                                              | 标签。             |
| defaultLabel | Label             | [默认标签](/zh/api/model/edge#labels-和-defaultlabel) | 默认标签。         |

```ts
graph.addEdge({
  shape: 'edge',
  source: 'node1',
  target: 'node2',
})
```

## 配置边

下面分别看下上面的配置如何使用。

### source/target

边的源和目标节点(点)。

```ts
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

路径点。边从起始点开始，按顺序经过路径点，最后到达终止点。

```ts
graph.addEdge({
  source: rect1,
  target: rect2,
  vertices: [
    { x: 100, y: 200 },
    { x: 300, y: 120 },
  ],
})
```



```ts
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 })
```



### router

路由 `router` 将对 `vertices` 进一步处理，并在必要时添加额外的点，然后返回处理后的点。例如，经过 [orth 路由](/zh/api/registry/router#orth)处理后，边的每一条链接线段都是水平或垂直的。

```ts
graph.addEdge({
  source: rect1,
  target: rect2,
  vertices: [
    { x: 100, y: 200 },
    { x: 300, y: 120 },
  ],
  // 如果没有 args 参数，可以简写为 router: 'orth'
  router: {
    name: 'orth',
    args: {},
  },
})
```



```ts
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 })
```



X6 默认提供了以下几种路由，点击下面的链接查看每种路由的使用方式。

- [normal](/zh/api/registry/router#normal)
- [orth](/zh/api/registry/router#orth)
- [oneSide](/zh/api/registry/router#oneside)
- [manhattan](/zh/api/registry/router#manhattan)
- [metro](/zh/api/registry/router#metro)
- [er](/zh/api/registry/router#er)

另外，我们也可以注册自定义路由，详情请参考[自定义路由](/zh/api/registry/router#registry)教程。

### connector

连接器 `connector` 将路由 `router` 返回的点加工成渲染边所需要的 [pathData](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d)。例如，`rounded` 连接器将连线之间的倒角处理为圆弧倒角。

```ts
graph.addEdge({
  source: rect1,
  target: rect2,
  vertices: [
    { x: 100, y: 200 },
    { x: 300, y: 120 },
  ],
  router: 'orth',
  // 如果没有 args 参数，可以简写为 connector: 'rounded'
  connector: {
    name: 'rounded',
    args: {},
  },
})
```



```ts
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 })
```



X6 默认提供了以下几种连接器，点击下面的链接查看每种连接器的使用方式。

- [normal](/zh/api/registry/connector#normal)
- [rounded](/zh/api/registry/connector#rounded)
- [smooth](/zh/api/registry/connector#smooth)
- [jumpover](/zh/api/registry/connector#jumpover)

另外，我们也可以注册自定义连接器，详情请参考[自定义连接器](/zh/api/registry/connector)。

### labels

用于设置标签文本、位置、样式等。通过数组形式支持多标签，`labels` 指定的每一项都将与 [defaultLabel](/zh/api/model/edge#labels-和-defaultlabel) 进行 [merge](https://lodash.com/docs/4.17.15#merge) 后使用。

```ts
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 设置单个标签，当只设置标签文本时可以简化为此写法
})
```



```ts
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 })
```



除了设置文本，还可以使用 Label 在边上创建一些复杂图形，我们在 [API](/zh/api/model/labels) 中会详细介绍。

### defaultLabel

默认标签。默认标签可以简化标签配置项，`labels` 指定的每一项都将与 `defaultLabel` 进行 merge 后使用。

## 使用箭头

我们定义了 `sourceMarker` 和 `targetMarker` 两个特殊属性来为边定制起始和终止箭头。例如，对 `Shape.Edge` 我们可以通过 `line` 选择器来指定起始和终止箭头。

### 内置箭头

X6 提供了以下几种内置箭头，使用时只需要指定箭头名和参数（可省略）即可。

- [block](/zh/api/model/marker#block)
- [classic](/zh/api/model/marker#classic)
- [diamond](/zh/api/model/marker#diamond)
- [cross](/zh/api/model/marker#cross)
- [async](/zh/api/model/marker#async)
- [path](/zh/api/model/marker#path)
- [circle](/zh/api/model/marker#circle)
- [circlePlus](/zh/api/model/marker#circleplus)
- [ellipse](/zh/api/model/marker#ellipse)

```ts
graph.addEdge({
  shape: 'edge',
  source: [100, 100],
  target: [500, 500],
  attrs: {
    line: {
      sourceMarker: 'block', // 实心箭头
      targetMarker: {
        name: 'ellipse', // 椭圆
        rx: 10, // 椭圆箭头的 x 半径
        ry: 6, // 椭圆箭头的 y 半径
      },
    },
  },
})
```



```ts
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 })
```



> **提示**

 X6 中边默认自带 `classic` 箭头，如果要去掉，可以将 `targetMarker` 设置为 `null`。

### 自定义箭头

我们也可以通过 `tagName` 指定的 SVG 元素来渲染箭头，例如下面我们使用 `<path>` 元素来渲染箭头，箭头默认继承边的填充色 `fill` 和边框色 `stroke`。

```ts
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` 属性，这是因为我们会自动计算箭头方向，简单来说，我们在定义箭头时，只需要定义一个**向左指向坐标原点**的箭头即可。



```ts
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 })
```



更多箭头的案例和定制技巧请参考 [API](/zh/api/model/marker)。

## 定制边

和节点一样，我们可以通过 `markup` 和 `attrs` 来定制边的形状和样式，也可以注册自定义边来达到复用效果。X6 默认的边 `Shape.Edge` 中定义了 `line`（代表 path 元素）和 `wrap`（代表透明的 path 元素，用于响应交互）两个选择器。我们在创建边时可以像下面这样定义边的样式。



```ts
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 })
```



## 修改边

和节点类似，在渲染完成之后，我们还可以通过 API 修改边的所有属性。我们会常用到下面两个方法：

- edge.prop(path, value)，详细使用见 [prop](/zh/api/model/cell#节点和边的属性-properties)。
- edge.attr(path, value)，详细使用见 [attr](/zh/api/model/cell#元素属性-attrs)。

下面我们看一下 X6 默认提供的边的 `prop`。

```ts
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` 方法。

```ts
edge.prop('target', { x: 300, y: 300 }) // 修改终点
edge.attr('line/stroke', '#ccc') // 修改边颜色，等价于 edge.prop('attrs/line/stroke', '#ccc')
```



```ts
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 })
```
### docs/demos/tutorial/basic/edge/vertices/index.less

```less
.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;
  }
}
```
### docs/demos/tutorial/basic/edge/router/index.less

```less
.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;
  }
}
```
### docs/demos/tutorial/basic/edge/connector/index.less

```less
.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;
  }
}
```
### docs/demos/tutorial/basic/edge/labels/index.less

```less
.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;
  }
}
```
### docs/demos/tutorial/basic/edge/native-marker/index.less

```less
.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;
  }
}
```
### docs/demos/tutorial/basic/edge/custom-marker/index.less

```less
.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;
  }
}
```
### docs/demos/tutorial/basic/edge/registry/index.less

```less
.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%);
  }
}
```
### docs/demos/tutorial/basic/edge/prop/index.less

```less
.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%);
  }
}
```
