@coinbase/cds-mobile-visualization 3.4.0-beta.1 → 3.4.0-beta.11
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.
- package/CHANGELOG.md +60 -0
- package/dts/chart/CartesianChart.d.ts +57 -33
- package/dts/chart/CartesianChart.d.ts.map +1 -1
- package/dts/chart/ChartContextBridge.d.ts +28 -0
- package/dts/chart/ChartContextBridge.d.ts.map +1 -0
- package/dts/chart/Path.d.ts +77 -34
- package/dts/chart/Path.d.ts.map +1 -1
- package/dts/chart/PeriodSelector.d.ts +2 -2
- package/dts/chart/PeriodSelector.d.ts.map +1 -1
- package/dts/chart/area/Area.d.ts +42 -27
- package/dts/chart/area/Area.d.ts.map +1 -1
- package/dts/chart/area/AreaChart.d.ts +51 -10
- package/dts/chart/area/AreaChart.d.ts.map +1 -1
- package/dts/chart/area/DottedArea.d.ts +21 -2
- package/dts/chart/area/DottedArea.d.ts.map +1 -1
- package/dts/chart/area/GradientArea.d.ts +19 -13
- package/dts/chart/area/GradientArea.d.ts.map +1 -1
- package/dts/chart/area/SolidArea.d.ts +17 -2
- package/dts/chart/area/SolidArea.d.ts.map +1 -1
- package/dts/chart/axis/Axis.d.ts +86 -118
- package/dts/chart/axis/Axis.d.ts.map +1 -1
- package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
- package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
- package/dts/chart/axis/XAxis.d.ts +1 -1
- package/dts/chart/axis/XAxis.d.ts.map +1 -1
- package/dts/chart/axis/YAxis.d.ts +2 -2
- package/dts/chart/axis/YAxis.d.ts.map +1 -1
- package/dts/chart/axis/index.d.ts +1 -0
- package/dts/chart/axis/index.d.ts.map +1 -1
- package/dts/chart/bar/Bar.d.ts +16 -13
- package/dts/chart/bar/Bar.d.ts.map +1 -1
- package/dts/chart/bar/BarChart.d.ts +36 -20
- package/dts/chart/bar/BarChart.d.ts.map +1 -1
- package/dts/chart/bar/BarPlot.d.ts +2 -1
- package/dts/chart/bar/BarPlot.d.ts.map +1 -1
- package/dts/chart/bar/BarStack.d.ts +39 -48
- package/dts/chart/bar/BarStack.d.ts.map +1 -1
- package/dts/chart/bar/BarStackGroup.d.ts +1 -0
- package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBar.d.ts +1 -1
- package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
- package/dts/chart/gradient/Gradient.d.ts +25 -0
- package/dts/chart/gradient/Gradient.d.ts.map +1 -0
- package/dts/chart/gradient/index.d.ts +2 -0
- package/dts/chart/gradient/index.d.ts.map +1 -0
- package/dts/chart/index.d.ts +3 -1
- package/dts/chart/index.d.ts.map +1 -1
- package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
- package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
- package/dts/chart/line/DottedLine.d.ts +13 -5
- package/dts/chart/line/DottedLine.d.ts.map +1 -1
- package/dts/chart/line/Line.d.ts +64 -25
- package/dts/chart/line/Line.d.ts.map +1 -1
- package/dts/chart/line/LineChart.d.ts +43 -9
- package/dts/chart/line/LineChart.d.ts.map +1 -1
- package/dts/chart/line/ReferenceLine.d.ts +68 -20
- package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
- package/dts/chart/line/SolidLine.d.ts +8 -5
- package/dts/chart/line/SolidLine.d.ts.map +1 -1
- package/dts/chart/line/index.d.ts +1 -1
- package/dts/chart/line/index.d.ts.map +1 -1
- package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
- package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
- package/dts/chart/point/Point.d.ts +120 -0
- package/dts/chart/point/Point.d.ts.map +1 -0
- package/dts/chart/point/index.d.ts +3 -0
- package/dts/chart/point/index.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +8 -0
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
- package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
- package/dts/chart/scrubber/Scrubber.d.ts +172 -43
- package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +44 -0
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +31 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
- package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
- package/dts/chart/scrubber/index.d.ts +3 -0
- package/dts/chart/scrubber/index.d.ts.map +1 -1
- package/dts/chart/text/ChartText.d.ts +151 -77
- package/dts/chart/text/ChartText.d.ts.map +1 -1
- package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
- package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
- package/dts/chart/text/index.d.ts +1 -1
- package/dts/chart/text/index.d.ts.map +1 -1
- package/dts/chart/utils/axis.d.ts +25 -1
- package/dts/chart/utils/axis.d.ts.map +1 -1
- package/dts/chart/utils/chart.d.ts +34 -7
- package/dts/chart/utils/chart.d.ts.map +1 -1
- package/dts/chart/utils/context.d.ts +28 -7
- package/dts/chart/utils/context.d.ts.map +1 -1
- package/dts/chart/utils/gradient.d.ts +117 -0
- package/dts/chart/utils/gradient.d.ts.map +1 -0
- package/dts/chart/utils/index.d.ts +3 -0
- package/dts/chart/utils/index.d.ts.map +1 -1
- package/dts/chart/utils/path.d.ts +53 -0
- package/dts/chart/utils/path.d.ts.map +1 -1
- package/dts/chart/utils/point.d.ts +71 -7
- package/dts/chart/utils/point.d.ts.map +1 -1
- package/dts/chart/utils/scale.d.ts +102 -0
- package/dts/chart/utils/scale.d.ts.map +1 -1
- package/dts/chart/utils/scrubber.d.ts +39 -0
- package/dts/chart/utils/scrubber.d.ts.map +1 -0
- package/dts/chart/utils/transition.d.ts +140 -0
- package/dts/chart/utils/transition.d.ts.map +1 -0
- package/esm/chart/CartesianChart.js +164 -70
- package/esm/chart/ChartContextBridge.js +148 -0
- package/esm/chart/Path.js +198 -113
- package/esm/chart/PeriodSelector.js +2 -2
- package/esm/chart/__stories__/CartesianChart.stories.js +378 -131
- package/esm/chart/__stories__/Chart.stories.js +2 -4
- package/esm/chart/__stories__/PeriodSelector.stories.js +103 -75
- package/esm/chart/area/Area.js +25 -35
- package/esm/chart/area/AreaChart.js +17 -12
- package/esm/chart/area/DottedArea.js +61 -109
- package/esm/chart/area/GradientArea.js +35 -91
- package/esm/chart/area/SolidArea.js +22 -8
- package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
- package/esm/chart/axis/Axis.js +5 -39
- package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
- package/esm/chart/axis/XAxis.js +148 -66
- package/esm/chart/axis/YAxis.js +149 -65
- package/esm/chart/axis/__stories__/Axis.stories.js +259 -1
- package/esm/chart/axis/index.js +1 -0
- package/esm/chart/bar/Bar.js +3 -1
- package/esm/chart/bar/BarChart.js +15 -37
- package/esm/chart/bar/BarPlot.js +41 -35
- package/esm/chart/bar/BarStack.js +75 -38
- package/esm/chart/bar/BarStackGroup.js +6 -16
- package/esm/chart/bar/DefaultBar.js +26 -48
- package/esm/chart/bar/DefaultBarStack.js +23 -58
- package/esm/chart/bar/__stories__/BarChart.stories.js +502 -77
- package/esm/chart/gradient/Gradient.js +53 -0
- package/esm/chart/gradient/index.js +1 -0
- package/esm/chart/index.js +3 -1
- package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
- package/esm/chart/line/DottedLine.js +29 -14
- package/esm/chart/line/Line.js +106 -67
- package/esm/chart/line/LineChart.js +20 -14
- package/esm/chart/line/ReferenceLine.js +80 -63
- package/esm/chart/line/SolidLine.js +25 -10
- package/esm/chart/line/__stories__/LineChart.stories.js +2101 -1977
- package/esm/chart/line/__stories__/ReferenceLine.stories.js +83 -28
- package/esm/chart/line/index.js +1 -1
- package/esm/chart/point/DefaultPointLabel.js +39 -0
- package/esm/chart/point/Point.js +188 -0
- package/esm/chart/point/index.js +2 -0
- package/esm/chart/scrubber/DefaultScrubberBeacon.js +179 -0
- package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
- package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
- package/esm/chart/scrubber/Scrubber.js +126 -146
- package/esm/chart/scrubber/ScrubberBeaconGroup.js +161 -0
- package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +185 -0
- package/esm/chart/scrubber/ScrubberProvider.js +46 -54
- package/esm/chart/scrubber/index.js +3 -1
- package/esm/chart/text/ChartText.js +242 -174
- package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
- package/esm/chart/text/index.js +1 -1
- package/esm/chart/utils/axis.js +45 -29
- package/esm/chart/utils/chart.js +44 -3
- package/esm/chart/utils/gradient.js +305 -0
- package/esm/chart/utils/index.js +3 -0
- package/esm/chart/utils/path.js +76 -8
- package/esm/chart/utils/point.js +171 -17
- package/esm/chart/utils/scale.js +242 -2
- package/esm/chart/utils/scrubber.js +139 -0
- package/esm/chart/utils/transition.js +185 -0
- package/esm/sparkline/__stories__/Sparkline.stories.js +11 -7
- package/esm/sparkline/__stories__/SparklineGradient.stories.js +7 -4
- package/esm/sparkline/sparkline-interactive/__stories__/SparklineInteractive.stories.js +51 -26
- package/esm/sparkline/sparkline-interactive-header/__stories__/SparklineInteractiveHeader.stories.js +17 -9
- package/package.json +15 -9
- package/dts/chart/Point.d.ts +0 -103
- package/dts/chart/Point.d.ts.map +0 -1
- package/dts/chart/line/GradientLine.d.ts +0 -45
- package/dts/chart/line/GradientLine.d.ts.map +0 -1
- package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
- package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
- package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
- package/esm/chart/Point.js +0 -111
- package/esm/chart/line/GradientLine.js +0 -62
- package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
package/esm/chart/utils/point.js
CHANGED
|
@@ -1,20 +1,47 @@
|
|
|
1
|
-
import { isCategoricalScale, isLogScale, isNumericScale } from './scale';
|
|
1
|
+
import { applyBandScale, 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.
|
|
5
|
-
*
|
|
6
|
-
* @
|
|
7
|
-
* @param
|
|
8
|
-
* @param
|
|
9
|
-
* @returns
|
|
13
|
+
*
|
|
14
|
+
* @param dataValue - The data value to convert to a pixel position.
|
|
15
|
+
* @param scale - The scale function.
|
|
16
|
+
* @param anchor (@default 'middle') - For band scales, where to anchor the point within the band.
|
|
17
|
+
* @returns The pixel value (@default 0 if data value is not defined in scale).
|
|
10
18
|
*/
|
|
11
|
-
export const getPointOnScale = (dataValue, scale)
|
|
12
|
-
var
|
|
19
|
+
export const getPointOnScale = function (dataValue, scale, anchor) {
|
|
20
|
+
var _scale;
|
|
21
|
+
if (anchor === void 0) {
|
|
22
|
+
anchor = 'middle';
|
|
23
|
+
}
|
|
13
24
|
if (isCategoricalScale(scale)) {
|
|
14
|
-
var
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
25
|
+
var _bandScale$bandwidth, _bandScale$step;
|
|
26
|
+
const bandScale = scale;
|
|
27
|
+
const bandStart = bandScale(dataValue);
|
|
28
|
+
if (bandStart === undefined) return 0;
|
|
29
|
+
const bandwidth = (_bandScale$bandwidth = bandScale.bandwidth == null ? void 0 : bandScale.bandwidth()) != null ? _bandScale$bandwidth : 0;
|
|
30
|
+
const step = (_bandScale$step = bandScale.step == null ? void 0 : bandScale.step()) != null ? _bandScale$step : bandwidth;
|
|
31
|
+
const paddingOffset = (step - bandwidth) / 2;
|
|
32
|
+
const stepStart = bandStart - paddingOffset;
|
|
33
|
+
switch (anchor) {
|
|
34
|
+
case 'stepStart':
|
|
35
|
+
return stepStart;
|
|
36
|
+
case 'bandStart':
|
|
37
|
+
return bandStart;
|
|
38
|
+
case 'middle':
|
|
39
|
+
return bandStart + bandwidth / 2;
|
|
40
|
+
case 'bandEnd':
|
|
41
|
+
return bandStart + bandwidth;
|
|
42
|
+
case 'stepEnd':
|
|
43
|
+
return stepStart + step;
|
|
44
|
+
}
|
|
18
45
|
}
|
|
19
46
|
|
|
20
47
|
// For log scales, ensure the value is positive
|
|
@@ -22,9 +49,75 @@ export const getPointOnScale = (dataValue, scale) => {
|
|
|
22
49
|
if (isLogScale(scale) && dataValue <= 0) {
|
|
23
50
|
adjustedValue = 0.001; // Use a small positive value for log scales
|
|
24
51
|
}
|
|
25
|
-
return (
|
|
52
|
+
return (_scale = scale(adjustedValue)) != null ? _scale : 0;
|
|
26
53
|
};
|
|
27
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Get a point from a data value and a serializable scale (worklet-compatible).
|
|
57
|
+
*
|
|
58
|
+
* @param dataValue - The data value to convert to a pixel position.
|
|
59
|
+
* @param scale - The serializable scale function.
|
|
60
|
+
* @param anchor (@default 'middle') - For band scales, where to anchor the point within the band.
|
|
61
|
+
* @returns The pixel value (@default 0 if data value is not defined in scale).
|
|
62
|
+
*/
|
|
63
|
+
export function getPointOnSerializableScale(dataValue, scale, anchor) {
|
|
64
|
+
'worklet';
|
|
65
|
+
|
|
66
|
+
// Handle band scales with the specified position
|
|
67
|
+
if (anchor === void 0) {
|
|
68
|
+
anchor = 'middle';
|
|
69
|
+
}
|
|
70
|
+
if (scale.type === 'band') {
|
|
71
|
+
const bandScale = scale;
|
|
72
|
+
const [domainMin, domainMax] = bandScale.domain;
|
|
73
|
+
const index = dataValue - domainMin;
|
|
74
|
+
const n = domainMax - domainMin + 1;
|
|
75
|
+
if (index < 0 || index >= n) {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
const bandStart = applyBandScale(dataValue, bandScale);
|
|
79
|
+
const paddingOffset = (bandScale.step - bandScale.bandwidth) / 2;
|
|
80
|
+
const stepStart = bandStart - paddingOffset;
|
|
81
|
+
switch (anchor) {
|
|
82
|
+
case 'stepStart':
|
|
83
|
+
return stepStart;
|
|
84
|
+
case 'bandStart':
|
|
85
|
+
return bandStart;
|
|
86
|
+
case 'middle':
|
|
87
|
+
return bandStart + bandScale.bandwidth / 2;
|
|
88
|
+
case 'bandEnd':
|
|
89
|
+
return bandStart + bandScale.bandwidth;
|
|
90
|
+
case 'stepEnd':
|
|
91
|
+
return stepStart + bandScale.step;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For log scales, ensure the value is positive
|
|
96
|
+
if (scale.type === 'log' && dataValue <= 0) {
|
|
97
|
+
dataValue = 0.001; // Use a small positive value for log scales
|
|
98
|
+
}
|
|
99
|
+
return applySerializableScale(dataValue, scale);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Projects a single data point to pixel coordinates using serializable scales.
|
|
104
|
+
* This is the worklet-compatible version for use in react-native-reanimated.
|
|
105
|
+
*/
|
|
106
|
+
export function projectPointWithSerializableScale(_ref) {
|
|
107
|
+
'worklet';
|
|
108
|
+
|
|
109
|
+
let {
|
|
110
|
+
x,
|
|
111
|
+
y,
|
|
112
|
+
xScale,
|
|
113
|
+
yScale
|
|
114
|
+
} = _ref;
|
|
115
|
+
return {
|
|
116
|
+
x: getPointOnSerializableScale(x, xScale),
|
|
117
|
+
y: getPointOnSerializableScale(y, yScale)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
28
121
|
/**
|
|
29
122
|
* Projects a data point to pixel coordinates using the chart scale.
|
|
30
123
|
* Automatically handles log scale transformations for zero/negative values.
|
|
@@ -40,13 +133,13 @@ export const getPointOnScale = (dataValue, scale) => {
|
|
|
40
133
|
* const pixelCoord = projectPoint({ x: 2, y: 10, chartScale, xData: ['Jan', 'Feb', 'Mar'] });
|
|
41
134
|
* ```
|
|
42
135
|
*/
|
|
43
|
-
export const projectPoint =
|
|
136
|
+
export const projectPoint = _ref2 => {
|
|
44
137
|
let {
|
|
45
138
|
x,
|
|
46
139
|
y,
|
|
47
140
|
xScale,
|
|
48
141
|
yScale
|
|
49
|
-
} =
|
|
142
|
+
} = _ref2;
|
|
50
143
|
return {
|
|
51
144
|
x: getPointOnScale(x, xScale),
|
|
52
145
|
y: getPointOnScale(y, yScale)
|
|
@@ -65,14 +158,14 @@ export const projectPoint = _ref => {
|
|
|
65
158
|
* const pixelPoints = projectPoints({ data, chartScale, xData: ['Jan', 'Feb', 'Mar'] });
|
|
66
159
|
* ```
|
|
67
160
|
*/
|
|
68
|
-
export const projectPoints =
|
|
161
|
+
export const projectPoints = _ref3 => {
|
|
69
162
|
let {
|
|
70
163
|
data,
|
|
71
164
|
xScale,
|
|
72
165
|
yScale,
|
|
73
166
|
xData,
|
|
74
167
|
yData
|
|
75
|
-
} =
|
|
168
|
+
} = _ref3;
|
|
76
169
|
if (data.length === 0) {
|
|
77
170
|
return [];
|
|
78
171
|
}
|
|
@@ -115,4 +208,65 @@ export const projectPoints = _ref2 => {
|
|
|
115
208
|
yScale
|
|
116
209
|
});
|
|
117
210
|
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Determines text alignment based on label position.
|
|
215
|
+
* For example, a 'top' position needs the text aligned to the 'bottom' so it appears above the point.
|
|
216
|
+
*/
|
|
217
|
+
export const getAlignmentFromPosition = position => {
|
|
218
|
+
let horizontalAlignment = 'center';
|
|
219
|
+
let verticalAlignment = 'middle';
|
|
220
|
+
switch (position) {
|
|
221
|
+
case 'top':
|
|
222
|
+
verticalAlignment = 'bottom';
|
|
223
|
+
break;
|
|
224
|
+
case 'bottom':
|
|
225
|
+
verticalAlignment = 'top';
|
|
226
|
+
break;
|
|
227
|
+
case 'left':
|
|
228
|
+
horizontalAlignment = 'right';
|
|
229
|
+
break;
|
|
230
|
+
case 'right':
|
|
231
|
+
horizontalAlignment = 'left';
|
|
232
|
+
break;
|
|
233
|
+
case 'center':
|
|
234
|
+
default:
|
|
235
|
+
horizontalAlignment = 'center';
|
|
236
|
+
verticalAlignment = 'middle';
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
horizontalAlignment,
|
|
241
|
+
verticalAlignment
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Calculates the final label coordinates by applying offset based on position.
|
|
247
|
+
*/
|
|
248
|
+
export const getLabelCoordinates = (x, y, position, offset) => {
|
|
249
|
+
let dx = 0;
|
|
250
|
+
let dy = 0;
|
|
251
|
+
switch (position) {
|
|
252
|
+
case 'top':
|
|
253
|
+
dy = -offset;
|
|
254
|
+
break;
|
|
255
|
+
case 'bottom':
|
|
256
|
+
dy = offset;
|
|
257
|
+
break;
|
|
258
|
+
case 'left':
|
|
259
|
+
dx = -offset;
|
|
260
|
+
break;
|
|
261
|
+
case 'right':
|
|
262
|
+
dx = offset;
|
|
263
|
+
break;
|
|
264
|
+
case 'center':
|
|
265
|
+
default:
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
x: x + dx,
|
|
270
|
+
y: y + dy
|
|
271
|
+
};
|
|
118
272
|
};
|
package/esm/chart/utils/scale.js
CHANGED
|
@@ -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
|
|
@@ -43,6 +53,236 @@ export const getCategoricalScale = _ref2 => {
|
|
|
43
53
|
const domainArray = Array.from({
|
|
44
54
|
length: domain.max - domain.min + 1
|
|
45
55
|
}, (_, i) => i);
|
|
46
|
-
const scale = scaleBand().domain(domainArray).range([range.min, range.max]).padding(padding);
|
|
56
|
+
const scale = scaleBand().domain(domainArray).range([range.min, range.max]).paddingInner(padding).paddingOuter(padding / 2);
|
|
47
57
|
return scale;
|
|
48
|
-
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Anchor position for points on a scale. Currently used only for band scales.
|
|
62
|
+
*
|
|
63
|
+
* For band scales, this determines where within the band to position a point:
|
|
64
|
+
* - `'stepStart'` - At the start of the step
|
|
65
|
+
* - `'bandStart'` - At the start of the band
|
|
66
|
+
* - `'middle'` - At the center of the band
|
|
67
|
+
* - `'bandEnd'` - At the end of the band
|
|
68
|
+
* - `'stepEnd'` - At the end of the step
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Convert a D3 scale to a serializable scale configuration that can be used in worklets
|
|
73
|
+
*/
|
|
74
|
+
export function convertToSerializableScale(d3Scale) {
|
|
75
|
+
if (!d3Scale) return undefined;
|
|
76
|
+
const domain = d3Scale.domain();
|
|
77
|
+
const range = d3Scale.range();
|
|
78
|
+
|
|
79
|
+
// Handle band/categorical scales
|
|
80
|
+
if (isCategoricalScale(d3Scale)) {
|
|
81
|
+
var _step;
|
|
82
|
+
const bandScale = d3Scale;
|
|
83
|
+
const bandwidth = bandScale.bandwidth();
|
|
84
|
+
const step = (_step = bandScale.step == null ? void 0 : bandScale.step()) != null ? _step : (range[1] - range[0]) / domain.length;
|
|
85
|
+
return {
|
|
86
|
+
type: 'band',
|
|
87
|
+
domain: [domain[0], domain[domain.length - 1]],
|
|
88
|
+
range: [range[0], range[range.length - 1]],
|
|
89
|
+
bandwidth,
|
|
90
|
+
step
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle log scales
|
|
95
|
+
if (isLogScale(d3Scale)) {
|
|
96
|
+
var _base;
|
|
97
|
+
const logScale = d3Scale;
|
|
98
|
+
// D3 log scales default to base 10
|
|
99
|
+
const base = (_base = logScale.base == null ? void 0 : logScale.base()) != null ? _base : 10;
|
|
100
|
+
return {
|
|
101
|
+
type: 'log',
|
|
102
|
+
domain: [domain[0], domain[domain.length - 1]],
|
|
103
|
+
range: [range[0], range[range.length - 1]],
|
|
104
|
+
base
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle linear scales (default)
|
|
109
|
+
if (isNumericScale(d3Scale)) {
|
|
110
|
+
return {
|
|
111
|
+
type: 'linear',
|
|
112
|
+
domain: [domain[0], domain[domain.length - 1]],
|
|
113
|
+
range: [range[0], range[range.length - 1]]
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Convert multiple D3 scales to serializable scales
|
|
121
|
+
*/
|
|
122
|
+
export function convertScalesToSerializableScales(xScale, yScales) {
|
|
123
|
+
const result = {
|
|
124
|
+
yScales: {}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Convert X scale
|
|
128
|
+
if (xScale) {
|
|
129
|
+
result.xScale = convertToSerializableScale(xScale);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Convert Y scales
|
|
133
|
+
if (yScales) {
|
|
134
|
+
yScales.forEach((scale, id) => {
|
|
135
|
+
const serializableScale = convertToSerializableScale(scale);
|
|
136
|
+
if (serializableScale) {
|
|
137
|
+
result.yScales[id] = serializableScale;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Serializable scale implementations based on D3 scale concepts.
|
|
146
|
+
* These scales can be used directly on the UI thread in Reanimated worklets.
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Serializable linear scale function
|
|
151
|
+
*/
|
|
152
|
+
export function applyLinearScale(value, scale) {
|
|
153
|
+
'worklet';
|
|
154
|
+
|
|
155
|
+
const [d0, d1] = scale.domain;
|
|
156
|
+
const [r0, r1] = scale.range;
|
|
157
|
+
const t = (value - d0) / (d1 - d0); // normalize to [0, 1]
|
|
158
|
+
return r0 + t * (r1 - r0); // interpolate in range
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Serializable log scale function
|
|
163
|
+
*/
|
|
164
|
+
export function applyLogScale(value, scale) {
|
|
165
|
+
'worklet';
|
|
166
|
+
|
|
167
|
+
var _scale$base;
|
|
168
|
+
const [d0, d1] = scale.domain;
|
|
169
|
+
const [r0, r1] = scale.range;
|
|
170
|
+
const base = (_scale$base = scale.base) != null ? _scale$base : 10;
|
|
171
|
+
const logBase = base === 10 ? Math.log10 : base === Math.E ? Math.log : x => Math.log(x) / Math.log(base);
|
|
172
|
+
const t = (logBase(value) - logBase(d0)) / (logBase(d1) - logBase(d0));
|
|
173
|
+
return r0 + t * (r1 - r0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Serializable band scale function
|
|
178
|
+
*/
|
|
179
|
+
export function applyBandScale(value, scale) {
|
|
180
|
+
'worklet';
|
|
181
|
+
|
|
182
|
+
const [r0, r1] = scale.range;
|
|
183
|
+
const [domainMin, domainMax] = scale.domain;
|
|
184
|
+
const n = domainMax - domainMin + 1;
|
|
185
|
+
const step = scale.step;
|
|
186
|
+
const index = value - domainMin;
|
|
187
|
+
if (index < 0 || index >= n) {
|
|
188
|
+
return r0;
|
|
189
|
+
}
|
|
190
|
+
const paddingOffset = (step - scale.bandwidth) / 2;
|
|
191
|
+
const bandStart = r0 + step * index + paddingOffset;
|
|
192
|
+
return bandStart;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Universal serializable scale function that handles any scale type
|
|
197
|
+
*/
|
|
198
|
+
export function applySerializableScale(value, scale) {
|
|
199
|
+
'worklet';
|
|
200
|
+
|
|
201
|
+
switch (scale.type) {
|
|
202
|
+
case 'linear':
|
|
203
|
+
return applyLinearScale(value, scale);
|
|
204
|
+
case 'log':
|
|
205
|
+
return applyLogScale(value, scale);
|
|
206
|
+
case 'band':
|
|
207
|
+
return applyBandScale(value, scale);
|
|
208
|
+
default:
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get bandwidth for band scales (returns 0 for other scale types)
|
|
215
|
+
*/
|
|
216
|
+
export function getScaleBandwidth(scale) {
|
|
217
|
+
'worklet';
|
|
218
|
+
|
|
219
|
+
if (scale.type === 'band') {
|
|
220
|
+
return scale.bandwidth;
|
|
221
|
+
}
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Invert a linear scale - convert from range value back to domain value
|
|
227
|
+
*/
|
|
228
|
+
export function invertLinearScale(rangeValue, scale) {
|
|
229
|
+
'worklet';
|
|
230
|
+
|
|
231
|
+
const [d0, d1] = scale.domain;
|
|
232
|
+
const [r0, r1] = scale.range;
|
|
233
|
+
const t = (rangeValue - r0) / (r1 - r0); // normalize to [0, 1]
|
|
234
|
+
return d0 + t * (d1 - d0); // interpolate in domain
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Invert a log scale - convert from range value back to domain value
|
|
239
|
+
*/
|
|
240
|
+
export function invertLogScale(rangeValue, scale) {
|
|
241
|
+
'worklet';
|
|
242
|
+
|
|
243
|
+
var _scale$base2;
|
|
244
|
+
const [d0, d1] = scale.domain;
|
|
245
|
+
const [r0, r1] = scale.range;
|
|
246
|
+
const base = (_scale$base2 = scale.base) != null ? _scale$base2 : 10;
|
|
247
|
+
const logBase = base === 10 ? Math.log10 : base === Math.E ? Math.log : x => Math.log(x) / Math.log(base);
|
|
248
|
+
const t = (rangeValue - r0) / (r1 - r0); // normalize to [0, 1]
|
|
249
|
+
const logValue = logBase(d0) + t * (logBase(d1) - logBase(d0));
|
|
250
|
+
|
|
251
|
+
// Convert back from log space
|
|
252
|
+
return base === 10 ? Math.pow(10, logValue) : base === Math.E ? Math.exp(logValue) : Math.pow(base, logValue);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Invert a band scale - convert from range value back to domain index
|
|
257
|
+
*/
|
|
258
|
+
export function invertBandScale(rangeValue, scale) {
|
|
259
|
+
'worklet';
|
|
260
|
+
|
|
261
|
+
const [r0, r1] = scale.range;
|
|
262
|
+
const n = scale.domain.length;
|
|
263
|
+
const step = (r1 - r0) / n;
|
|
264
|
+
|
|
265
|
+
// Find which band this range value falls into
|
|
266
|
+
const index = Math.floor((rangeValue - r0) / step);
|
|
267
|
+
|
|
268
|
+
// Clamp to valid range
|
|
269
|
+
return Math.max(0, Math.min(index, n - 1));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Universal serializable scale invert function that handles any scale type
|
|
274
|
+
*/
|
|
275
|
+
export function invertSerializableScale(rangeValue, scale) {
|
|
276
|
+
'worklet';
|
|
277
|
+
|
|
278
|
+
switch (scale.type) {
|
|
279
|
+
case 'linear':
|
|
280
|
+
return invertLinearScale(rangeValue, scale);
|
|
281
|
+
case 'log':
|
|
282
|
+
return invertLogScale(rangeValue, scale);
|
|
283
|
+
case 'band':
|
|
284
|
+
return invertBandScale(rangeValue, scale);
|
|
285
|
+
default:
|
|
286
|
+
return 0;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -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
|
+
};
|