跳到主要内容

连接桩标签布局

连接桩标签布局算法是一个具有如下签名的函数,返回标签相对于连接桩的位置和旋转角度。

type Definition<T> = (
  portPosition: Point, // 连接桩的位置
  elemBBox: Rectangle, // 节点的包围盒
  args: T, // 标签位置参数
) => Result

interface Result {
  position: Point.PointLike // 标签相对于位置
  angle: number // 标签的旋转角度
  attrs: Attr.CellAttrs // 标签的属性
}

创建连接桩时,可以在群组 groups 中指定该组的标签定位,也可以在 items 中覆盖群组中的定义:

graph.addNode(
  ...,
  ports: {
    // 连接桩分组
    groups: {
      group1: {
        label: {
          position: {      // 标签布局算法
            name: 'xxx',   // 标签布局算法名称
            args: { ... }, // 标签布局算法参数
          },
        },
      },
    },

    // 连接桩定义
    items: [
      {
        groups: 'group1',
        label: {
          position: { // 覆盖 group1 中指定的标签定位算法
            name: 'xxx',
            args: { ... },
          }
        },
      }
    ],
  },
)

下面我们一起来看看如何使用内置的标签布局算法,以及如何自定并注册自定义布局算法。

内置连接桩标签布局

Side

标签位于连接桩的某一侧。

  • 'left' 标签位于连接桩左侧。
  • 'right' 标签位于连接桩右侧。
  • 'top' 标签位于连接桩顶部。
  • 'bottom' 标签位于连接桩底部。

可以通过 args 来指定标签的位置和旋转角度。

interface SideArgs {
  x?: number
  y?: number
  angle?: number
  attrs?: Attr.CellAttrs
}
名称 类型 必选 默认值 描述
x number - 用指定的 X 坐标替换计算结果中的 X 坐标。
y number - 用指定的 Y 坐标替换计算结果中的 Y 坐标。
angle number - 用指定的旋转角度替换计算结果中的旋转角度。
attrs Attr.CellAttrs - 标签属性。
label: {
  position: {
    name : 'right',
    args: {
      y: 10, // 强制指定 y 坐标为 10,替换计算结果中的 y 坐标
      attrs: {
        text: {
          fill: 'red', // 设置标签颜色为红色
        },
      },
    },
  },
}
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph, Node } from '@antv/x6'
import { Settings, State } from './settings.tsx'

export default class Example extends React.Component {
  private container: HTMLDivElement
  private node: Node

  componentDidMount() {
    const graph = new Graph({
      container: this.container,
      background: {
        color: '#F2F7FA',
      },
    })

    this.node = graph.addNode({
      shape: 'rect',
      x: 80,
      y: 68,
      width: 240,
      height: 80,
      attrs: {
        label: {
          text: 'left',
          fill: '#6a6c8a',
        },
        body: {
          stroke: '#8f8f8f',
          strokeWidth: 1,
          fill: '#fff',
          rx: 6,
          ry: 6,
        },
      },
      ports: {
        groups: {
          a: {
            position: {
              name: 'top',
              args: {
                dr: 0,
                dx: 0,
                dy: -10,
              },
            },
            label: {
              position: {
                name: 'left',
              },
            },
            attrs: {
              circle: {
                fill: '#ffffff',
                stroke: '#8f8f8f',
                strokeWidth: 1,
                r: 10,
              },
              text: {
                fill: '#6a6c8a',
              },
            },
          },
        },
      },
    })

    Array.from({ length: 3 }).forEach((_, index) => {
      const label =
        index === 2
          ? {
              position: { args: { x: 20, y: -20 } },
            }
          : {}
      const stroke = index === 2 ? { stroke: 'red' } : {}
      const fill = index === 2 ? { fill: 'red' } : {}
      this.node.addPort({
        label,
        group: 'a',
        attrs: {
          circle: { magnet: true, ...stroke },
          text: { text: `P${index}`, ...fill },
        },
      })
    })
  }

