docs(examples): add loopback edge example (#1708)
docs(examples): add loopback example Signed-off-by: braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -20,7 +20,7 @@ import { NodeResizerApp, ResizableNode } from './node-resizer'
|
|||||||
import { ToolbarApp, ToolbarNode } from './node-toolbar'
|
import { ToolbarApp, ToolbarNode } from './node-toolbar'
|
||||||
import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useLayout, useRunProcess, useShuffle } from './layout'
|
import { LayoutApp, LayoutEdge, LayoutElements, LayoutIcon, LayoutNode, useLayout, useRunProcess, useShuffle } from './layout'
|
||||||
import { SimpleLayoutApp, SimpleLayoutElements, SimpleLayoutIcon, useSimpleLayout } from './layout-simple'
|
import { SimpleLayoutApp, SimpleLayoutElements, SimpleLayoutIcon, useSimpleLayout } from './layout-simple'
|
||||||
|
import { LoopbackApp, LoopbackCSS, LoopbackEdge } from './loopback'
|
||||||
import { MathApp, MathCSS, MathElements, MathIcon, MathOperatorNode, MathResultNode, MathValueNode } from './math'
|
import { MathApp, MathCSS, MathElements, MathIcon, MathOperatorNode, MathResultNode, MathValueNode } from './math'
|
||||||
import { ConfirmApp, ConfirmDialog, useDialog } from './confirm-delete'
|
import { ConfirmApp, ConfirmDialog, useDialog } from './confirm-delete'
|
||||||
|
|
||||||
@@ -165,4 +165,9 @@ export const exampleImports = {
|
|||||||
'Dialog.vue': ConfirmDialog,
|
'Dialog.vue': ConfirmDialog,
|
||||||
'useDialog.js': useDialog,
|
'useDialog.js': useDialog,
|
||||||
},
|
},
|
||||||
|
loopback: {
|
||||||
|
'App.vue': LoopbackApp,
|
||||||
|
'LoopbackEdge.vue': LoopbackEdge,
|
||||||
|
'style.css': LoopbackCSS,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Background } from '@vue-flow/background'
|
||||||
|
import { Panel, Position, VueFlow, useVueFlow } from '@vue-flow/core'
|
||||||
|
import LoopbackEdge from './LoopbackEdge.vue'
|
||||||
|
|
||||||
|
const { updateEdgeData, updateNode } = useVueFlow()
|
||||||
|
|
||||||
|
const nodes = ref([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
data: { label: 'I connect to myself' },
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const pathType = ref('bezier')
|
||||||
|
|
||||||
|
const isHorizontal = ref(false)
|
||||||
|
|
||||||
|
const edges = ref([{ id: 'e1-1', type: 'loopback', source: '1', target: '1', data: { pathType: pathType.value } }])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VueFlow :nodes="nodes" :edges="edges" fit-view-on-init>
|
||||||
|
<template #edge-loopback="customEdgeProps">
|
||||||
|
<LoopbackEdge
|
||||||
|
:id="customEdgeProps.id"
|
||||||
|
:source-x="customEdgeProps.sourceX"
|
||||||
|
:source-y="customEdgeProps.sourceY"
|
||||||
|
:target-x="customEdgeProps.targetX"
|
||||||
|
:target-y="customEdgeProps.targetY"
|
||||||
|
:source-position="customEdgeProps.sourcePosition"
|
||||||
|
:target-position="customEdgeProps.targetPosition"
|
||||||
|
:source-node="customEdgeProps.sourceNode"
|
||||||
|
:target-node="customEdgeProps.targetNode"
|
||||||
|
:data="customEdgeProps.data"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<Background />
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<select v-model="pathType" @change="updateEdgeData('e1-1', { pathType })">
|
||||||
|
<option value="bezier">Bezier</option>
|
||||||
|
<option value="smoothstep">Smoothstep</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<label for="is-horizontal"
|
||||||
|
>{{ isHorizontal ? 'Vertical' : 'Horizontal' }}
|
||||||
|
<input
|
||||||
|
id="is-horizontal"
|
||||||
|
v-model="isHorizontal"
|
||||||
|
type="checkbox"
|
||||||
|
@change="
|
||||||
|
updateNode('1', {
|
||||||
|
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
|
||||||
|
targetPosition: isHorizontal ? Position.Left : Position.Top,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</Panel>
|
||||||
|
</VueFlow>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<script setup>
|
||||||
|
import { Position, getBezierPath, getSmoothStepPath } from '@vue-flow/core'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourceX: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourceY: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetX: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetY: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourcePosition: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetPosition: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourceNode: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetNode: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const path = computed(() => {
|
||||||
|
if (props.sourceNode && props.targetNode) {
|
||||||
|
if (props.data.pathType === 'bezier') {
|
||||||
|
if (
|
||||||
|
(props.sourcePosition === Position.Bottom && props.targetPosition === Position.Top) ||
|
||||||
|
(props.sourcePosition === Position.Top && props.targetPosition === Position.Bottom)
|
||||||
|
) {
|
||||||
|
// for horizontal loopback edges
|
||||||
|
const radiusX = 60
|
||||||
|
const radiusY = props.sourceY - props.targetY
|
||||||
|
|
||||||
|
return [`M ${props.sourceX} ${props.sourceY} A ${radiusX} ${radiusY} 0 1 0 ${props.targetX} ${props.targetY}`]
|
||||||
|
} else if (
|
||||||
|
(props.sourcePosition === Position.Left && props.targetPosition === Position.Right) ||
|
||||||
|
(props.sourcePosition === Position.Right && props.targetPosition === Position.Left)
|
||||||
|
) {
|
||||||
|
// for vertical loopback edges
|
||||||
|
const radiusX = (props.sourceX - props.targetX) * 0.6
|
||||||
|
const radiusY = 50
|
||||||
|
|
||||||
|
return [`M ${props.sourceX} ${props.sourceY} A ${radiusX} ${radiusY} 0 1 0 ${props.targetX} ${props.targetY}`]
|
||||||
|
}
|
||||||
|
} else if (props.data.pathType === 'smoothstep') {
|
||||||
|
let centerX, centerY
|
||||||
|
if (props.sourceNode === props.targetNode) {
|
||||||
|
if (
|
||||||
|
(props.sourcePosition === Position.Bottom && props.targetPosition === Position.Top) ||
|
||||||
|
(props.sourcePosition === Position.Top && props.targetPosition === Position.Bottom)
|
||||||
|
) {
|
||||||
|
const source = props.sourceNode
|
||||||
|
centerX = props.sourceX - 40 - source.dimensions.width / 2
|
||||||
|
centerY = (props.sourceY + props.targetY) / 2
|
||||||
|
} else if (
|
||||||
|
(props.sourcePosition === Position.Left && props.targetPosition === Position.Right) ||
|
||||||
|
(props.sourcePosition === Position.Right && props.targetPosition === Position.Left)
|
||||||
|
) {
|
||||||
|
const source = props.sourceNode
|
||||||
|
centerX = (props.sourceX + props.targetX) / 2
|
||||||
|
centerY = props.sourceY + 40 + source.dimensions.height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSmoothStepPath({
|
||||||
|
sourcePosition: props.sourcePosition,
|
||||||
|
targetPosition: props.targetPosition,
|
||||||
|
centerX,
|
||||||
|
centerY,
|
||||||
|
...props,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to bezier path
|
||||||
|
return getBezierPath(props)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<path :d="path[0]" fill="none" stroke="black" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as LoopbackApp } from './App.vue?raw'
|
||||||
|
export { default as LoopbackEdge } from './LoopbackEdge.vue?raw'
|
||||||
|
export { default as LoopbackCSS } from './style.css?inline'
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
.vue-flow__panel {
|
||||||
|
background-color: #2d3748;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.vue-flow__panel label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vue-flow__panel label input {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -245,6 +245,7 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
|
|||||||
{ text: 'Custom Connection Line', link: '/examples/edges/connection-line' },
|
{ text: 'Custom Connection Line', link: '/examples/edges/connection-line' },
|
||||||
{ text: 'Connection Validation', link: '/examples/edges/validation' },
|
{ text: 'Connection Validation', link: '/examples/edges/validation' },
|
||||||
{ text: 'Connection Radius', link: '/examples/edges/connection-radius' },
|
{ text: 'Connection Radius', link: '/examples/edges/connection-radius' },
|
||||||
|
{ text: 'Loopback Edge', link: '/examples/edges/loopback' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Loopback Edges
|
||||||
|
|
||||||
|
Loopback edges are edges that connect a node to itself. They are useful for representing self-referential relationships.
|
||||||
|
This example walks you through creating loopback edges using Vue Flow and custom edges.
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<Repl example="loopback"></Repl>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user