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 ( ); } );