使用自定义svg标签时,发现示例并没有指明如何构建复杂的svg标签,“点到为止”。同样也是需要查看源码才能熟悉如何配置复杂场景下的svg标签。以下是我为了满足业务场景配合源码学习到的关于自定义svg标签的碎片。当然写博客的时候才发现原来有详细的文档,😓

x6 自定义 SVG 标签使用碎片

定义边

官网的示例如下:官网示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Graph.registerEdge(
"custom-edge-label",
{
inherit: "edge",
defaultLabel: {
markup: [
{
tagName: "rect",
selector: "body",
},
{
tagName: "text",
selector: "label",
},
],
attrs: {
label: {
fill: "#000",
fontSize: 14,
textAnchor: "middle",
textVerticalAnchor: "middle",
pointerEvents: "none",
},
body: {
ref: "label",
fill: "#ffd591",
stroke: "#ffa940",
strokeWidth: 2,
rx: 4,
ry: 4,
refWidth: "140%",
refHeight: "140%",
refX: "-20%",
refY: "-20%",
},
},
position: {
distance: 200,
options: {
absoluteDistance: true,
reverseDistance: true,
},
},
},
},
true
);

官网示例-自定义SVG标签

这次一点点来拆解说明

‘custom-edge-label’

定义当前特定边的名字,可以随意取名,有实际意义即可

inherit

定义当前边的继承类型,一般边定义从 edge 继承即可

defaultLabel

markup

定义了标签的渲染层次结构。如果 markup 指定为一个对象,则 x6 会将其转换为数组,所以本质这里需要一个数组渲染。其每一项的结构类似:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
tagName: String, // 必选项,定义节点的标签类型,如 div,svg,span等
/* 定义节点的选择器,在defaultLabel.attrs 以及添加边时定义的labels.attrs中可以定义选择到的节点的属性 */
selector: String,
/* ns属性定义生成节点的命名空间,默认使用svg的命名空间,使用createElement()创建节点,若指定,则使用createElementNS()创建节点 */
ns: String,
attrs: String, // 定义节点的属性,如 aria-hidden 等
className: String, // 定义节点的class
textContent: String, //定义节点内的文本内容
groupSelector: String | Array, // 本质需要一个数组,大概是定义一类选择器,没有细看
style: String, // 定义节点的style,会使用Jquery的css挂载方法将其挂在节点上
chilren: Array(Markup) // 重点!使用此属性可以构造复杂的伪dom树,从而大大加强标签的多样性
},

markup中最核心的就是children属性,它使得markup约等于一个伪dom树,源码中的parseJSONMarkup()方法会BFS逐层解析并生成dom结构,并使用renderMarkup()方法渲染该dom。
其中可选的ns值有:

  • svg: http://www.w3.org/2000/svg
  • xmlns: http://www.w3.org/2000/xmlns/
  • xml: http://www.w3.org/XML/1998/namespace
  • xlink: http://www.w3.org/1999/xlink
  • xhtml: http://www.w3.org/1999/xhtml

attrs

attrs是一个属性选择器组,其中的对象都是之前在markup中定义好的选择器的名字,可以选中相应的节点,其属性可以定义对应节点的属性,除了节点本身可以有的属性外,还可以定义

  • ref:定义关联的节点,用来设置相对属性
  • refWidth:根据ref关联的节点的宽度,定义宽度,百分比值
  • refHeight:根据ref关联的节点的高度,定义高度,百分比值
  • refX:根据ref关联的节点的X,定义X方向的偏移,可以是百分比值,也可以是绝对值
  • refY:根据ref关联的节点的Y,定义Y方向的偏移,可以是百分比值,也可以是绝对值
  • textAnchor:定义文本节点的水平基准点,可选start/end/middle
  • textVerticalAnchor:定义文本节点的垂直基准点,可选 top/bottom/middle

详见官网文档

position

position定义标签整体的位置,已知的结构如下

1
2
3
4
5
6
7
8
9
10
{
distance: Number | String, // 定义标签关于边的位置,从边起点开始算距离
/* 定义选项,示例中给出的absoluteDistance和reverseDistance我没测出来有啥区别,看名字应该是绝对距离和反向测距的意思吧*/
options: Object,
offset: Object { // 定义标签整体的偏移
x: Number | String,
y: Number | String
},
...
}

更详尽的文档见官网API

添加边

到此,已经可以构建复杂的标签,但是这些定义都是静态的,如何与业务数据连接起来呢?看一下官网示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
graph.addEdge({
source: [170, 160],
target: [550, 160],
shape: 'custom-edge-label',
attrs: {
line: {
stroke: '#ccc',
},
},
labels: [
{
attrs: {
line: {
stroke: '#73d13d',
},
text: {
text: 'Custom Label',
},
},
},
],
})

其中的labels类似定义边时的defaultLabel.attrs一样可以选择到对应的节点,不知是不是有意为之,这里官网的代码中的line选择器并不能选中任何节点,如果要改变图中矩形的边框,需要把line变成body。

业务相关

我们的项目需要引入自定义的svg图标,平时在项目中使用的是类似如下结构的vue组件

1
2
3
4
5
6
7
<template>
<i class="svg-icon-wrapper">
<svg class="svg-icon">
<use :xlink:href="iconName"></use>
</svg>
</i>
</template>

出于业务需要,我们构建的标签需要添加对应的svg。由此使用上述的定义边,构造类似上述组件的伪dom层次结构,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
Graph.registerEdge(
'custom-edge-label',
{
inherit: 'edge',
defaultLabel: {
markup: [
{
tagName: 'foreignObject',
selector: 'foreign',
className: 'relation-line-label',
children: [
{
tagName: 'body',
selector: 'body',
ns: 'http://www.w3.org/1999/xhtml',
children: [
{
tagName: 'i',
selector: 'i',
ns: 'http://www.w3.org/1999/xhtml',
className: 'svg-icon-wrapper',
children: [
{
tagName: 'svg',
selector: 'svg',
ns: 'http://www.w3.org/2000/svg',
className: 'svg-icon',
children: [
{
tagName: 'use',
ns: 'http://www.w3.org/2000/svg',
selector: 'use'
}
]
}
]
}
]
}
]
},
{
tagName: 'text',
selector: 'label'
}
],
attrs: {
label: {
fill: '#303133',
fontSize: 12,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
pointerEvents: 'none'
},
body: {
style: 'width: 24px; height: 24px; background-color: transparent;'
},
foreign: {
width: 24,
height: 24,
ref: 'label',
refX: '-30',
refY: '-30%'
}
},
position: {
distance: 0.5,
offset: {
y: -10
}
}
}
},
true
)

其中有以下几点值得注意:

  1. svg需要使用foreignObject标签包裹html部分,因此需要多一层foreignObject
  2. ns命名空间必须定义清楚,如svg的ns必须定义为http://www.w3.org/2000/svg,否则实际生成的svg标签实际是一个HtmlUnknownElement元素,同理use的ns也必须定义为该命名空间
  3. use可以全局调用已经解析好的svg,只需要指定xlink:href属性即可,因此可以将需要的svg的名字使用addEgde参数里的labels.attrs属性指定xlink:href,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
source: [124, 260],
target: [224, 260],
shape: 'custom-edge-label',
labels: [
{
attrs: {
text: {
text: '标签文本'
},
use: {
'xlink:href': '#svg-loading'
}
}
}
],
...
}

于是完成了自定义SVG标签,如图:

自定义SVG标签示例