EdgeTool

7 min read

由于边通常是一个具有较小宽度的折线/曲线,就不太方便与边进行交互。为了解决这个问题,我们一方面在渲染边的同时还渲染了一个与边同形状的 <path> 路径,但宽度更宽且透明的边,来方便用户与边进行交互;另外,我们也可以为边添加一些小工具来增强边的交互能力,如路径点小工具使路径点可以被移动、线段小工具使边中的线段可以被移动等。我们在 Registry.EdgeTool.presets 命名空间中提供了一些边的小工具,可以在下面这些场景使用。

场景一:添加指定的小工具。

// 创建边时添加小工具
graph.addEdge({
  source,
  target,
  tools:[
    { name: 'vertices' },
    {
      name: 'button-remove',
      args: { distance: 20  },
    },
  ],
})


// 创建边后添加小工具
edge.addTools([
  { name: 'vertices' },
  {
    name: 'button-remove',
    args: { distance: 20  },
  },
])

场景二:鼠标动态添加/删除小工具。

graph.on('edge:mouseenter', ({ cell }) => {
  cell.addTools(
    [
      { name: 'vertices' },
      {
        name: 'button-remove',
        args: { distance: 20  },
      },
    ],
  )
})

graph.on('edge:mouseleave', ({ cell }) => {
  if (cell.hasTool('button-remove')) {
    cell.removeTool('button-remove')
  }
})

在 X6 中默认提供了以下几个用于边的小工具:

  • vertices 路径点工具,在路径点位置渲染一个小圆点,拖动小圆点修改路径点位置,双击小圆点删除路径点,在边上单击添加路径点。
  • segments 线段工具。在边的每条线段的中心渲染一个工具条,可以拖动工具条调整线段两端的路径点的位置。
  • boundary 根据边的包围盒渲染一个包围边的矩形。注意,该工具仅仅渲染一个矩形,不带任何交互。
  • button 在指定位置处渲染一个按钮,支持自定义按钮的点击交互。
  • button-remove 在指定的位置处,渲染一个删除按钮,点击时删除对应的边。
  • source-arrowhead-和-target-arrowhead 在边的起点或终点渲染一个图形(默认是箭头),拖动该图形来修改边的起点或终点。
  • edge-editor 提供边上文本编辑功能。

presets

vertices

路径点工具,在路径点位置渲染一个小圆点,拖动小圆点修改路径点位置,双击小圆点删除路径点,在边上单击添加路径点。

参数

参数名类型默认值说明
attrsKeyValueundefined小圆点的属性。
snapRadiusnumber20移动路径点过程中的吸附半径。当路径点与邻近的路径点的某个坐标 (x, y) 距离在半径范围内时,将当前路径点的对应坐标 (x, y) 吸附到邻居路径的路径点。
addablebooleantrue在边上按下鼠标时,是否可以添加新的路径点。
removablebooleantrue是否可以通过双击移除路径点。
removeRedundanciesbooleantrue是否自动移除冗余的路径点。
stopPropagationbooleantrue是否阻止工具上的鼠标事件冒泡到边视图上。阻止后鼠标与工具交互时将不会触发边的 mousedownmousemovemouseup 事件。

使用

// 创建 edge 时添加小工具
const edge1 = graph.addEdge({
  ...,
  tools: {
    items: [
      {
        name: 'vertices',
        args: {
          attrs: { fill: '#666' },
        },
      },
    ],
  },
})

// 鼠标 Hover 时添加小工具
graph.on('edge:mouseenter', ({ cell }) => {
  if (cell === edge2) {
    cell.setTools({
      name: 'onhover',
      tools: ['vertices'],
    })
  }
})

graph.on('edge:mouseleave', ({ cell }) => {
  if (cell.hasTools('onhover')) {
    cell.removeTools()
  }
})

segments

线段工具。在边的每条线段的中心渲染一个工具条,可以拖动工具条调整线段两端的路径点的位置。

参数

参数名类型默认值说明
attrsobject{width: 20, height: 8, x: -10, y: -4, rx: 4, ry: 4, fill: '#333', stroke: '#fff', stroke-width: 2}元素的属性。
precisionnumber0.5线段的两个端点的 X 或 Y 轴的坐标差小于 precision 时才渲染工具,默认 0.5 表示只对垂直和水平线段渲染工具。
thresholdnumber40线段长度超过 threshold 时才渲染工具。
snapRadiusnumber10调整线段过程中的吸附半径。
removeRedundanciesbooleantrue是否自动移除冗余的路径点。
stopPropagationbooleantrue是否阻止工具上的鼠标事件冒泡到边视图上。阻止后鼠标与工具交互时将不会触发边的 mousedownmousemovemouseup 事件。

