svgBezierCurve.ts 2.2 KB

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