diff --git a/examples/react/src/examples/EdgeRouting/index.tsx b/examples/react/src/examples/EdgeRouting/index.tsx index 4e78212b..4f1fc97d 100644 --- a/examples/react/src/examples/EdgeRouting/index.tsx +++ b/examples/react/src/examples/EdgeRouting/index.tsx @@ -69,6 +69,55 @@ const nodes: Node[] = [ targetPosition: Position.Bottom, style: { background: 'rgba(255,255,255,0.5)' }, }, + // Bend Position Examples + { + id: '9', + position: { x: 300, y: 0 }, + data: { label: 'Source' }, + sourcePosition: Position.Right, + targetPosition: Position.Right, + style: { background: 'rgba(255,255,255,0.5)' }, + }, + { + id: '10', + position: { x: 600, y: 150 }, + data: { label: 'Target' }, + sourcePosition: Position.Left, + targetPosition: Position.Left, + style: { background: 'rgba(255,255,255,0.5)' }, + }, + { + id: '11', + position: { x: 300, y: 300 }, + data: { label: 'Source' }, + sourcePosition: Position.Right, + targetPosition: Position.Right, + style: { background: 'rgba(255,255,255,0.5)' }, + }, + { + id: '12', + position: { x: 600, y: 450 }, + data: { label: 'Target' }, + sourcePosition: Position.Left, + targetPosition: Position.Left, + style: { background: 'rgba(255,255,255,0.5)' }, + }, + { + id: '13', + position: { x: 300, y: 600 }, + data: { label: 'Source' }, + sourcePosition: Position.Right, + targetPosition: Position.Right, + style: { background: 'rgba(255,255,255,0.5)' }, + }, + { + id: '14', + position: { x: 600, y: 750 }, + data: { label: 'Target' }, + sourcePosition: Position.Left, + targetPosition: Position.Left, + style: { background: 'rgba(255,255,255,0.5)' }, + }, ]; const edges: Edge[] = [ @@ -102,6 +151,38 @@ const edges: Edge[] = [ source: '7', target: '8', }, + + // Bend Position Examples + { + id: 'e9-10', + source: '9', + target: '10', + label: 'bendPosition: 0.2', + pathOptions: { + bendPosition: 0.2, + }, + interactionWidth: 0, + }, + { + id: 'e11-12', + source: '11', + target: '12', + label: 'bendPosition: 0.5 (default)', + pathOptions: { + bendPosition: 0.5, + }, + interactionWidth: 0, + }, + { + id: 'e13-14', + source: '13', + target: '14', + label: 'bendPosition: 0.8', + pathOptions: { + bendPosition: 0.8, + }, + interactionWidth: 0, + }, ]; const defaultEdgeOptions = { diff --git a/packages/react/src/components/Edges/SmoothStepEdge.tsx b/packages/react/src/components/Edges/SmoothStepEdge.tsx index 8b1475cf..45771496 100644 --- a/packages/react/src/components/Edges/SmoothStepEdge.tsx +++ b/packages/react/src/components/Edges/SmoothStepEdge.tsx @@ -36,6 +36,7 @@ function createSmoothStepEdge(params: { isInternal: boolean }) { targetPosition, borderRadius: pathOptions?.borderRadius, offset: pathOptions?.offset, + bendPosition: pathOptions?.bendPosition, }); const _id = params.isInternal ? undefined : id; diff --git a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte index a1cae125..ff47d7f5 100644 --- a/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte +++ b/packages/svelte/src/lib/components/edges/SmoothStepEdge.svelte @@ -30,7 +30,8 @@ sourcePosition, targetPosition, borderRadius: pathOptions?.borderRadius, - offset: pathOptions?.offset + offset: pathOptions?.offset, + bendPosition: pathOptions?.bendPosition, }) ); diff --git a/packages/system/src/types/edges.ts b/packages/system/src/types/edges.ts index c0568200..3acfab99 100644 --- a/packages/system/src/types/edges.ts +++ b/packages/system/src/types/edges.ts @@ -45,6 +45,7 @@ export type EdgeBase< export type SmoothStepPathOptions = { offset?: number; borderRadius?: number; + bendPosition?: number; }; export type StepPathOptions = { diff --git a/packages/system/src/utils/edges/smoothstep-edge.ts b/packages/system/src/utils/edges/smoothstep-edge.ts index b10c5a07..8e90e7c8 100644 --- a/packages/system/src/utils/edges/smoothstep-edge.ts +++ b/packages/system/src/utils/edges/smoothstep-edge.ts @@ -26,6 +26,12 @@ export interface GetSmoothStepPathParams { centerY?: number; /** @default 20 */ offset?: number; + /** + * Controls where the bend occurs along the path. + * 0 = at source, 1 = at target, 0.5 = midpoint + * @default 0.5 + */ + bendPosition?: number; } const handleDirections = { @@ -63,6 +69,7 @@ function getPoints({ targetPosition = Position.Top, center, offset, + bendPosition, }: { source: XYPosition; sourcePosition: Position; @@ -70,6 +77,7 @@ function getPoints({ targetPosition: Position; center: Partial; offset: number; + bendPosition: number; }): [XYPosition[], number, number, number, number] { const sourceDir = handleDirections[sourcePosition]; const targetDir = handleDirections[targetPosition]; @@ -88,17 +96,22 @@ function getPoints({ const sourceGapOffset = { x: 0, y: 0 }; const targetGapOffset = { x: 0, y: 0 }; - const [defaultCenterX, defaultCenterY, defaultOffsetX, defaultOffsetY] = getEdgeCenter({ + const [, , defaultOffsetX, defaultOffsetY] = getEdgeCenter({ sourceX: source.x, sourceY: source.y, targetX: target.x, targetY: target.y, }); + // Calculate bend position based on the bendPosition parameter + const bendX = sourceGapped.x + (targetGapped.x - sourceGapped.x) * bendPosition; + const bendY = sourceGapped.y + (targetGapped.y - sourceGapped.y) * bendPosition; + // opposite handle positions, default case if (sourceDir[dirAccessor] * targetDir[dirAccessor] === -1) { - centerX = center.x ?? defaultCenterX; - centerY = center.y ?? defaultCenterY; + centerX = center.x ?? bendX; + centerY = center.y ?? bendY; + /* * ---> * | @@ -252,7 +265,8 @@ export function getSmoothStepPath({ centerX, centerY, offset = 20, -}: GetSmoothStepPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] { + bendPosition = 0.5, +}: GetSmoothStepPathParams): [path: string, labelX: number, labelY: number, offsetX: number, offsetY: number] { const [points, labelX, labelY, offsetX, offsetY] = getPoints({ source: { x: sourceX, y: sourceY }, sourcePosition, @@ -260,6 +274,7 @@ export function getSmoothStepPath({ targetPosition, center: { x: centerX, y: centerY }, offset, + bendPosition, }); const path = points.reduce((res, p, i) => {