fix(core): correct connection line calculation
This commit is contained in:
@@ -1,168 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { computed, inject } from 'vue'
|
|
||||||
import { toRef } from '@vueuse/core'
|
|
||||||
import type { GraphNode } from '../../types'
|
|
||||||
import { ConnectionLineType, ConnectionMode, Position } from '../../types'
|
|
||||||
import { getHandlePosition, getMarkerId } from '../../utils'
|
|
||||||
import { useVueFlow } from '../../composables'
|
|
||||||
import { Slots } from '../../context'
|
|
||||||
import { getBezierPath, getSimpleBezierPath, getSmoothStepPath, getStraightPath } from '../Edges/utils'
|
|
||||||
|
|
||||||
const { sourceNode } = defineProps<{ sourceNode: GraphNode }>()
|
|
||||||
|
|
||||||
const {
|
|
||||||
connectionMode,
|
|
||||||
connectionStartHandle,
|
|
||||||
connectionEndHandle,
|
|
||||||
connectionPosition,
|
|
||||||
connectionLineType,
|
|
||||||
connectionLineStyle,
|
|
||||||
connectionLineOptions,
|
|
||||||
connectionStatus,
|
|
||||||
viewport,
|
|
||||||
findNode,
|
|
||||||
} = useVueFlow()
|
|
||||||
|
|
||||||
const oppositePosition = {
|
|
||||||
[Position.Left]: Position.Right,
|
|
||||||
[Position.Right]: Position.Left,
|
|
||||||
[Position.Top]: Position.Bottom,
|
|
||||||
[Position.Bottom]: Position.Top,
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectionLineComponent = inject(Slots)?.['connection-line']
|
|
||||||
|
|
||||||
const startHandleId = connectionStartHandle.value!.handleId
|
|
||||||
|
|
||||||
const type = connectionStartHandle.value!.type
|
|
||||||
|
|
||||||
const targetNode = computed(() => (connectionEndHandle.value?.handleId && findNode(connectionEndHandle.value.nodeId)) || null)
|
|
||||||
|
|
||||||
const sourceHandle = computed(
|
|
||||||
() =>
|
|
||||||
(connectionMode.value === ConnectionMode.Strict
|
|
||||||
? sourceNode.handleBounds[type]?.find((d) => d.id === startHandleId)
|
|
||||||
: [...(sourceNode.handleBounds.source || []), ...(sourceNode.handleBounds.target || [])]?.find(
|
|
||||||
(d) => d.id === startHandleId,
|
|
||||||
)) || sourceNode.handleBounds[type ?? 'source']?.[0],
|
|
||||||
)
|
|
||||||
|
|
||||||
const targetHandle = computed(() => {
|
|
||||||
return (
|
|
||||||
(targetNode.value &&
|
|
||||||
connectionEndHandle.value?.handleId &&
|
|
||||||
((connectionMode.value === ConnectionMode.Strict
|
|
||||||
? targetNode.value.handleBounds[type === 'source' ? 'target' : 'source']?.find(
|
|
||||||
(d) => d.id === connectionEndHandle.value?.handleId,
|
|
||||||
)
|
|
||||||
: [...(targetNode.value.handleBounds.source || []), ...(targetNode.value.handleBounds.target || [])]?.find(
|
|
||||||
(d) => d.id === connectionEndHandle.value?.handleId,
|
|
||||||
)) ||
|
|
||||||
targetNode.value.handleBounds[type ?? 'target']?.[0])) ||
|
|
||||||
null
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fromXY = computed(() => {
|
|
||||||
if (sourceHandle.value) {
|
|
||||||
return getHandlePosition(
|
|
||||||
sourceHandle.value?.position || Position.Top,
|
|
||||||
{ ...sourceNode.dimensions, ...sourceNode.computedPosition },
|
|
||||||
sourceHandle.value,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: sourceNode.dimensions.width / 2,
|
|
||||||
y: sourceNode.dimensions.height / 2,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const targetPosition = toRef(() => (sourceHandle.value?.position ? oppositePosition[sourceHandle.value?.position] : undefined))
|
|
||||||
|
|
||||||
// todo: rename corresponding props
|
|
||||||
const toX = computed(() => (connectionPosition.value.x - viewport.value.x) / viewport.value.zoom)
|
|
||||||
const toY = computed(() => (connectionPosition.value.y - viewport.value.y) / viewport.value.zoom)
|
|
||||||
|
|
||||||
const dAttr = computed(() => {
|
|
||||||
let path
|
|
||||||
|
|
||||||
const pathParams = {
|
|
||||||
sourceX: fromXY.value.x,
|
|
||||||
sourceY: fromXY.value.y,
|
|
||||||
sourcePosition: sourceHandle.value?.position,
|
|
||||||
targetX: toX.value,
|
|
||||||
targetY: toY.value,
|
|
||||||
targetPosition: targetPosition.value,
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = connectionLineType.value ?? connectionLineOptions.value.type
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case ConnectionLineType.Bezier:
|
|
||||||
;[path] = getBezierPath(pathParams)
|
|
||||||
break
|
|
||||||
case ConnectionLineType.Step:
|
|
||||||
;[path] = getSmoothStepPath({
|
|
||||||
...pathParams,
|
|
||||||
borderRadius: 0,
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case ConnectionLineType.SmoothStep:
|
|
||||||
;[path] = getSmoothStepPath(pathParams)
|
|
||||||
break
|
|
||||||
case ConnectionLineType.SimpleBezier:
|
|
||||||
;[path] = getSimpleBezierPath(pathParams)
|
|
||||||
break
|
|
||||||
case ConnectionLineType.Straight:
|
|
||||||
;[path] = getStraightPath(pathParams)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
;[path] = getBezierPath(pathParams)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return path
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export default {
|
|
||||||
name: 'ConnectionLine',
|
|
||||||
compatConfig: { MODE: 3 },
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<g class="vue-flow__connection">
|
|
||||||
<component
|
|
||||||
:is="connectionLineComponent as any"
|
|
||||||
v-if="connectionLineComponent"
|
|
||||||
v-bind="{
|
|
||||||
sourceX: fromXY.x,
|
|
||||||
sourceY: fromXY.y,
|
|
||||||
sourcePosition: sourceHandle?.position,
|
|
||||||
targetX: toX,
|
|
||||||
targetY: toY,
|
|
||||||
targetPosition,
|
|
||||||
sourceNode,
|
|
||||||
sourceHandle,
|
|
||||||
targetNode,
|
|
||||||
targetHandle,
|
|
||||||
markerEnd: `url(#${getMarkerId(connectionLineOptions.markerEnd)})`,
|
|
||||||
markerStart: `url(#${getMarkerId(connectionLineOptions.markerStart)})`,
|
|
||||||
connectionStatus,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<path
|
|
||||||
v-else
|
|
||||||
:d="dAttr"
|
|
||||||
class="vue-flow__connection-path"
|
|
||||||
:class="[connectionLineOptions.class, connectionStatus]"
|
|
||||||
:style="connectionLineStyle || connectionLineOptions.style || {}"
|
|
||||||
:marker-end="`url(#${getMarkerId(connectionLineOptions.markerEnd)})`"
|
|
||||||
:marker-start="`url(#${getMarkerId(connectionLineOptions.markerStart)})`"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</template>
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import { defineComponent, h, inject } from 'vue'
|
||||||
|
import { ConnectionLineType, ConnectionMode, Position } from '~/types'
|
||||||
|
import { getMarkerId } from '~/utils'
|
||||||
|
import { useVueFlow } from '~/composables'
|
||||||
|
import { Slots } from '~/context'
|
||||||
|
import { getBezierPath, getSimpleBezierPath, getSmoothStepPath } from '~/components/Edges/utils'
|
||||||
|
|
||||||
|
const oppositePosition = {
|
||||||
|
[Position.Left]: Position.Right,
|
||||||
|
[Position.Right]: Position.Left,
|
||||||
|
[Position.Top]: Position.Bottom,
|
||||||
|
[Position.Bottom]: Position.Top,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConnectionLine = defineComponent({
|
||||||
|
name: 'ConnectionLine',
|
||||||
|
compatConfig: { MODE: 3 },
|
||||||
|
setup() {
|
||||||
|
const {
|
||||||
|
connectionMode,
|
||||||
|
connectionStartHandle,
|
||||||
|
connectionEndHandle,
|
||||||
|
connectionPosition,
|
||||||
|
connectionLineType,
|
||||||
|
connectionLineStyle,
|
||||||
|
connectionLineOptions,
|
||||||
|
connectionStatus,
|
||||||
|
viewport,
|
||||||
|
findNode,
|
||||||
|
} = useVueFlow()
|
||||||
|
|
||||||
|
const connectionLineComponent = inject(Slots)?.['connection-line']
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!connectionStartHandle.value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromNode = findNode(connectionStartHandle.value.nodeId)
|
||||||
|
|
||||||
|
if (!fromNode) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleId = connectionStartHandle.value.handleId
|
||||||
|
|
||||||
|
const handleType = connectionStartHandle.value.type
|
||||||
|
|
||||||
|
const targetNode = (connectionEndHandle.value?.handleId && findNode(connectionEndHandle.value.nodeId)) || null
|
||||||
|
|
||||||
|
const toX = (connectionPosition.value.x - viewport.value.x) / viewport.value.zoom
|
||||||
|
const toY = (connectionPosition.value.y - viewport.value.y) / viewport.value.zoom
|
||||||
|
|
||||||
|
const fromHandleBounds = fromNode.handleBounds
|
||||||
|
let handleBounds = fromHandleBounds?.[handleType]
|
||||||
|
|
||||||
|
if (connectionMode.value === ConnectionMode.Loose) {
|
||||||
|
handleBounds = handleBounds || fromHandleBounds?.[handleType === 'source' ? 'target' : 'source']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handleBounds) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromHandle = handleId ? handleBounds.find((d) => d.id === handleId) : handleBounds[0]
|
||||||
|
const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.dimensions.width ?? 0) / 2
|
||||||
|
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.dimensions.height ?? 0
|
||||||
|
const fromX = (fromNode.computedPosition?.x ?? 0) + fromHandleX
|
||||||
|
const fromY = (fromNode.computedPosition?.y ?? 0) + fromHandleY
|
||||||
|
const fromPosition = fromHandle?.position
|
||||||
|
const toHandle =
|
||||||
|
(targetNode &&
|
||||||
|
connectionEndHandle.value?.handleId &&
|
||||||
|
((connectionMode.value === ConnectionMode.Strict
|
||||||
|
? targetNode.handleBounds[handleType === 'source' ? 'target' : 'source']?.find(
|
||||||
|
(d) => d.id === connectionEndHandle.value?.handleId,
|
||||||
|
)
|
||||||
|
: [...(targetNode.handleBounds.source || []), ...(targetNode.handleBounds.target || [])]?.find(
|
||||||
|
(d) => d.id === connectionEndHandle.value?.handleId,
|
||||||
|
)) ||
|
||||||
|
targetNode.handleBounds[handleType ?? 'target']?.[0])) ||
|
||||||
|
null
|
||||||
|
|
||||||
|
const toPosition = fromPosition ? oppositePosition[fromPosition] : null
|
||||||
|
|
||||||
|
if (!fromPosition || !toPosition) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = connectionLineType.value ?? connectionLineOptions.value.type
|
||||||
|
|
||||||
|
let dAttr = ''
|
||||||
|
|
||||||
|
const pathParams = {
|
||||||
|
sourceX: fromX,
|
||||||
|
sourceY: fromY,
|
||||||
|
sourcePosition: fromPosition,
|
||||||
|
targetX: toX,
|
||||||
|
targetY: toY,
|
||||||
|
targetPosition: toPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === ConnectionLineType.Bezier) {
|
||||||
|
// we assume the destination position is opposite to the source position
|
||||||
|
;[dAttr] = getBezierPath(pathParams)
|
||||||
|
} else if (type === ConnectionLineType.Step) {
|
||||||
|
;[dAttr] = getSmoothStepPath({
|
||||||
|
...pathParams,
|
||||||
|
borderRadius: 0,
|
||||||
|
})
|
||||||
|
} else if (type === ConnectionLineType.SmoothStep) {
|
||||||
|
;[dAttr] = getSmoothStepPath(pathParams)
|
||||||
|
} else if (type === ConnectionLineType.SimpleBezier) {
|
||||||
|
;[dAttr] = getSimpleBezierPath(pathParams)
|
||||||
|
} else {
|
||||||
|
dAttr = `M${fromX},${fromY} ${toX},${toY}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(
|
||||||
|
'svg',
|
||||||
|
{ class: 'vue-flow__edges vue-flow__connectionline vue-flow__container' },
|
||||||
|
h(
|
||||||
|
'g',
|
||||||
|
{ class: 'vue-flow__connection' },
|
||||||
|
connectionLineComponent
|
||||||
|
? h(connectionLineComponent, {
|
||||||
|
sourceX: fromX,
|
||||||
|
sourceY: fromY,
|
||||||
|
sourcePosition: fromPosition,
|
||||||
|
targetX: toX,
|
||||||
|
targetY: toY,
|
||||||
|
targetPosition: toPosition,
|
||||||
|
sourceNode: fromNode,
|
||||||
|
sourceHandle: fromHandle,
|
||||||
|
targetNode,
|
||||||
|
targetHandle: toHandle,
|
||||||
|
markerEnd: `url(#${getMarkerId(connectionLineOptions.value.markerEnd)})`,
|
||||||
|
markerStart: `url(#${getMarkerId(connectionLineOptions.value.markerStart)})`,
|
||||||
|
connectionStatus: connectionStatus.value,
|
||||||
|
})
|
||||||
|
: h('path', {
|
||||||
|
'd': dAttr,
|
||||||
|
'class': [connectionLineOptions.value.class, connectionStatus, 'vue-flow__connection-path'],
|
||||||
|
'style': connectionLineStyle.value || connectionLineOptions.value.style,
|
||||||
|
'marker-end': `url(#${getMarkerId(connectionLineOptions.value.markerEnd)})`,
|
||||||
|
'marker-start': `url(#${getMarkerId(connectionLineOptions.value.markerStart)})`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default ConnectionLine
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import { getCurrentInstance, inject, resolveComponent } from 'vue'
|
import { getCurrentInstance, inject, resolveComponent } from 'vue'
|
||||||
import { controlledComputed } from '@vueuse/core'
|
import { controlledComputed } from '@vueuse/core'
|
||||||
import EdgeWrapper from '../../components/Edges/EdgeWrapper'
|
import EdgeWrapper from '../../components/Edges/EdgeWrapper'
|
||||||
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
|
import ConnectionLine from '../../components/ConnectionLine'
|
||||||
import type { EdgeComponent, EdgeUpdatable, GraphEdge } from '../../types'
|
import type { EdgeComponent, EdgeUpdatable, GraphEdge } from '../../types'
|
||||||
import { Slots } from '../../context'
|
import { Slots } from '../../context'
|
||||||
import { useVueFlow } from '../../composables'
|
import { useVueFlow } from '../../composables'
|
||||||
@@ -12,8 +12,6 @@ import MarkerDefinitions from './MarkerDefinitions.vue'
|
|||||||
const slots = inject(Slots)
|
const slots = inject(Slots)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
connectionStartHandle,
|
|
||||||
nodesConnectable,
|
|
||||||
edgesUpdatable,
|
edgesUpdatable,
|
||||||
edgesFocusable,
|
edgesFocusable,
|
||||||
elementsSelectable,
|
elementsSelectable,
|
||||||
@@ -28,28 +26,6 @@ const {
|
|||||||
emits,
|
emits,
|
||||||
} = useVueFlow()
|
} = useVueFlow()
|
||||||
|
|
||||||
const sourceNode = controlledComputed(
|
|
||||||
() => connectionStartHandle.value?.nodeId,
|
|
||||||
() => {
|
|
||||||
if (connectionStartHandle.value?.nodeId) {
|
|
||||||
return findNode(connectionStartHandle.value.nodeId)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const connectionLineVisible = controlledComputed(
|
|
||||||
() => connectionStartHandle.value?.nodeId,
|
|
||||||
() =>
|
|
||||||
!!(
|
|
||||||
sourceNode.value &&
|
|
||||||
(typeof sourceNode.value.connectable === 'undefined' ? nodesConnectable.value : sourceNode.value.connectable) &&
|
|
||||||
connectionStartHandle.value?.nodeId &&
|
|
||||||
connectionStartHandle.value?.type
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const groups = controlledComputed(
|
const groups = controlledComputed(
|
||||||
[
|
[
|
||||||
() => edges.value.map((e) => e.zIndex),
|
() => edges.value.map((e) => e.zIndex),
|
||||||
@@ -127,8 +103,6 @@ export default {
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<svg v-if="connectionLineVisible && !!sourceNode" class="vue-flow__edges vue-flow__connectionline vue-flow__container">
|
<ConnectionLine />
|
||||||
<ConnectionLine :source-node="sourceNode" />
|
|
||||||
</svg>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user