使用

graph.addEdge({
  ...,
  tools: {
    name: 'segments',
    args: {
      snapRadius: 20,
      attrs: {
        fill: '#444',
      },
    },
  },
})

boundary

根据边的包围盒渲染一个包围边的矩形。注意,该工具仅仅渲染一个矩形,不带任何交互。

参数

参数名类型默认值说明
tagNamestringrect使用何种图形渲染。
paddingnumber10边距。
attrsKeyValueobject图形属性。

其中 attrs 的默认值(默认样式)为:

{
  fill: 'none',
  stroke: '#333',
  'stroke-width': 0.5,
  'stroke-dasharray': '5, 5',
  'pointer-events': 'none',
}

使用

graph.addEdge({
  ...,
  tools: {
    name: 'boundary',
    args: {
      padding: 5,
      attrs: {
        fill: '#7c68fc',
        stroke: '#333',
        'stroke-width': 0.5,
        'fill-opacity': 0.2,
      },
    },
  },
})

button

在指定位置处渲染一个按钮,支持自定义按钮的点击交互。

参数

参数名类型默认值说明
distancenumberundefined偏离起点的距离或比例。
offsetnumber | Point.PointLike0distance 基础上的偏移量。
rotatebooleanundefined是否跟随边旋转。
markupMarkup.JSONMarkupundefined渲染按钮的 Markup 定义。
onClick(args: {e: JQuery.MouseDownEvent, cell: Cell, view: CellView }) => voidundefined点击按钮的回调函数。

使用

graph.addEdge({
  ...,
  tools: [
    {
      name: 'button',
      args: {
        markup: [
          {
            tagName: 'circle',
            selector: 'button',
            attrs: {
              r: 18,
              stroke: '#fe854f',
              'stroke-width': 2,
              fill: 'white',
              cursor: 'pointer',
            },
          },
          {
            tagName: 'text',
            textContent: 'Btn B',
            selector: 'icon',
            attrs: {
              fill: '#fe854f',
              'font-size': 10,
              'text-anchor': 'middle',
              'pointer-events': 'none',
              y: '0.3em',
            },
          },
        ],
        distance: -40,
        onClick({ view }: any) {
          const edge = view.cell
          const source = edge.getSource()
          const target = edge.getTarget()
          edge.setSource(target)
          edge.setTarget(source)
        },
      },
    },
    {
      name: 'button',
      args: {
        markup: [
          {
            tagName: 'circle',
            selector: 'button',
            attrs: {
              r: 18,
              stroke: '#fe854f',
              'stroke-width': 2,
              fill: 'white',
              cursor: 'pointer',
            },
          },
          {
            tagName: 'text',
            textContent: 'Btn A',
            selector: 'icon',
            attrs: {
              fill: '#fe854f',
              'font-size': 10,
              'text-anchor': 'middle',
              'pointer-events': 'none',
              y: '0.3em',
            },
          },
        ],
        distance: -100,
        offset: { x: 0, y: 20 },
        onClick({ view }: any) {
          const edge = view.cell
          edge.attr({
            line: {
              strokeDasharray: '5, 1',
              strokeDashoffset:
                (edge.attr('line/strokeDashoffset') | 0) + 20,
            },
          })
        },
      },
    },
  ],
})

button-remove

在指定的位置处,渲染一个删除按钮,点击时删除对应的边。

参数

参数名类型默认值说明
distancenumber60偏离起点的距离或比例。
offsetnumber | Point.PointLike0distance 基础上的偏移量。
rotatebooleanundefined是否跟随边旋转。
markupMarkup.JSONMarkupundefined渲染按钮的 Markup 定义。
onClick(args: {e: JQuery.MouseDownEvent, cell: Cell, view: CellView }) => voidundefined点击按钮的回调函数。

使用

graph.addEdge({
  ...,
  tools: {
    name: 'button-remove',
    args: { distance: -40 },
  },
})

source-arrowhead 和 target-arrowhead

在边的起点或终点渲染一个图形(默认是箭头),拖动该图形来修改边的起点或终点。

参数

参数名类型默认值说明
tagNamestringpath使用何种图形来渲染。
attrsAttr.SimpleAttrsobject图形的属性。