  onAttrsChanged = ({ position }: State) => {
    this.node.prop('ports/groups/a/label/position/name', position)
    this.node.attr('label/text', position)
  }

  refContainer = (container: HTMLDivElement) => {
    this.container = container
  }

  render() {
    return (
      <div className="port-label-side-app">
        <div className="app-side">
          <Settings onChange={this.onAttrsChanged} />
        </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 })

Inside/Outside

标签位于节点的内部或者外部,支持一下四种布局:

  • 'inside' 标签位于节点内围(靠近边线的内侧)。
  • 'outside' 标签位于节点外围(靠近边线的外侧)。
  • 'insideOriented' 标签位于节点内围,而且根据坐所在位自动调整文本的方向。
  • 'outsideOriented' 标签位于节点外围,而且根据坐所在位自动调整文本的方向。

支持通过 args.offset 来设置标签沿节点中到标签位置方向的偏移量。

interface InOutArgs {
  offset?: number
  x?: number
  y?: number
  angle?: number
  attrs?: Attr.CellAttrs
}
名称 类型 必选 默认值 描述
offset number 15 从节点中心到标签位置的方向上的偏移量。
x number - 用指定的 X 坐标替换计算结果中的 X 坐标。
y number - 用指定的 Y 坐标替换计算结果中的 Y 坐标。
angle number - 用指定的旋转角度替换计算结果中的旋转角度。
attrs Attr.CellAttrs - 标签属性。
label: {
  position: {
    name : 'outside',
  },
}
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph, Node } from '@antv/x6'
import { Settings, State } from './settings.tsx'

export default class Example extends React.Component {
  private container: HTMLDivElement
  private node: Node

  componentDidMount() {
    const graph = new Graph({
      container: this.container,
      background: {
        color: '#F2F7FA',
      },
    })

    this.node = graph.addNode({
      shape: 'ellipse',
      x: 70,
      y: 85,
      width: 260,
      height: 100,
      attrs: {
        label: {
          text: 'outside',
          fill: '#888',
          fontSize: 12,
        },
        body: {
          stroke: '#8f8f8f',
          strokeWidth: 1,
          fill: '#fff',
          rx: 6,
          ry: 6,
        },
      },
      ports: {
        groups: {
          a: {
            position: {
              name: 'ellipseSpread',
              args: {
                compensateRotate: true,
              },
            },
            label: {
              position: {
                name: 'outside',
              },
            },
            attrs: {
              circle: {
                fill: '#ffffff',
                stroke: '#8f8f8f',
                strokeWidth: 1,
                r: 10,
                magnet: true,
              },
              text: {
                fill: '#6a6c8a',
                fontSize: 12,
              },
            },
          },
        },
      },
    })

    Array.from({ length: 10 }).forEach((_, index) => {
      this.node.addPort({ attrs: { text: { text: `P ${index}` } }, group: 'a' })
    })
  }

  onAttrsChanged = ({ position, offset }: State) => {
    this.node.prop('ports/groups/a/label/position', {
      name: position,
      args: { offset },
    })

    this.node.attr('label/text', position)
  }

  refContainer = (container: HTMLDivElement) => {
    this.container = container
  }

  render() {
    return (
      <div className="port-label-inside-outside-app">
        <div className="app-side">
          <Settings onChange={this.onAttrsChanged} />
        </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 })

Radial

将标签放在圆形或椭圆形节点的外围。支持一下两种布局:

