fix(core): correct connection line calculation

This commit is contained in:
braks
2023-06-15 19:48:37 +02:00
committed by Braks
parent c4c9283f3a
commit 33b6fe121a
3 changed files with 156 additions and 196 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -2,7 +2,7 @@
import { getCurrentInstance, inject, resolveComponent } from 'vue'
import { controlledComputed } from '@vueuse/core'
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 { Slots } from '../../context'
import { useVueFlow } from '../../composables'
@@ -12,8 +12,6 @@ import MarkerDefinitions from './MarkerDefinitions.vue'
const slots = inject(Slots)
const {
connectionStartHandle,
nodesConnectable,
edgesUpdatable,
edgesFocusable,
elementsSelectable,
@@ -28,28 +26,6 @@ const {
emits,
} = 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(
[
() => edges.value.map((e) => e.zIndex),
@@ -127,8 +103,6 @@ export default {
</g>
</svg>
<svg v-if="connectionLineVisible && !!sourceNode" class="vue-flow__edges vue-flow__connectionline vue-flow__container">
<ConnectionLine :source-node="sourceNode" />
</svg>
<ConnectionLine />
</template>
</template>