util.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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.97
  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. * @param p0
  12. * @param p1
  13. * @param p2
  14. */
  15. function slope(p0, p1, p2) {
  16. const dx = p1[0] - p0[0];
  17. const dx1 = p2[0] - p1[0];
  18. const dy = p1[1] - p0[1];
  19. const dy1 = p2[1] - p1[1];
  20. const m = dy / (dx || (dx1 < 0 && 0));
  21. const m1 = dy1 / (dx1 || (dx < 0 && 0));
  22. const p = (m * dx1 + m1 * dx) / (dx + dx1);
  23. return (Math.sign(m) + Math.sign(m1)) * Math.min(Math.abs(m), Math.abs(m1), 0.5 * Math.abs(p)) || 0;
  24. }
  25. /**
  26. * Calculate slope for just one tangent (single-sided)
  27. *
  28. * @param p0
  29. * @param p1
  30. * @param m
  31. */
  32. function slopeSingle(p0, p1, m) {
  33. const dx = p1[0] - p0[0];
  34. const dy = p1[1] - p0[1];
  35. return dx ? ((3 * dy) / dx - m) / 2 : m;
  36. }
  37. /**
  38. * Given two points and their tangent slopes,
  39. * calculate the bezier handle coordinates and return draw command.
  40. *
  41. * Translates Hermite Spline to Beziér curve:
  42. * stackoverflow.com/questions/42574940/
  43. *
  44. * @param p0
  45. * @param p1
  46. * @param m0
  47. * @param m1
  48. * @param t
  49. */
  50. function bezier(p0, p1, m0, m1, t) {
  51. const [x0, y0] = p0;
  52. const [x1, y1] = p1;
  53. const dx = (x1 - x0) / 3;
  54. const h1 = t([x0 + dx, y0 + dx * m0]).join(",");
  55. const h2 = t([x1 - dx, y1 - dx * m1]).join(",");
  56. const p = t([x1, y1]).join(",");
  57. return `C ${h1} ${h2} ${p}`;
  58. }
  59. /**
  60. * Generate a function which will translate a point
  61. * from the data coordinate space to svg viewbox oriented pixels
  62. *
  63. * @param root0
  64. * @param root0.width
  65. * @param root0.height
  66. * @param root0.min
  67. * @param root0.max
  68. */
  69. export function translate({ width, height, min, max }) {
  70. const rangeX = max[0] - min[0];
  71. const rangeY = max[1] - min[1];
  72. return (point) => {
  73. const x = ((point[0] - min[0]) / rangeX) * width;
  74. const y = height - (point[1] / rangeY) * height;
  75. return [x, y];
  76. };
  77. }
  78. /**
  79. * Get the min and max values from the dataset
  80. *
  81. * @param data
  82. */
  83. export function range(data) {
  84. const [startX, startY] = data[0];
  85. const min = [startX, startY];
  86. const max = [startX, startY];
  87. return data.reduce(({ min, max }, [x, y]) => ({
  88. min: [Math.min(min[0], x), Math.min(min[1], y)],
  89. max: [Math.max(max[0], x), Math.max(max[1], y)]
  90. }), { min, max });
  91. }
  92. /**
  93. * Generate drawing commands for an area graph
  94. * returns a string can can be passed directly to a path element's `d` attribute
  95. *
  96. * @param root0
  97. * @param root0.data
  98. * @param root0.min
  99. * @param root0.max
  100. * @param root0.t
  101. */
  102. export function area({ data, min, max, t }) {
  103. if (data.length === 0) {
  104. return "";
  105. }
  106. // important points for beginning and ending the path
  107. const [startX, startY] = t(data[0]);
  108. const [minX, minY] = t(min);
  109. const [maxX] = t(max);
  110. // keep track of previous slope/points
  111. let m;
  112. let p0;
  113. let p1;
  114. // iterate over data points, calculating command for each
  115. const commands = data.reduce((acc, point, i) => {
  116. p0 = data[i - 2];
  117. p1 = data[i - 1];
  118. if (i > 1) {
  119. const m1 = slope(p0, p1, point);
  120. const m0 = m === undefined ? slopeSingle(p0, p1, m1) : m;
  121. const command = bezier(p0, p1, m0, m1, t);
  122. m = m1;
  123. return `${acc} ${command}`;
  124. }
  125. return acc;
  126. }, `M ${minX},${minY} L ${minX},${startY} L ${startX},${startY}`);
  127. // close the path
  128. const last = data[data.length - 1];
  129. const end = bezier(p1, last, m, slopeSingle(p1, last, m), t);
  130. return `${commands} ${end} L ${maxX},${minY} Z`;
  131. }