@coinbase/cds-web-visualization 3.3.2 → 3.4.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/CHANGELOG.md +50 -2
  2. package/dts/chart/CartesianChart.d.ts +72 -0
  3. package/dts/chart/CartesianChart.d.ts.map +1 -0
  4. package/dts/chart/ChartProvider.d.ts +6 -0
  5. package/dts/chart/ChartProvider.d.ts.map +1 -0
  6. package/dts/chart/Path.d.ts +54 -0
  7. package/dts/chart/Path.d.ts.map +1 -0
  8. package/dts/chart/PeriodSelector.d.ts +57 -0
  9. package/dts/chart/PeriodSelector.d.ts.map +1 -0
  10. package/dts/chart/area/Area.d.ts +78 -0
  11. package/dts/chart/area/Area.d.ts.map +1 -0
  12. package/dts/chart/area/AreaChart.d.ts +79 -0
  13. package/dts/chart/area/AreaChart.d.ts.map +1 -0
  14. package/dts/chart/area/DottedArea.d.ts +45 -0
  15. package/dts/chart/area/DottedArea.d.ts.map +1 -0
  16. package/dts/chart/area/GradientArea.d.ts +39 -0
  17. package/dts/chart/area/GradientArea.d.ts.map +1 -0
  18. package/dts/chart/area/SolidArea.d.ts +23 -0
  19. package/dts/chart/area/SolidArea.d.ts.map +1 -0
  20. package/dts/chart/area/index.d.ts +6 -0
  21. package/dts/chart/area/index.d.ts.map +1 -0
  22. package/dts/chart/axis/Axis.d.ts +255 -0
  23. package/dts/chart/axis/Axis.d.ts.map +1 -0
  24. package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
  25. package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
  26. package/dts/chart/axis/XAxis.d.ts +16 -0
  27. package/dts/chart/axis/XAxis.d.ts.map +1 -0
  28. package/dts/chart/axis/YAxis.d.ts +21 -0
  29. package/dts/chart/axis/YAxis.d.ts.map +1 -0
  30. package/dts/chart/axis/index.d.ts +5 -0
  31. package/dts/chart/axis/index.d.ts.map +1 -0
  32. package/dts/chart/bar/Bar.d.ts +94 -0
  33. package/dts/chart/bar/Bar.d.ts.map +1 -0
  34. package/dts/chart/bar/BarChart.d.ts +62 -0
  35. package/dts/chart/bar/BarChart.d.ts.map +1 -0
  36. package/dts/chart/bar/BarPlot.d.ts +30 -0
  37. package/dts/chart/bar/BarPlot.d.ts.map +1 -0
  38. package/dts/chart/bar/BarStack.d.ts +103 -0
  39. package/dts/chart/bar/BarStack.d.ts.map +1 -0
  40. package/dts/chart/bar/BarStackGroup.d.ts +36 -0
  41. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -0
  42. package/dts/chart/bar/DefaultBar.d.ts +17 -0
  43. package/dts/chart/bar/DefaultBar.d.ts.map +1 -0
  44. package/dts/chart/bar/DefaultBarStack.d.ts +16 -0
  45. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -0
  46. package/dts/chart/bar/index.d.ts +8 -0
  47. package/dts/chart/bar/index.d.ts.map +1 -0
  48. package/dts/chart/gradient/Gradient.d.ts +35 -0
  49. package/dts/chart/gradient/Gradient.d.ts.map +1 -0
  50. package/dts/chart/gradient/index.d.ts +2 -0
  51. package/dts/chart/gradient/index.d.ts.map +1 -0
  52. package/dts/chart/index.d.ts +14 -0
  53. package/dts/chart/index.d.ts.map +1 -0
  54. package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
  55. package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
  56. package/dts/chart/line/DottedLine.d.ts +26 -0
  57. package/dts/chart/line/DottedLine.d.ts.map +1 -0
  58. package/dts/chart/line/Line.d.ts +122 -0
  59. package/dts/chart/line/Line.d.ts.map +1 -0
  60. package/dts/chart/line/LineChart.d.ts +77 -0
  61. package/dts/chart/line/LineChart.d.ts.map +1 -0
  62. package/dts/chart/line/ReferenceLine.d.ts +178 -0
  63. package/dts/chart/line/ReferenceLine.d.ts.map +1 -0
  64. package/dts/chart/line/SolidLine.d.ts +25 -0
  65. package/dts/chart/line/SolidLine.d.ts.map +1 -0
  66. package/dts/chart/line/index.d.ts +7 -0
  67. package/dts/chart/line/index.d.ts.map +1 -0
  68. package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
  69. package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
  70. package/dts/chart/point/Point.d.ts +201 -0
  71. package/dts/chart/point/Point.d.ts.map +1 -0
  72. package/dts/chart/point/index.d.ts +3 -0
  73. package/dts/chart/point/index.d.ts.map +1 -0
  74. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +24 -0
  75. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
  76. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
  77. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
  78. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +10 -0
  79. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
  80. package/dts/chart/scrubber/Scrubber.d.ts +290 -0
  81. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -0
  82. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +70 -0
  83. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
  84. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +32 -0
  85. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
  86. package/dts/chart/scrubber/ScrubberProvider.d.ts +17 -0
  87. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -0
  88. package/dts/chart/scrubber/index.d.ts +5 -0
  89. package/dts/chart/scrubber/index.d.ts.map +1 -0
  90. package/dts/chart/text/ChartText.d.ts +117 -0
  91. package/dts/chart/text/ChartText.d.ts.map +1 -0
  92. package/dts/chart/text/ChartTextGroup.d.ts +61 -0
  93. package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
  94. package/dts/chart/text/index.d.ts +3 -0
  95. package/dts/chart/text/index.d.ts.map +1 -0
  96. package/dts/chart/utils/axis.d.ts +342 -0
  97. package/dts/chart/utils/axis.d.ts.map +1 -0
  98. package/dts/chart/utils/bar.d.ts +20 -0
  99. package/dts/chart/utils/bar.d.ts.map +1 -0
  100. package/dts/chart/utils/chart.d.ts +117 -0
  101. package/dts/chart/utils/chart.d.ts.map +1 -0
  102. package/dts/chart/utils/context.d.ts +101 -0
  103. package/dts/chart/utils/context.d.ts.map +1 -0
  104. package/dts/chart/utils/gradient.d.ts +104 -0
  105. package/dts/chart/utils/gradient.d.ts.map +1 -0
  106. package/dts/chart/utils/index.d.ts +12 -0
  107. package/dts/chart/utils/index.d.ts.map +1 -0
  108. package/dts/chart/utils/interpolate.d.ts +112 -0
  109. package/dts/chart/utils/interpolate.d.ts.map +1 -0
  110. package/dts/chart/utils/path.d.ts +130 -0
  111. package/dts/chart/utils/path.d.ts.map +1 -0
  112. package/dts/chart/utils/point.d.ts +104 -0
  113. package/dts/chart/utils/point.d.ts.map +1 -0
  114. package/dts/chart/utils/scale.d.ts +43 -0
  115. package/dts/chart/utils/scale.d.ts.map +1 -0
  116. package/dts/chart/utils/scrubber.d.ts +39 -0
  117. package/dts/chart/utils/scrubber.d.ts.map +1 -0
  118. package/dts/chart/utils/transition.d.ts +65 -0
  119. package/dts/chart/utils/transition.d.ts.map +1 -0
  120. package/dts/index.d.ts +1 -0
  121. package/dts/index.d.ts.map +1 -1
  122. package/dts/sparkline/Sparkline.d.ts +44 -9
  123. package/dts/sparkline/Sparkline.d.ts.map +1 -1
  124. package/dts/sparkline/SparklineArea.d.ts +4 -0
  125. package/dts/sparkline/SparklineArea.d.ts.map +1 -1
  126. package/dts/sparkline/SparklineAreaPattern.d.ts +5 -0
  127. package/dts/sparkline/SparklineAreaPattern.d.ts.map +1 -1
  128. package/dts/sparkline/SparklineGradient.d.ts +5 -0
  129. package/dts/sparkline/SparklineGradient.d.ts.map +1 -1
  130. package/dts/sparkline/generateSparklineWithId.d.ts +1 -0
  131. package/dts/sparkline/generateSparklineWithId.d.ts.map +1 -1
  132. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts +9 -0
  133. package/dts/sparkline/sparkline-interactive/SparklineInteractive.d.ts.map +1 -1
  134. package/dts/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.d.ts +3 -0
  135. package/dts/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.d.ts.map +1 -1
  136. package/dts/sparkline/sparkline-interactive/SparklineInteractivePaths.d.ts +2 -1
  137. package/dts/sparkline/sparkline-interactive/SparklineInteractivePaths.d.ts.map +1 -1
  138. package/esm/chart/CartesianChart.css +1 -0
  139. package/esm/chart/CartesianChart.js +313 -0
  140. package/esm/chart/ChartProvider.js +10 -0
  141. package/esm/chart/Path.js +95 -0
  142. package/esm/chart/PeriodSelector.css +1 -0
  143. package/esm/chart/PeriodSelector.js +112 -0
  144. package/esm/chart/area/Area.js +75 -0
  145. package/esm/chart/area/AreaChart.js +173 -0
  146. package/esm/chart/area/DottedArea.js +87 -0
  147. package/esm/chart/area/GradientArea.js +65 -0
  148. package/esm/chart/area/SolidArea.js +47 -0
  149. package/esm/chart/area/index.js +7 -0
  150. package/esm/chart/axis/Axis.js +25 -0
  151. package/esm/chart/axis/DefaultAxisTickLabel.js +15 -0
  152. package/esm/chart/axis/XAxis.css +2 -0
  153. package/esm/chart/axis/XAxis.js +219 -0
  154. package/esm/chart/axis/YAxis.css +2 -0
  155. package/esm/chart/axis/YAxis.js +214 -0
  156. package/esm/chart/axis/index.js +6 -0
  157. package/esm/chart/bar/Bar.js +61 -0
  158. package/esm/chart/bar/BarChart.js +130 -0
  159. package/esm/chart/bar/BarPlot.js +97 -0
  160. package/esm/chart/bar/BarStack.js +561 -0
  161. package/esm/chart/bar/BarStackGroup.js +86 -0
  162. package/esm/chart/bar/DefaultBar.js +61 -0
  163. package/esm/chart/bar/DefaultBarStack.js +58 -0
  164. package/esm/chart/bar/index.js +9 -0
  165. package/esm/chart/gradient/Gradient.js +104 -0
  166. package/esm/chart/gradient/index.js +1 -0
  167. package/esm/chart/index.js +15 -0
  168. package/esm/chart/line/DefaultReferenceLineLabel.js +81 -0
  169. package/esm/chart/line/DottedLine.js +59 -0
  170. package/esm/chart/line/Line.js +185 -0
  171. package/esm/chart/line/LineChart.js +132 -0
  172. package/esm/chart/line/ReferenceLine.js +140 -0
  173. package/esm/chart/line/SolidLine.js +55 -0
  174. package/esm/chart/line/index.js +8 -0
  175. package/esm/chart/point/DefaultPointLabel.js +44 -0
  176. package/esm/chart/point/Point.css +2 -0
  177. package/esm/chart/point/Point.js +180 -0
  178. package/esm/chart/point/index.js +2 -0
  179. package/esm/chart/scrubber/DefaultScrubberBeacon.js +155 -0
  180. package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +46 -0
  181. package/esm/chart/scrubber/DefaultScrubberLabel.js +30 -0
  182. package/esm/chart/scrubber/Scrubber.js +189 -0
  183. package/esm/chart/scrubber/ScrubberBeaconGroup.js +166 -0
  184. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +186 -0
  185. package/esm/chart/scrubber/ScrubberProvider.js +228 -0
  186. package/esm/chart/scrubber/index.js +4 -0
  187. package/esm/chart/text/ChartText.js +230 -0
  188. package/esm/chart/text/ChartTextGroup.js +227 -0
  189. package/esm/chart/text/index.js +4 -0
  190. package/esm/chart/utils/axis.js +593 -0
  191. package/esm/chart/utils/bar.js +24 -0
  192. package/esm/chart/utils/chart.js +255 -0
  193. package/esm/chart/utils/context.js +15 -0
  194. package/esm/chart/utils/gradient.js +257 -0
  195. package/esm/chart/utils/index.js +13 -0
  196. package/esm/chart/utils/interpolate.js +644 -0
  197. package/esm/chart/utils/path.js +227 -0
  198. package/esm/chart/utils/point.js +187 -0
  199. package/esm/chart/utils/scale.js +48 -0
  200. package/esm/chart/utils/scrubber.js +132 -0
  201. package/esm/chart/utils/transition.js +111 -0
  202. package/esm/index.js +4 -1
  203. package/esm/sparkline/Sparkline.js +129 -15
  204. package/esm/sparkline/SparklineArea.js +7 -2
  205. package/esm/sparkline/SparklineAreaPattern.js +4 -2
  206. package/esm/sparkline/SparklineGradient.js +16 -58
  207. package/esm/sparkline/generateSparklineWithId.js +3 -2
  208. package/esm/sparkline/sparkline-interactive/SparklineInteractive.js +5 -1
  209. package/esm/sparkline/sparkline-interactive/SparklineInteractiveAnimatedPath.js +5 -2
  210. package/esm/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.js +1 -1
  211. package/esm/sparkline/sparkline-interactive/SparklineInteractivePaths.js +4 -0
  212. package/package.json +13 -9
