RenderUtil.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import {
  2. isNumber
  3. } from 'min-dash';
  4. import {
  5. attr as svgAttr,
  6. create as svgCreate
  7. } from 'tiny-svg';
  8. /**
  9. * @param { [ string, ...any[] ][] } elements
  10. *
  11. * @return { string }
  12. */
  13. export function componentsToPath(elements) {
  14. return elements.flat().join(',').replace(/,?([A-z]),?/g, '$1');
  15. }
  16. export function toSVGPoints(points) {
  17. var result = '';
  18. for (var i = 0, p; (p = points[i]); i++) {
  19. result += p.x + ',' + p.y + ' ';
  20. }
  21. return result;
  22. }
  23. function move(point) {
  24. return [ 'M', point.x, point.y ];
  25. }
  26. function lineTo(point) {
  27. return [ 'L', point.x, point.y ];
  28. }
  29. function curveTo(p1, p2, p3) {
  30. return [ 'C', p1.x, p1.y, p2.x, p2.y, p3.x, p3.y ];
  31. }
  32. function drawPath(waypoints, cornerRadius) {
  33. const pointCount = waypoints.length;
  34. const path = [ move(waypoints[0]) ];
  35. for (let i = 1; i < pointCount; i++) {
  36. const pointBefore = waypoints[i - 1];
  37. const point = waypoints[i];
  38. const pointAfter = waypoints[i + 1];
  39. if (!pointAfter || !cornerRadius) {
  40. path.push(lineTo(point));
  41. continue;
  42. }
  43. const effectiveRadius = Math.min(
  44. cornerRadius,
  45. vectorLength(point.x - pointBefore.x, point.y - pointBefore.y),
  46. vectorLength(pointAfter.x - point.x, pointAfter.y - point.y)
  47. );
  48. if (!effectiveRadius) {
  49. path.push(lineTo(point));
  50. continue;
  51. }
  52. const beforePoint = getPointAtLength(point, pointBefore, effectiveRadius);
  53. const beforePoint2 = getPointAtLength(point, pointBefore, effectiveRadius * .5);
  54. const afterPoint = getPointAtLength(point, pointAfter, effectiveRadius);
  55. const afterPoint2 = getPointAtLength(point, pointAfter, effectiveRadius * .5);
  56. path.push(lineTo(beforePoint));
  57. path.push(curveTo(beforePoint2, afterPoint2, afterPoint));
  58. }
  59. return path;
  60. }
  61. function getPointAtLength(start, end, length) {
  62. const deltaX = end.x - start.x;
  63. const deltaY = end.y - start.y;
  64. const totalLength = vectorLength(deltaX, deltaY);
  65. const percent = length / totalLength;
  66. return {
  67. x: start.x + deltaX * percent,
  68. y: start.y + deltaY * percent
  69. };
  70. }
  71. function vectorLength(x, y) {
  72. return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
  73. }
  74. /**
  75. * @param { { x: number, y: number }[] } points
  76. * @param { any } [attrs]
  77. * @param { number } [radius]
  78. *
  79. * @return {SVGElement}
  80. */
  81. export function createLine(points, attrs, radius) {
  82. if (isNumber(attrs)) {
  83. radius = attrs;
  84. attrs = null;
  85. }
  86. if (!attrs) {
  87. attrs = {};
  88. }
  89. const line = svgCreate('path', attrs);
  90. if (isNumber(radius)) {
  91. line.dataset.cornerRadius = String(radius);
  92. }
  93. return updateLine(line, points);
  94. }
  95. /**
  96. * @param { SVGElement } gfx
  97. * @param { { x: number, y: number }[]} points
  98. *
  99. * @return {SVGElement}
  100. */
  101. export function updateLine(gfx, points) {
  102. const cornerRadius = parseInt(gfx.dataset.cornerRadius, 10) || 0;
  103. svgAttr(gfx, {
  104. d: componentsToPath(drawPath(points, cornerRadius))
  105. });
  106. return gfx;
  107. }