refactor Bezier function

This commit is contained in:
Ze-Zheng Wu
2022-03-18 18:18:39 +08:00
parent 2199ca3fd4
commit 0ff63803b6
3 changed files with 218 additions and 81 deletions
+8 -3
View File
@@ -82,6 +82,8 @@ export default ({
targetY: number, targetY: number,
targetPosition: Position | undefined; targetPosition: Position | undefined;
let toCurvature: 'sourceCurvature' | 'targetCurvature';
switch (connectionHandleType) { switch (connectionHandleType) {
case 'source': case 'source':
{ {
@@ -91,6 +93,7 @@ export default ({
targetX = toX; targetX = toX;
targetY = toY; targetY = toY;
targetPosition = toPosition; targetPosition = toPosition;
toCurvature = 'targetCurvature';
} }
break; break;
case 'target': case 'target':
@@ -101,6 +104,7 @@ export default ({
targetX = fromX; targetX = fromX;
targetY = fromY; targetY = fromY;
targetPosition = fromPosition; targetPosition = fromPosition;
toCurvature = 'sourceCurvature';
} }
break; break;
} }
@@ -139,9 +143,10 @@ export default ({
}; };
if (connectionLineType === ConnectionLineType.Bezier) { if (connectionLineType === ConnectionLineType.Bezier) {
// @TODO: we need another getBezier function, that handles a connection line. // we don't know the destination position, so we can zero the to curvature
// Since we don't know the target position, we can't use the default bezier function here. dAttr = getBezierPath({ ...pathParams, [toCurvature]: 0 });
dAttr = getBezierPath({ ...pathParams, curvature: 0 }); // or we assume the destination position is opposite to the source position
// dAttr = getBezierPath(pathParams);
} else if (connectionLineType === ConnectionLineType.Step) { } else if (connectionLineType === ConnectionLineType.Step) {
dAttr = getSmoothStepPath({ dAttr = getSmoothStepPath({
...pathParams, ...pathParams,
+93 -70
View File
@@ -11,12 +11,78 @@ export interface GetBezierPathParams {
targetY: number; targetY: number;
targetPosition?: Position; targetPosition?: Position;
curvature?: number; curvature?: number;
sourceCurvature?: number;
targetCurvature?: number;
centerX?: number; centerX?: number;
centerY?: number; centerY?: number;
} }
// @TODO: refactor getBezierPath function. It's too long and hard to understand. interface GetControlWithCurvatureParams {
// We should reuse the curvature handling for top/bottom and left/right. 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({ export function getBezierPath({
sourceX, sourceX,
sourceY, sourceY,
@@ -25,78 +91,35 @@ export function getBezierPath({
targetY, targetY,
targetPosition = Position.Top, targetPosition = Position.Top,
curvature = 0.25, curvature = 0.25,
sourceCurvature = curvature,
targetCurvature = curvature,
centerX, centerX,
centerY, centerY,
}: GetBezierPathParams): string { }: GetBezierPathParams): string {
const leftAndRight = [Position.Left, Position.Right];
const hasCurvature = curvature > 0;
const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }); const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY });
centerX = centerX ?? _centerX;
if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { centerY = centerY ?? _centerY;
const cX = typeof centerX !== 'undefined' ? centerX : _centerX; const [sourceControlX, sourceControlY] = getControlWithCurvature({
const distanceX = targetX - sourceX; pos: sourcePosition,
const absDistanceX = Math.abs(distanceX); x1: sourceX,
const amtX = (Math.sqrt(absDistanceX) / 2) * (50 * curvature); y1: sourceY,
x2: targetX,
let hx1 = cX; y2: targetY,
let hx2 = cX; cX: centerX,
cY: centerY,
if (hasCurvature) { c: sourceCurvature,
const sourceAndTargetRight = sourcePosition === Position.Right && targetPosition === Position.Right; });
const sourceAndTargetLeft = sourcePosition === Position.Left && targetPosition === Position.Left; const [targetControlX, targetControlY] = getControlWithCurvature({
pos: targetPosition,
hx1 = sourceX + amtX; x1: targetX,
hx2 = targetX - amtX; y1: targetY,
x2: sourceX,
if (sourceAndTargetLeft) { y2: sourceY,
hx1 = sourceX - amtX; cX: centerX,
} else if (sourceAndTargetRight) { cY: centerY,
hx2 = targetX + amtX; c: targetCurvature,
} else if (sourcePosition === Position.Left && targetX <= sourceX) { });
hx1 = cX; return `M${sourceX},${sourceY} C${sourceControlX},${sourceControlY} ${targetControlX},${targetControlY} ${targetX},${targetY}`;
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}`;
} }
export default memo( export default memo(
+117 -8
View File
@@ -1,12 +1,121 @@
import React, { memo } from 'react'; import React, { memo } from 'react';
import { EdgeProps, Position } from '../../types';
import BaseEdge from './BaseEdge';
import { getCenter } from './utils';
import BezierEdge, { getBezierPath, GetBezierPathParams } from './BezierEdge'; export interface GetSimpleBezierPathParams {
import { EdgeProps } from '../../types'; sourceX: number;
sourceY: number;
export function getSimpleBezierPath(props: GetBezierPathParams): string { sourcePosition?: Position;
return getBezierPath({ ...props, curvature: 0 }); targetX: number;
targetY: number;
targetPosition?: Position;
centerX?: number;
centerY?: number;
} }
export default memo((props: EdgeProps) => { interface GetControlParams {
return <BezierEdge {...props} curvature={0} />; 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 (
<BaseEdge
path={path}
centerX={centerX}
centerY={centerY}
label={label}
labelStyle={labelStyle}
labelShowBg={labelShowBg}
labelBgStyle={labelBgStyle}
labelBgPadding={labelBgPadding}
labelBgBorderRadius={labelBgBorderRadius}
style={style}
markerEnd={markerEnd}
markerStart={markerStart}
/>
);
}
);