import { Position } from '~/types' interface GetControlWithCurvatureParams { pos: Position x1: number y1: number x2: number y2: number c: number } export interface GetBezierPathParams { sourceX: number sourceY: number sourcePosition?: Position targetX: number targetY: number targetPosition?: Position curvature?: number centerX?: number centerY?: 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}` } 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] }