  • 'radial' 标签位于圆形或椭圆形节点的外围。
  • 'radialOriented' 标签位于圆形或椭圆形节点的外围,并使标签文本自动沿圆弧方向旋转。
interface RadialArgs {
  offset?: number
  x?: number
  y?: number
  angle?: number
  attrs?: Attr.CellAttrs
}
名称 类型 必选 默认值 描述
offset number 20 从节点中心到标签位置的方向上的偏移量。
x number - 用指定的 X 坐标替换计算结果中的 X 坐标。
y number - 用指定的 Y 坐标替换计算结果中的 Y 坐标。
angle number - 用指定的旋转角度替换计算结果中的旋转角度。
attrs Attr.CellAttrs - 标签属性。
label: {
  position: {
    name : 'radial',
  },
}
import './index.less'
import { createRoot } from 'react-dom/client'
import React from 'react'
import { Graph, Node } from '@antv/x6'
import { Settings, State } from './settings.tsx'

export default class Example extends React.Component {
  private container: HTMLDivElement
  private node: Node

  componentDidMount() {
    const graph = new Graph({
      container: this.container,
      background: {
        color: '#F2F7FA',
      },
    })

    this.node = graph.addNode({
      shape: 'ellipse',
      x: 70,
      y: 50,
      width: 260,
      height: 100,
      attrs: {
        label: {
          text: 'outside',
          fill: '#888',
          fontSize: 12,
        },
        body: {
          stroke: '#8f8f8f',
          strokeWidth: 1,
          fill: '#fff',
          rx: 6,
          ry: 6,
        },
      },
      ports: {
        groups: {
          a: {
            position: {
              name: 'ellipseSpread',
              args: {
                compensateRotate: true,
              },
            },
            label: {
              position: {
                name: 'outside',
              },
            },
            attrs: {
              circle: {
                fill: '#ffffff',
                stroke: '#8f8f8f',
                strokeWidth: 1,
                r: 10,
                magnet: true,
              },
              text: {
                fill: '#6a6c8a',
                fontSize: 12,
              },
            },
          },
        },
      },
    })

    Array.from({ length: 10 }).forEach((_, index) => {
      this.node.addPort({ attrs: { text: { text: `P ${index}` } }, group: 'a' })
    })
  }

  onAttrsChanged = ({ position, offset }: State) => {
    this.node.prop('ports/groups/a/label/position', {
      name: position,
      args: { offset },
    })

    this.node.attr('label/text', position)
  }

  refContainer = (container: HTMLDivElement) => {
    this.container = container
  }

  render() {
    return (
      <div className="port-label-radial-app">
        <div className="app-side">
          <Settings onChange={this.onAttrsChanged} />
        </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 })

自定义连接桩标签布局

连接桩标签定位是一个具有如下签名的函数,返回标签相对于连接桩的位置和旋转角度。

type Definition<T> = (
  portPosition: Point, // 连接桩的位置
  elemBBox: Rectangle, // 节点的包围盒
  args: T, // 标签位置参数
) => Result

interface Result {
  position: Point.PointLike // 标签相对于位置
  angle: number // 标签的旋转角度
  attrs: Attr.CellAttrs // 标签的属性
}

所以我们可以按照上面规则来创建自定义布局算法,例如,实现一个位于连接桩右下角的布局:

function bottomRight(portPosition, elemBBox, args) {
  const dx = args.dx || 10
  const dy = args.dy || 10

  return {
    position: {
     portPosition.x + dx,
     portPosition.y + dy,
    }
  }
}

布局算法实现后,需要注册到系统,注册后就可以像内置布局算法那样来使用。

Graph.registerPortLabelLayout('bottomRight', bottomRight)

注册以后,我们就可以像内置布局算法那样来使用:

const rect = graph.addNode({
  ports: {
    groups: {
      group1: {
        position: {
          name: 'top',
        },
        label: {
          position: {
            name: 'bottomRight',
          },
        },
      },
    },

    items: [
      { id: 'port1', group: 'group1' },
      { id: 'port2', label: { position: 'bottomRight' } },
    ],
  },
})
© Copyright 2026 Ant Group Co., Ltd..备案号:京ICP备15032932号-38
全站搜索
文档与示例输入关键词开始搜索

输入关键词,查找文档、API 与示例

↑↓选择↵打开Esc关闭