feat: Add SimpleBezierEdge
* Implement base edge
This commit is contained in:
@@ -10,7 +10,7 @@ const {
|
|||||||
zoomOnPinch,
|
zoomOnPinch,
|
||||||
panOnScroll,
|
panOnScroll,
|
||||||
panOnScrollMode,
|
panOnScrollMode,
|
||||||
paneMovable,
|
panOnDrag,
|
||||||
onConnect,
|
onConnect,
|
||||||
onNodeDragStart,
|
onNodeDragStart,
|
||||||
onNodeDragStop,
|
onNodeDragStop,
|
||||||
@@ -102,7 +102,7 @@ onMoveEnd((flowTransform) => console.log('move end', flowTransform))
|
|||||||
<div>
|
<div>
|
||||||
<label for="panemoveable">
|
<label for="panemoveable">
|
||||||
paneMovable
|
paneMovable
|
||||||
<input id="panemoveable" v-model="paneMovable" type="checkbox" class="vue-flow__panemoveable" />
|
<input id="panemoveable" v-model="panOnDrag" type="checkbox" class="vue-flow__panemoveable" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Position } from '../../types/flow'
|
||||||
|
import type { EdgeProps } from '../../types/edge'
|
||||||
|
import { getSimpleBezierPath, getSimpleBezierCenter } from './utils'
|
||||||
|
import BaseEdge from './BaseEdge.vue'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<EdgeProps>(), {
|
||||||
|
selected: false,
|
||||||
|
sourcePosition: 'bottom' as Position,
|
||||||
|
targetPosition: 'top' as Position,
|
||||||
|
labelStyle: () => ({}),
|
||||||
|
label: () => '',
|
||||||
|
labelShowBg: true,
|
||||||
|
labelBgStyle: () => ({}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const centered = computed(() =>
|
||||||
|
getSimpleBezierPath({
|
||||||
|
sourceX: props.sourceX,
|
||||||
|
sourceY: props.sourceY,
|
||||||
|
targetX: props.targetX,
|
||||||
|
targetY: props.targetY,
|
||||||
|
sourcePosition: props.sourcePosition,
|
||||||
|
targetPosition: props.targetPosition,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const path = computed(() => {
|
||||||
|
if (props.sourceX && props.sourceY)
|
||||||
|
return getSimpleBezierCenter({
|
||||||
|
sourceX: props.sourceX,
|
||||||
|
sourceY: props.sourceY,
|
||||||
|
targetX: props.targetX,
|
||||||
|
targetY: props.targetY,
|
||||||
|
sourcePosition: props.sourcePosition,
|
||||||
|
targetPosition: props.targetPosition,
|
||||||
|
})
|
||||||
|
else return ''
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'SimpleBezierEdge',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<BaseEdge
|
||||||
|
:path="path"
|
||||||
|
:center-x="centered[0]"
|
||||||
|
:center-y="centered[1]"
|
||||||
|
:label="props.label"
|
||||||
|
:label-style="props.labelStyle"
|
||||||
|
:label-show-bg="props.labelShowBg"
|
||||||
|
:label-bg-style="props.labelBgStyle"
|
||||||
|
:label-bg-padding="props.labelBgPadding"
|
||||||
|
:label-bg-border-radius="props.labelBgBorderRadius"
|
||||||
|
:style="props.style"
|
||||||
|
:marker-end="props.markerEnd"
|
||||||
|
:marker-start="props.markerStart"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import { Position } from '../../types'
|
import { Position } from '../../types'
|
||||||
import type { SmoothStepEdgeProps } from '../../types/edge'
|
import type { SmoothStepEdgeProps } from '../../types/edge'
|
||||||
import { getCenter, getSmoothStepPath } from './utils'
|
import { getCenter, getSmoothStepPath } from './utils'
|
||||||
import EdgeText from './EdgeText.vue'
|
import BaseEdge from './BaseEdge.vue'
|
||||||
|
|
||||||
const props = withDefaults(defineProps<SmoothStepEdgeProps>(), {
|
const props = withDefaults(defineProps<SmoothStepEdgeProps>(), {
|
||||||
selected: false,
|
selected: false,
|
||||||
@@ -16,14 +16,25 @@ const props = withDefaults(defineProps<SmoothStepEdgeProps>(), {
|
|||||||
|
|
||||||
const centered = computed(() =>
|
const centered = computed(() =>
|
||||||
getCenter({
|
getCenter({
|
||||||
...props,
|
sourceX: props.sourceX,
|
||||||
|
sourceY: props.sourceY,
|
||||||
|
targetX: props.targetX,
|
||||||
|
targetY: props.targetY,
|
||||||
|
sourcePosition: props.sourcePosition,
|
||||||
|
targetPosition: props.targetPosition,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const path = computed(() => {
|
const path = computed(() => {
|
||||||
if (props.sourceX && props.sourceY)
|
if (props.sourceX && props.sourceY)
|
||||||
return getSmoothStepPath({
|
return getSmoothStepPath({
|
||||||
...props,
|
sourceX: props.sourceX,
|
||||||
|
sourceY: props.sourceY,
|
||||||
|
targetX: props.targetX,
|
||||||
|
targetY: props.targetY,
|
||||||
|
sourcePosition: props.sourcePosition,
|
||||||
|
targetPosition: props.targetPosition,
|
||||||
|
borderRadius: props.borderRadius,
|
||||||
})
|
})
|
||||||
else return ''
|
else return ''
|
||||||
})
|
})
|
||||||
@@ -31,28 +42,21 @@ const path = computed(() => {
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'SmoothStepEdge',
|
name: 'SmoothStepEdge',
|
||||||
inheritAttrs: false,
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<path
|
<BaseEdge
|
||||||
class="vue-flow__edge-path"
|
:path="path"
|
||||||
|
:center-x="centered[0]"
|
||||||
|
:center-y="centered[1]"
|
||||||
|
:label="props.label"
|
||||||
|
:label-style="props.labelStyle"
|
||||||
|
:label-show-bg="props.labelShowBg"
|
||||||
|
:label-bg-style="props.labelBgStyle"
|
||||||
|
:label-bg-padding="props.labelBgPadding"
|
||||||
|
:label-bg-border-radius="props.labelBgBorderRadius"
|
||||||
:style="props.style"
|
:style="props.style"
|
||||||
:d="path"
|
|
||||||
:marker-end="props.markerEnd"
|
:marker-end="props.markerEnd"
|
||||||
:marker-start="props.markerStart"
|
:marker-start="props.markerStart"
|
||||||
/>
|
/>
|
||||||
<slot :x="centered[0]" :y="centered[1]" v-bind="props">
|
|
||||||
<EdgeText
|
|
||||||
v-if="props.label"
|
|
||||||
:x="centered[0]"
|
|
||||||
:y="centered[1]"
|
|
||||||
:label="props.label"
|
|
||||||
:label-style="props.labelStyle"
|
|
||||||
:label-show-bg="props.labelShowBg"
|
|
||||||
:label-bg-style="props.labelBgStyle"
|
|
||||||
:label-bg-padding="props.labelBgPadding"
|
|
||||||
:label-bg-border-radius="props.labelBgBorderRadius"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -19,7 +19,5 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<SmoothStepEdge v-bind="props" :border-radius="0">
|
<SmoothStepEdge v-bind="props" :border-radius="0" />
|
||||||
<slot />
|
|
||||||
</SmoothStepEdge>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Position } from '../../types'
|
import { Position } from '../../types'
|
||||||
import type { EdgeProps } from '../../types/edge'
|
import type { EdgeProps } from '../../types/edge'
|
||||||
import EdgeText from './EdgeText.vue'
|
import BaseEdge from './BaseEdge.vue'
|
||||||
import { getBezierPath } from './utils'
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<EdgeProps>(), {
|
const props = withDefaults(defineProps<EdgeProps>(), {
|
||||||
selected: false,
|
selected: false,
|
||||||
@@ -23,39 +22,26 @@ const centerX = computed(() => {
|
|||||||
return props.targetX < props.sourceX ? props.targetX + xOffset : props.targetX - xOffset
|
return props.targetX < props.sourceX ? props.targetX + xOffset : props.targetX - xOffset
|
||||||
})
|
})
|
||||||
|
|
||||||
const path = computed(() => {
|
const path = computed(() => `M ${props.sourceX},${props.sourceY}L ${props.targetX},${props.targetY}`)
|
||||||
if (props.sourceX && props.sourceY)
|
|
||||||
return getBezierPath({
|
|
||||||
...props,
|
|
||||||
})
|
|
||||||
else return ''
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
name: 'StraightEdge',
|
name: 'StraightEdge',
|
||||||
inheritAttrs: false,
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<path
|
<BaseEdge
|
||||||
class="vue-flow__edge-path"
|
:path="path"
|
||||||
|
:center-x="centered[0]"
|
||||||
|
:center-y="centered[1]"
|
||||||
|
:label="props.label"
|
||||||
|
:label-style="props.labelStyle"
|
||||||
|
:label-show-bg="props.labelShowBg"
|
||||||
|
:label-bg-style="props.labelBgStyle"
|
||||||
|
:label-bg-padding="props.labelBgPadding"
|
||||||
|
:label-bg-border-radius="props.labelBgBorderRadius"
|
||||||
:style="props.style"
|
:style="props.style"
|
||||||
:d="path"
|
|
||||||
:marker-end="props.markerEnd"
|
:marker-end="props.markerEnd"
|
||||||
:marker-start="props.markerStart"
|
:marker-start="props.markerStart"
|
||||||
/>
|
/>
|
||||||
<slot :x="centerX" :y="centerY" v-bind="props">
|
|
||||||
<EdgeText
|
|
||||||
v-if="props.label"
|
|
||||||
:x="centerX"
|
|
||||||
:y="centerY"
|
|
||||||
:label="props.label"
|
|
||||||
:label-style="props.labelStyle"
|
|
||||||
:label-show-bg="props.labelShowBg"
|
|
||||||
:label-bg-style="props.labelBgStyle"
|
|
||||||
:label-bg-padding="props.labelBgPadding"
|
|
||||||
:label-bg-border-radius="props.labelBgBorderRadius"
|
|
||||||
/>
|
|
||||||
</slot>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { default as BezierEdge } from './BezierEdge.vue'
|
export { default as BezierEdge } from './BezierEdge.vue'
|
||||||
|
export { default as SimpleBezierEdge } from './SimpleBezierEdge.vue'
|
||||||
export { default as StepEdge } from './StepEdge.vue'
|
export { default as StepEdge } from './StepEdge.vue'
|
||||||
export { default as SmoothStepEdge } from './SmoothStepEdge.vue'
|
export { default as SmoothStepEdge } from './SmoothStepEdge.vue'
|
||||||
export { default as StraightEdge } from './StraightEdge.vue'
|
export { default as StraightEdge } from './StraightEdge.vue'
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { Position } from '~/types'
|
||||||
|
|
||||||
|
interface GetControlWithCurvatureParams {
|
||||||
|
pos: Position
|
||||||
|
x1: number
|
||||||
|
y1: number
|
||||||
|
x2: number
|
||||||
|
y2: number
|
||||||
|
c: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetBezierPathParams {
|
||||||
|
sourceX: number
|
||||||
|
sourceY: number
|
||||||
|
sourcePosition?: Position
|
||||||
|
targetX: number
|
||||||
|
targetY: number
|
||||||
|
targetPosition?: Position
|
||||||
|
curvature?: number
|
||||||
|
centerX?: number
|
||||||
|
centerY?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateControlOffset(distance: number, curvature: number): number {
|
||||||
|
if (distance >= 0) {
|
||||||
|
return 0.5 * distance
|
||||||
|
} else {
|
||||||
|
return curvature * 25 * Math.sqrt(-distance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurvatureParams): [number, number] {
|
||||||
|
let ctX: number, ctY: number
|
||||||
|
switch (pos) {
|
||||||
|
case Position.Left:
|
||||||
|
ctX = x1 - calculateControlOffset(x1 - x2, c)
|
||||||
|
ctY = y1
|
||||||
|
break
|
||||||
|
case Position.Right:
|
||||||
|
ctX = x1 + calculateControlOffset(x2 - x1, c)
|
||||||
|
ctY = y1
|
||||||
|
break
|
||||||
|
case Position.Top:
|
||||||
|
ctX = x1
|
||||||
|
ctY = y1 - calculateControlOffset(y1 - y2, c)
|
||||||
|
break
|
||||||
|
case Position.Bottom:
|
||||||
|
ctX = x1
|
||||||
|
ctY = y1 + calculateControlOffset(y2 - y1, c)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return [ctX, ctY]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBezierPath({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourcePosition = Position.Bottom,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
targetPosition = Position.Top,
|
||||||
|
curvature = 0.25,
|
||||||
|
}: GetBezierPathParams): string {
|
||||||
|
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
||||||
|
pos: sourcePosition,
|
||||||
|
x1: sourceX,
|
||||||
|
y1: sourceY,
|
||||||
|
x2: targetX,
|
||||||
|
y2: targetY,
|
||||||
|
c: curvature,
|
||||||
|
})
|
||||||
|
const [targetControlX, targetControlY] = getControlWithCurvature({
|
||||||
|
pos: targetPosition,
|
||||||
|
x1: targetX,
|
||||||
|
y1: targetY,
|
||||||
|
x2: sourceX,
|
||||||
|
y2: sourceY,
|
||||||
|
c: curvature,
|
||||||
|
})
|
||||||
|
return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBezierCenter({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourcePosition = Position.Bottom,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
targetPosition = Position.Top,
|
||||||
|
curvature = 0.25,
|
||||||
|
}: GetBezierPathParams): [number, number, number, number] {
|
||||||
|
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
||||||
|
pos: sourcePosition,
|
||||||
|
x1: sourceX,
|
||||||
|
y1: sourceY,
|
||||||
|
x2: targetX,
|
||||||
|
y2: targetY,
|
||||||
|
c: curvature,
|
||||||
|
})
|
||||||
|
const [targetControlX, targetControlY] = getControlWithCurvature({
|
||||||
|
pos: targetPosition,
|
||||||
|
x1: targetX,
|
||||||
|
y1: targetY,
|
||||||
|
x2: sourceX,
|
||||||
|
y2: sourceY,
|
||||||
|
c: curvature,
|
||||||
|
})
|
||||||
|
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
||||||
|
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
||||||
|
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125
|
||||||
|
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125
|
||||||
|
const xOffset = Math.abs(centerX - sourceX)
|
||||||
|
const yOffset = Math.abs(centerY - sourceY)
|
||||||
|
return [centerX, centerY, xOffset, yOffset]
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Position } from '~/types'
|
||||||
|
|
||||||
|
export interface GetCenterParams {
|
||||||
|
sourceX: number
|
||||||
|
sourceY: number
|
||||||
|
targetX: number
|
||||||
|
targetY: number
|
||||||
|
sourcePosition?: Position
|
||||||
|
targetPosition?: Position
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeftOrRight = [Position.Left, Position.Right]
|
||||||
|
|
||||||
|
export const getCenter = ({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
sourcePosition = Position.Bottom,
|
||||||
|
targetPosition = Position.Top,
|
||||||
|
}: GetCenterParams): [number, number, number, number] => {
|
||||||
|
const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition)
|
||||||
|
const targetIsLeftOrRight = LeftOrRight.includes(targetPosition)
|
||||||
|
|
||||||
|
// we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom)
|
||||||
|
// a mixed edge is when one the source is on the left and the target is on the top for example.
|
||||||
|
const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight)
|
||||||
|
|
||||||
|
if (mixedEdge) {
|
||||||
|
const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0
|
||||||
|
const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset
|
||||||
|
|
||||||
|
const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY)
|
||||||
|
const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset
|
||||||
|
|
||||||
|
return [centerX, centerY, xOffset, yOffset]
|
||||||
|
}
|
||||||
|
|
||||||
|
const xOffset = Math.abs(targetX - sourceX) / 2
|
||||||
|
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset
|
||||||
|
|
||||||
|
const yOffset = Math.abs(targetY - sourceY) / 2
|
||||||
|
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset
|
||||||
|
|
||||||
|
return [centerX, centerY, xOffset, yOffset]
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './bezier'
|
||||||
|
export * from './simple-bezier'
|
||||||
|
export * from './smoothstep'
|
||||||
|
export * from './general'
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { Position } from '~/types'
|
||||||
|
|
||||||
|
export interface GetSimpleBezierPathParams {
|
||||||
|
sourceX: number
|
||||||
|
sourceY: number
|
||||||
|
sourcePosition?: Position
|
||||||
|
targetX: number
|
||||||
|
targetY: number
|
||||||
|
targetPosition?: Position
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetControlParams {
|
||||||
|
pos: Position
|
||||||
|
x1: number
|
||||||
|
y1: number
|
||||||
|
x2: number
|
||||||
|
y2: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function getControl({ pos, x1, y1, x2, y2 }: GetControlParams): [number, number] {
|
||||||
|
let ctX: number, ctY: number
|
||||||
|
switch (pos) {
|
||||||
|
case Position.Left:
|
||||||
|
case Position.Right:
|
||||||
|
ctX = 0.5 * (x1 + x2)
|
||||||
|
ctY = y1
|
||||||
|
break
|
||||||
|
case Position.Top:
|
||||||
|
case Position.Bottom:
|
||||||
|
ctX = x1
|
||||||
|
ctY = 0.5 * (y1 + y2)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return [ctX, ctY]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSimpleBezierPath({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourcePosition = Position.Bottom,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
targetPosition = Position.Top,
|
||||||
|
}: GetSimpleBezierPathParams): string {
|
||||||
|
const [sourceControlX, sourceControlY] = getControl({
|
||||||
|
pos: sourcePosition,
|
||||||
|
x1: sourceX,
|
||||||
|
y1: sourceY,
|
||||||
|
x2: targetX,
|
||||||
|
y2: targetY,
|
||||||
|
})
|
||||||
|
const [targetControlX, targetControlY] = getControl({
|
||||||
|
pos: targetPosition,
|
||||||
|
x1: targetX,
|
||||||
|
y1: targetY,
|
||||||
|
x2: sourceX,
|
||||||
|
y2: sourceY,
|
||||||
|
})
|
||||||
|
return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO: this function will recalculate the control points
|
||||||
|
// one option is to let getXXXPath() return center points
|
||||||
|
// but will introduce breaking changes
|
||||||
|
// the getCenter() of other types of edges might need to change, too
|
||||||
|
export function getSimpleBezierCenter({
|
||||||
|
sourceX,
|
||||||
|
sourceY,
|
||||||
|
sourcePosition = Position.Bottom,
|
||||||
|
targetX,
|
||||||
|
targetY,
|
||||||
|
targetPosition = Position.Top,
|
||||||
|
}: GetSimpleBezierPathParams): [number, number, number, number] {
|
||||||
|
const [sourceControlX, sourceControlY] = getControl({
|
||||||
|
pos: sourcePosition,
|
||||||
|
x1: sourceX,
|
||||||
|
y1: sourceY,
|
||||||
|
x2: targetX,
|
||||||
|
y2: targetY,
|
||||||
|
})
|
||||||
|
const [targetControlX, targetControlY] = getControl({
|
||||||
|
pos: targetPosition,
|
||||||
|
x1: targetX,
|
||||||
|
y1: targetY,
|
||||||
|
x2: sourceX,
|
||||||
|
y2: sourceY,
|
||||||
|
})
|
||||||
|
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
||||||
|
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
||||||
|
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125
|
||||||
|
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125
|
||||||
|
const xOffset = Math.abs(centerX - sourceX)
|
||||||
|
const yOffset = Math.abs(centerY - sourceY)
|
||||||
|
return [centerX, centerY, xOffset, yOffset]
|
||||||
|
}
|
||||||
@@ -1,132 +1,16 @@
|
|||||||
import { GetBezierPathParams, GetCenterParams, GetControlWithCurvatureParams, GetSmoothStepPathParams, Position } from '~/types'
|
import { getCenter } from './general'
|
||||||
|
import { Position } from '~/types'
|
||||||
|
|
||||||
const LeftOrRight = [Position.Left, Position.Right]
|
export interface GetSmoothStepPathParams {
|
||||||
|
sourceX: number
|
||||||
export const getCenter = ({
|
sourceY: number
|
||||||
sourceX,
|
sourcePosition?: Position
|
||||||
sourceY,
|
targetX: number
|
||||||
targetX,
|
targetY: number
|
||||||
targetY,
|
targetPosition?: Position
|
||||||
sourcePosition = Position.Bottom,
|
borderRadius?: number
|
||||||
targetPosition = Position.Top,
|
centerX?: number
|
||||||
}: GetCenterParams): [number, number, number, number] => {
|
centerY?: number
|
||||||
const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition)
|
|
||||||
const targetIsLeftOrRight = LeftOrRight.includes(targetPosition)
|
|
||||||
|
|
||||||
// we expect flows to be horizontal or vertical (all handles left or right respectively top or bottom)
|
|
||||||
// a mixed edge is when one the source is on the left and the target is on the top for example.
|
|
||||||
const mixedEdge = (sourceIsLeftOrRight && !targetIsLeftOrRight) || (targetIsLeftOrRight && !sourceIsLeftOrRight)
|
|
||||||
|
|
||||||
if (mixedEdge) {
|
|
||||||
const xOffset = sourceIsLeftOrRight ? Math.abs(targetX - sourceX) : 0
|
|
||||||
const centerX = sourceX > targetX ? sourceX - xOffset : sourceX + xOffset
|
|
||||||
|
|
||||||
const yOffset = sourceIsLeftOrRight ? 0 : Math.abs(targetY - sourceY)
|
|
||||||
const centerY = sourceY < targetY ? sourceY + yOffset : sourceY - yOffset
|
|
||||||
|
|
||||||
return [centerX, centerY, xOffset, yOffset]
|
|
||||||
}
|
|
||||||
|
|
||||||
const xOffset = Math.abs(targetX - sourceX) / 2
|
|
||||||
const centerX = targetX < sourceX ? targetX + xOffset : targetX - xOffset
|
|
||||||
|
|
||||||
const yOffset = Math.abs(targetY - sourceY) / 2
|
|
||||||
const centerY = targetY < sourceY ? targetY + yOffset : targetY - yOffset
|
|
||||||
|
|
||||||
return [centerX, centerY, xOffset, yOffset]
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateControlOffset(distance: number, curvature: number): number {
|
|
||||||
if (distance >= 0) {
|
|
||||||
return 0.5 * distance
|
|
||||||
} else {
|
|
||||||
return curvature * 25 * Math.sqrt(-distance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurvatureParams): [number, number] {
|
|
||||||
let ctX: number, ctY: number
|
|
||||||
switch (pos) {
|
|
||||||
case Position.Left:
|
|
||||||
ctX = x1 - calculateControlOffset(x1 - x2, c)
|
|
||||||
ctY = y1
|
|
||||||
break
|
|
||||||
case Position.Right:
|
|
||||||
ctX = x1 + calculateControlOffset(x2 - x1, c)
|
|
||||||
ctY = y1
|
|
||||||
break
|
|
||||||
case Position.Top:
|
|
||||||
ctX = x1
|
|
||||||
ctY = y1 - calculateControlOffset(y1 - y2, c)
|
|
||||||
break
|
|
||||||
case Position.Bottom:
|
|
||||||
ctX = x1
|
|
||||||
ctY = y1 + calculateControlOffset(y2 - y1, c)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return [ctX, ctY]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBezierPath({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
sourcePosition = Position.Bottom,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
targetPosition = Position.Top,
|
|
||||||
curvature = 0.25,
|
|
||||||
}: GetBezierPathParams): string {
|
|
||||||
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
|
||||||
pos: sourcePosition,
|
|
||||||
x1: sourceX,
|
|
||||||
y1: sourceY,
|
|
||||||
x2: targetX,
|
|
||||||
y2: targetY,
|
|
||||||
c: curvature,
|
|
||||||
})
|
|
||||||
const [targetControlX, targetControlY] = getControlWithCurvature({
|
|
||||||
pos: targetPosition,
|
|
||||||
x1: targetX,
|
|
||||||
y1: targetY,
|
|
||||||
x2: sourceX,
|
|
||||||
y2: sourceY,
|
|
||||||
c: curvature,
|
|
||||||
})
|
|
||||||
return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBezierCenter({
|
|
||||||
sourceX,
|
|
||||||
sourceY,
|
|
||||||
sourcePosition = Position.Bottom,
|
|
||||||
targetX,
|
|
||||||
targetY,
|
|
||||||
targetPosition = Position.Top,
|
|
||||||
curvature = 0.25,
|
|
||||||
}: GetBezierPathParams): [number, number, number, number] {
|
|
||||||
const [sourceControlX, sourceControlY] = getControlWithCurvature({
|
|
||||||
pos: sourcePosition,
|
|
||||||
x1: sourceX,
|
|
||||||
y1: sourceY,
|
|
||||||
x2: targetX,
|
|
||||||
y2: targetY,
|
|
||||||
c: curvature,
|
|
||||||
})
|
|
||||||
const [targetControlX, targetControlY] = getControlWithCurvature({
|
|
||||||
pos: targetPosition,
|
|
||||||
x1: targetX,
|
|
||||||
y1: targetY,
|
|
||||||
x2: sourceX,
|
|
||||||
y2: sourceY,
|
|
||||||
c: curvature,
|
|
||||||
})
|
|
||||||
// cubic bezier t=0.5 mid point, not the actual mid point, but easy to calculate
|
|
||||||
// https://stackoverflow.com/questions/67516101/how-to-find-distance-mid-point-of-bezier-curve
|
|
||||||
const centerX = sourceX * 0.125 + sourceControlX * 0.375 + targetControlX * 0.375 + targetX * 0.125
|
|
||||||
const centerY = sourceY * 0.125 + sourceControlY * 0.375 + targetControlY * 0.375 + targetY * 0.125
|
|
||||||
const xOffset = Math.abs(centerX - sourceX)
|
|
||||||
const yOffset = Math.abs(centerY - sourceY)
|
|
||||||
return [centerX, centerY, xOffset, yOffset]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are some helper methods for drawing the round corners
|
// These are some helper methods for drawing the round corners
|
||||||
+13
-1
@@ -1,7 +1,19 @@
|
|||||||
export { default as VueFlow } from './container/VueFlow/VueFlow.vue'
|
export { default as VueFlow } from './container/VueFlow/VueFlow.vue'
|
||||||
export { default as Handle } from './components/Handle/Handle.vue'
|
export { default as Handle } from './components/Handle/Handle.vue'
|
||||||
export { default as EdgeText } from './components/Edges/EdgeText.vue'
|
export { default as EdgeText } from './components/Edges/EdgeText.vue'
|
||||||
export { getBezierPath, getSmoothStepPath, getCenter as getEdgeCenter, getBezierCenter } from './components/Edges/utils'
|
export { default as StraightEdge } from './components/Edges/StraightEdge.vue'
|
||||||
|
export { default as StepEdge } from './components/Edges/StepEdge.vue'
|
||||||
|
export { default as BezierEdge } from './components/Edges/BezierEdge.vue'
|
||||||
|
export { default as SimpleBezierEdge } from './components/Edges/SimpleBezierEdge.vue'
|
||||||
|
export { default as SmoothStepEdge } from './components/Edges/SmoothStepEdge.vue'
|
||||||
|
export {
|
||||||
|
getBezierPath,
|
||||||
|
getBezierCenter,
|
||||||
|
getSimpleBezierPath,
|
||||||
|
getSimpleBezierCenter,
|
||||||
|
getSmoothStepPath,
|
||||||
|
getCenter as getEdgeCenter,
|
||||||
|
} from './components/Edges/utils'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
isNode,
|
isNode,
|
||||||
|
|||||||
+11
-1
@@ -8,7 +8,16 @@ import {
|
|||||||
ConnectionLineType,
|
ConnectionLineType,
|
||||||
FlowOptions,
|
FlowOptions,
|
||||||
} from '~/types'
|
} from '~/types'
|
||||||
import { DefaultNode, InputNode, OutputNode, BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components'
|
import {
|
||||||
|
DefaultNode,
|
||||||
|
InputNode,
|
||||||
|
OutputNode,
|
||||||
|
BezierEdge,
|
||||||
|
SmoothStepEdge,
|
||||||
|
StepEdge,
|
||||||
|
StraightEdge,
|
||||||
|
SimpleBezierEdge,
|
||||||
|
} from '~/components'
|
||||||
|
|
||||||
export const defaultNodeTypes: DefaultNodeTypes = {
|
export const defaultNodeTypes: DefaultNodeTypes = {
|
||||||
input: InputNode,
|
input: InputNode,
|
||||||
@@ -21,6 +30,7 @@ export const defaultEdgeTypes: DefaultEdgeTypes = {
|
|||||||
straight: StraightEdge,
|
straight: StraightEdge,
|
||||||
step: StepEdge,
|
step: StepEdge,
|
||||||
smoothstep: SmoothStepEdge,
|
smoothstep: SmoothStepEdge,
|
||||||
|
simplebezier: SimpleBezierEdge,
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
|
const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export type EdgeComponent<E = any> =
|
|||||||
| DefineComponent<EdgeProps<E>, any, any, any, any, any>
|
| DefineComponent<EdgeProps<E>, any, any, any, any, any>
|
||||||
| GlobalComponentName
|
| GlobalComponentName
|
||||||
|
|
||||||
export type DefaultEdgeTypes = { [key in 'default' | 'straight' | 'smoothstep' | 'step']: EdgeComponent }
|
export type DefaultEdgeTypes = { [key in 'default' | 'straight' | 'smoothstep' | 'step' | 'simplebezier']: EdgeComponent }
|
||||||
export type DefaultNodeTypes = { [key in 'input' | 'output' | 'default']: NodeComponent }
|
export type DefaultNodeTypes = { [key in 'input' | 'output' | 'default']: NodeComponent }
|
||||||
|
|
||||||
export interface BackgroundProps {
|
export interface BackgroundProps {
|
||||||
|
|||||||
@@ -168,45 +168,3 @@ export interface SmoothStepEdgeProps<Data = any> extends EdgeProps<Data> {
|
|||||||
markerEnd?: string
|
markerEnd?: string
|
||||||
borderRadius?: number
|
borderRadius?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetCenterParams {
|
|
||||||
sourceX: number
|
|
||||||
sourceY: number
|
|
||||||
targetX: number
|
|
||||||
targetY: number
|
|
||||||
sourcePosition?: Position
|
|
||||||
targetPosition?: Position
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetBezierPathParams {
|
|
||||||
sourceX: number
|
|
||||||
sourceY: number
|
|
||||||
sourcePosition?: Position
|
|
||||||
targetX: number
|
|
||||||
targetY: number
|
|
||||||
targetPosition?: Position
|
|
||||||
curvature?: number
|
|
||||||
centerX?: number
|
|
||||||
centerY?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetSmoothStepPathParams {
|
|
||||||
sourceX: number
|
|
||||||
sourceY: number
|
|
||||||
sourcePosition?: Position
|
|
||||||
targetX: number
|
|
||||||
targetY: number
|
|
||||||
targetPosition?: Position
|
|
||||||
borderRadius?: number
|
|
||||||
centerX?: number
|
|
||||||
centerY?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetControlWithCurvatureParams {
|
|
||||||
pos: Position
|
|
||||||
x1: number
|
|
||||||
y1: number
|
|
||||||
x2: number
|
|
||||||
y2: number
|
|
||||||
c: number
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user