Appearance Customization
Specifying style configurations by values
<script setup lang="ts">
import { reactive } from "vue"
import * as vNG from "v-network-graph"
import data from "./data"
const initialConfigs = vNG.defineConfigs({
node: {
selectable: true,
normal: {
type: "circle",
radius: 16,
// for type is "rect" -->
width: 32,
height: 32,
borderRadius: 4,
// <-- for type is "rect"
strokeWidth: 0,
strokeColor: "#000000",
strokeDasharray: "0",
color: "#4466cc",
},
hover: {
type: "circle",
radius: 16,
// for type is "rect" -->
width: 32,
height: 32,
borderRadius: 4,
// <-- for type is "rect"
strokeWidth: 0,
strokeColor: "#000000",
strokeDasharray: "0",
color: "#dd2288",
},
selected: {
type: "circle",
radius: 16,
// for type is "rect" -->
width: 32,
height: 32,
borderRadius: 4,
// <-- for type is "rect"
strokeWidth: 0,
strokeColor: "#000000",
strokeDasharray: "0",
color: "#4466cc",
},
label: {
visible: true,
fontFamily: undefined,
fontSize: 11,
lineHeight: 1.1,
color: "#000000",
margin: 4,
direction: "south",
background: {
visible: false,
color: "#ffffff",
padding: {
vertical: 1,
horizontal: 4,
},
borderRadius: 2,
},
},
focusring: {
visible: true,
width: 4,
padding: 3,
color: "#eebb00",
dasharray: "0",
},
},
edge: {
selectable: true,
normal: {
width: 3,
color: "#4466cc",
dasharray: "0",
linecap: "butt",
animate: false,
animationSpeed: 50,
},
hover: {
width: 4,
color: "#3355bb",
dasharray: "0",
linecap: "butt",
animate: false,
animationSpeed: 50,
},
selected: {
width: 3,
color: "#dd8800",
dasharray: "6",
linecap: "round",
animate: false,
animationSpeed: 50,
},
gap: 3,
type: "straight",
summarize: true,
summarized: {
label: {
fontSize: 10,
color: "#4466cc",
},
shape: {
type: "rect",
radius: 6, // for type is "circle"
width: 12,
height: 12,
borderRadius: 3,
color: "#ffffff",
strokeWidth: 1,
strokeColor: "#4466cc",
strokeDasharray: "0",
},
stroke: {
width: 5,
color: "#4466cc",
dasharray: "0",
linecap: "butt",
animate: false,
animationSpeed: 50,
},
},
},
})
const configs = reactive(initialConfigs)
</script>
<template>
<div class="demo-control-panel">
<el-tabs type="border-card">
<el-tab-pane label="Node">
<el-tabs>
<el-tab-pane label="normal">
<demo-node-config-panel
v-model:type="configs.node.normal.type"
v-model:radius="configs.node.normal.radius"
v-model:width="configs.node.normal.width"
v-model:height="configs.node.normal.height"
v-model:borderRadius="configs.node.normal.borderRadius"
v-model:strokeWidth="configs.node.normal.strokeWidth"
v-model:strokeColor="configs.node.normal.strokeColor"
v-model:strokeDasharray="configs.node.normal.strokeDasharray"
v-model:color="configs.node.normal.color"
/>
</el-tab-pane>
<el-tab-pane label="hover">
<demo-node-config-panel
v-model:type="configs.node.hover.type"
v-model:radius="configs.node.hover.radius"
v-model:width="configs.node.hover.width"
v-model:height="configs.node.hover.height"
v-model:borderRadius="configs.node.hover.borderRadius"
v-model:strokeWidth="configs.node.hover.strokeWidth"
v-model:strokeColor="configs.node.hover.strokeColor"
v-model:strokeDasharray="configs.node.hover.strokeDasharray"
v-model:color="configs.node.hover.color"
/>
</el-tab-pane>
<el-tab-pane label="selected">
<demo-node-config-panel
v-model:type="configs.node.selected.type"
v-model:radius="configs.node.selected.radius"
v-model:width="configs.node.selected.width"
v-model:height="configs.node.selected.height"
v-model:borderRadius="configs.node.selected.borderRadius"
v-model:strokeWidth="configs.node.selected.strokeWidth"
v-model:strokeColor="configs.node.selected.strokeColor"
v-model:strokeDasharray="configs.node.selected.strokeDasharray"
v-model:color="configs.node.selected.color"
/>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="Label">
<el-tabs>
<el-tab-pane label="basic">
<demo-label-config-panel
v-model:visible="configs.node.label.visible"
v-model:fontFamily="configs.node.label.fontFamily"
v-model:fontSize="configs.node.label.fontSize"
v-model:lineHeight="configs.node.label.lineHeight"
v-model:color="configs.node.label.color"
v-model:margin="configs.node.label.margin"
v-model:direction="configs.node.label.direction"
/>
</el-tab-pane>
<el-tab-pane label="background">
<demo-label-background-config-panel
v-model:visible="configs.node.label.background.visible"
v-model:color="configs.node.label.background.color"
v-model:paddingVertical="configs.node.label.background.padding.vertical"
v-model:paddingHorizontal="
configs.node.label.background.padding.horizontal
"
v-model:borderRadius="configs.node.label.background.borderRadius"
/>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="Focus">
<demo-focus-config-panel
v-model:visible="configs.node.focusring.visible"
v-model:width="configs.node.focusring.width"
v-model:padding="configs.node.focusring.padding"
v-model:color="configs.node.focusring.color"
v-model:dasharray="configs.node.focusring.dasharray"
/>
</el-tab-pane>
<el-tab-pane label="Edge">
<el-tabs>
<el-tab-pane label="normal">
<demo-edge-config-panel
v-model:width="configs.edge.normal.width"
v-model:color="configs.edge.normal.color"
v-model:dasharray="configs.edge.normal.dasharray"
v-model:linecap="configs.edge.normal.linecap"
v-model:animate="configs.edge.normal.animate"
v-model:animationSpeed="configs.edge.normal.animationSpeed"
/>
</el-tab-pane>
<el-tab-pane label="hover">
<demo-edge-config-panel
v-model:width="configs.edge.hover.width"
v-model:color="configs.edge.hover.color"
v-model:dasharray="configs.edge.hover.dasharray"
v-model:linecap="configs.edge.hover.linecap"
v-model:animate="configs.edge.hover.animate"
v-model:animationSpeed="configs.edge.hover.animationSpeed"
/>
</el-tab-pane>
<el-tab-pane label="selected">
<demo-edge-config-panel
v-model:width="configs.edge.selected.width"
v-model:color="configs.edge.selected.color"
v-model:dasharray="configs.edge.selected.dasharray"
v-model:linecap="configs.edge.selected.linecap"
v-model:animate="configs.edge.hover.animate"
v-model:animationSpeed="configs.edge.hover.animationSpeed"
/>
</el-tab-pane>
<el-tab-pane label="multiple edge">
<demo-multiple-edge-config-panel
v-model:gap="configs.edge.gap"
v-model:line-type="configs.edge.type"
v-model:summarize="configs.edge.summarize"
/>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="Summarized Edge">
<el-tabs>
<el-tab-pane label="stroke">
<demo-edge-config-panel
v-model:width="configs.edge.summarized.stroke.width"
v-model:color="configs.edge.summarized.stroke.color"
v-model:dasharray="configs.edge.summarized.stroke.dasharray"
v-model:linecap="configs.edge.summarized.stroke.linecap"
v-model:animate="configs.edge.summarized.stroke.animate"
v-model:animationSpeed="configs.edge.summarized.stroke.animationSpeed"
/>
</el-tab-pane>
<el-tab-pane label="label">
<demo-summarized-edge-label-config-panel
v-model:labelFontSize="configs.edge.summarized.label.fontSize"
v-model:labelColor="configs.edge.summarized.label.color"
v-model:type="configs.edge.summarized.shape.type"
v-model:radius="configs.edge.summarized.shape.radius"
v-model:width="configs.edge.summarized.shape.width"
v-model:height="configs.edge.summarized.shape.height"
v-model:borderRadius="configs.edge.summarized.shape.borderRadius"
v-model:strokeWidth="configs.edge.summarized.shape.strokeWidth"
v-model:strokeColor="configs.edge.summarized.shape.strokeColor"
v-model:strokeDasharray="configs.edge.summarized.shape.strokeDasharray"
v-model:color="configs.edge.summarized.shape.color"
/>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
</el-tabs>
</div>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "Node 1" },
node2: { name: "Node 2" },
node3: { name: "Node 3" },
node4: { name: "Node 4\nMultiline" },
node5: { name: "Node 5\nMultiline" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node3", target: "node4" },
edge4: { source: "node3", target: "node4" },
edge5: { source: "node4", target: "node5" },
edge6: { source: "node4", target: "node5" },
edge7: { source: "node4", target: "node5" },
edge8: { source: "node4", target: "node5" },
edge9: { source: "node4", target: "node5" },
edge10: { source: "node4", target: "node5" },
edge11: { source: "node4", target: "node5" },
edge12: { source: "node4", target: "node5" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 160, y: 0 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
},
}
export default {
nodes,
edges,
layouts,
}
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
Change the appearance of each node and edge
In Node and Edge configuration, instead of concrete values, you can specify functions that return a configuration value with each node or edge as an argument. In addition, by explicitly specifying the Node or Edge type in UserConfigs, you can specify the argument type of the function.
<script setup lang="ts">
import { reactive } from "vue"
import * as vNG from "v-network-graph"
interface Node extends vNG.Node {
size: number
color: string
label?: boolean
}
interface Edge extends vNG.Edge {
width: number
color: string
dashed?: boolean
}
const nodes: Record<string, Node> = {
node1: { name: "Node 1", size: 16, color: "gray" },
node2: { name: "Node 2", size: 32, color: "hotpink", label: true },
node3: { name: "Node 3", size: 24, color: "lightskyblue", label: true },
node4: { name: "Node 4", size: 16, color: "gray" },
node5: { name: "Node 5", size: 32, color: "hotpink", label: true },
}
const edges: Record<string, Edge> = {
edge1: { source: "node1", target: "node2", width: 1, color: "black" },
edge2: { source: "node2", target: "node3", width: 5, color: "gray", dashed: true },
edge3: { source: "node3", target: "node4", width: 1, color: "black" },
edge4: { source: "node3", target: "node4", width: 3, color: "skyblue" },
edge5: { source: "node4", target: "node5", width: 3, color: "hotpink" },
edge6: { source: "node4", target: "node5", width: 1, color: "black" },
edge7: { source: "node4", target: "node5", width: 3, color: "skyblue" },
}
const layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 160, y: 0 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
},
}
// In Node and Edge configuration, instead of concrete values,
// you can specify functions that return a configuration value
// with each node or edge as an argument.
// In addition, custom types for Node and Edge can be explicitly
// specified in `defineConfigs` to specify the argument types
// for callback functions.
const configs = reactive(
vNG.defineConfigs<Node, Edge>({
node: {
normal: {
type: "circle",
radius: node => node.size, // Use the value of each node object
color: node => node.color,
},
hover: {
radius: node => node.size + 2,
color: node => node.color,
},
selectable: true,
label: {
visible: node => !!node.label,
},
focusring: {
color: "darkgray",
},
},
edge: {
normal: {
width: edge => edge.width, // Use the value of each edge object
color: edge => edge.color,
dasharray: edge => (edge.dashed ? "4" : "0"),
},
},
})
)
</script>
<template>
<v-network-graph
:nodes="nodes"
:edges="edges"
:layouts="layouts"
:configs="configs"
/>
</template>
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Adding or removing nodes and links with customized appearance can also be reflected by changing the data of reactive data.
<script setup lang="ts">
import { reactive, ref } from "vue"
import * as vNG from "v-network-graph"
import data, { Node, Edge } from "./data"
const nodes = reactive({ ...data.nodes })
const edges = reactive({ ...data.edges })
// In Node and Edge configuration, instead of concrete values,
// you can specify functions that return a configuration value
// with each node or edge as an argument.
// In addition, custom types for Node and Edge can be explicitly
// specified in `defineConfigs` to specify the argument types
// for callback functions.
const configs = reactive(
vNG.defineConfigs<Node, Edge>({
node: {
normal: {
type: "circle",
radius: node => node.size, // Use the value of each node object
color: node => node.color,
},
hover: {
radius: node => node.size + 2,
color: node => node.color,
},
selectable: true,
label: {
visible: node => !!node.label,
},
focusring: {
color: "darkgray",
},
},
edge: {
normal: {
width: edge => edge.width, // Use the value of each edge object
color: edge => edge.color,
dasharray: edge => (edge.dashed ? "4" : "0"),
},
},
})
)
const nextNodeIndex = ref(Object.keys(data.nodes).length + 1)
const nextEdgeIndex = ref(Object.keys(data.edges).length + 1)
const selectedNodes = ref<string[]>([])
const selectedEdges = ref<string[]>([])
function addSkyBlueNode() {
addNode({ size: 24, color: "lightskyblue", label: true })
}
function addHotPinkNode() {
addNode({ size: 32, color: "hotpink", label: true })
}
function addGrayNode() {
addNode({ size: 16, color: "gray", label: false })
}
function addBlackNode() {
addNode({ size: 48, color: "black", label: false })
}
function addNode(node: Omit<Node, "name">) {
const nodeId = `node${nextNodeIndex.value}`
const name = `Node ${nextNodeIndex.value}`
nodes[nodeId] = { name, ...node } as Node
nextNodeIndex.value++
}
function removeNode() {
for (const nodeId of selectedNodes.value) {
delete nodes[nodeId]
}
}
function addSkyBlueEdge() {
addEdge({ width: 3, color: "skyblue" })
}
function addHotPinkEdge() {
addEdge({ width: 3, color: "hotpink" })
}
function addGrayEdge() {
addEdge({ width: 5, color: "gray", dashed: true })
}
function addBlackEdge() {
addEdge({ width: 1, color: "black" })
}
function addEdge(edge: Omit<Edge, "source" | "target">) {
if (selectedNodes.value.length !== 2) return
const [source, target] = selectedNodes.value
const edgeId = `edge${nextEdgeIndex.value++}`
edges[edgeId] = { source, target, ...edge } as Edge
}
function removeEdge() {
for (const edgeId of selectedEdges.value) {
delete edges[edgeId]
}
}
function isEdgeAddable() {
return selectedNodes.value.length == 2
}
</script>
<template>
<div class="demo-control-panel appearance">
<div>
<label>Node:</label>
<el-button @click="addSkyBlueNode">Add SkyBlue</el-button>
<el-button @click="addHotPinkNode">Add HotPink</el-button>
<el-button @click="addGrayNode">Add Gray</el-button>
<el-button @click="addBlackNode">Add Black</el-button>
<el-button :disabled="selectedNodes.length == 0" @click="removeNode"
>Remove</el-button
>
</div>
<div>
<label>Edge:</label>
<el-button :disabled="!isEdgeAddable()" @click="addSkyBlueEdge"
>Add SkyBlue</el-button
>
<el-button :disabled="!isEdgeAddable()" @click="addHotPinkEdge"
>Add HotPink</el-button
>
<el-button :disabled="!isEdgeAddable()" @click="addGrayEdge"
>Add Gray</el-button
>
<el-button :disabled="!isEdgeAddable()" @click="addBlackEdge"
>Add Black</el-button
>
<el-button :disabled="selectedEdges.length == 0" @click="removeEdge"
>Remove</el-button
>
</div>
</div>
<v-network-graph
v-model:selected-nodes="selectedNodes"
v-model:selected-edges="selectedEdges"
:nodes="nodes"
:edges="edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { reactive } from "vue"
import * as vNG from "v-network-graph"
interface Node extends vNG.Node {
size: number
color: string
label?: boolean
}
interface Edge extends vNG.Edge {
width: number
color: string
dashed?: boolean
}
const nodes = reactive<Record<string, Node>>({
node1: { name: "Node 1", size: 16, color: "gray" },
node2: { name: "Node 2", size: 32, color: "hotpink", label: true },
node3: { name: "Node 3", size: 24, color: "lightskyblue", label: true },
node4: { name: "Node 4", size: 16, color: "gray" },
node5: { name: "Node 5", size: 32, color: "hotpink", label: true },
})
const edges = reactive<Record<string, Edge>>({
edge1: { source: "node1", target: "node2", width: 1, color: "black" },
edge2: { source: "node2", target: "node3", width: 5, color: "gray", dashed: true },
edge3: { source: "node3", target: "node4", width: 1, color: "black" },
edge4: { source: "node3", target: "node4", width: 3, color: "skyblue" },
edge5: { source: "node4", target: "node5", width: 3, color: "hotpink" },
edge6: { source: "node4", target: "node5", width: 1, color: "black" },
edge7: { source: "node4", target: "node5", width: 3, color: "skyblue" },
})
const layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 160, y: 0 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
},
}
export type { Node, Edge }
export default {
nodes,
edges,
layouts,
}
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
Custom Node
In case you want to change not only the shape of the node, but also its elements and behavior, it is now possible to replace the components of the node.
The following is an example of replacing the default node component with a combination of a circle and an icon.
Arranging the SVG elements in the override-node
slot will replace the default nodes with them. In this slot, the (0, 0) position is the center of the node.
Note that if you want to replace the label of a node, you can do by using the override-node-label
slot.
<script setup lang="ts">
import { Nodes } from "v-network-graph"
import data from "./data"
// icon code point searched from: https://fonts.google.com/icons
const nodes: Nodes = {
node1: { name: "N1", icon: "" /* Laptop Mac */ },
node2: { name: "N2", icon: "" /* Router */ },
node3: { name: "N3", icon: "" /* Tablet Mac */ },
node4: { name: "N4", icon: "" /* Cloud */ },
node5: { name: "N5", icon: "" /* Support Agent */ },
node6: { name: "N6", icon: "" /* Video Settings */ },
}
</script>
<template>
<v-network-graph
:nodes="nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="data.configs"
>
<!-- Use CSS to define references to external fonts.
To use CSS within SVG, use <defs>. -->
<defs>
<!-- Cannot use <style> directly due to restrictions of Vue. -->
<component is="style">
@font-face { font-family: 'Material Icons'; font-style: normal; font-weight:
400; src:
url(https://fonts.gstatic.com/s/materialicons/v97/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2)
format('woff2'); }
</component>
</defs>
<!-- Replace the node component -->
<template #override-node="{ nodeId, scale, config, ...slotProps }">
<circle :r="config.radius * scale" :fill="config.color" v-bind="slotProps" />
<!-- Use v-html to interpret escape sequences for icon characters. -->
<text
font-family="Material Icons"
:font-size="22 * scale"
fill="#ffffff"
text-anchor="middle"
dominant-baseline="central"
style="pointer-events: none"
v-html="nodes[nodeId].icon"
/>
</template>
</v-network-graph>
</template>
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
import { defineConfigs, Edges, Layouts } from "v-network-graph"
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node2", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 0, y: 160 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
const configs = defineConfigs({
node: {
selectable: true,
normal: {
radius: 20,
},
hover: {
radius: 22,
},
},
})
export default {
edges,
layouts,
configs,
}
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
An example of specifying an image instead of an icon is shown below.
The images of the faces ware created https://generated.photos/.
<script setup lang="ts">
import { Nodes } from "v-network-graph"
import data from "./data"
// photo generated by https://generated.photos/
const nodes: Nodes = {
node1: { name: "User 1", face: "0023286.png" },
node2: { name: "User 2", face: "0700202.png" },
node3: { name: "User 3", face: "0037725.png" },
node4: { name: "User 4", face: "0062138.png" },
node5: { name: "User 5", face: "0103755.png" },
node6: { name: "User 6", face: "0092035.png" },
}
</script>
<template>
<v-network-graph
:nodes="nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="data.configs"
>
<defs>
<!--
Define the path for clipping the face image.
To change the size of the applied node as it changes,
add the `clipPathUnits="objectBoundingBox"` attribute
and specify the relative size (0.0~1.0).
-->
<clipPath id="faceCircle" clipPathUnits="objectBoundingBox">
<circle cx="0.5" cy="0.5" r="0.5" />
</clipPath>
</defs>
<!-- Replace the node component -->
<template #override-node="{ nodeId, scale, config, ...slotProps }">
<!-- circle for filling background -->
<circle
class="face-circle"
:r="config.radius * scale"
fill="#ffffff"
v-bind="slotProps"
/>
<!--
The base position of the <image /> is top left. The node's
center should be (0,0), so slide it by specifying x and y.
-->
<image
class="face-picture"
:x="-config.radius * scale"
:y="-config.radius * scale"
:width="config.radius * scale * 2"
:height="config.radius * scale * 2"
:xlink:href="`./faces/${nodes[nodeId].face}`"
clip-path="url(#faceCircle)"
/>
<!-- circle for drawing stroke -->
<circle
class="face-circle"
:r="config.radius * scale"
fill="none"
stroke="#808080"
:stroke-width="1 * scale"
v-bind="slotProps"
/>
</template>
</v-network-graph>
</template>
<style lang="scss" scoped>
// transitions when scaling on mouseover.
.face-circle,
.face-picture {
transition: all 0.1s linear;
}
// suppress image events so that mouse events are received
// by the background circle.
.face-picture {
pointer-events: none;
}
</style>
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
76
77
78
79
80
81
82
import { defineConfigs, Edges, Layouts } from "v-network-graph"
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node2", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 0, y: 160 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
const configs = defineConfigs({
node: {
selectable: true,
normal: {
radius: 20,
},
hover: {
radius: 22,
},
},
})
export default {
edges,
layouts,
configs,
}
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
Z-order of Nodes
The z-index of nodes can be specified to control the order of the nodes in the z-direction.
Since there is no concept of z-index in SVG, this feature is achieved by sorting the internal node objects. It is disabled by default for performance reasons. If configs.node.zOrder.enabled
is set to true
, this feature will be enabled.
In addition to specifying the z-index based on the properties of the node itself, it is also possible to move to the topmost when it is mouse hovered or selected.
<script setup lang="ts">
import { reactive, ref } from "vue"
import ColorConvert from "color-convert"
import * as vNG from "v-network-graph"
import data from "./data"
const configs = reactive(
vNG.defineConfigs({
node: {
selectable: true,
normal: {
radius: n => n.radius,
color: n => n.color,
strokeWidth: 3,
strokeColor: n => darker(n.color, 20),
},
hover: {
color: n => darker(n.color, 10),
strokeColor: n => darker(n.color, 30),
},
label: {
text: n => `z-index: ${n.zIndex}`,
fontSize: 12,
},
zOrder: {
enabled: true,
zIndex: n => n.zIndex,
bringToFrontOnHover: true,
bringToFrontOnSelected: true,
},
},
})
)
function darker(hex: string, level: number) {
const hsv = ColorConvert.hex.hsv(hex)
hsv[2] -= level
return "#" + ColorConvert.hsv.hex(hsv)
}
</script>
<template>
<div class="demo-control-panel">
<el-checkbox v-model="configs.node.zOrder.bringToFrontOnHover"
>Bring to front on hover</el-checkbox
>
<el-checkbox v-model="configs.node.zOrder.bringToFrontOnSelected"
>Bring to front on selected</el-checkbox
>
</div>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "Node 1", zIndex: 1, color: "#4466cc", radius: 24 },
node2: { name: "Node 2", zIndex: 2, color: "#44cccc", radius: 36 },
node3: { name: "Node 3", zIndex: 2, color: "#44cccc", radius: 36 },
node4: { name: "Node 4", zIndex: 1, color: "#4466cc", radius: 24 },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node3", target: "node4" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 55, y: 0 },
node3: { x: 180, y: 0 },
node4: { x: 235, y: 0 },
},
}
export default {
nodes,
edges,
layouts,
}
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
Z-order of Edges
It is also possible to control the z-order of the edges as well as the nodes.
<script setup lang="ts">
import { reactive } from "vue"
import ColorConvert from "color-convert"
import * as vNG from "v-network-graph"
import data from "./data"
const configs = reactive(
vNG.defineConfigs({
edge: {
selectable: true,
normal: {
color: e => e.color,
width: 6,
},
hover: {
color: e => darker(e.color, 10),
width: 8,
},
selected: {
dasharray: "4 2",
width: 6,
},
zOrder: {
enabled: true,
zIndex: n => n.zIndex,
bringToFrontOnHover: true,
bringToFrontOnSelected: true,
},
},
})
)
function darker(hex: string, level: number) {
const hsv = ColorConvert.hex.hsv(hex)
hsv[2] -= level
return "#" + ColorConvert.hsv.hex(hsv)
}
</script>
<template>
<div class="demo-control-panel">
<el-checkbox v-model="configs.edge.zOrder.bringToFrontOnHover">Bring to front on hover</el-checkbox>
<el-checkbox v-model="configs.edge.zOrder.bringToFrontOnSelected">Bring to front on selected</el-checkbox>
</div>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "Node 1" },
node2: { name: "Node 2" },
node3: { name: "Node 3" },
node4: { name: "Node 4" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node3", color: "#aa8888", zIndex: 2 },
edge2: { source: "node2", target: "node4", color: "#44cccc", zIndex: 1 },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 40, y: 50 },
node3: { x: 240, y: 0 },
node4: { x: 200, y: -50 },
},
}
export default {
nodes,
edges,
layouts,
}
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
Arrow on edges
Markers such as arrow head can be specified for edges.
<script setup lang="ts">
import { reactive } from "vue"
import * as vNG from "v-network-graph"
import data from "./data"
const initialConfigs = vNG.defineConfigs({
node: {
normal: {
color: "#4466cc88",
},
},
edge: {
selectable: true,
normal: {
width: 3,
color: "#4466cc",
dasharray: "0",
linecap: "butt",
animate: false,
animationSpeed: 50,
},
hover: {
width: 4,
color: "#3355bb",
dasharray: "0",
linecap: "butt",
animate: false,
animationSpeed: 50,
},
selected: {
width: 3,
color: "#dd8800",
dasharray: "6",
linecap: "round",
animate: false,
animationSpeed: 50,
},
gap: 5,
type: "straight",
margin: 2,
marker: {
source: {
type: "none",
width: 4,
height: 4,
margin: -1,
offset: 0,
units: "strokeWidth",
color: null,
},
target: {
type: "arrow",
width: 4,
height: 4,
margin: -1,
offset: 0,
units: "strokeWidth",
color: null,
},
},
},
})
const configs = reactive(initialConfigs)
</script>
<template>
<div class="demo-control-panel">
<el-tabs type="border-card">
<el-tab-pane label="Marker" class="demo-marker-panel">
<div>Source:</div>
<demo-edge-marker-config-panel
v-model:type="configs.edge.marker.source.type"
v-model:width="configs.edge.marker.source.width"
v-model:height="configs.edge.marker.source.height"
v-model:margin="configs.edge.marker.source.margin"
v-model:offset="configs.edge.marker.source.offset"
v-model:color="configs.edge.marker.source.color"
v-model:units="configs.edge.marker.source.units"
/>
<div>Target:</div>
<demo-edge-marker-config-panel
v-model:type="configs.edge.marker.target.type"
v-model:width="configs.edge.marker.target.width"
v-model:height="configs.edge.marker.target.height"
v-model:margin="configs.edge.marker.target.margin"
v-model:offset="configs.edge.marker.target.offset"
v-model:color="configs.edge.marker.target.color"
v-model:units="configs.edge.marker.target.units"
/>
</el-tab-pane>
<el-tab-pane label="Edge Margin/Gap/Type">
<demo-edge-margin-gap-config-panel
v-model:margin="configs.edge.margin"
v-model:lineType="configs.edge.type"
v-model:gap="configs.edge.gap"
/>
</el-tab-pane>
<el-tab-pane label="Edge Stroke">
<el-tabs>
<el-tab-pane label="normal">
<demo-edge-config-panel
v-model:width="configs.edge.normal.width"
v-model:color="configs.edge.normal.color"
v-model:dasharray="configs.edge.normal.dasharray"
v-model:linecap="configs.edge.normal.linecap"
v-model:animate="configs.edge.normal.animate"
v-model:animationSpeed="configs.edge.normal.animationSpeed"
/>
</el-tab-pane>
<el-tab-pane label="hover">
<demo-edge-config-panel
v-model:width="configs.edge.hover.width"
v-model:color="configs.edge.hover.color"
v-model:dasharray="configs.edge.hover.dasharray"
v-model:linecap="configs.edge.hover.linecap"
v-model:animate="configs.edge.hover.animate"
v-model:animationSpeed="configs.edge.hover.animationSpeed"
/>
</el-tab-pane>
<el-tab-pane label="selected">
<demo-edge-config-panel
v-model:width="configs.edge.selected.width"
v-model:color="configs.edge.selected.color"
v-model:dasharray="configs.edge.selected.dasharray"
v-model:linecap="configs.edge.selected.linecap"
v-model:animate="configs.edge.hover.animate"
v-model:animationSpeed="configs.edge.hover.animationSpeed"
/>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
</el-tabs>
</div>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "N1" },
node2: { name: "N2" },
node3: { name: "N3" },
node4: { name: "N4" },
node5: { name: "N5" },
node6: { name: "N6" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node2", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node5", target: "node4" },
edge6: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 0, y: 160 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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
The following configuration items can be specified for configs.edge.marker.source
and configs.edge.marker.target
.
config | description | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
type | Type of marker. The supported values are as follows:
| ||||||||||
width | Horizontal length of the marker when the edge is placed horizontally. | ||||||||||
height | Vertical length of the marker when the edge is placed horizontally. | ||||||||||
margin | Distance between the marker and the end of the edge. A negative value can also be specified. | ||||||||||
offset | Offset perpendicular to the edge line. When the edge faces up, offset to the right if this value is positive and to the left if it is negative. | ||||||||||
color | Color of the marker. If null specified, the marker will be the same color as the edge stroke.In this way, if an edge is selected or mouse hovered over, and the color of the edge changes, the marker will automatically change along with the color of the edge. | ||||||||||
units | This config is reflected in the markerUnits attribute of the SVG <marker> element.If "strokeWidth" is specified, the width, height, and margin configs of the marker will be applied in units of the current edge's stroke-width value as 1. Therefore, these sizes will also change depending on the thickness of the edge.If "userSpaceOnUse" is specified, the width, height, and margin configs of the marker will be the same as the ordinary coordinate units. It will not be affected by the thickness of the current edge. | ||||||||||
customId | When "custom" is specified for type , specify the ID of the marker you created in this config. |
Custom markers on edges
In addition to the preset marker types, you can also specify your own markers.
<script setup lang="ts">
import { reactive } from "vue"
import * as vNG from "v-network-graph"
import data from "./data"
const configs = reactive(
vNG.defineConfigs({
edge: {
selectable: true,
margin: 1,
marker: {
source: {
type: "custom",
width: 4,
height: 4,
margin: -1,
units: "strokeWidth",
color: null,
customId: "marker-custom-diamond",
},
target: {
type: "custom",
width: 4,
height: 4,
margin: -1,
units: "strokeWidth",
color: null,
customId: "marker-custom-diamond",
},
},
},
})
)
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
>
<!-- The elements specified in the default slot
will be placed in the root of <svg>. -->
<defs>
<marker
id="marker-custom-diamond"
markerWidth="6"
markerHeight="6"
refX="3"
refY="3"
orient="auto"
markerUnits="strokeWidth"
>
<polygon points="0 3, 3 0, 6 3, 3 6" fill="#ff6666" />
</marker>
</defs>
</v-network-graph>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "N1" },
node2: { name: "N2" },
node3: { name: "N3" },
node4: { name: "N4" },
node5: { name: "N5" },
node6: { name: "N6" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node2", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 0, y: 160 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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
Place any object on the edge
The markers shown above can only be displayed at the terminate point of an edge.
Placing an object besides the endpoint of an edge can be achieved by using an "edge-overlay"
slot.
In the following example, a triangle toward the target is displayed at the center of the edge.
<script setup lang="ts">
import * as vNG from "v-network-graph"
import data from "./data"
const configs = vNG.defineConfigs({
node: {
selectable: true,
},
edge: {
selectable: true,
normal: { color: "#5555dd" },
hover: { color: "#dd5555" },
selected: { color: "#dddd55" },
gap: 10,
},
})
/**
* Make `transform` value of the object placing on the edge.
* @param center - center position
* @param edgePos - edge source and target positions
* @param scale - zooming scale
*/
function makeTransform(center: vNG.Point, edgePos: vNG.EdgePosition, scale: number) {
const radian = Math.atan2(
edgePos.target.y - edgePos.source.y,
edgePos.target.x - edgePos.source.x
)
const degree = (radian * 180.0) / Math.PI
return [
`translate(${center.x} ${center.y})`,
`scale(${scale}, ${scale})`,
`rotate(${degree})`,
].join(" ")
}
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
>
<!-- To use CSS within SVG, use <defs>. -->
<defs>
<!-- Cannot use <style> directly due to restrictions of Vue. -->
<component is="style">
<!-- prettier-ignore -->
.marker {
fill: {{ configs.edge.normal.color }};
transition: fill 0.1s linear;
pointer-events: none;
}
.marker.hovered { fill: {{ configs.edge.hover.color }}; }
.marker.selected { fill: {{ configs.edge.selected.color }}; }
</component>
</defs>
<template #edge-overlay="{ scale, center, position, hovered, selected }">
<!-- Place the triangle at the center of the edge -->
<path
class="marker"
:class="{ hovered, selected }"
d="M-5 -5 L5 0 L-5 5 Z"
:transform="makeTransform(center, position, scale)"
/>
</template>
</v-network-graph>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node0: { name: "N0" },
node1: { name: "N1" },
node2: { name: "N2" },
}
const edges: Edges = {
edge1: { id: "edge1", source: "node0", target: "node1" },
edge2: { id: "edge2", source: "node1", target: "node2" },
edge3: { id: "edge3", source: "node2", target: "node1" },
edge4: { id: "edge4", source: "node2", target: "node3" },
edge5: { id: "edge5", source: "node0", target: "node2" },
}
const layouts: Layouts = {
nodes: {
node0: { x: 80, y: 0 },
node1: { x: 0, y: 120 },
node2: { x: 160, y: 120 },
},
}
export default {
nodes,
edges,
layouts,
}
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
The "edge-overlay"
slot properties contains the length of the edge and a function to get the coordinates of the specified length from the source side. By using these properties, any object can be drawn on the path of the edge.
<script setup lang="ts">
import { defineConfigs, Edges } from "v-network-graph"
import data from "./data"
const edges: Edges = {
edge1: { source: "node0", target: "node1", sourceState: "off", targetState: "on" },
edge2: { source: "node1", target: "node2", sourceState: "off", targetState: "on" },
edge3: { source: "node1", target: "node2", sourceState: "on", targetState: "on" },
edge4: { source: "node2", target: "node3", sourceState: "on", targetState: "off" },
}
const configs = defineConfigs({
edge: {
type: "curve",
gap: 40,
},
})
const icons = {
on: "", // wifi
off: "", // wifi off
} as Record<string, string>
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="edges"
:layouts="data.layouts"
:configs="configs"
>
<!-- Use CSS to define references to external fonts.
To use CSS within SVG, use <defs>. -->
<defs>
<!-- Cannot use <style> directly due to restrictions of Vue. -->
<component is="style">
@font-face { font-family: 'Material Icons'; font-style: normal; font-weight:
400; src:
url(https://fonts.gstatic.com/s/materialicons/v97/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2)
format('woff2'); } .edge-icon { pointer-events: none; }
</component>
</defs>
<template #edge-overlay="{ edge, scale, length, pointAtLength }">
<!-- source side -->
<g class="edge-icon">
<!-- pointAtLength():Calculate the coordinates advanced
the specified length from the source side. -->
<circle
:cx="pointAtLength(40 * scale).x"
:cy="pointAtLength(40 * scale).y"
:r="10 * scale"
stroke="#444"
:stroke-width="1 * scale"
:fill="edge.sourceState === 'off' ? '#fcc' : '#fff'"
/>
<text
v-bind="pointAtLength(40 * scale)"
font-family="Material Icons"
text-anchor="middle"
dominant-baseline="central"
:font-size="16 * scale"
v-html="icons[edge.sourceState]"
/>
</g>
<!-- target side -->
<g class="edge-icon">
<!-- length: The total length of the edge. -->
<circle
:cx="pointAtLength(length - 40 * scale).x"
:cy="pointAtLength(length - 40 * scale).y"
:r="10 * scale"
stroke="#444"
:stroke-width="1 * scale"
:fill="edge.targetState === 'off' ? '#fcc' : '#fff'"
/>
<text
v-bind="pointAtLength(length - 40 * scale)"
font-family="Material Icons"
text-anchor="middle"
dominant-baseline="central"
:font-size="16 * scale"
v-html="icons[edge.targetState]"
/>
</g>
</template>
</v-network-graph>
</template>
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
76
77
78
79
80
81
82
83
84
85
86
87
import { Nodes, Layouts } from "v-network-graph"
const nodes: Nodes = {
node0: { name: "N0" },
node1: { name: "N1" },
node2: { name: "N2" },
node3: { name: "N3" },
}
const layouts: Layouts = {
nodes: {
node0: { x: 0, y: 0 },
node1: { x: 150, y: 100 },
node2: { x: 300, y: 0 },
node3: { x: 450, y: 100 },
},
}
export default {
nodes,
layouts,
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Keep order of edges
In some cases, when there are multiple edges between two nodes, it is necessary to arrange the order of the edges from top to bottom, no matter which node is placed on the left side.
It is possible to handle such a case by specifying the edge.keepOrder
config.
The following values can be specified for this config.
value | description |
---|---|
"clock" | Keep the forward/backward when viewed as a clock. |
"vertical" | Keep the vertical alignment. Even if the left and right sides of a node are swapped, the edge on top remains on top. |
"horizontal" | Keep the horizontal alignment. Even if the top and bottom of the node are swapped, the edge on the left remains on the left. |
Note that the basic display order is the order in which the edges appear in the given edges.
<script setup lang="ts">
import { reactive } from "vue"
import * as vNG from "v-network-graph"
import data from "./data"
const initialConfigs = vNG.defineConfigs({
node: {
normal: {
color: "#aabbff",
},
},
edge: {
normal: {
color: (edge: vNG.Edge) => edge.color,
},
hover: {
color: (edge: vNG.Edge) => edge.color,
},
margin: 4,
marker: {
source: { type: "arrow" },
},
gap: 10,
keepOrder: "clock",
},
})
const configs = reactive(initialConfigs)
</script>
<template>
<div class="demo-control-panel">
<label>Keep order:</label>
<el-select v-model="configs.edge.keepOrder">
<el-option label="clock" value="clock" />
<el-option label="vertical" value="vertical" />
<el-option label="horizontal" value="horizontal" />
</el-select>
</div>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "N1" },
node2: { name: "N2" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2", color: "red" },
edge2: { source: "node2", target: "node1", color: "green" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
},
}
export default {
nodes,
edges,
layouts,
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Self-loop edge
If the source and target of an edge are the same node, the edge is displayed as an arc.
Configuration regarding self-loop configs.edge.selfLoop
can also specify a function that takes an edge as an argument.
This implementation is a simplified one and, different from normal edges, has the following limitations.
- No label can be displayed.
- Multiple edges cannot be summarized on a single line.
- Supports only circle as node type
<script setup lang="ts">
import { reactive } from "vue"
import * as vNG from "v-network-graph"
import data from "./data"
const initialConfigs = vNG.defineConfigs({
node: {
normal: {
radius: 20,
},
label: {
visible: false,
},
},
edge: {
marker: {
target: { type: "arrow" },
},
selfLoop: {
radius: 14,
offset: 16,
angle: 180,
isClockwise: true,
},
},
})
const configs = reactive(initialConfigs)
</script>
<template>
<div class="demo-control-panel">
<demo-edge-self-loop-config-panel
v-model:radius="configs.edge.selfLoop.radius"
v-model:angle="configs.edge.selfLoop.angle"
v-model:offset="configs.edge.selfLoop.offset"
v-model:clockwise="configs.edge.selfLoop.isClockwise"
/>
</div>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: {},
node2: {},
node3: {},
}
const edges: Edges = {
edge1: { id: "edge1", source: "node1", target: "node2" },
edge2: { id: "edge2", source: "node2", target: "node3" },
edge3: { id: "edge3", source: "node1", target: "node1" }, // self-loop
edge4: { id: "edge4", source: "node3", target: "node3" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 120, y: 0 },
node3: { x: 240, y: 0 },
},
}
export default {
nodes,
edges,
layouts,
}
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
Style examples
<script setup lang="ts">
import * as vNG from "v-network-graph"
import data from "./data"
const configs = vNG.defineConfigs({
node: {
selectable: true,
normal: {
radius: 16,
color: "#6013ad",
},
hover: {
color: "#430d78",
},
label: {
fontSize: 11,
color: "#ffffff",
direction: "center",
},
},
edge: {
normal: {
width: 3,
color: "#58139c",
},
hover: {
color: "#3a0d66",
},
},
})
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "N1" },
node2: { name: "N2" },
node3: { name: "N3" },
node4: { name: "N4" },
node5: { name: "N5" },
node6: { name: "N6" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node2", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 0, y: 160 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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
<script setup lang="ts">
import * as vNG from "v-network-graph"
import data from "./data"
const configs = vNG.defineConfigs({
node: {
selectable: true,
normal: {
type: "rect",
width: 32,
height: 32,
borderRadius: 8,
color: "#ff6f00",
},
hover: {
color: "#ff5500",
width: 36,
height: 36,
borderRadius: 8,
},
label: {
fontSize: 16,
color: "#000000",
direction: "north",
},
},
edge: {
normal: {
width: 2,
color: "#ff6f00",
dasharray: "4 6",
linecap: "round",
},
hover: {
color: "#ff5500",
},
},
})
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "N1" },
node2: { name: "N2" },
node3: { name: "N3" },
node4: { name: "N4" },
node5: { name: "N5" },
node6: { name: "N6" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node2", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 0, y: 160 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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
<script setup lang="ts">
import * as vNG from "v-network-graph"
import data from "./data"
const configs = vNG.defineConfigs({
node: {
selectable: true,
normal: {
radius: 10,
color: "#ffffff",
strokeColor: "#ff00dd",
strokeWidth: 3,
},
hover: {
radius: 14,
color: "#ffffff",
strokeColor: "#ff00dd",
strokeWidth: 3,
},
label: {
visible: false,
},
focusring: {
color: "#ff00dd30", // alpha
},
},
edge: {
normal: {
width: edge => (edge.animate ? 2 : 1),
color: "#ff00dd",
dasharray: edge => (edge.animate ? "4" : "0"),
animate: edge => !!edge.animate,
},
hover: {
color: "#ff00dd",
},
},
})
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "N1" },
node2: { name: "N2" },
node3: { name: "N3" },
node4: { name: "N4" },
node5: { name: "N5" },
node6: { name: "N6" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node2" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node2", target: "node4", animate: true },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 80, y: 80 },
node3: { x: 0, y: 160 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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
<script setup lang="ts">
import * as vNG from "v-network-graph"
import data from "./data"
const configs = vNG.defineConfigs({
node: {
selectable: true,
normal: {
radius: 16,
color: "#ffffff",
strokeWidth: 1,
strokeColor: "#000000",
},
hover: {
color: "#f0f0f0",
},
label: {
fontSize: 12,
color: "#000000",
direction: "center",
},
},
edge: {
normal: {
width: 1,
color: "#444444",
},
hover: {
color: "#222222",
},
marker: {
target: {
type: "angle",
width: 10,
height: 10,
margin: -10,
},
},
},
})
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "A" },
node2: { name: "B" },
node3: { name: "C" },
node4: { name: "D" },
node5: { name: "E" },
node6: { name: "F" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node3" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node3", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 0, y: 160 },
node3: { x: 80, y: 80 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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
<script setup lang="ts">
import { reactive } from "vue"
import * as vNG from "v-network-graph"
import data from "./data"
const ACTIVE = "#00ee00"
const INACTIVE = "#ff0000"
const nodes = reactive(data.nodes)
const configs = vNG.defineConfigs({
node: {
normal: {
radius: 16,
color: "#aaaaaa",
},
hover: {
color: "#bbbbbb",
},
label: {
visible: false,
},
},
edge: {
normal: {
width: 2,
color: "#888888",
dasharray: edge =>
nodes[edge.source].active && nodes[edge.target].active ? 4 : 0,
animate: edge => nodes[edge.source].active && nodes[edge.target].active,
},
hover: {
color: "#222222",
},
margin: 2,
marker: {
source: {
type: "circle",
width: 5,
height: 5,
margin: 1,
color: ([edge, _stroke]) => (nodes[edge.source].active ? ACTIVE : INACTIVE),
},
target: {
type: "circle",
width: 5,
height: 5,
margin: 1,
color: ([edge, _stroke]) => (nodes[edge.target].active ? ACTIVE : INACTIVE),
},
},
},
})
const eventHandlers: vNG.EventHandlers = {
"node:click": ({ node }) => {
// toggle
nodes[node].active = !nodes[node].active
},
}
</script>
<template>
<v-network-graph
:nodes="nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
:event-handlers="eventHandlers"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { active: true },
node2: { active: true },
node3: { active: true },
node4: { active: false },
node5: { active: true },
node6: { active: true },
}
const edges: Edges = {
edge1: { source: "node1", target: "node3" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node3", target: "node4" },
edge4: { source: "node4", target: "node5" },
edge5: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 0, y: 160 },
node3: { x: 80, y: 80 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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
<script setup lang="ts">
import * as nNG from "v-network-graph"
import data from "./data"
const configs = nNG.defineConfigs({
node: {
selectable: true,
normal: {
radius: 20,
color: "#8888ff",
strokeWidth: 2,
strokeColor: "#0000aa",
},
hover: {
color: "#6666ff",
},
label: {
fontSize: 12,
color: "#000000",
},
},
edge: {
normal: {
width: 1,
color: "#2222aa",
},
hover: {
width: 2,
color: "#0000aa",
},
gap: 20,
type: "curve",
margin: 6,
marker: {
target: {
type: "arrow",
width: 8,
height: 8,
},
},
},
})
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:layouts="data.layouts"
:configs="configs"
/>
</template>
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
import { Nodes, Edges, Layouts } from "v-network-graph"
const nodes: Nodes = {
node1: { name: "N1" },
node2: { name: "N2" },
node3: { name: "N3" },
node4: { name: "N4" },
node5: { name: "N5" },
node6: { name: "N6" },
}
const edges: Edges = {
edge1: { source: "node1", target: "node3" },
edge2: { source: "node2", target: "node3" },
edge3: { source: "node3", target: "node4" },
edge4: { source: "node3", target: "node4" },
edge5: { source: "node4", target: "node3" },
edge6: { source: "node4", target: "node3" },
edge7: { source: "node4", target: "node5" },
edge8: { source: "node4", target: "node6" },
}
const layouts: Layouts = {
nodes: {
node1: { x: 0, y: 0 },
node2: { x: 0, y: 160 },
node3: { x: 80, y: 80 },
node4: { x: 240, y: 80 },
node5: { x: 320, y: 0 },
node6: { x: 320, y: 160 },
},
}
export default {
nodes,
edges,
layouts,
}
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