Files
xyflow/src/components/Edges/BezierEdge.tsx
T
2022-03-21 19:05:03 +08:00

178 lines
4.5 KiB
TypeScript

import React, { memo } from 'react';
import { EdgeProps, Position } from '../../types';
import BaseEdge from './BaseEdge';
export interface GetBezierPathParams {
sourceX: number;
sourceY: number;
sourcePosition?: Position;
targetX: number;
targetY: number;
targetPosition?: Position;
curvature?: number;
}
interface GetControlWithCurvatureParams {
pos: Position;
x1: number;
y1: number;
x2: number;
y2: number;
c: number;
}
function calculateControlOffset(distance: number, curvature: number): number {
if (distance >= 0) {
return 0.5 * distance;
} else {
return curvature * 25 * Math.sqrt(-distance);
}
}
function getControlWithCurvature({ pos, x1, y1, x2, y2, c }: GetControlWithCurvatureParams): [number, number] {
let ctX: number, ctY: number;
switch (pos) {
case Position.Left:
{
ctX = x1 - calculateControlOffset(x1 - x2, c);
ctY = y1;
}
break;
case Position.Right:
{
ctX = x1 + calculateControlOffset(x2 - x1, c);
ctY = y1;
}
break;
case Position.Top:
{
ctX = x1;
ctY = y1 - calculateControlOffset(y1 - y2, c);
}
break;
case Position.Bottom:
{
ctX = x1;
ctY = y1 + calculateControlOffset(y2 - y1, c);
}
break;
}
return [ctX, ctY];
}
export function getBezierPath({
sourceX,
sourceY,
sourcePosition = Position.Bottom,
targetX,
targetY,
targetPosition = Position.Top,
curvature = 0.25,
}: GetBezierPathParams): string {
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,
});
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,
});
// 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;
const xOffset = Math.abs(centerX - sourceX);
const yOffset = Math.abs(centerY - sourceY);
return [centerX, centerY, xOffset, yOffset];
}
export default memo(
({
sourceX,
sourceY,
targetX,
targetY,
sourcePosition = Position.Bottom,
targetPosition = Position.Top,
label,
labelStyle,
labelShowBg,
labelBgStyle,
labelBgPadding,
labelBgBorderRadius,
style,
markerEnd,
markerStart,
curvature,
}: EdgeProps) => {
const params = {
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
curvature,
};
const path = getBezierPath(params);
const [centerX, centerY] = getBezierCenter(params);
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}
/>
);
}
);