From 0ff63803b62b0557705d9214a875b83ad5c9b895 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 18 Mar 2022 18:09:04 +0800 Subject: [PATCH] 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 ( + + ); + } +);