feat(examples): Add floating edges example
This commit is contained in:
@@ -61,6 +61,10 @@ export const routes: RouterOptions['routes'] = [
|
|||||||
path: '/node-type-change',
|
path: '/node-type-change',
|
||||||
component: () => import('./src/NodeTypeChange/NodeTypeChangeExample.vue'),
|
component: () => import('./src/NodeTypeChange/NodeTypeChangeExample.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/floating-edges',
|
||||||
|
component: () => import('./src/FloatingEdges/FloatingEdges.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/overview',
|
path: '/overview',
|
||||||
component: () => import('./src/Overview/Overview.vue'),
|
component: () => import('./src/Overview/Overview.vue'),
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { getBezierPath, GraphNode, Position } from '@braks/vue-flow'
|
||||||
|
import { getEdgeParams } from './floating-edge-utils'
|
||||||
|
|
||||||
|
interface FloatingConnectionLineProps {
|
||||||
|
targetX: number
|
||||||
|
targetY: number
|
||||||
|
sourcePosition: Position
|
||||||
|
targetPosition: Position
|
||||||
|
sourceNode: GraphNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<FloatingConnectionLineProps>()
|
||||||
|
|
||||||
|
const targetNode = computed(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
id: 'connection-target',
|
||||||
|
position: { x: props.targetX, y: props.targetY },
|
||||||
|
dimensions: { width: 1, height: 1 },
|
||||||
|
} as GraphNode),
|
||||||
|
)
|
||||||
|
|
||||||
|
const edgeParams = computed(() => getEdgeParams(props.sourceNode, targetNode.value))
|
||||||
|
const d = computed(() =>
|
||||||
|
getBezierPath({
|
||||||
|
sourceX: edgeParams.value.sx,
|
||||||
|
sourceY: edgeParams.value.sy,
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<g>
|
||||||
|
<path fill="none" stroke="#222" :stroke-width="1.5" class="animated" :d="d" />
|
||||||
|
<circle :cx="props.targetX" :cy="props.targetY" fill="#fff" :r="3" stroke="#222" :stroke-width="1.5" />
|
||||||
|
</g>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { CSSProperties } from 'vue'
|
||||||
|
import { getBezierPath, GraphNode, EdgeProps, MarkerType } from '@braks/vue-flow'
|
||||||
|
import { getEdgeParams } from './floating-edge-utils'
|
||||||
|
|
||||||
|
interface FloatingEdgeProps extends EdgeProps {
|
||||||
|
id: string
|
||||||
|
source: string
|
||||||
|
target: string
|
||||||
|
markerEndId?: string
|
||||||
|
sourceNode: GraphNode
|
||||||
|
targetNode: GraphNode
|
||||||
|
nodes: GraphNode[]
|
||||||
|
style?: CSSProperties
|
||||||
|
markerEnd: MarkerType
|
||||||
|
markerStart: MarkerType
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<FloatingEdgeProps>()
|
||||||
|
const edgeParams = computed(() => getEdgeParams(props.sourceNode, props.targetNode))
|
||||||
|
|
||||||
|
const d = computed(
|
||||||
|
() =>
|
||||||
|
(edgeParams.value.sx &&
|
||||||
|
getBezierPath({
|
||||||
|
sourceX: edgeParams.value.sx,
|
||||||
|
sourceY: edgeParams.value.sy,
|
||||||
|
targetX: edgeParams.value.tx,
|
||||||
|
targetY: edgeParams.value.ty,
|
||||||
|
sourcePosition: edgeParams.value.sourcePos,
|
||||||
|
targetPosition: edgeParams.value.targetPos,
|
||||||
|
})) ||
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<g class="vue-flow__connection">
|
||||||
|
<path
|
||||||
|
:id="props.id"
|
||||||
|
class="vue-flow__edge-path"
|
||||||
|
:d="d"
|
||||||
|
:marker-start="props.markerStart"
|
||||||
|
:marker-end="props.markerEnd"
|
||||||
|
:style="props.style"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { VueFlow, Background, MiniMap, Controls, MarkerType, useVueFlow } from '@braks/vue-flow'
|
||||||
|
import FloatingEdge from './FloatingEdge.vue'
|
||||||
|
import FloatingConnectionLine from './FloatingConnectionLine.vue'
|
||||||
|
import { createElements } from './floating-edge-utils'
|
||||||
|
|
||||||
|
const { addEdges, onConnect, onPaneReady, getNodes } = useVueFlow({
|
||||||
|
modelValue: createElements(),
|
||||||
|
})
|
||||||
|
onPaneReady(({ fitView }) => fitView())
|
||||||
|
onConnect((params) => addEdges([{ ...params, type: 'floating', markerEnd: MarkerType.Arrow }]))
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="floatingedges">
|
||||||
|
<VueFlow>
|
||||||
|
<Background variant="lines" :gap="24" />
|
||||||
|
<MiniMap />
|
||||||
|
<Controls />
|
||||||
|
|
||||||
|
<template #connection-line="props">
|
||||||
|
<FloatingConnectionLine v-bind="props" />
|
||||||
|
</template>
|
||||||
|
<template #edge-floating="props">
|
||||||
|
<FloatingEdge v-bind="props" :nodes="getNodes" />
|
||||||
|
</template>
|
||||||
|
</VueFlow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.floatingedges {
|
||||||
|
flex-direction: column;
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floatingedges .vue-flow__handle {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { Position, MarkerType, XYPosition, GraphNode, Edge } from '@braks/vue-flow'
|
||||||
|
|
||||||
|
// this helper function returns the intersection point
|
||||||
|
// of the line between the center of the intersectionNode and the target node
|
||||||
|
function getNodeIntersection(intersectionNode: GraphNode, targetNode: GraphNode): XYPosition {
|
||||||
|
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
|
||||||
|
const {
|
||||||
|
dimensions: { width: intersectionNodeWidth, height: intersectionNodeHeight },
|
||||||
|
position: intersectionNodePosition,
|
||||||
|
} = intersectionNode
|
||||||
|
const targetPosition = targetNode.position
|
||||||
|
|
||||||
|
const w = intersectionNodeWidth / 2
|
||||||
|
const h = intersectionNodeHeight / 2
|
||||||
|
|
||||||
|
const x2 = intersectionNodePosition.x + w
|
||||||
|
const y2 = intersectionNodePosition.y + h
|
||||||
|
const x1 = targetPosition.x + w
|
||||||
|
const y1 = targetPosition.y + h
|
||||||
|
|
||||||
|
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h)
|
||||||
|
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h)
|
||||||
|
const a = 1 / (Math.abs(xx1) + Math.abs(yy1))
|
||||||
|
const xx3 = a * xx1
|
||||||
|
const yy3 = a * yy1
|
||||||
|
const x = w * (xx3 + yy3) + x2
|
||||||
|
const y = h * (-xx3 + yy3) + y2
|
||||||
|
|
||||||
|
return { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the position (top,right,bottom or right) passed node compared to the intersection point
|
||||||
|
function getEdgePosition(node: GraphNode, intersectionPoint: XYPosition) {
|
||||||
|
const n = { ...node.position, ...node.dimensions }
|
||||||
|
const nx = Math.round(n.x)
|
||||||
|
const ny = Math.round(n.y)
|
||||||
|
const px = Math.round(intersectionPoint.x)
|
||||||
|
const py = Math.round(intersectionPoint.y)
|
||||||
|
|
||||||
|
if (px <= nx + 1) {
|
||||||
|
return Position.Left
|
||||||
|
}
|
||||||
|
if (px >= nx + n.width - 1) {
|
||||||
|
return Position.Right
|
||||||
|
}
|
||||||
|
if (py <= ny + 1) {
|
||||||
|
return Position.Top
|
||||||
|
}
|
||||||
|
if (py >= n.y + n.height - 1) {
|
||||||
|
return Position.Bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
return Position.Top
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
|
||||||
|
export function getEdgeParams(source: GraphNode, target: GraphNode) {
|
||||||
|
const sourceIntersectionPoint = getNodeIntersection(source, target)
|
||||||
|
const targetIntersectionPoint = getNodeIntersection(target, source)
|
||||||
|
|
||||||
|
const sourcePos = getEdgePosition(source, sourceIntersectionPoint)
|
||||||
|
const targetPos = getEdgePosition(target, targetIntersectionPoint)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sx: sourceIntersectionPoint.x,
|
||||||
|
sy: sourceIntersectionPoint.y,
|
||||||
|
tx: targetIntersectionPoint.x,
|
||||||
|
ty: targetIntersectionPoint.y,
|
||||||
|
sourcePos,
|
||||||
|
targetPos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createElements() {
|
||||||
|
const win = typeof window !== 'undefined' ? window : ({} as any)
|
||||||
|
const elements = []
|
||||||
|
const center = { x: win.innerWidth / 2, y: win.innerHeight / 2 }
|
||||||
|
|
||||||
|
elements.push({ id: 'target', label: 'Target', position: center })
|
||||||
|
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
const degrees = i * (360 / 8)
|
||||||
|
const radians = degrees * (Math.PI / 180)
|
||||||
|
const x = 250 * Math.cos(radians) + center.x
|
||||||
|
const y = 250 * Math.sin(radians) + center.y
|
||||||
|
|
||||||
|
elements.push({ id: `${i}`, label: 'Source', position: { x, y } })
|
||||||
|
|
||||||
|
elements.push({
|
||||||
|
id: `edge-${i}`,
|
||||||
|
target: 'target',
|
||||||
|
source: `${i}`,
|
||||||
|
type: 'floating',
|
||||||
|
markerEnd: MarkerType.Arrow,
|
||||||
|
markerStart: {
|
||||||
|
id: 'markerstart',
|
||||||
|
type: MarkerType.Arrow,
|
||||||
|
orient: 'auto-start-reverse',
|
||||||
|
},
|
||||||
|
} as Edge)
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user