diff --git a/examples/Interaction/InteractionExample.vue b/examples/Interaction/InteractionExample.vue index 69b1a6ad..4685428b 100644 --- a/examples/Interaction/InteractionExample.vue +++ b/examples/Interaction/InteractionExample.vue @@ -10,7 +10,7 @@ const { zoomOnPinch, panOnScroll, panOnScrollMode, - paneMovable, + panOnDrag, onConnect, onNodeDragStart, onNodeDragStop, @@ -102,7 +102,7 @@ onMoveEnd((flowTransform) => console.log('move end', flowTransform))
diff --git a/src/components/Edges/SimpleBezierEdge.vue b/src/components/Edges/SimpleBezierEdge.vue new file mode 100644 index 00000000..130c95a8 --- /dev/null +++ b/src/components/Edges/SimpleBezierEdge.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/components/Edges/SmoothStepEdge.vue b/src/components/Edges/SmoothStepEdge.vue index 433cf58d..e735ce95 100644 --- a/src/components/Edges/SmoothStepEdge.vue +++ b/src/components/Edges/SmoothStepEdge.vue @@ -2,7 +2,7 @@ import { Position } from '../../types' import type { SmoothStepEdgeProps } from '../../types/edge' import { getCenter, getSmoothStepPath } from './utils' -import EdgeText from './EdgeText.vue' +import BaseEdge from './BaseEdge.vue' const props = withDefaults(defineProps(), { selected: false, @@ -16,14 +16,25 @@ const props = withDefaults(defineProps(), { const centered = computed(() => getCenter({ - ...props, + 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 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 '' }) @@ -31,28 +42,21 @@ const path = computed(() => { diff --git a/src/components/Edges/StepEdge.vue b/src/components/Edges/StepEdge.vue index 945dc21e..6d4f7a5b 100644 --- a/src/components/Edges/StepEdge.vue +++ b/src/components/Edges/StepEdge.vue @@ -19,7 +19,5 @@ export default { } diff --git a/src/components/Edges/StraightEdge.vue b/src/components/Edges/StraightEdge.vue index 655a28db..345c3aec 100644 --- a/src/components/Edges/StraightEdge.vue +++ b/src/components/Edges/StraightEdge.vue @@ -1,8 +1,7 @@ diff --git a/src/components/Edges/index.ts b/src/components/Edges/index.ts index 20a92f54..23c09f7f 100644 --- a/src/components/Edges/index.ts +++ b/src/components/Edges/index.ts @@ -1,4 +1,5 @@ export { default as BezierEdge } from './BezierEdge.vue' +export { default as SimpleBezierEdge } from './SimpleBezierEdge.vue' export { default as StepEdge } from './StepEdge.vue' export { default as SmoothStepEdge } from './SmoothStepEdge.vue' export { default as StraightEdge } from './StraightEdge.vue' diff --git a/src/components/Edges/utils/bezier.ts b/src/components/Edges/utils/bezier.ts new file mode 100644 index 00000000..848c9904 --- /dev/null +++ b/src/components/Edges/utils/bezier.ts @@ -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] +} diff --git a/src/components/Edges/utils/general.ts b/src/components/Edges/utils/general.ts new file mode 100644 index 00000000..60a59866 --- /dev/null +++ b/src/components/Edges/utils/general.ts @@ -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] +} diff --git a/src/components/Edges/utils/index.ts b/src/components/Edges/utils/index.ts new file mode 100644 index 00000000..ef856385 --- /dev/null +++ b/src/components/Edges/utils/index.ts @@ -0,0 +1,4 @@ +export * from './bezier' +export * from './simple-bezier' +export * from './smoothstep' +export * from './general' diff --git a/src/components/Edges/utils/simple-bezier.ts b/src/components/Edges/utils/simple-bezier.ts new file mode 100644 index 00000000..6f8aca45 --- /dev/null +++ b/src/components/Edges/utils/simple-bezier.ts @@ -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] +} diff --git a/src/components/Edges/utils.ts b/src/components/Edges/utils/smoothstep.ts similarity index 52% rename from src/components/Edges/utils.ts rename to src/components/Edges/utils/smoothstep.ts index 6cff639a..54225d31 100644 --- a/src/components/Edges/utils.ts +++ b/src/components/Edges/utils/smoothstep.ts @@ -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 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] -} - -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] +export interface GetSmoothStepPathParams { + sourceX: number + sourceY: number + sourcePosition?: Position + targetX: number + targetY: number + targetPosition?: Position + borderRadius?: number + centerX?: number + centerY?: number } // These are some helper methods for drawing the round corners diff --git a/src/index.ts b/src/index.ts index b91f75c4..e0639952 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,19 @@ export { default as VueFlow } from './container/VueFlow/VueFlow.vue' export { default as Handle } from './components/Handle/Handle.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 { isNode, diff --git a/src/store/state.ts b/src/store/state.ts index c190417a..956a6a06 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -8,7 +8,16 @@ import { ConnectionLineType, FlowOptions, } 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 = { input: InputNode, @@ -21,6 +30,7 @@ export const defaultEdgeTypes: DefaultEdgeTypes = { straight: StraightEdge, step: StepEdge, smoothstep: SmoothStepEdge, + simplebezier: SimpleBezierEdge, } const isDef = (val: T): val is NonNullable => typeof val !== 'undefined' diff --git a/src/types/components.ts b/src/types/components.ts index 5184d7db..5d185ba4 100644 --- a/src/types/components.ts +++ b/src/types/components.ts @@ -19,7 +19,7 @@ export type EdgeComponent = | DefineComponent, any, any, any, any, any> | 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 interface BackgroundProps { diff --git a/src/types/edge.ts b/src/types/edge.ts index ad1ec930..f5912449 100644 --- a/src/types/edge.ts +++ b/src/types/edge.ts @@ -168,45 +168,3 @@ export interface SmoothStepEdgeProps extends EdgeProps { markerEnd?: string 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 -}