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;