Skip to content

Commit 4d09783

Browse files
authored
docs(examples): add loopback edge example (#1708)
docs(examples): add loopback example Signed-off-by: braks <[email protected]>
1 parent 0d24680 commit 4d09783

File tree

7 files changed

+222
-1
lines changed

7 files changed

+222
-1
lines changed

docs/examples/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { NodeResizerApp, ResizableNode } from './node-resizer'
2020
import { ToolbarApp, ToolbarNode } from './node-toolbar'
2121
import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useLayout, useRunProcess, useShuffle } from './layout'
2222
import { SimpleLayoutApp, SimpleLayoutElements, SimpleLayoutIcon, useSimpleLayout } from './layout-simple'
23-
23+
import { LoopbackApp, LoopbackCSS, LoopbackEdge } from './loopback'
2424
import { MathApp, MathCSS, MathElements, MathIcon, MathOperatorNode, MathResultNode, MathValueNode } from './math'
2525
import { ConfirmApp, ConfirmDialog, useDialog } from './confirm-delete'
2626

@@ -165,4 +165,9 @@ export const exampleImports = {
165165
'Dialog.vue': ConfirmDialog,
166166
'useDialog.js': useDialog,
167167
},
168+
loopback: {
169+
'App.vue': LoopbackApp,
170+
'LoopbackEdge.vue': LoopbackEdge,
171+
'style.css': LoopbackCSS,
172+
},
168173
}

docs/examples/loopback/App.vue

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<script setup>
2+
import { ref } from 'vue'
3+
import { Background } from '@vue-flow/background'
4+
import { Panel, Position, VueFlow, useVueFlow } from '@vue-flow/core'
5+
import LoopbackEdge from './LoopbackEdge.vue'
6+
7+
const { updateEdgeData, updateNode } = useVueFlow()
8+
9+
const nodes = ref([
10+
{
11+
id: '1',
12+
position: { x: 0, y: 0 },
13+
data: { label: 'I connect to myself' },
14+
},
15+
])
16+
17+
const pathType = ref('bezier')
18+
19+
const isHorizontal = ref(false)
20+
21+
const edges = ref([{ id: 'e1-1', type: 'loopback', source: '1', target: '1', data: { pathType: pathType.value } }])
22+
</script>
23+
24+
<template>
25+
<VueFlow :nodes="nodes" :edges="edges" fit-view-on-init>
26+
<template #edge-loopback="customEdgeProps">
27+
<LoopbackEdge
28+
:id="customEdgeProps.id"
29+
:source-x="customEdgeProps.sourceX"
30+
:source-y="customEdgeProps.sourceY"
31+
:target-x="customEdgeProps.targetX"
32+
:target-y="customEdgeProps.targetY"
33+
:source-position="customEdgeProps.sourcePosition"
34+
:target-position="customEdgeProps.targetPosition"
35+
:source-node="customEdgeProps.sourceNode"
36+
:target-node="customEdgeProps.targetNode"
37+
:data="customEdgeProps.data"
38+
/>
39+
</template>
40+
41+
<Background />
42+
43+
<Panel>
44+
<select v-model="pathType" @change="updateEdgeData('e1-1', { pathType })">
45+
<option value="bezier">Bezier</option>
46+
<option value="smoothstep">Smoothstep</option>
47+
</select>
48+
49+
<label for="is-horizontal"
50+
>{{ isHorizontal ? 'Vertical' : 'Horizontal' }}
51+
<input
52+
id="is-horizontal"
53+
v-model="isHorizontal"
54+
type="checkbox"
55+
@change="
56+
updateNode('1', {
57+
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
58+
targetPosition: isHorizontal ? Position.Left : Position.Top,
59+
})
60+
"
61+
/>
62+
</label>
63+
</Panel>
64+
</VueFlow>
65+
</template>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<script setup>
2+
import { Position, getBezierPath, getSmoothStepPath } from '@vue-flow/core'
3+
import { computed } from 'vue'
4+
5+
const props = defineProps({
6+
id: {
7+
type: String,
8+
required: true,
9+
},
10+
sourceX: {
11+
type: Number,
12+
required: true,
13+
},
14+
sourceY: {
15+
type: Number,
16+
required: true,
17+
},
18+
targetX: {
19+
type: Number,
20+
required: true,
21+
},
22+
targetY: {
23+
type: Number,
24+
required: true,
25+
},
26+
sourcePosition: {
27+
type: String,
28+
required: true,
29+
},
30+
targetPosition: {
31+
type: String,
32+
required: true,
33+
},
34+
sourceNode: {
35+
type: Object,
36+
required: true,
37+
},
38+
targetNode: {
39+
type: Object,
40+
required: true,
41+
},
42+
data: {
43+
type: Object,
44+
required: true,
45+
},
46+
})
47+
48+
const path = computed(() => {
49+
if (props.sourceNode && props.targetNode) {
50+
if (props.data.pathType === 'bezier') {
51+
if (
52+
(props.sourcePosition === Position.Bottom && props.targetPosition === Position.Top) ||
53+
(props.sourcePosition === Position.Top && props.targetPosition === Position.Bottom)
54+
) {
55+
// for horizontal loopback edges
56+
const radiusX = 60
57+
const radiusY = props.sourceY - props.targetY
58+
59+
return [`M ${props.sourceX} ${props.sourceY} A ${radiusX} ${radiusY} 0 1 0 ${props.targetX} ${props.targetY}`]
60+
} else if (
61+
(props.sourcePosition === Position.Left && props.targetPosition === Position.Right) ||
62+
(props.sourcePosition === Position.Right && props.targetPosition === Position.Left)
63+
) {
64+
// for vertical loopback edges
65+
const radiusX = (props.sourceX - props.targetX) * 0.6
66+
const radiusY = 50
67+
68+
return [`M ${props.sourceX} ${props.sourceY} A ${radiusX} ${radiusY} 0 1 0 ${props.targetX} ${props.targetY}`]
69+
}
70+
} else if (props.data.pathType === 'smoothstep') {
71+
let centerX, centerY
72+
if (props.sourceNode === props.targetNode) {
73+
if (
74+
(props.sourcePosition === Position.Bottom && props.targetPosition === Position.Top) ||
75+
(props.sourcePosition === Position.Top && props.targetPosition === Position.Bottom)
76+
) {
77+
const source = props.sourceNode
78+
centerX = props.sourceX - 40 - source.dimensions.width / 2
79+
centerY = (props.sourceY + props.targetY) / 2
80+
} else if (
81+
(props.sourcePosition === Position.Left && props.targetPosition === Position.Right) ||
82+
(props.sourcePosition === Position.Right && props.targetPosition === Position.Left)
83+
) {
84+
const source = props.sourceNode
85+
centerX = (props.sourceX + props.targetX) / 2
86+
centerY = props.sourceY + 40 + source.dimensions.height / 2
87+
}
88+
}
89+
90+
return getSmoothStepPath({
91+
sourcePosition: props.sourcePosition,
92+
targetPosition: props.targetPosition,
93+
centerX,
94+
centerY,
95+
...props,
96+
})
97+
}
98+
}
99+
100+
// default to bezier path
101+
return getBezierPath(props)
102+
})
103+
</script>
104+
105+
<script>
106+
export default {
107+
inheritAttrs: false,
108+
}
109+
</script>
110+
111+
<template>
112+
<path :d="path[0]" fill="none" stroke="black" />
113+
</template>

