util.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.82
  5. */
  6. /**
  7. * Calculate slope of the tangents
  8. * uses Steffen interpolation as it's monotonic
  9. * http://jrwalsh1.github.io/posts/interpolations/
  10. */
  11. function slope(p0, p1, p2) {
  12. const dx = p1[0] - p0[0];
  13. const dx1 = p2[0] - p1[0];
  14. const dy = p1[1] - p0[1];
  15. const dy1 = p2[1] - p1[1];
  16. const m = dy / (dx || (dx1 < 0 && 0));
  17. const m1 = dy1 / (dx1 || (dx < 0 && 0));
  18. const p = (m * dx1 + m1 * dx) / (dx + dx1);
  19. return (Math.sign(m) + Math.sign(m1)) * Math.min(Math.abs(m), Math.abs(m1), 0.5 * Math.abs(p)) || 0;
  20. }
  21. /**
  22. * Calculate slope for just one tangent (single-sided)
  23. */
  24. function slopeSingle(p0, p1, m) {
  25. const dx = p1[0] - p0[0];
  26. const dy = p1[1] - p0[1];
  27. return dx ? ((3 * dy) / dx - m) / 2 : m;
  28. }
  29. /**
  30. * Given two points and their tangent slopes,
  31. * calculate the bezier handle coordinates and return draw command.
  32. *
  33. * Translates Hermite Spline to Beziér curve:
  34. * stackoverflow.com/questions/42574940/
  35. */
  36. function bezier(p0, p1, m0, m1, t) {
  37. const [x0, y0] = p0;
  38. const [x1, y1] = p1;
  39. const dx = (x1 - x0) / 3;
  40. const h1 = t([x0 + dx, y0 + dx * m0]).join(",");
  41. const h2 = t([x1 - dx, y1 - dx * m1]).join(",");
  42. const p = t([x1, y1]).join(",");
  43. return `C ${h1} ${h2} ${p}`;
  44. }
  45. /**
  46. * Generate a function which will translate a point
  47. * from the data coordinate space to svg viewbox oriented pixels
  48. */
  49. export function translate({ width, height, min, max }) {
  50. const rangeX = max[0] - min[0];
  51. const rangeY = max[1] - min[1];
  52. return (point) => {
  53. const x = ((point[0] - min[0]) / rangeX) * width;
  54. const y = height - (point[1] / rangeY) * height;
  55. return [x, y];
  56. };
  57. }
  58. /**
  59. * Get the min and max values from the dataset
  60. */
  61. export function range(data) {
  62. const [startX, startY] = data[0];
  63. const min = [startX, startY];
  64. const max = [startX, startY];
  65. return data.reduce(({ min, max }, [x, y]) => ({
  66. min: [Math.min(min[0], x), Math.min(min[1], y)],
  67. max: [Math.max(max[0], x), Math.max(max[1], y)]
  68. }), { min, max });
  69. }
  70. /**
  71. * Generate drawing commands for an area graph
  72. * returns a string can can be passed directly to a path element's `d` attribute
  73. */
  74. export function area({ data, min, max, t }) {
  75. if (data.length === 0) {
  76. return "";
  77. }
  78. // important points for beginning and ending the path
  79. const [startX, startY] = t(data[0]);
  80. const [minX, minY] = t(min);
  81. const [maxX] = t(max);
  82. // keep track of previous slope/points
  83. let m;
  84. let p0;
  85. let p1;
  86. // iterate over data points, calculating command for each
  87. const commands = data.reduce((acc, point, i) => {
  88. p0 = data[i - 2];
  89. p1 = data[i - 1];
  90. if (i > 1) {
  91. const m1 = slope(p0, p1, point);
  92. const m0 = m === undefined ? slopeSingle(p0, p1, m1) : m;
  93. const command = bezier(p0, p1, m0, m1, t);
  94. m = m1;
  95. return `${acc} ${command}`;
  96. }
  97. return acc;
  98. }, `M ${minX},${minY} L ${minX},${startY} L ${startX},${startY}`);
  99. // close the path
  100. const last = data[data.length - 1];
  101. const end = bezier(p1, last, m, slopeSingle(p1, last, m), t);
  102. return `${commands} ${end} L ${maxX},${minY} Z`;
  103. }