@@ -0,0 +1,227 @@
1
+ import { area as d3Area, curveBumpX, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveNatural, curveStep, curveStepAfter, curveStepBefore, line as d3Line } from 'd3-shape';
2
+ import { projectPoint, projectPoints } from './point';
3
+ import { isCategoricalScale } from './scale';
4
+ /**
5
+ * Get the d3 curve function for a path.
6
+ * See https://d3js.org/d3-shape/curve
7
+ * @param curve - The curve type. Defaults to 'linear'.
8
+ * @returns The d3 curve function.
9
+ */
10
+ export const getPathCurveFunction = function () {
11
+ let curve = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'linear';
12
+ switch (curve) {
13
+ case 'catmullRom':
14
+ return curveCatmullRom;
15
+ case 'monotone':
16
+ // When we support layout="vertical" this should dynamically switch to curveMonotoneY
17
+ return curveMonotoneX;
18
+ case 'natural':
19
+ return curveNatural;
20
+ case 'step':
21
+ return curveStep;
22
+ case 'stepBefore':
23
+ return curveStepBefore;
24
+ case 'stepAfter':
25
+ return curveStepAfter;
26
+ case 'bump':
27
+ // When we support layout="vertical" this should dynamically switch to curveBumpY
28
+ return curveBumpX;
29
+ case 'linearClosed':
30
+ return curveLinearClosed;
31
+ case 'linear':
32
+ default:
33
+ return curveLinear;
34
+ }
35
+ };
36
+
37
+ /**
38
+ * Generates an SVG line path string from data using chart scale functions.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const chartScale = getChartScale({ chartRect, domain, range, xScale, yScale });
43
+ * const path = getLinePath({ data: [1, 2, 3], chartScale, curve: 'bump' });
44
+ * ```
45
+ */
46
+ export const getLinePath = _ref => {
47
+ var _pathGenerator;
48
+ let {
49
+ data,
50
+ curve = 'bump',
51
+ xScale,
52
+ yScale,
53
+ xData,
54
+ connectNulls
55
+ } = _ref;
56
+ if (data.length === 0) {
57
+ return '';
58
+ }
59
+ const curveFunction = getPathCurveFunction(curve);
60
+ const dataPoints = projectPoints({
61
+ data,
62
+ xScale,
63
+ yScale,
64
+ xData
65
+ });
66
+
67
+ // When connectNulls is true, filter out null values before rendering
68
+ // When false, use defined() to create gaps in the line
69
+ const filteredPoints = connectNulls ? dataPoints.filter(d => d !== null) : dataPoints;
70
+ const pathGenerator = d3Line().x(d => d.x).y(d => d.y).curve(curveFunction).defined(d => connectNulls || d !== null);
71
+ return (_pathGenerator = pathGenerator(filteredPoints)) !== null && _pathGenerator !== void 0 ? _pathGenerator : '';
72
+ };
73
+
74
+ /**
75
+ * Generates an SVG area path string from data using chart scale functions.
76
+ * Supports both single values (area from baseline to value) and tuples ([baseline, value]).
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // Single values - area from baseline to value
81
+ * const area = getAreaPath({
82
+ * data: [1, 2, 3],
83
+ * xScale,
84
+ * yScale,
85
+ * });
86
+ *
87
+ * // Range values - area from low to high
88
+ * const rangeArea = getAreaPath({
89
+ * data: [[0, 3], [2, 4], [1, 5]],
90
+ * xScale,
91
+ * yScale,
92
+ * curve: 'monotone'
93
+ * });
94
+ * ```
95
+ */
96
+ export const getAreaPath = _ref2 => {
97
+ let {
98
+ data,
99
+ curve = 'bump',
100
+ xScale,
101
+ yScale,
102
+ xData,
103
+ connectNulls
104
+ } = _ref2;
105
+ if (data.length === 0) {
106
+ return '';
107
+ }
108
+ const curveFunction = getPathCurveFunction(curve);
109
+ const yDomain = yScale.domain();
110
+ const yMin = Math.min(...yDomain);
111
+ const normalizedData = data.map((item, index) => {
112
+ if (item === null) {
113
+ return null;
114
+ }
115
+ if (Array.isArray(item)) {
116
+ if (item.length >= 2 && typeof item[0] === 'number' && typeof item[1] === 'number') {
117
+ return [item[0], item[1]];
118
+ }
119
+ return null;
120
+ }
121
+ if (typeof item === 'number') {
122
+ return [yMin, item];
123
+ }
124
+ return null;
125
+ });
126
+ const dataPoints = normalizedData.map((range, index) => {
127
+ if (range === null) {
128
+ return {
129
+ x: 0,
130
+ low: null,
131
+ high: null,
132
+ isValid: false
133
+ };
134
+ }
135
+ let xValue = index;
136
+ if (!isCategoricalScale(xScale) && xData && xData[index] !== undefined) {
137
+ xValue = xData[index];
138
+ }
139
+ const xPoint = projectPoint({
140
+ x: xValue,
141
+ y: 0,
142
+ xScale,
143
+ yScale
144
+ });
145
+ const lowPoint = projectPoint({
146
+ x: xValue,
147
+ y: range[0],
148
+ xScale,
149
+ yScale
150
+ });
151
+ const highPoint = projectPoint({
152
+ x: xValue,
153
+ y: range[1],
154
+ xScale,
155
+ yScale
156
+ });
157
+ return {
158
+ x: xPoint.x,
159
+ low: lowPoint.y,
160
+ high: highPoint.y,
161
+ isValid: true
162
+ };
163
+ });
164
+
165
+ // When connectNulls is true, filter out invalid points before rendering
166
+ // When false, use defined() to create gaps in the area
167
+ const filteredPoints = connectNulls ? dataPoints.filter(d => d.isValid) : dataPoints;
168
+ const areaGenerator = d3Area().x(d => d.x).y0(d => {
169
+ var _d$low;
170
+ return (_d$low = d.low) !== null && _d$low !== void 0 ? _d$low : 0;
171
+ }) // Bottom boundary (low values), fallback to 0
172
+ .y1(d => {
173
+ var _d$high;
174
+ return (_d$high = d.high) !== null && _d$high !== void 0 ? _d$high : 0;
175
+ }) // Top boundary (high values), fallback to 0
176
+ .curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null); // Only draw where both values exist
177
+
178
+ const result = areaGenerator(filteredPoints);
179
+ return result !== null && result !== void 0 ? result : '';
180
+ };
181
+
182
+ /**
183
+ * Converts line coordinates to an SVG path string.
184
+ * Useful for rendering axis lines and tick marks.
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * const path = lineToPath(0, 0, 100, 100);
189
+ * // Returns: "M 0 0 L 100 100"
190
+ * ```
191
+ */
192
+ export const lineToPath = (x1, y1, x2, y2) => {
193
+ return "M".concat(x1, ",").concat(y1, " L").concat(x2, ",").concat(y2);
194
+ };
195
+
196
+ /**
197
+ * Creates an SVG path string for a rectangle with selective corner rounding.
198
+ * Useful for creating bars in charts with optional rounded corners.
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * // Simple rectangle bar
203
+ * const barPath = getBarPath(10, 20, 50, 100, 0, false, false);
204
+ *
205
+ * // Bar with rounded top corners
206
+ * const roundedPath = getBarPath(10, 20, 50, 100, 8, true, false);
207
+ * ```
208
+ */
209
+ export const getBarPath = (x, y, width, height, radius, roundTop, roundBottom) => {
210
+ const roundBothSides = roundTop && roundBottom;
211
+ const r = Math.min(radius, width / 2, roundBothSides ? height / 2 : height);
212
+ const topR = roundTop ? r : 0;
213
+ const bottomR = roundBottom ? r : 0;
214
+
215
+ // Build path with selective rounding
216
+ let path = "M ".concat(x + (roundTop ? r : 0), " ").concat(y);
217
+ path += " L ".concat(x + width - topR, " ").concat(y);
218
+ path += " A ".concat(topR, " ").concat(topR, " 0 0 1 ").concat(x + width, " ").concat(y + topR);
219
+ path += " L ".concat(x + width, " ").concat(y + height - bottomR);
220
+ path += " A ".concat(bottomR, " ").concat(bottomR, " 0 0 1 ").concat(x + width - bottomR, " ").concat(y + height);
221
+ path += " L ".concat(x + bottomR, " ").concat(y + height);
222
+ path += " A ".concat(bottomR, " ").concat(bottomR, " 0 0 1 ").concat(x, " ").concat(y + height - bottomR);
223
+ path += " L ".concat(x, " ").concat(y + topR);
224
+ path += " A ".concat(topR, " ").concat(topR, " 0 0 1 ").concat(x + topR, " ").concat(y);
225
+ path += ' Z';
226
+ return path;
227
+ };
@@ -0,0 +1,187 @@
1
+ import { isCategoricalScale, isLogScale, isNumericScale } from './scale';
2
+
3
+ /**
4
+ * Position a label should be placed relative to the point
5
+ *
6
+ * @example
7
+ * 'top' would have the label be located above the point itself,
8
+ * and thus the vertical alignment of that text would be bottom.
9
+ */
10
+
11
+ /**
12
+ * Get a point from a data value and a scale.
13
+ * @note for categorical scales, the point will be centered within the band.
14
+ * @note for log scales, zero and negative values are clamped to a small positive value.
15
+ * @param data - the data value.
16
+ * @param scale - the scale function.
17
+ * @returns the pixel value (defaulting to 0 if data value is not defined in scale).
18
+ */
19
+ export const getPointOnScale = (dataValue, scale) => {
20
+ var _scale2;
21
+ if (isCategoricalScale(scale)) {
22
+ var _scale, _scale$bandwidth;
23
+ const bandStart = (_scale = scale(dataValue)) !== null && _scale !== void 0 ? _scale : 0;
24
+ const bandwidth = (_scale$bandwidth = scale.bandwidth()) !== null && _scale$bandwidth !== void 0 ? _scale$bandwidth : 0;
25
+ return bandStart + bandwidth / 2;
26
+ }
27
+
28
+ // For log scales, ensure the value is positive
29
+ let adjustedValue = dataValue;
30
+ if (isLogScale(scale) && dataValue <= 0) {
31
+ adjustedValue = 0.001; // Use a small positive value for log scales
32
+ }
33
+ return (_scale2 = scale(adjustedValue)) !== null && _scale2 !== void 0 ? _scale2 : 0;
34
+ };
35
+
36
+ /**
37
+ * Projects a data point to pixel coordinates using the chart scale.
38
+ * Automatically handles log scale transformations for zero/negative values.
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const chartScale = getChartScale({ chartRect, domain, range, xScale, yScale });
43
+ * const pixelCoord = projectPoint({ x: 5, y: 10, chartScale });
44
+ * ```
45
+ * @example
46
+ * ```typescript
47
+ * const chartScale = getChartScale({ chartRect, domain, range, xScale, yScale });
48
+ * const pixelCoord = projectPoint({ x: 2, y: 10, chartScale, xData: ['Jan', 'Feb', 'Mar'] });
49
+ * ```
50
+ */
51
+ export const projectPoint = _ref => {
52
+ let {
53
+ x,
54
+ y,
55
+ xScale,
56
+ yScale
57
+ } = _ref;
58
+ return {
59
+ x: getPointOnScale(x, xScale),
60
+ y: getPointOnScale(y, yScale)
61
+ };
62
+ };
63
+
64
+ /**
65
+ * Projects multiple data points to pixel coordinates using chart scale functions.
66
+ * Handles both numeric and band scales automatically.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const chartScale = getChartScale({ chartRect, domain, range, xScale, yScale });
71
+ * const pixelPoints = projectPoints({ data, chartScale });
72
+ * // For mixed scales
73
+ * const pixelPoints = projectPoints({ data, chartScale, xData: ['Jan', 'Feb', 'Mar'] });
74
+ * ```
75
+ */
76
+ export const projectPoints = _ref2 => {
77
+ let {
78
+ data,
79
+ xScale,
80
+ yScale,
81
+ xData,
82
+ yData
83
+ } = _ref2;
84
+ if (data.length === 0) {
85
+ return [];
86
+ }
87
+ return data.map((value, index) => {
88
+ if (value === null) {
89
+ return null;
90
+ }
91
+ if (typeof value === 'object' && 'x' in value && 'y' in value) {
92
+ return projectPoint({
93
+ x: value.x,
94
+ y: value.y,
95
+ xScale,
96
+ yScale
97
+ });
98
+ }
99
+
100
+ // For scales with axis data, determine the correct x value
101
+ let xValue = index;
102
+
103
+ // For band scales, always use the index
104
+ if (!isCategoricalScale(xScale)) {
105
+ // For numeric scales with axis data, use the axis data values instead of indices
106
+ if (xData && Array.isArray(xData) && xData.length > 0) {
107
+ // Check if it's numeric data
108
+ if (typeof xData[0] === 'number') {
109
+ var _numericXData$index;
110
+ const numericXData = xData;
111
+ xValue = (_numericXData$index = numericXData[index]) !== null && _numericXData$index !== void 0 ? _numericXData$index : index;
112
+ }
113
+ }
114
+ }
115
+ let yValue = value;
116
+ if (isNumericScale(yScale) && yData && Array.isArray(yData) && yData.length > 0 && typeof yData[0] === 'number' && typeof value === 'number') {
117
+ yValue = value;
118
+ }
119
+ return projectPoint({
120
+ x: xValue,
121
+ y: yValue,
122
+ xScale,
123
+ yScale
124
+ });
125
+ });
126
+ };
127
+
128
+ /**
129
+ * Determines text alignment based on label position.
130
+ * For example, a 'top' position needs the text aligned to the 'bottom' so it appears above the point.
131
+ */
132
+ export const getAlignmentFromPosition = position => {
133
+ let horizontalAlignment = 'center';
134
+ let verticalAlignment = 'middle';
135
+ switch (position) {
136
+ case 'top':
137
+ verticalAlignment = 'bottom';
138
+ break;
139
+ case 'bottom':
140
+ verticalAlignment = 'top';
141
+ break;
142
+ case 'left':
143
+ horizontalAlignment = 'right';
144
+ break;
145
+ case 'right':
146
+ horizontalAlignment = 'left';
147
+ break;
148
+ case 'center':
149
+ default:
150
+ horizontalAlignment = 'center';
151
+ verticalAlignment = 'middle';
152
+ break;
153
+ }
154
+ return {
155
+ horizontalAlignment,
156
+ verticalAlignment
157
+ };
158
+ };
159
+
160
+ /**
161
+ * Calculates the final label coordinates by applying offset based on position.
162
+ */
163
+ export const getLabelCoordinates = (x, y, position, offset) => {
164
+ let dx = 0;
165
+ let dy = 0;
166
+ switch (position) {
167
+ case 'top':
168
+ dy = -offset;
169
+ break;
170
+ case 'bottom':
171
+ dy = offset;
172
+ break;
173
+ case 'left':
174
+ dx = -offset;
175
+ break;
176
+ case 'right':
177
+ dx = offset;
178
+ break;
179
+ case 'center':
180
+ default:
181
+ break;
182
+ }
183
+ return {
184
+ x: x + dx,
185
+ y: y + dy
186
+ };
187
+ };
@@ -0,0 +1,48 @@
1
+ import { scaleBand, scaleLinear, scaleLog } from 'd3-scale';
2
+
3
+ // https://d3js.org/d3-scale - ideal next scale would be time
4
+
5
+ export const isCategoricalScale = scale => {
6
+ return scale !== undefined && 'bandwidth' in scale && typeof scale.bandwidth === 'function';
7
+ };
8
+ export const isNumericScale = scale => {
9
+ return scale !== undefined && !isCategoricalScale(scale);
10
+ };
11
+
12
+ /**
13
+ * Type guard to check if a scale is logarithmic.
14
+ */
15
+ export const isLogScale = scale => {
16
+ return scale !== undefined && 'base' in scale && typeof scale.base === 'function';
17
+ };
18
+
19
+ /**
20
+ * Create a numeric scale (linear or logarithmic)
21
+ * @returns A numeric scale function
22
+ */
23
+ export const getNumericScale = _ref => {
24
+ let {
25
+ scaleType,
26
+ domain,
27
+ range
28
+ } = _ref;
29
+ const scale = scaleType === 'log' ? scaleLog() : scaleLinear();
30
+ return scale.domain([domain.min, domain.max]).range([range.min, range.max]);
31
+ };
32
+
33
+ /**
34
+ * Create a categorical scale (band)
35
+ * @returns A categorical scale function
36
+ */
37
+ export const getCategoricalScale = _ref2 => {
38
+ let {
39
+ domain,
40
+ range,
41
+ padding = 0.1
42
+ } = _ref2;
43
+ const domainArray = Array.from({
44
+ length: domain.max - domain.min + 1
45
+ }, (_, i) => i);
46
+ const scale = scaleBand().domain(domainArray).range([range.min, range.max]).padding(padding);
47
+ return scale;
48
+ };
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Determines which side (left/right) to place scrubber labels based on available space.
3
+ * Prefers right side, switches to left when labels would overflow.
4
+ */
5
+ export const getLabelPosition = function (beaconX, maxLabelWidth, drawingArea) {
6
+ let xOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 16;
7
+ if (drawingArea.width <= 0 || drawingArea.height <= 0) {
8
+ return 'right';
9
+ }
10
+ const availableRightSpace = drawingArea.x + drawingArea.width - beaconX;
11
+ const requiredSpace = maxLabelWidth + xOffset;
12
+ return requiredSpace <= availableRightSpace ? 'right' : 'left';
13
+ };
14
+ /**
15
+ * Calculates Y positions for all labels avoiding overlaps while maintaining order.
16
+ */
17
+ export const calculateLabelYPositions = (dimensions, drawingArea, labelHeight, minGap) => {
18
+ if (dimensions.length === 0) {
19
+ return new Map();
20
+ }
21
+
22
+ // Sort by preferred Y values and create working labels
23
+ const sortedLabels = [...dimensions].sort((a, b) => a.preferredY - b.preferredY).map(dim => ({
24
+ seriesId: dim.seriesId,
25
+ preferredY: dim.preferredY,
26
+ finalY: dim.preferredY
27
+ }));
28
+
29
+ // Initial bounds fitting
30
+ const minY = drawingArea.y + labelHeight / 2;
31
+ const maxY = drawingArea.y + drawingArea.height - labelHeight / 2;
32
+ const requiredDistance = labelHeight + minGap;
33
+ for (const label of sortedLabels) {
34
+ // Clamp each label to the drawing area
35
+ label.finalY = Math.max(minY, Math.min(maxY, label.preferredY));
36
+ }
37
+
38
+ // First pass: push down any overlapping labels
39
+ for (let i = 1; i < sortedLabels.length; i++) {
40
+ const prev = sortedLabels[i - 1];
41
+ const current = sortedLabels[i];
42
+ const minAllowedY = prev.finalY + requiredDistance;
43
+ if (current.finalY < minAllowedY) {
44
+ current.finalY = minAllowedY;
45
+ }
46
+ }
47
+
48
+ // Find collision groups - groups of labels that are tightly packed (gap < minGap between them)
49
+ const collisionGroups = [];
50
+ let currentGroup = [sortedLabels[0]];
51
+ for (let i = 1; i < sortedLabels.length; i++) {
52
+ const prev = sortedLabels[i - 1];
53
+ const current = sortedLabels[i];
54
+ const gap = current.finalY - prev.finalY - labelHeight;
55
+ if (gap < minGap + 0.01) {
56
+ // Labels are touching or very close - part of same collision group
57
+ currentGroup.push(current);
58
+ } else {
59
+ // Gap is large enough - start new group
60
+ collisionGroups.push(currentGroup);
61
+ currentGroup = [current];
62
+ }
63
+ }
64
+ collisionGroups.push(currentGroup);
65
+
66
+ // Process each collision group - optimize positioning to minimize displacement
67
+ for (const group of collisionGroups) {
68
+ if (group.length === 1) {
69
+ // Single label, already at best position
70
+ continue;
71
+ }
72
+ const groupLastLabel = group[group.length - 1];
73
+ const groupFirstLabel = group[0];
74
+ const groupOverflow = groupLastLabel.finalY + labelHeight / 2 - (drawingArea.y + drawingArea.height);
75
+
76
+ // Calculate the ideal center point for this group
77
+ const groupPreferredCenter = group.reduce((sum, label) => sum + label.preferredY, 0) / group.length;
78
+ const groupTotalNeeded = group.length * labelHeight + (group.length - 1) * minGap;
79
+ if (groupOverflow <= 0) {
80
+ // Group fits, but let's center it better if possible
81
+ // Calculate how much we can shift up/down to center around preferred positions
82
+ const currentCenter = (groupFirstLabel.finalY + groupLastLabel.finalY) / 2;
83
+ const desiredShift = groupPreferredCenter - currentCenter;
84
+
85
+ // Calculate max shift in each direction
86
+ const maxShiftUp = groupFirstLabel.finalY - minY;
87
+ const maxShiftDown = maxY - groupLastLabel.finalY;
88
+
89
+ // Apply the shift, constrained by boundaries
90
+ const actualShift = Math.max(-maxShiftUp, Math.min(maxShiftDown, desiredShift));
91
+ if (Math.abs(actualShift) > 0.01) {
92
+ for (const label of group) {
93
+ label.finalY += actualShift;
94
+ }
95
+ }
96
+ } else {
97
+ // Group overflows - need to adjust
98
+ const groupStartY = groupFirstLabel.finalY - labelHeight / 2;
99
+ const availableSpace = drawingArea.y + drawingArea.height - groupStartY;
100
+ const maxShiftUp = groupFirstLabel.finalY - minY;
101
+ if (maxShiftUp >= groupOverflow) {
102
+ // Can shift entire group up to fit
103
+ for (const label of group) {
104
+ label.finalY -= groupOverflow;
105
+ }
106
+ } else if (groupTotalNeeded <= availableSpace) {
107
+ // Can't shift enough, but there's room - redistribute with proper spacing
108
+ let currentY = Math.max(minY, groupFirstLabel.finalY - maxShiftUp);
109
+ const gap = (availableSpace - group.length * labelHeight) / Math.max(1, group.length - 1);
110
+ for (const label of group) {
111
+ label.finalY = currentY;
112
+ currentY += labelHeight + gap;
113
+ }
114
+ } else {
115
+ // Not enough space even with compression - compress gaps and fit to bottom
116
+ const compressedGap = Math.max(1, (availableSpace - group.length * labelHeight) / Math.max(1, group.length - 1));
117
+ // Position so last label is at maxY
118
+ let currentY = maxY - (group.length - 1) * (labelHeight + compressedGap);
119
+ currentY = Math.max(minY, currentY);
120
+ for (const label of group) {
121
+ label.finalY = currentY;
122
+ currentY += labelHeight + compressedGap;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ const result = new Map();
128
+ for (const label of sortedLabels) {
129
+ result.set(label.seriesId, label.finalY);
130
+ }
131
+ return result;
132
+ };
@@ -0,0 +1,111 @@
1
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
2
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
3
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
4
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
5
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
6
+ import { useEffect, useRef } from 'react';
7
+ import { interpolatePath } from 'd3-interpolate-path';
8
+ import { animate, useMotionValue, useTransform } from 'framer-motion';
9
+
10
+ /**
11
+ * Default transition configuration used across all chart components.
12
+ */
13
+ export const defaultTransition = {
14
+ type: 'spring',
15
+ stiffness: 900,
16
+ damping: 120,
17
+ mass: 4
18
+ };
19
+
20
+ /**
21
+ * Duration in seconds for accessory elements to fade in.
22
+ */
23
+ export const accessoryFadeTransitionDuration = 0.15;
24
+
25
+ /**
26
+ * Delay in seconds before accessory elements fade in.
27
+ */
28
+ export const accessoryFadeTransitionDelay = 0.35;
29
+
30
+ /**
31
+ * Hook for path animation state and transitions.
32
+ *
33
+ * @param currentPath - Current target path to animate to
34
+ * @param initialPath - Initial path for enter animation. When provided, the first animation will go from initialPath to currentPath.
35
+ * @param transition - Transition configuration
36
+ * @returns MotionValue containing the current interpolated path string
37
+ *
38
+ * @example
39
+ * // Simple path transition
40
+ * const animatedPath = usePathTransition({
41
+ * currentPath: d ?? '',
42
+ * transition: {
43
+ * type: 'spring',
44
+ * stiffness: 300,
45
+ * damping: 20
46
+ * }
47
+ * });
48
+ *
49
+ * @example
50
+ * // Time based animation
51
+ * const animatedPath = usePathTransition({
52
+ * currentPath: targetPath,
53
+ * initialPath: baselinePath,
54
+ * transition: {
55
+ * type: 'tween',
56
+ * duration: 0.3,
57
+ * ease: 'easeInOut'
58
+ * }
59
+ * });
60
+ */
61
+ export const usePathTransition = _ref => {
62
+ let {
63
+ currentPath,
64
+ initialPath,
65
+ transition = defaultTransition
66
+ } = _ref;
67
+ const isInitialRender = useRef(true);
68
+ const previousPathRef = useRef(initialPath !== null && initialPath !== void 0 ? initialPath : currentPath);
69
+ const targetPathRef = useRef(currentPath);
70
+ const animationRef = useRef(null);
71
+ const progress = useMotionValue(0);
72
+
73
+ // Derive the interpolated path from progress using useTransform
74
+ const interpolatedPath = useTransform(progress, latest => {
75
+ const pathInterpolator = interpolatePath(previousPathRef.current, targetPathRef.current);
76
+ return pathInterpolator(latest);
77
+ });
78
+ useEffect(() => {
79
+ // Only proceed if the target path has actually changed
80
+ if (targetPathRef.current !== currentPath) {
81
+ // Cancel any ongoing animation before starting a new one
82
+ const wasAnimating = !!animationRef.current;
83
+ if (animationRef.current) {
84
+ animationRef.current.cancel();
85
+ animationRef.current = null;
86
+ }
87
+ const currentInterpolatedPath = interpolatedPath.get();
88
+
89
+ // If we were animating and the interpolated path is different from both start and end,
90
+ // use it as the starting point for the next animation (smooth interruption)
91
+ const isInterpolatedPosition = currentInterpolatedPath !== previousPathRef.current && currentInterpolatedPath !== currentPath;
92
+ if (wasAnimating && isInterpolatedPosition) {
93
+ previousPathRef.current = currentInterpolatedPath;
94
+ }
95
+ targetPathRef.current = currentPath;
96
+ progress.set(0);
97
+ animationRef.current = animate(progress, 1, _objectSpread(_objectSpread({}, transition), {}, {
98
+ onComplete: () => {
99
+ previousPathRef.current = currentPath;
100
+ }
101
+ }));
102
+ isInitialRender.current = false;
103
+ }
104
+ return () => {
105
+ if (animationRef.current) {
106
+ animationRef.current.cancel();
107
+ }
108
+ };
109
+ }, [currentPath, transition, progress, interpolatedPath]);
110
+ return interpolatedPath;
111
+ };