其中 source-arrowhead 的属性默认值为

{
  d: 'M 10 -8 -10 0 10 8 Z',
  fill: '#333',
  stroke: '#fff',
  'stroke-width': 2,
  cursor: 'move',
}

target-arrowhead 的属性默认值为

{
  d: 'M -10 -8 10 0 -10 8 Z',
  fill: '#333',
  stroke: '#fff',
  'stroke-width': 2,
  cursor: 'move',
}

使用

graph.on('edge:mouseenter', ({ cell }) => {
  cell.addTools([
    'source-arrowhead',
    {
      name: 'target-arrowhead',
      args: {
        attrs: {
          fill: 'red',
        },
      },
    },
  ])
})

graph.on('edge:mouseleave', ({ cell }) => {
  cell.removeTools()
})

edge-editor

提供边上文本编辑功能。

参数

参数名类型默认值说明
eventJQuery.MouseEventBase-触发文本编辑的事件参数
labelAddablebooleantrue点击非文本位置是否新建label
attrs/fontSizestring14编辑文本字体大小
attrs/colorstring#000编辑文本字体颜色
attrs/fontFamilystringArial, helvetica, sans-serif编辑文本的字体
attrs/backgroundColorstring#fff编辑区域的背景色
getText(this: CellView, args: {cell: Cell}) => string-获取原文本方法,在自定义 markup 场景需要自定义 getText 方法
setText(this: CellView, args: {cell: Cell, value: string}) => void-设置新文本,在自定义 markup 场景需要自定义 setText 方法

使用

// 双击进入编辑模式
graph.on('edge:dblclick', ({ cell, e }) => {
  cell.addTools({
    name: 'edge-editor',
    args: {
      event: e,
    },
  })
})

registry

我们在 Registry.EdgeTool.registry 对象上提供了注册和取消注册工具的方法,工具实际上是一个继承自 ToolItem 的视图

export type Definition = typeof ToolItem | (new (options: ToolItem.Options) => ToolItem)

例如,上面提到的 'button' 工具的对应的定义为

export class Button extends ToolsView.ToolItem<EdgeView | NodeView, Button.Options> {
  protected onRender() { ... }
  protected onMouseDown() { ... }
}

创建工具类之后就可以使用下面的 register 方法来注册到系统中。

register

register(entities: { [name: string]: Definition }, force?: boolean): void
register(name: string, entity: Definition, force?: boolean): Definition

注册工具。

unregister

unregister(name: string): Definition | null

取消注册工具。

实际上,我们将 registry 对象的 registerunregister 方法分别挂载为 Graph 的两个静态方法 Graph.registerEdgeToolGraph.unregisterEdgeTool,看下面使用示例。

自定义工具

场景一:继承 ToolItem 实现一个工具类,难度较高,要求对视图基类ToolItem 类都有所了解,可以参考上述内置工具的源码,这里不展开叙述。

Graph.registerEdgeTool('button', Button)

场景二:继承已经注册的工具,为继承的工具指定默认选项或者默认样式。我们在 ToolItem 基类上提供了一个静态方法 define 来快速实现继承并配置默认选项。

import { Vertices } from '@antv/x6/es/registry/tool/vertices'

const RedVertices = Vertices.define<Vertices.Options>({
  name: 'red-vertices', // 工具名称,可省略,指定后其大驼峰形式同时作为继承的类的类名。
  attrs: {
    fill: 'red',
  },
})

Graph.registerEdgeTool('red-vertices', RedVertices, true)

同时,我们为 Graph.registerEdgeTool 方法提供了一种快速继承并指定默认选项的实现:

Graph.registerEdgeTool('red-vertices', {
  'red-vertices',     // 工具名称,可省略,指定后其大驼峰形式同时作为继承的类的类名。
  inherit:'vertices', // 基类名称,使用已经注册的工具名称。
  attrs: {            // 其他选项,作为继承的类的默认选项。
    fill: 'red',
  },
})

通过该方法,我们可以快速定义并注册一个圆形的端点 circle-target-arrowhead

Graph.registerEdgeTool('circle-target-arrowhead', {
  inherit: 'target-arrowhead',
  tagName: 'circle',
  attrs: {
    r: 18,
    fill: '#31d0c6',
    'fill-opacity': 0.3,
    stroke: '#fe854f',
    'stroke-width': 4,
    cursor: 'move',
  },
})