svgBezierCurve.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. type PointType = { x: number; y: number };
  2. type LineFunc = (
  3. pointA: PointType,
  4. pointB: PointType
  5. ) => { length: number; angle: number };
  6. type ControlPointClosure = (
  7. current: PointType,
  8. previous: PointType,
  9. next: PointType,
  10. reverse?: boolean
  11. ) => number[];
  12. type ControlPointFunc = (
  13. lineCale: LineFunc,
  14. smooth: number
  15. ) => ControlPointClosure;
  16. type BezierCommandClosure = (
  17. point: PointType,
  18. i: number,
  19. a: PointType[]
  20. ) => string;
  21. type BezierCommandFunc = (
  22. controlPoint: ControlPointClosure
  23. ) => BezierCommandClosure;
  24. type SvgPathFunc = (
  25. points: PointType[],
  26. command: BezierCommandClosure
  27. ) => string;
  28. export const line: LineFunc = (pointA, pointB) => {
  29. const lengthX = pointB.x - pointA.x;
  30. const lengthY = pointB.y - pointA.y;
  31. return {
  32. length: Math.sqrt(lengthX ** 2 + lengthY ** 2),
  33. angle: Math.atan2(lengthY, lengthX),
  34. };
  35. };
  36. export const controlPoint: ControlPointFunc = (lineCale, smooth) => (
  37. current,
  38. previous,
  39. next,
  40. reverse
  41. ): number[] => {
  42. // When 'current' is the first or last point of the array
  43. // 'previous' or 'next' don't exist.
  44. // Replace with 'current'
  45. const p = previous || current;
  46. const n = next || current;
  47. // Properties of the opposed-line
  48. const o = lineCale(p, n);
  49. // If is end-control-point, add PI to the angle to go backward
  50. const angle = o.angle + (reverse ? Math.PI : 0);
  51. const length = o.length * smooth;
  52. // The control point position is relative to the current point
  53. const x = current.x + Math.cos(angle) * length;
  54. const y = current.y + Math.sin(angle) * length;
  55. return [x, y];
  56. };
  57. export const bezierCommand: BezierCommandFunc = controlPointCalc => (
  58. point,
  59. i,
  60. a
  61. ): string => {
  62. // start control point
  63. const [cpsX, cpsY] = controlPointCalc(a[i - 1], a[i - 2], point);
  64. // end control point
  65. const [cpeX, cpeY] = controlPointCalc(point, a[i - 1], a[i + 1], true);
  66. return `C ${cpsX},${cpsY} ${cpeX},${cpeY} ${point.x},${point.y}`;
  67. };
  68. export const svgPath: SvgPathFunc = (points, command) => {
  69. const d = points.reduce(
  70. (acc, point, i, a) =>
  71. i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`,
  72. ''
  73. );
  74. return d;
  75. };
  76. export default line;