docs/examples/loopback/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { default as LoopbackApp } from './App.vue?raw'
2+
export { default as LoopbackEdge } from './LoopbackEdge.vue?raw'
3+
export { default as LoopbackCSS } from './style.css?inline'

docs/examples/loopback/style.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.vue-flow__panel {
2+
background-color: #2d3748;
3+
padding: 10px;
4+
border-radius: 8px;
5+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
6+
color: white;
7+
display: flex;
8+
flex-direction: column;
9+
gap: 1rem;
10+
font-size: 12px;
11+
font-weight: 600;
12+
width: 100px;
13+
}
14+
15+
16+
.vue-flow__panel label {
17+
display: flex;
18+
justify-content: space-between;
19+
align-items: center;
20+
gap: 5px;
21+
cursor: pointer;
22+
}
23+
24+
.vue-flow__panel label input {
25+
cursor: pointer;
26+
}

docs/src/.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
245245
{ text: 'Custom Connection Line', link: '/examples/edges/connection-line' },
246246
{ text: 'Connection Validation', link: '/examples/edges/validation' },
247247
{ text: 'Connection Radius', link: '/examples/edges/connection-radius' },
248+
{ text: 'Loopback Edge', link: '/examples/edges/loopback' },
248249
],
249250
},
250251
],

docs/src/examples/edges/loopback.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Loopback Edges
2+
3+
Loopback edges are edges that connect a node to itself. They are useful for representing self-referential relationships.
4+
This example walks you through creating loopback edges using Vue Flow and custom edges.
5+
6+
<div class="mt-6">
7+
<Repl example="loopback"></Repl>
8+
</div>

0 commit comments

Comments
 (0)