自定义节点
我们在 X6 中内置了一些基础图形,如 Rect
、Circle
和 Ellipse
等,但这些还远远不能满足我们的实际需求,我们需要能够定义具有业务意义的节点,例如 ER 图的表格节点。自定义节点也不是什么难事,其实就是在组合使用 SVG 中的 <rect>
、<circle>
、<ellipse>
、<image>
、<text>
、<path>
等基础元素,如果你对这些基础元素还不熟悉,可以参考 MDN 提供的教程,使用这些基础元素可以定义出任何我们想要的图形。
原理
自定义节点实际上是从基础节点派生(继承)出我们自己的节点,并覆盖基类的某些选项和方法。
三步法
以内置节点 Rect
为例,自定义节点可以分以下三步走。
第一步:继承
import { Node } from '@antv/x6'
class Rect extends Node {
// 省略实现细节
}
第二步:配置
调用继承的静态方法 config(options)
来配置节点选项的默认值、自定义选项和自定义属性,最常用选项的是通过 markup 来指定节点默认的 SVG/HTML 结构,通过 attrs 来指定节点的默认属性样式。
名称 | 类型 | 是否必选 | 默认值 | 说明 |
---|---|---|---|---|
propHooks | Function | Function[] | Object | 否 | undefined | 自定义选项钩子。 |
attrHooks | Object | 否 | undefined | 自定义属性钩子。 |
...others | Object | 节点选项。 |
看下面 Rect
节点的默认配置。
Rect.config({
width: 100,
height: 40,
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 2,
},
label: {
fontSize: 14,
fill: '#333333',
refX: '50%',
refY: '50%',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
},
// 通过钩子将自定义选项 label 应用到 'attrs/text/text' 属性上
propHooks(metadata) {
const { label, ...others } = metadata
if (label) {
ObjectExt.setByPath(others, 'attrs/text/text', label)
}
return others
},
})
上面代码中,我们通过 width
和 height
指定了节点的默认大小,然后通过 markup
定义了节点由 <rect>
和 <text>
两个 SVG 元素构成,并分别指定了 body
和 label
两个选择器,接着就可以在 attrs
中通过这两个选择器来指定节点的默认样式。最后通过 propHooks
定义了一个自定义选项 label
,这样我们就可以通过 label 设置标签文本。
第三步:注册
调用 Graph 的静态方法 registerNode 来注册节点,注册以后就可以像使用内置节点那样来使用节点。
Graph.registerNode(name: string, cls: typeof Node, overwrite?: boolean)
参数名 | 类型 | 是否必选 | 默认值 | 说明 |
---|---|---|---|---|
name | String | 是 | 注册的节点名。 | |
cls | typeof Node | 是 | 节点类,直接或间接继承 Node 的类。 | |
overwrite | Boolean | 否 | false | 重名时是否覆盖,默认为 false 不覆盖(重名时报错)。 |
例如,注册名为 'rect'
的节点。
Graph.registerNode('rect', Rect)
注册以后,我们可以像下面这样来使用。
graph.addNode({
shape: 'rect',
x: 30,
y: 40,
})
便捷方法一
有时候我们可能在继承节点后并不需要任何扩展任何方法,而只是覆盖某些默认样式。例如,定义一个红色边框的矩形。
class RedRect extends Rect { }
// 覆盖默认边框颜色
RedRect.config({
attrs: {
body: {
stroke: 'red',
},
},
})
上面第一行代码就显得有点尴尬:实现了继承但没有扩展任何方法,有点大材小用的感觉。所以我们也提供了一个更加便捷的静态方法 define
来定义这类节点。
const RedRect = Rect.define({
attrs: {
body: {
stroke: 'red',
},
},
})
Graph.registerNode('red-rect', RedRect)
该方法将其调用者(如上面的 Rect
)作为基类,继承出一个新的节点,然后调用新节点的静态方法 config
来配置默认选项。
需要注意的是,上面代码生成的 RedRect
类的类名并不是 'RedRect'
,而是系统自动生成的类名,当指定 constructorName
选项后,其大驼峰(CamelCase)形式将作为新节点的类名。
const RedRect = Rect.define({
constructorName: 'red-rect',
attrs: {
body: {
stroke: 'red',
},
},
})
Graph.registerNode('red-rect', RedRect)
如果我们提供了 shape
选项,那么系统将自动为你注册节点。当没有指定 constructorName
选项时,shape
的大驼峰形式(CamelCase)也将作新节点的类名,也就是说下面代码定义的节点类名为 'RedRect'
。
Rect.define({
shape: 'red-rect', // 自动注册名为 'red-rect' 的节点,并且节点类名为 'RedRect'。
attrs: {
body: {
stroke: 'red',
},
},
})
除了 constructorName
和 shape
两个特殊选项外,其他选项都与 config 方法的选项保持一致。下表是 define
方法支持的选项。
名称 | 是否必选 | 类型 | 说明 |
---|---|---|---|
constructorName | 否 | String | 类名。 |
shape | 否 | String | 自动注册的节点名,当 constructorName 缺省时其大驼峰(CamelCase)形式也将作为类名。 |
...others | Object | config 方法的选项。 |
便捷方法二
上面提到的 Graph.registerNode
方法还有另外一种签名,使用该方法可以同时实现定义和注册节点。
Graph.registerNode(name: string, options: Object, overwrite?: boolean)
参数名 | 类型 | 是否必选 | 默认值 | 说明 |
---|---|---|---|---|
name | String | 是 | 注册的节点名。 | |
options | Object | 是 | 选项。 | |
overwrite | Boolean | 否 | false | 重名时是否覆盖,默认为 false 不覆盖(重名时报错)。 |
通过 options.inherit
来指定继承的基类,默认值为 Node
类,支持字符串或节点类,当 options.inherit
为字符串时将自动从已注册的节点中找到对应的节点作为基类,options
的其他选项与 define 方法一致。当 options.constructorName
类名缺省时,第一个参数 name
的大驼峰形式(CamelCase)也将作为自定义节点的类名。
Graph.registerNode('red-rect', {
inherit: Rect, // 或 'rect'
attrs: {
body: {
stroke: 'red',
},
},
})
案例
接下来我们就基于 Shape.Rect
来自定义一个矩形 CustomRect
,这里不修改矩形的 markup
定义,而仅仅是做样式覆盖。
使用便捷方法一。
import { Shape } from '@antv/x6'
Shape.Rect.define({
shape: 'custom-rect',
width: 300, // 默认宽度
height: 40, // 默认高度
attrs: {
body: {
rx: 10, // 圆角矩形
ry: 10,
strokeWidth: 1,
fill: '#5755a1',
stroke: '#5755a1',
},
label: {
fill: '#fff',
fontSize: 18,
refX: 10, // x 轴偏移,类似 css 中的 margin-left
textAnchor: 'left', // 左对齐
}
},
})
使用便捷方法二。
Graph.registerNode('custom-rect', {
inherit: 'rect', // 继承自 Shape.Rect
width: 300, // 默认宽度
height: 40, // 默认高度
attrs: {
body: {
rx: 10, // 圆角矩形
ry: 10,
strokeWidth: 1,
fill: '#5755a1',
stroke: '#5755a1',
},
label: {
fill: '#fff',
fontSize: 18,
refX: 10, // x 轴偏移,类似 css 中的 margin-left
textAnchor: 'left', // 左对齐
}
},
})
然后我们可以这样来使用。
graph.addNode({
x: 100,
y: 60,
shape: 'custom-rect',
label: 'My Custom Rect', // label 继承于基类的自定义选项
});