type PointType = { x: number; y: number }; type LineFunc = ( pointA: PointType, pointB: PointType, ) => { length: number; angle: number }; type ControlPointClosure = ( current: PointType, previous: PointType, next: PointType, reverse?: boolean, ) => number[]; type ControlPointFunc = ( lineCale: LineFunc, smooth: number, ) => ControlPointClosure; type BezierCommandClosure = ( point: PointType, i: number, a: PointType[], ) => string; type BezierCommandFunc = ( controlPoint: ControlPointClosure, ) => BezierCommandClosure; type SvgPathFunc = ( points: PointType[], command: BezierCommandClosure, ) => string; export const line: LineFunc = (pointA, pointB) => { const lengthX = pointB.x - pointA.x; const lengthY = pointB.y - pointA.y; return { length: Math.sqrt(lengthX ** 2 + lengthY ** 2), angle: Math.atan2(lengthY, lengthX), }; }; export const controlPoint: ControlPointFunc = (lineCale, smooth) => ( current, previous, next, reverse, ): number[] => { // When 'current' is the first or last point of the array // 'previous' or 'next' don't exist. // Replace with 'current' const p = previous || current; const n = next || current; // Properties of the opposed-line const o = lineCale(p, n); // If is end-control-point, add PI to the angle to go backward const angle = o.angle + (reverse ? Math.PI : 0); const length = o.length * smooth; // The control point position is relative to the current point const x = current.x + Math.cos(angle) * length; const y = current.y + Math.sin(angle) * length; return [x, y]; }; export const bezierCommand: BezierCommandFunc = (controlPointCalc) => ( point, i, a, ): string => { // start control point const [cpsX, cpsY] = controlPointCalc(a[i - 1], a[i - 2], point); // end control point const [cpeX, cpeY] = controlPointCalc(point, a[i - 1], a[i + 1], true); return `C ${cpsX},${cpsY} ${cpeX},${cpeY} ${point.x},${point.y}`; }; export const svgPath: SvgPathFunc = (points, command) => { const d = points.reduce( (acc, point, i, a) => i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`, '', ); return d; }; export default line;