@coinbase/cds-mobile-visualization 3.4.0-beta.5 → 3.4.0-beta.6

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 (179) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dts/chart/CartesianChart.d.ts +57 -33
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/ChartContextBridge.d.ts +28 -0
  5. package/dts/chart/ChartContextBridge.d.ts.map +1 -0
  6. package/dts/chart/Path.d.ts +77 -34
  7. package/dts/chart/Path.d.ts.map +1 -1
  8. package/dts/chart/PeriodSelector.d.ts +1 -1
  9. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  10. package/dts/chart/area/Area.d.ts +42 -27
  11. package/dts/chart/area/Area.d.ts.map +1 -1
  12. package/dts/chart/area/AreaChart.d.ts +51 -10
  13. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  14. package/dts/chart/area/DottedArea.d.ts +21 -2
  15. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  16. package/dts/chart/area/GradientArea.d.ts +19 -13
  17. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  18. package/dts/chart/area/SolidArea.d.ts +17 -2
  19. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  20. package/dts/chart/axis/Axis.d.ts +68 -78
  21. package/dts/chart/axis/Axis.d.ts.map +1 -1
  22. package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
  23. package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
  24. package/dts/chart/axis/XAxis.d.ts +1 -1
  25. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  26. package/dts/chart/axis/YAxis.d.ts +2 -2
  27. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  28. package/dts/chart/axis/index.d.ts +1 -0
  29. package/dts/chart/axis/index.d.ts.map +1 -1
  30. package/dts/chart/bar/Bar.d.ts +16 -13
  31. package/dts/chart/bar/Bar.d.ts.map +1 -1
  32. package/dts/chart/bar/BarChart.d.ts +36 -20
  33. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  34. package/dts/chart/bar/BarPlot.d.ts +2 -1
  35. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  36. package/dts/chart/bar/BarStack.d.ts +39 -48
  37. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  38. package/dts/chart/bar/BarStackGroup.d.ts +1 -0
  39. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  40. package/dts/chart/bar/DefaultBar.d.ts +1 -1
  41. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  42. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  43. package/dts/chart/gradient/Gradient.d.ts +25 -0
  44. package/dts/chart/gradient/Gradient.d.ts.map +1 -0
  45. package/dts/chart/gradient/index.d.ts +2 -0
  46. package/dts/chart/gradient/index.d.ts.map +1 -0
  47. package/dts/chart/index.d.ts +3 -1
  48. package/dts/chart/index.d.ts.map +1 -1
  49. package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
  50. package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
  51. package/dts/chart/line/DottedLine.d.ts +13 -5
  52. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  53. package/dts/chart/line/Line.d.ts +62 -25
  54. package/dts/chart/line/Line.d.ts.map +1 -1
  55. package/dts/chart/line/LineChart.d.ts +43 -9
  56. package/dts/chart/line/LineChart.d.ts.map +1 -1
  57. package/dts/chart/line/ReferenceLine.d.ts +65 -22
  58. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  59. package/dts/chart/line/SolidLine.d.ts +8 -5
  60. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  61. package/dts/chart/line/index.d.ts +1 -1
  62. package/dts/chart/line/index.d.ts.map +1 -1
  63. package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
  64. package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
  65. package/dts/chart/point/Point.d.ts +120 -0
  66. package/dts/chart/point/Point.d.ts.map +1 -0
  67. package/dts/chart/point/index.d.ts +3 -0
  68. package/dts/chart/point/index.d.ts.map +1 -0
  69. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +8 -0
  70. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
  71. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
  72. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
  73. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
  74. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
  75. package/dts/chart/scrubber/Scrubber.d.ts +168 -41
  76. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  77. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +44 -0
  78. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
  79. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +31 -0
  80. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
  81. package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
  82. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  83. package/dts/chart/scrubber/index.d.ts +3 -0
  84. package/dts/chart/scrubber/index.d.ts.map +1 -1
  85. package/dts/chart/text/ChartText.d.ts +151 -77
  86. package/dts/chart/text/ChartText.d.ts.map +1 -1
  87. package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
  88. package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
  89. package/dts/chart/text/index.d.ts +1 -1
  90. package/dts/chart/text/index.d.ts.map +1 -1
  91. package/dts/chart/utils/chart.d.ts +34 -7
  92. package/dts/chart/utils/chart.d.ts.map +1 -1
  93. package/dts/chart/utils/context.d.ts +28 -7
  94. package/dts/chart/utils/context.d.ts.map +1 -1
  95. package/dts/chart/utils/gradient.d.ts +117 -0
  96. package/dts/chart/utils/gradient.d.ts.map +1 -0
  97. package/dts/chart/utils/index.d.ts +3 -0
  98. package/dts/chart/utils/index.d.ts.map +1 -1
  99. package/dts/chart/utils/path.d.ts +53 -0
  100. package/dts/chart/utils/path.d.ts.map +1 -1
  101. package/dts/chart/utils/point.d.ts +60 -1
  102. package/dts/chart/utils/point.d.ts.map +1 -1
  103. package/dts/chart/utils/scale.d.ts +91 -0
  104. package/dts/chart/utils/scale.d.ts.map +1 -1
  105. package/dts/chart/utils/scrubber.d.ts +39 -0
  106. package/dts/chart/utils/scrubber.d.ts.map +1 -0
  107. package/dts/chart/utils/transition.d.ts +140 -0
  108. package/dts/chart/utils/transition.d.ts.map +1 -0
  109. package/esm/chart/CartesianChart.js +164 -70
  110. package/esm/chart/ChartContextBridge.js +148 -0
  111. package/esm/chart/Path.js +196 -113
  112. package/esm/chart/PeriodSelector.js +1 -1
  113. package/esm/chart/__stories__/CartesianChart.stories.js +371 -129
  114. package/esm/chart/__stories__/Chart.stories.js +2 -4
  115. package/esm/chart/area/Area.js +25 -35
  116. package/esm/chart/area/AreaChart.js +17 -12
  117. package/esm/chart/area/DottedArea.js +61 -109
  118. package/esm/chart/area/GradientArea.js +35 -91
  119. package/esm/chart/area/SolidArea.js +22 -8
  120. package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
  121. package/esm/chart/axis/Axis.js +2 -0
  122. package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
  123. package/esm/chart/axis/XAxis.js +62 -56
  124. package/esm/chart/axis/YAxis.js +58 -52
  125. package/esm/chart/axis/__stories__/Axis.stories.js +0 -1
  126. package/esm/chart/axis/index.js +1 -0
  127. package/esm/chart/bar/Bar.js +3 -1
  128. package/esm/chart/bar/BarChart.js +15 -37
  129. package/esm/chart/bar/BarPlot.js +41 -35
  130. package/esm/chart/bar/BarStack.js +75 -38
  131. package/esm/chart/bar/BarStackGroup.js +6 -16
  132. package/esm/chart/bar/DefaultBar.js +26 -48
  133. package/esm/chart/bar/DefaultBarStack.js +23 -58
  134. package/esm/chart/bar/__stories__/BarChart.stories.js +463 -77
  135. package/esm/chart/gradient/Gradient.js +53 -0
  136. package/esm/chart/gradient/index.js +1 -0
  137. package/esm/chart/index.js +3 -1
  138. package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
  139. package/esm/chart/line/DottedLine.js +29 -14
  140. package/esm/chart/line/Line.js +106 -67
  141. package/esm/chart/line/LineChart.js +20 -14
  142. package/esm/chart/line/ReferenceLine.js +73 -62
  143. package/esm/chart/line/SolidLine.js +25 -10
  144. package/esm/chart/line/__stories__/LineChart.stories.js +2098 -1975
  145. package/esm/chart/line/__stories__/ReferenceLine.stories.js +83 -28
  146. package/esm/chart/line/index.js +1 -1
  147. package/esm/chart/point/DefaultPointLabel.js +39 -0
  148. package/esm/chart/point/Point.js +188 -0
  149. package/esm/chart/point/index.js +2 -0
  150. package/esm/chart/scrubber/DefaultScrubberBeacon.js +179 -0
  151. package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
  152. package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
  153. package/esm/chart/scrubber/Scrubber.js +130 -148
  154. package/esm/chart/scrubber/ScrubberBeaconGroup.js +161 -0
  155. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +185 -0
  156. package/esm/chart/scrubber/ScrubberProvider.js +46 -54
  157. package/esm/chart/scrubber/index.js +3 -1
  158. package/esm/chart/text/ChartText.js +242 -174
  159. package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
  160. package/esm/chart/text/index.js +1 -1
  161. package/esm/chart/utils/chart.js +44 -3
  162. package/esm/chart/utils/gradient.js +305 -0
  163. package/esm/chart/utils/index.js +3 -0
  164. package/esm/chart/utils/path.js +76 -8
  165. package/esm/chart/utils/point.js +116 -5
  166. package/esm/chart/utils/scale.js +230 -1
  167. package/esm/chart/utils/scrubber.js +139 -0
  168. package/esm/chart/utils/transition.js +221 -0
  169. package/package.json +7 -5
  170. package/dts/chart/Point.d.ts +0 -103
  171. package/dts/chart/Point.d.ts.map +0 -1
  172. package/dts/chart/line/GradientLine.d.ts +0 -45
  173. package/dts/chart/line/GradientLine.d.ts.map +0 -1
  174. package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
  175. package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
  176. package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
  177. package/esm/chart/Point.js +0 -111
  178. package/esm/chart/line/GradientLine.js +0 -62
  179. package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
