From 9cc873d64a922e4b41b7c99990a1db804faac05a Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 18 Mar 2022 11:53:10 +0800 Subject: [PATCH 1/8] refactor connection line --- src/components/ConnectionLine/index.tsx | 100 ++++++++++++++++-------- src/types/edges.ts | 3 + 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 7efb1aae..c9cf3df0 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -4,15 +4,7 @@ import shallow from 'zustand/shallow'; import { useStore } from '../../store'; import { getBezierPath } from '../Edges/BezierEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; -import { - HandleElement, - ConnectionLineType, - ConnectionLineComponent, - HandleType, - Node, - ReactFlowState, - Position, -} from '../../types'; +import { ConnectionLineType, ConnectionLineComponent, HandleType, Node, ReactFlowState, Position } from '../../types'; import { getSimpleBezierPath } from '../Edges/SimpleBezierEdge'; interface ConnectionLineProps { @@ -29,13 +21,6 @@ interface ConnectionLineProps { const selector = (s: ReactFlowState) => ({ nodeInternals: s.nodeInternals, transform: s.transform }); -const getSourceHandle = (handleId: string | null, sourceNode: Node, connectionHandleType: HandleType) => { - const handleTypeInverted = connectionHandleType === 'source' ? 'target' : 'source'; - const handleBound = sourceNode.handleBounds?.[connectionHandleType] || sourceNode.handleBounds?.[handleTypeInverted]; - - return handleId ? handleBound?.find((d: HandleElement) => d.id === handleId) : handleBound?.[0]; -}; - export default ({ connectionNodeId, connectionHandleId, @@ -51,28 +36,74 @@ export default ({ const handleId = connectionHandleId; const { nodeInternals, transform } = useStore(selector, shallow); - const sourceNode = useRef(nodeInternals.get(nodeId)); + const fromNode = useRef(nodeInternals.get(nodeId)); if ( - !sourceNode.current || - !sourceNode.current || + !fromNode.current || + !fromNode.current || !isConnectable || - !sourceNode.current.handleBounds?.[connectionHandleType] + !fromNode.current.handleBounds?.[connectionHandleType] ) { return null; } - const sourceHandle = getSourceHandle(handleId, sourceNode.current, connectionHandleType); - const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (sourceNode.current?.width ?? 0) / 2; - const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : sourceNode.current?.height ?? 0; - const sourceX = (sourceNode.current.positionAbsolute?.x || 0) + sourceHandleX; - const sourceY = (sourceNode.current.positionAbsolute?.y || 0) + sourceHandleY; + const handleBound = fromNode.current.handleBounds?.[connectionHandleType]; + const fromHandle = handleId ? handleBound?.find((d) => d.id === handleId) : handleBound?.[0]; + const fromHandleX = fromHandle ? fromHandle.x + fromHandle.width / 2 : (fromNode.current?.width ?? 0) / 2; + const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode.current?.height ?? 0; + const fromX = (fromNode.current.positionAbsolute?.x || 0) + fromHandleX; + const fromY = (fromNode.current.positionAbsolute?.y || 0) + fromHandleY; - const targetX = (connectionPositionX - transform[0]) / transform[2]; - const targetY = (connectionPositionY - transform[1]) / transform[2]; + const toX = (connectionPositionX - transform[0]) / transform[2]; + const toY = (connectionPositionY - transform[1]) / transform[2]; - const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right; - const targetPosition = isRightOrLeft ? Position.Left : Position.Top; + const fromPostion = fromHandle?.position; + + let toPostion: Position | undefined; + switch (fromPostion) { + case Position.Left: + toPostion = Position.Right; + break; + case Position.Right: + toPostion = Position.Left; + break; + case Position.Top: + toPostion = Position.Bottom; + break; + case Position.Bottom: + toPostion = Position.Top; + break; + } + + let sourceX: number, + sourceY: number, + sourcePosition: Position | undefined, + targetX: number, + targetY: number, + targetPosition: Position | undefined; + + switch (connectionHandleType) { + case 'source': + { + sourceX = fromX; + sourceY = fromY; + sourcePosition = fromPostion; + targetX = toX; + targetY = toY; + targetPosition = toPostion; + } + break; + case 'target': + { + sourceX = toX; + sourceY = toY; + sourcePosition = toPostion; + targetX = fromX; + targetY = fromY; + targetPosition = fromPostion; + } + break; + } if (CustomConnectionLineComponent) { return ( @@ -80,14 +111,17 @@ export default ({ ); @@ -98,7 +132,7 @@ export default ({ const pathParams = { sourceX, sourceY, - sourcePosition: sourceHandle?.position, + sourcePosition, targetX, targetY, targetPosition, diff --git a/src/types/edges.ts b/src/types/edges.ts index 2f83eac7..d480ec15 100644 --- a/src/types/edges.ts +++ b/src/types/edges.ts @@ -155,6 +155,9 @@ export type ConnectionLineComponentProps = { targetPosition?: Position; connectionLineStyle?: CSSProperties; connectionLineType: ConnectionLineType; + fromNode?: Node; + fromHandle?: HandleElement; + // backward compatibility, mark as deprecated? sourceNode?: Node; sourceHandle?: HandleElement; }; From 2199ca3fd4169c0d63c779c554fdd7415e74a0b0 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 18 Mar 2022 13:38:33 +0800 Subject: [PATCH 2/8] fix typo --- src/components/ConnectionLine/index.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index c9cf3df0..895df605 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -57,21 +57,21 @@ export default ({ const toX = (connectionPositionX - transform[0]) / transform[2]; const toY = (connectionPositionY - transform[1]) / transform[2]; - const fromPostion = fromHandle?.position; + const fromPosition = fromHandle?.position; - let toPostion: Position | undefined; - switch (fromPostion) { + let toPosition: Position | undefined; + switch (fromPosition) { case Position.Left: - toPostion = Position.Right; + toPosition = Position.Right; break; case Position.Right: - toPostion = Position.Left; + toPosition = Position.Left; break; case Position.Top: - toPostion = Position.Bottom; + toPosition = Position.Bottom; break; case Position.Bottom: - toPostion = Position.Top; + toPosition = Position.Top; break; } @@ -87,20 +87,20 @@ export default ({ { sourceX = fromX; sourceY = fromY; - sourcePosition = fromPostion; + sourcePosition = fromPosition; targetX = toX; targetY = toY; - targetPosition = toPostion; + targetPosition = toPosition; } break; case 'target': { sourceX = toX; sourceY = toY; - sourcePosition = toPostion; + sourcePosition = toPosition; targetX = fromX; targetY = fromY; - targetPosition = fromPostion; + targetPosition = fromPosition; } break; } From 0ff63803b62b0557705d9214a875b83ad5c9b895 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 18 Mar 2022 18:09:04 +0800 Subject: [PATCH 3/8] refactor Bezier function --- src/components/ConnectionLine/index.tsx | 11 +- src/components/Edges/BezierEdge.tsx | 163 ++++++++++++---------- src/components/Edges/SimpleBezierEdge.tsx | 125 +++++++++++++++-- 3 files changed, 218 insertions(+), 81 deletions(-) diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 895df605..be358821 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -82,6 +82,8 @@ export default ({ targetY: number, targetPosition: Position | undefined; + let toCurvature: 'sourceCurvature' | 'targetCurvature'; + switch (connectionHandleType) { case 'source': { @@ -91,6 +93,7 @@ export default ({ targetX = toX; targetY = toY; targetPosition = toPosition; + toCurvature = 'targetCurvature'; } break; case 'target': @@ -101,6 +104,7 @@ export default ({ targetX = fromX; targetY = fromY; targetPosition = fromPosition; + toCurvature = 'sourceCurvature'; } break; } @@ -139,9 +143,10 @@ export default ({ }; if (connectionLineType === ConnectionLineType.Bezier) { - // @TODO: we need another getBezier function, that handles a connection line. - // Since we don't know the target position, we can't use the default bezier function here. - dAttr = getBezierPath({ ...pathParams, curvature: 0 }); + // we don't know the destination position, so we can zero the to curvature + dAttr = getBezierPath({ ...pathParams, [toCurvature]: 0 }); + // or we assume the destination position is opposite to the source position + // dAttr = getBezierPath(pathParams); } else if (connectionLineType === ConnectionLineType.Step) { dAttr = getSmoothStepPath({ ...pathParams, diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index 36e31486..df9ab8fd 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -11,12 +11,78 @@ export interface GetBezierPathParams { targetY: number; targetPosition?: Position; curvature?: number; + sourceCurvature?: number; + targetCurvature?: number; centerX?: number; centerY?: number; } -// @TODO: refactor getBezierPath function. It's too long and hard to understand. -// We should reuse the curvature handling for top/bottom and left/right. +interface GetControlWithCurvatureParams { + pos: Position; + x1: number; + y1: number; + x2: number; + y2: number; + cX: number; + cY: number; + c: number; +} + +function calculateControlOffset(distance: number, curvature: number): number { + return curvature * 25 * Math.sqrt(distance); +} + +function getControlWithCurvature({ pos, x1, y1, x2, y2, cX, cY, c }: GetControlWithCurvatureParams): [number, number] { + let ctX: number, ctY: number; + switch (pos) { + case Position.Left: + { + const d = x2 - x1; + ctY = y1; + if (d <= 0) { + ctX = cX; + } else { + ctX = x1 - calculateControlOffset(d, c); + } + } + break; + case Position.Right: + { + const d = x1 - x2; + ctY = y1; + if (d <= 0) { + ctX = cX; + } else { + ctX = x1 + calculateControlOffset(d, c); + } + } + break; + case Position.Top: + { + const d = y2 - y1; + ctX = x1; + if (d <= 0) { + ctY = cY; + } else { + ctY = y1 - calculateControlOffset(d, c); + } + } + break; + case Position.Bottom: + { + const d = y1 - y2; + ctX = x1; + if (d <= 0) { + ctY = cY; + } else { + ctY = y1 + calculateControlOffset(d, c); + } + } + break; + } + return [ctX, ctY]; +} + export function getBezierPath({ sourceX, sourceY, @@ -25,78 +91,35 @@ export function getBezierPath({ targetY, targetPosition = Position.Top, curvature = 0.25, + sourceCurvature = curvature, + targetCurvature = curvature, centerX, centerY, }: GetBezierPathParams): string { - const leftAndRight = [Position.Left, Position.Right]; - const hasCurvature = curvature > 0; const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }); - - if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { - const cX = typeof centerX !== 'undefined' ? centerX : _centerX; - const distanceX = targetX - sourceX; - const absDistanceX = Math.abs(distanceX); - const amtX = (Math.sqrt(absDistanceX) / 2) * (50 * curvature); - - let hx1 = cX; - let hx2 = cX; - - if (hasCurvature) { - const sourceAndTargetRight = sourcePosition === Position.Right && targetPosition === Position.Right; - const sourceAndTargetLeft = sourcePosition === Position.Left && targetPosition === Position.Left; - - hx1 = sourceX + amtX; - hx2 = targetX - amtX; - - if (sourceAndTargetLeft) { - hx1 = sourceX - amtX; - } else if (sourceAndTargetRight) { - hx2 = targetX + amtX; - } else if (sourcePosition === Position.Left && targetX <= sourceX) { - hx1 = cX; - hx2 = cX; - } else if (sourcePosition === Position.Left && targetX > sourceX) { - hx1 = sourceX - amtX; - hx2 = targetX + amtX; - } - } - - return `M${sourceX},${sourceY} C${hx1},${sourceY} ${hx2},${targetY}, ${targetX},${targetY}`; - } else if (leftAndRight.includes(targetPosition)) { - return `M${sourceX},${sourceY} Q${sourceX},${targetY} ${targetX},${targetY}`; - } else if (leftAndRight.includes(sourcePosition)) { - return `M${sourceX},${sourceY} Q${targetX},${sourceY} ${targetX},${targetY}`; - } - - const cY = typeof centerY !== 'undefined' ? centerY : _centerY; - const distanceY = targetY - sourceY; - const absDistanceY = Math.abs(distanceY); - const amtY = (Math.sqrt(absDistanceY) / 2) * (50 * curvature); - - let hy1 = cY; - let hy2 = cY; - - if (hasCurvature) { - hy1 = sourceY + amtY; - hy2 = targetY - amtY; - - const sourceAndTargetTop = sourcePosition === Position.Top && targetPosition === Position.Top; - const sourceAndTargetBottom = sourcePosition === Position.Bottom && targetPosition === Position.Bottom; - - if (sourceAndTargetTop) { - hy1 = targetY - amtY; - } else if (sourceAndTargetBottom) { - hy2 = targetY + amtY; - } else if (sourcePosition === Position.Top && targetY <= sourceY) { - hy1 = cY; - hy2 = cY; - } else if (sourcePosition === Position.Top && targetY > sourceY) { - hy1 = sourceY - amtY; - hy2 = targetY + amtY; - } - } - - return `M${sourceX},${sourceY} C${sourceX},${hy1} ${targetX},${hy2} ${targetX},${targetY}`; + centerX = centerX ?? _centerX; + centerY = centerY ?? _centerY; + const [sourceControlX, sourceControlY] = getControlWithCurvature({ + pos: sourcePosition, + x1: sourceX, + y1: sourceY, + x2: targetX, + y2: targetY, + cX: centerX, + cY: centerY, + c: sourceCurvature, + }); + const [targetControlX, targetControlY] = getControlWithCurvature({ + pos: targetPosition, + x1: targetX, + y1: targetY, + x2: sourceX, + y2: sourceY, + cX: centerX, + cY: centerY, + c: targetCurvature, + }); + return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`; } export default memo( diff --git a/src/components/Edges/SimpleBezierEdge.tsx b/src/components/Edges/SimpleBezierEdge.tsx index 1bc671c9..7338e557 100644 --- a/src/components/Edges/SimpleBezierEdge.tsx +++ b/src/components/Edges/SimpleBezierEdge.tsx @@ -1,12 +1,121 @@ import React, { memo } from 'react'; +import { EdgeProps, Position } from '../../types'; +import BaseEdge from './BaseEdge'; +import { getCenter } from './utils'; -import BezierEdge, { getBezierPath, GetBezierPathParams } from './BezierEdge'; -import { EdgeProps } from '../../types'; - -export function getSimpleBezierPath(props: GetBezierPathParams): string { - return getBezierPath({ ...props, curvature: 0 }); +export interface GetSimpleBezierPathParams { + sourceX: number; + sourceY: number; + sourcePosition?: Position; + targetX: number; + targetY: number; + targetPosition?: Position; + centerX?: number; + centerY?: number; } -export default memo((props: EdgeProps) => { - return ; -}); +interface GetControlParams { + pos: Position; + x: number; + y: number; + cX: number; + cY: number; +} + +function getControl({ pos, x, y, cX, cY }: GetControlParams): [number, number] { + let ctX: number, ctY: number; + switch (pos) { + case Position.Left: + case Position.Right: + { + ctX = cX; + ctY = y; + } + break; + case Position.Top: + case Position.Bottom: + { + ctX = x; + ctY = cY; + } + break; + } + return [ctX, ctY]; +} + +export function getSimpleBezierPath({ + sourceX, + sourceY, + sourcePosition = Position.Bottom, + targetX, + targetY, + targetPosition = Position.Top, + centerX, + centerY, +}: GetSimpleBezierPathParams): string { + const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }); + centerX = centerX ?? _centerX; + centerY = centerY ?? _centerY; + const [sourceControlX, sourceControlY] = getControl({ + pos: sourcePosition, + x: sourceX, + y: sourceY, + cX: centerX, + cY: centerY, + }); + const [targetControlX, targetControlY] = getControl({ + pos: targetPosition, + x: targetX, + y: targetY, + cX: centerX, + cY: centerY, + }); + return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`; +} + +export default memo( + ({ + sourceX, + sourceY, + targetX, + targetY, + sourcePosition = Position.Bottom, + targetPosition = Position.Top, + label, + labelStyle, + labelShowBg, + labelBgStyle, + labelBgPadding, + labelBgBorderRadius, + style, + markerEnd, + markerStart, + }: EdgeProps) => { + const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }); + const path = getSimpleBezierPath({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + return ( + + ); + } +); From c1a29beb1dd91a0ca326a1d5dfc63c6aae69066a Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 18 Mar 2022 18:59:20 +0800 Subject: [PATCH 4/8] assume toPosition is opposite to fromPosition --- src/components/ConnectionLine/index.tsx | 10 ++-------- src/components/Edges/BezierEdge.tsx | 8 ++------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index be358821..dfdf248f 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -82,8 +82,6 @@ export default ({ targetY: number, targetPosition: Position | undefined; - let toCurvature: 'sourceCurvature' | 'targetCurvature'; - switch (connectionHandleType) { case 'source': { @@ -93,7 +91,6 @@ export default ({ targetX = toX; targetY = toY; targetPosition = toPosition; - toCurvature = 'targetCurvature'; } break; case 'target': @@ -104,7 +101,6 @@ export default ({ targetX = fromX; targetY = fromY; targetPosition = fromPosition; - toCurvature = 'sourceCurvature'; } break; } @@ -143,10 +139,8 @@ export default ({ }; if (connectionLineType === ConnectionLineType.Bezier) { - // we don't know the destination position, so we can zero the to curvature - dAttr = getBezierPath({ ...pathParams, [toCurvature]: 0 }); - // or we assume the destination position is opposite to the source position - // dAttr = getBezierPath(pathParams); + // we assume the destination position is opposite to the source position + dAttr = getBezierPath(pathParams); } else if (connectionLineType === ConnectionLineType.Step) { dAttr = getSmoothStepPath({ ...pathParams, diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index df9ab8fd..aac7e2de 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -11,8 +11,6 @@ export interface GetBezierPathParams { targetY: number; targetPosition?: Position; curvature?: number; - sourceCurvature?: number; - targetCurvature?: number; centerX?: number; centerY?: number; } @@ -91,8 +89,6 @@ export function getBezierPath({ targetY, targetPosition = Position.Top, curvature = 0.25, - sourceCurvature = curvature, - targetCurvature = curvature, centerX, centerY, }: GetBezierPathParams): string { @@ -107,7 +103,7 @@ export function getBezierPath({ y2: targetY, cX: centerX, cY: centerY, - c: sourceCurvature, + c: curvature, }); const [targetControlX, targetControlY] = getControlWithCurvature({ pos: targetPosition, @@ -117,7 +113,7 @@ export function getBezierPath({ y2: sourceY, cX: centerX, cY: centerY, - c: targetCurvature, + c: curvature, }); return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`; } From 0ee882d2c13331989612c4ea2e3bfe7dfe52e910 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Mon, 21 Mar 2022 17:06:46 +0800 Subject: [PATCH 5/8] clean code --- src/components/Edges/BezierEdge.tsx | 47 ++++++----------------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index aac7e2de..2608bef7 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -21,60 +21,42 @@ interface GetControlWithCurvatureParams { y1: number; x2: number; y2: number; - cX: number; - cY: number; c: number; } function calculateControlOffset(distance: number, curvature: number): number { - return curvature * 25 * Math.sqrt(distance); + if (distance >= 0) { + return 0.5 * distance; + } else { + return curvature * 25 * Math.sqrt(-distance); + } } -function getControlWithCurvature({ pos, x1, y1, x2, y2, cX, cY, c }: GetControlWithCurvatureParams): [number, number] { +function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurvatureParams): [number, number] { let ctX: number, ctY: number; switch (pos) { case Position.Left: { - const d = x2 - x1; + ctX = x1 - calculateControlOffset(x1 - x2, c); ctY = y1; - if (d <= 0) { - ctX = cX; - } else { - ctX = x1 - calculateControlOffset(d, c); - } } break; case Position.Right: { - const d = x1 - x2; + ctX = x1 + calculateControlOffset(x2 - x1, c); ctY = y1; - if (d <= 0) { - ctX = cX; - } else { - ctX = x1 + calculateControlOffset(d, c); - } } break; case Position.Top: { - const d = y2 - y1; ctX = x1; - if (d <= 0) { - ctY = cY; - } else { - ctY = y1 - calculateControlOffset(d, c); - } + ctY = y1 - calculateControlOffset(y1 - y2, c); } break; case Position.Bottom: { - const d = y1 - y2; ctX = x1; - if (d <= 0) { - ctY = cY; - } else { - ctY = y1 + calculateControlOffset(d, c); - } + ctY = y1 + calculateControlOffset(y2 - y1, c); } break; } @@ -89,20 +71,13 @@ export function getBezierPath({ targetY, targetPosition = Position.Top, curvature = 0.25, - centerX, - centerY, }: GetBezierPathParams): string { - const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }); - centerX = centerX ?? _centerX; - centerY = centerY ?? _centerY; const [sourceControlX, sourceControlY] = getControlWithCurvature({ pos: sourcePosition, x1: sourceX, y1: sourceY, x2: targetX, y2: targetY, - cX: centerX, - cY: centerY, c: curvature, }); const [targetControlX, targetControlY] = getControlWithCurvature({ @@ -111,8 +86,6 @@ export function getBezierPath({ y1: targetY, x2: sourceX, y2: sourceY, - cX: centerX, - cY: centerY, c: curvature, }); return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`; From 3b804d82ce5b1896503817ab0707dd019772d116 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Mon, 21 Mar 2022 17:37:18 +0800 Subject: [PATCH 6/8] fix center calculation of Bezier edges --- src/components/Edges/BezierEdge.tsx | 46 ++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index 2608bef7..ce6ae619 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -1,7 +1,6 @@ import React, { memo } from 'react'; import { EdgeProps, Position } from '../../types'; import BaseEdge from './BaseEdge'; -import { getCenter } from './utils'; export interface GetBezierPathParams { sourceX: number; @@ -91,6 +90,44 @@ export function getBezierPath({ 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 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, + }); + // quadratic 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 default memo( ({ sourceX, @@ -110,8 +147,7 @@ export default memo( markerStart, curvature, }: EdgeProps) => { - const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }); - const path = getBezierPath({ + const params = { sourceX, sourceY, sourcePosition, @@ -119,7 +155,9 @@ export default memo( targetY, targetPosition, curvature, - }); + }; + const path = getBezierPath(params); + const [centerX, centerY] = getBezierCenter(params); return ( Date: Mon, 21 Mar 2022 18:18:07 +0800 Subject: [PATCH 7/8] fix center calculation of simple Bezier edges --- src/components/Edges/BezierEdge.tsx | 2 - src/components/Edges/SimpleBezierEdge.tsx | 84 +++++++++++++++-------- 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index ce6ae619..9ac3f7f2 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -10,8 +10,6 @@ export interface GetBezierPathParams { targetY: number; targetPosition?: Position; curvature?: number; - centerX?: number; - centerY?: number; } interface GetControlWithCurvatureParams { diff --git a/src/components/Edges/SimpleBezierEdge.tsx b/src/components/Edges/SimpleBezierEdge.tsx index 7338e557..cf931a34 100644 --- a/src/components/Edges/SimpleBezierEdge.tsx +++ b/src/components/Edges/SimpleBezierEdge.tsx @@ -1,7 +1,6 @@ import React, { memo } from 'react'; import { EdgeProps, Position } from '../../types'; import BaseEdge from './BaseEdge'; -import { getCenter } from './utils'; export interface GetSimpleBezierPathParams { sourceX: number; @@ -10,33 +9,31 @@ export interface GetSimpleBezierPathParams { targetX: number; targetY: number; targetPosition?: Position; - centerX?: number; - centerY?: number; } interface GetControlParams { pos: Position; - x: number; - y: number; - cX: number; - cY: number; + x1: number; + y1: number; + x2: number; + y2: number; } -function getControl({ pos, x, y, cX, cY }: GetControlParams): [number, 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 = cX; - ctY = y; + ctX = 0.5 * (x1 + x2); + ctY = y1; } break; case Position.Top: case Position.Bottom: { - ctX = x; - ctY = cY; + ctX = x1; + ctY = 0.5 * (y1 + y2); } break; } @@ -50,29 +47,59 @@ export function getSimpleBezierPath({ targetX, targetY, targetPosition = Position.Top, - centerX, - centerY, }: GetSimpleBezierPathParams): string { - const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }); - centerX = centerX ?? _centerX; - centerY = centerY ?? _centerY; const [sourceControlX, sourceControlY] = getControl({ pos: sourcePosition, - x: sourceX, - y: sourceY, - cX: centerX, - cY: centerY, + x1: sourceX, + y1: sourceY, + x2: targetX, + y2: targetY, }); const [targetControlX, targetControlY] = getControl({ pos: targetPosition, - x: targetX, - y: targetY, - cX: centerX, - cY: centerY, + 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, + }); + // quadratic 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 default memo( ({ sourceX, @@ -91,15 +118,16 @@ export default memo( markerEnd, markerStart, }: EdgeProps) => { - const [centerX, centerY] = getCenter({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }); - const path = getSimpleBezierPath({ + const params = { sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, - }); + }; + const path = getSimpleBezierPath(params); + const [centerX, centerY] = getSimpleBezierCenter(params); return ( Date: Mon, 21 Mar 2022 19:03:40 +0800 Subject: [PATCH 8/8] typo: quadratic -> cubic --- src/components/Edges/BezierEdge.tsx | 2 +- src/components/Edges/SimpleBezierEdge.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx index 9ac3f7f2..38f5508e 100644 --- a/src/components/Edges/BezierEdge.tsx +++ b/src/components/Edges/BezierEdge.tsx @@ -117,7 +117,7 @@ export function getBezierCenter({ y2: sourceY, c: curvature, }); - // quadratic bezier t=0.5 mid point, not the actual mid point, but easy to calculate + // 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; diff --git a/src/components/Edges/SimpleBezierEdge.tsx b/src/components/Edges/SimpleBezierEdge.tsx index cf931a34..adf92f34 100644 --- a/src/components/Edges/SimpleBezierEdge.tsx +++ b/src/components/Edges/SimpleBezierEdge.tsx @@ -91,7 +91,7 @@ export function getSimpleBezierCenter({ x2: sourceX, y2: sourceY, }); - // quadratic bezier t=0.5 mid point, not the actual mid point, but easy to calculate + // 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;