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,
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,
+93 -70
View File
@@ -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(
+117 -8
View File
@@ -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 <BezierEdge {...props} curvature={0} />;
});
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 (
<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}
/>
);
}
);