@@ -1,4 +1,12 @@
1
- import { isCategoricalScale, isLogScale, isNumericScale } from './scale';
1
+ import { applySerializableScale, 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
+ */
2
10
 
3
11
  /**
4
12
  * Get a point from a data value and a scale.
@@ -25,6 +33,48 @@ export const getPointOnScale = (dataValue, scale) => {
25
33
  return (_scale2 = scale(adjustedValue)) != null ? _scale2 : 0;
26
34
  };
27
35
 
36
+ /**
37
+ * Get a point from a data value and a serializable scale (worklet-compatible).
38
+ * @note for categorical scales, the point will be centered within the band.
39
+ * @note for log scales, zero and negative values are clamped to a small positive value.
40
+ * @param dataValue - the data value.
41
+ * @param scale - the serializable scale object.
42
+ * @returns the pixel value (defaulting to 0 if data value is not defined in scale).
43
+ */
44
+ export function getPointOnSerializableScale(dataValue, scale) {
45
+ 'worklet';
46
+
47
+ if (scale.type === 'band') {
48
+ const bandStart = applySerializableScale(dataValue, scale);
49
+ return bandStart + scale.bandwidth / 2;
50
+ }
51
+
52
+ // For log scales, ensure the value is positive
53
+ if (scale.type === 'log' && dataValue <= 0) {
54
+ dataValue = 0.001; // Use a small positive value for log scales
55
+ }
56
+ return applySerializableScale(dataValue, scale);
57
+ }
58
+
59
+ /**
60
+ * Projects a single data point to pixel coordinates using serializable scales.
61
+ * This is the worklet-compatible version for use in react-native-reanimated.
62
+ */
63
+ export function projectPointWithSerializableScale(_ref) {
64
+ 'worklet';
65
+
66
+ let {
67
+ x,
68
+ y,
69
+ xScale,
70
+ yScale
71
+ } = _ref;
72
+ return {
73
+ x: getPointOnSerializableScale(x, xScale),
74
+ y: getPointOnSerializableScale(y, yScale)
75
+ };
76
+ }
77
+
28
78
  /**
29
79
  * Projects a data point to pixel coordinates using the chart scale.
30
80
  * Automatically handles log scale transformations for zero/negative values.
@@ -40,13 +90,13 @@ export const getPointOnScale = (dataValue, scale) => {
40
90
  * const pixelCoord = projectPoint({ x: 2, y: 10, chartScale, xData: ['Jan', 'Feb', 'Mar'] });
41
91
  * ```
42
92
  */
43
- export const projectPoint = _ref => {
93
+ export const projectPoint = _ref2 => {
44
94
  let {
45
95
  x,
46
96
  y,
47
97
  xScale,
48
98
  yScale
49
- } = _ref;
99
+ } = _ref2;
50
100
  return {
51
101
  x: getPointOnScale(x, xScale),
52
102
  y: getPointOnScale(y, yScale)
@@ -65,14 +115,14 @@ export const projectPoint = _ref => {
65
115
  * const pixelPoints = projectPoints({ data, chartScale, xData: ['Jan', 'Feb', 'Mar'] });
66
116
  * ```
67
117
  */
68
- export const projectPoints = _ref2 => {
118
+ export const projectPoints = _ref3 => {
69
119
  let {
70
120
  data,
71
121
  xScale,
72
122
  yScale,
73
123
  xData,
74
124
  yData
75
- } = _ref2;
125
+ } = _ref3;
76
126
  if (data.length === 0) {
77
127
  return [];
78
128
  }
@@ -115,4 +165,65 @@ export const projectPoints = _ref2 => {
115
165
  yScale
116
166
  });
117
167
  });
168
+ };
169
+
170
+ /**
171
+ * Determines text alignment based on label position.
172
+ * For example, a 'top' position needs the text aligned to the 'bottom' so it appears above the point.
173
+ */
174
+ export const getAlignmentFromPosition = position => {
175
+ let horizontalAlignment = 'center';
176
+ let verticalAlignment = 'middle';
177
+ switch (position) {
178
+ case 'top':
179
+ verticalAlignment = 'bottom';
180
+ break;
181
+ case 'bottom':
182
+ verticalAlignment = 'top';
183
+ break;
184
+ case 'left':
185
+ horizontalAlignment = 'right';
186
+ break;
187
+ case 'right':
188
+ horizontalAlignment = 'left';
189
+ break;
190
+ case 'center':
191
+ default:
192
+ horizontalAlignment = 'center';
193
+ verticalAlignment = 'middle';
194
+ break;
195
+ }
196
+ return {
197
+ horizontalAlignment,
198
+ verticalAlignment
199
+ };
200
+ };
201
+
202
+ /**
203
+ * Calculates the final label coordinates by applying offset based on position.
204
+ */
205
+ export const getLabelCoordinates = (x, y, position, offset) => {
206
+ let dx = 0;
207
+ let dy = 0;
208
+ switch (position) {
209
+ case 'top':
210
+ dy = -offset;
211
+ break;
212
+ case 'bottom':
213
+ dy = offset;
214
+ break;
215
+ case 'left':
216
+ dx = -offset;
217
+ break;
218
+ case 'right':
219
+ dx = offset;
220
+ break;
221
+ case 'center':
222
+ default:
223
+ break;
224
+ }
225
+ return {
226
+ x: x + dx,
227
+ y: y + dy
228
+ };
118
229
  };
@@ -16,6 +16,16 @@ export const isLogScale = scale => {
16
16
  return scale !== undefined && 'base' in scale && typeof scale.base === 'function';
17
17
  };
18
18
 
19
+ /**
20
+ * Type guard to check if a scale is a SerializableScale.
21
+ * This can be used in worklets to differentiate between scale objects and scale functions.
22
+ */
23
+ export const isSerializableScale = scale => {
24
+ 'worklet';
25
+
26
+ return typeof scale === 'object' && scale !== null && 'type' in scale && 'domain' in scale && 'range' in scale;
27
+ };
28
+
19
29
  /**
20
30
  * Create a numeric scale (linear or logarithmic)
21
31
  * @returns A numeric scale function
@@ -45,4 +55,223 @@ export const getCategoricalScale = _ref2 => {
45
55
  }, (_, i) => i);
46
56
  const scale = scaleBand().domain(domainArray).range([range.min, range.max]).padding(padding);
47
57
  return scale;
48
- };
58
+ };
59
+
60
+ /**
61
+ * Convert a D3 scale to a serializable scale configuration that can be used in worklets
62
+ */
63
+ export function convertToSerializableScale(d3Scale) {
64
+ if (!d3Scale) return undefined;
65
+ const domain = d3Scale.domain();
66
+ const range = d3Scale.range();
67
+
68
+ // Handle band/categorical scales
69
+ if (isCategoricalScale(d3Scale)) {
70
+ var _step;
71
+ const bandScale = d3Scale;
72
+ const bandwidth = bandScale.bandwidth();
73
+ const step = (_step = bandScale.step == null ? void 0 : bandScale.step()) != null ? _step : (range[1] - range[0]) / domain.length;
74
+ return {
75
+ type: 'band',
76
+ domain: [domain[0], domain[domain.length - 1]],
77
+ range: [range[0], range[range.length - 1]],
78
+ bandwidth,
79
+ step
80
+ };
81
+ }
82
+
83
+ // Handle log scales
84
+ if (isLogScale(d3Scale)) {
85
+ var _base;
86
+ const logScale = d3Scale;
87
+ // D3 log scales default to base 10
88
+ const base = (_base = logScale.base == null ? void 0 : logScale.base()) != null ? _base : 10;
89
+ return {
90
+ type: 'log',
91
+ domain: [domain[0], domain[domain.length - 1]],
92
+ range: [range[0], range[range.length - 1]],
93
+ base
94
+ };
95
+ }
96
+
97
+ // Handle linear scales (default)
98
+ if (isNumericScale(d3Scale)) {
99
+ return {
100
+ type: 'linear',
101
+ domain: [domain[0], domain[domain.length - 1]],
102
+ range: [range[0], range[range.length - 1]]
103
+ };
104
+ }
105
+ return undefined;
106
+ }
107
+
108
+ /**
109
+ * Convert multiple D3 scales to serializable scales
110
+ */
111
+ export function convertScalesToSerializableScales(xScale, yScales) {
112
+ const result = {
113
+ yScales: {}
114
+ };
115
+
116
+ // Convert X scale
117
+ if (xScale) {
118
+ result.xScale = convertToSerializableScale(xScale);
119
+ }
120
+
121
+ // Convert Y scales
122
+ if (yScales) {
123
+ yScales.forEach((scale, id) => {
124
+ const serializableScale = convertToSerializableScale(scale);
125
+ if (serializableScale) {
126
+ result.yScales[id] = serializableScale;
127
+ }
128
+ });
129
+ }
130
+ return result;
131
+ }
132
+
133
+ /**
134
+ * Serializable scale implementations based on D3 scale concepts.
135
+ * These scales can be used directly on the UI thread in Reanimated worklets.
136
+ */
137
+
138
+ /**
139
+ * Serializable linear scale function
140
+ */
141
+ export function applyLinearScale(value, scale) {
142
+ 'worklet';
143
+
144
+ const [d0, d1] = scale.domain;
145
+ const [r0, r1] = scale.range;
146
+ const t = (value - d0) / (d1 - d0); // normalize to [0, 1]
147
+ return r0 + t * (r1 - r0); // interpolate in range
148
+ }
149
+
150
+ /**
151
+ * Serializable log scale function
152
+ */
153
+ export function applyLogScale(value, scale) {
154
+ 'worklet';
155
+
156
+ var _scale$base;
157
+ const [d0, d1] = scale.domain;
158
+ const [r0, r1] = scale.range;
159
+ const base = (_scale$base = scale.base) != null ? _scale$base : 10;
160
+ const logBase = base === 10 ? Math.log10 : base === Math.E ? Math.log : x => Math.log(x) / Math.log(base);
161
+ const t = (logBase(value) - logBase(d0)) / (logBase(d1) - logBase(d0));
162
+ return r0 + t * (r1 - r0);
163
+ }
164
+
165
+ /**
166
+ * Serializable band scale function
167
+ */
168
+ export function applyBandScale(value, scale) {
169
+ 'worklet';
170
+
171
+ const [r0, r1] = scale.range;
172
+ const [domainMin, domainMax] = scale.domain;
173
+ const n = domainMax - domainMin + 1;
174
+ const step = scale.step;
175
+ const index = value - domainMin;
176
+ if (index < 0 || index >= n) {
177
+ return r0;
178
+ }
179
+ const paddingOffset = step - scale.bandwidth;
180
+ const bandStart = r0 + step * index + paddingOffset;
181
+ return bandStart;
182
+ }
183
+
184
+ /**
185
+ * Universal serializable scale function that handles any scale type
186
+ */
187
+ export function applySerializableScale(value, scale) {
188
+ 'worklet';
189
+
190
+ switch (scale.type) {
191
+ case 'linear':
192
+ return applyLinearScale(value, scale);
193
+ case 'log':
194
+ return applyLogScale(value, scale);
195
+ case 'band':
196
+ return applyBandScale(value, scale);
197
+ default:
198
+ return 0;
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Get bandwidth for band scales (returns 0 for other scale types)
204
+ */
205
+ export function getScaleBandwidth(scale) {
206
+ 'worklet';
207
+
208
+ if (scale.type === 'band') {
209
+ return scale.bandwidth;
210
+ }
211
+ return 0;
212
+ }
213
+
214
+ /**
215
+ * Invert a linear scale - convert from range value back to domain value
216
+ */
217
+ export function invertLinearScale(rangeValue, scale) {
218
+ 'worklet';
219
+
220
+ const [d0, d1] = scale.domain;
221
+ const [r0, r1] = scale.range;
222
+ const t = (rangeValue - r0) / (r1 - r0); // normalize to [0, 1]
223
+ return d0 + t * (d1 - d0); // interpolate in domain
224
+ }
225
+
226
+ /**
227
+ * Invert a log scale - convert from range value back to domain value
228
+ */
229
+ export function invertLogScale(rangeValue, scale) {
230
+ 'worklet';
231
+
232
+ var _scale$base2;
233
+ const [d0, d1] = scale.domain;
234
+ const [r0, r1] = scale.range;
235
+ const base = (_scale$base2 = scale.base) != null ? _scale$base2 : 10;
236
+ const logBase = base === 10 ? Math.log10 : base === Math.E ? Math.log : x => Math.log(x) / Math.log(base);
237
+ const t = (rangeValue - r0) / (r1 - r0); // normalize to [0, 1]
238
+ const logValue = logBase(d0) + t * (logBase(d1) - logBase(d0));
239
+
240
+ // Convert back from log space
241
+ return base === 10 ? Math.pow(10, logValue) : base === Math.E ? Math.exp(logValue) : Math.pow(base, logValue);
242
+ }
243
+
244
+ /**
245
+ * Invert a band scale - convert from range value back to domain index
246
+ */
247
+ export function invertBandScale(rangeValue, scale) {
248
+ 'worklet';
249
+
250
+ const [r0, r1] = scale.range;
251
+ const n = scale.domain.length;
252
+ const step = (r1 - r0) / n;
253
+
254
+ // Find which band this range value falls into
255
+ const index = Math.floor((rangeValue - r0) / step);
256
+
257
+ // Clamp to valid range
258
+ return Math.max(0, Math.min(index, n - 1));
259
+ }
260
+
261
+ /**
262
+ * Universal serializable scale invert function that handles any scale type
263
+ */
264
+ export function invertSerializableScale(rangeValue, scale) {
265
+ 'worklet';
266
+
267
+ switch (scale.type) {
268
+ case 'linear':
269
+ return invertLinearScale(rangeValue, scale);
270
+ case 'log':
271
+ return invertLogScale(rangeValue, scale);
272
+ case 'band':
273
+ return invertBandScale(rangeValue, scale);
274
+ default:
275
+ return 0;
276
+ }
277
+ }
@@ -0,0 +1,139 @@
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, xOffset) {
6
+ 'worklet';
7
+
8
+ // any regular functions in ui thread must be marked with 'worklet'
9
+ if (xOffset === void 0) {
10
+ xOffset = 16;
11
+ }
12
+ if (drawingArea.width <= 0 || drawingArea.height <= 0) {
13
+ return 'right';
14
+ }
15
+ const availableRightSpace = drawingArea.x + drawingArea.width - beaconX;
16
+ const requiredSpace = maxLabelWidth + xOffset;
17
+ return requiredSpace <= availableRightSpace ? 'right' : 'left';
18
+ };
19
+ /**
20
+ * Calculates Y positions for all labels avoiding overlaps while maintaining order.
21
+ */
22
+ export const calculateLabelYPositions = (dimensions, drawingArea, labelHeight, minGap) => {
23
+ 'worklet';
24
+
25
+ if (dimensions.length === 0) {
26
+ return new Map();
27
+ }
28
+
29
+ // Sort by preferred Y values and create working labels
30
+ const sortedLabels = [...dimensions].sort((a, b) => a.preferredY - b.preferredY).map(dim => ({
31
+ seriesId: dim.seriesId,
32
+ preferredY: dim.preferredY,
33
+ finalY: dim.preferredY
34
+ }));
35
+
36
+ // Initial bounds fitting
37
+ const minY = drawingArea.y + labelHeight / 2;
38
+ const maxY = drawingArea.y + drawingArea.height - labelHeight / 2;
39
+ const requiredDistance = labelHeight + minGap;
40
+ for (const label of sortedLabels) {
41
+ // Clamp each label to the drawing area
42
+ label.finalY = Math.max(minY, Math.min(maxY, label.preferredY));
43
+ }
44
+
45
+ // First pass: push down any overlapping labels
46
+ for (let i = 1; i < sortedLabels.length; i++) {
47
+ const prev = sortedLabels[i - 1];
48
+ const current = sortedLabels[i];
49
+ const minAllowedY = prev.finalY + requiredDistance;
50
+ if (current.finalY < minAllowedY) {
51
+ current.finalY = minAllowedY;
52
+ }
53
+ }
54
+
55
+ // Find collision groups - groups of labels that are tightly packed (gap < minGap between them)
56
+ const collisionGroups = [];
57
+ let currentGroup = [sortedLabels[0]];
58
+ for (let i = 1; i < sortedLabels.length; i++) {
59
+ const prev = sortedLabels[i - 1];
60
+ const current = sortedLabels[i];
61
+ const gap = current.finalY - prev.finalY - labelHeight;
62
+ if (gap < minGap + 0.01) {
63
+ // Labels are touching or very close - part of same collision group
64
+ currentGroup.push(current);
65
+ } else {
66
+ // Gap is large enough - start new group
67
+ collisionGroups.push(currentGroup);
68
+ currentGroup = [current];
69
+ }
70
+ }
71
+ collisionGroups.push(currentGroup);
72
+
73
+ // Process each collision group - optimize positioning to minimize displacement
74
+ for (const group of collisionGroups) {
75
+ if (group.length === 1) {
76
+ // Single label, already at best position
77
+ continue;
78
+ }
79
+ const groupLastLabel = group[group.length - 1];
80
+ const groupFirstLabel = group[0];
81
+ const groupOverflow = groupLastLabel.finalY + labelHeight / 2 - (drawingArea.y + drawingArea.height);
82
+
83
+ // Calculate the ideal center point for this group
84
+ const groupPreferredCenter = group.reduce((sum, label) => sum + label.preferredY, 0) / group.length;
85
+ const groupTotalNeeded = group.length * labelHeight + (group.length - 1) * minGap;
86
+ if (groupOverflow <= 0) {
87
+ // Group fits, but let's center it better if possible
88
+ // Calculate how much we can shift up/down to center around preferred positions
89
+ const currentCenter = (groupFirstLabel.finalY + groupLastLabel.finalY) / 2;
90
+ const desiredShift = groupPreferredCenter - currentCenter;
91
+
92
+ // Calculate max shift in each direction
93
+ const maxShiftUp = groupFirstLabel.finalY - minY;
94
+ const maxShiftDown = maxY - groupLastLabel.finalY;
95
+
96
+ // Apply the shift, constrained by boundaries
97
+ const actualShift = Math.max(-maxShiftUp, Math.min(maxShiftDown, desiredShift));
98
+ if (Math.abs(actualShift) > 0.01) {
99
+ for (const label of group) {
100
+ label.finalY += actualShift;
101
+ }
102
+ }
103
+ } else {
104
+ // Group overflows - need to adjust
105
+ const groupStartY = groupFirstLabel.finalY - labelHeight / 2;
106
+ const availableSpace = drawingArea.y + drawingArea.height - groupStartY;
107
+ const maxShiftUp = groupFirstLabel.finalY - minY;
108
+ if (maxShiftUp >= groupOverflow) {
109
+ // Can shift entire group up to fit
110
+ for (const label of group) {
111
+ label.finalY -= groupOverflow;
112
+ }
113
+ } else if (groupTotalNeeded <= availableSpace) {
114
+ // Can't shift enough, but there's room - redistribute with proper spacing
115
+ let currentY = Math.max(minY, groupFirstLabel.finalY - maxShiftUp);
116
+ const gap = (availableSpace - group.length * labelHeight) / Math.max(1, group.length - 1);
117
+ for (const label of group) {
118
+ label.finalY = currentY;
119
+ currentY += labelHeight + gap;
120
+ }
121
+ } else {
122
+ // Not enough space even with compression - compress gaps and fit to bottom
123
+ const compressedGap = Math.max(1, (availableSpace - group.length * labelHeight) / Math.max(1, group.length - 1));
124
+ // Position so last label is at maxY
125
+ let currentY = maxY - (group.length - 1) * (labelHeight + compressedGap);
126
+ currentY = Math.max(minY, currentY);
127
+ for (const label of group) {
128
+ label.finalY = currentY;
129
+ currentY += labelHeight + compressedGap;
130
+ }
131
+ }
132
+ }
133
+ }
134
+ const result = new Map();
135
+ for (const label of sortedLabels) {
136
+ result.set(label.seriesId, label.finalY);
137
+ }
138
+ return result;
139
+ };