@coinbase/cds-mobile-visualization 3.4.0-beta.22 → 3.4.0-beta.24
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 +12 -0
- package/dts/chart/CartesianChart.d.ts +58 -7
- package/dts/chart/CartesianChart.d.ts.map +1 -1
- package/dts/chart/Path.d.ts.map +1 -1
- package/dts/chart/area/Area.d.ts +7 -0
- package/dts/chart/area/Area.d.ts.map +1 -1
- package/dts/chart/area/AreaChart.d.ts +5 -5
- package/dts/chart/area/AreaChart.d.ts.map +1 -1
- package/dts/chart/area/DottedArea.d.ts.map +1 -1
- package/dts/chart/area/GradientArea.d.ts.map +1 -1
- package/dts/chart/area/SolidArea.d.ts.map +1 -1
- package/dts/chart/axis/Axis.d.ts +3 -1
- package/dts/chart/axis/Axis.d.ts.map +1 -1
- package/dts/chart/axis/XAxis.d.ts +6 -0
- package/dts/chart/axis/XAxis.d.ts.map +1 -1
- package/dts/chart/axis/YAxis.d.ts +1 -0
- package/dts/chart/axis/YAxis.d.ts.map +1 -1
- package/dts/chart/bar/Bar.d.ts +4 -2
- package/dts/chart/bar/Bar.d.ts.map +1 -1
- package/dts/chart/bar/BarChart.d.ts +49 -9
- package/dts/chart/bar/BarChart.d.ts.map +1 -1
- package/dts/chart/bar/BarPlot.d.ts.map +1 -1
- package/dts/chart/bar/BarStack.d.ts +30 -9
- package/dts/chart/bar/BarStack.d.ts.map +1 -1
- package/dts/chart/bar/BarStackGroup.d.ts +1 -1
- package/dts/chart/bar/BarStackGroup.d.ts.map +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 +5 -0
- package/dts/chart/gradient/Gradient.d.ts.map +1 -1
- package/dts/chart/line/DottedLine.d.ts.map +1 -1
- package/dts/chart/line/Line.d.ts +7 -0
- package/dts/chart/line/Line.d.ts.map +1 -1
- package/dts/chart/line/LineChart.d.ts +22 -8
- package/dts/chart/line/LineChart.d.ts.map +1 -1
- package/dts/chart/line/ReferenceLine.d.ts +1 -0
- package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
- package/dts/chart/line/SolidLine.d.ts.map +1 -1
- package/dts/chart/point/Point.d.ts +7 -0
- package/dts/chart/point/Point.d.ts.map +1 -1
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +2 -1
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -1
- package/dts/chart/scrubber/Scrubber.d.ts +8 -0
- package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
- package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts +12 -0
- package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -1
- package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
- package/dts/chart/utils/axis.d.ts +20 -9
- package/dts/chart/utils/axis.d.ts.map +1 -1
- package/dts/chart/utils/bar.d.ts +4 -3
- package/dts/chart/utils/bar.d.ts.map +1 -1
- package/dts/chart/utils/chart.d.ts +13 -0
- package/dts/chart/utils/chart.d.ts.map +1 -1
- package/dts/chart/utils/context.d.ts +21 -6
- package/dts/chart/utils/context.d.ts.map +1 -1
- package/dts/chart/utils/gradient.d.ts +3 -1
- package/dts/chart/utils/gradient.d.ts.map +1 -1
- package/dts/chart/utils/path.d.ts +20 -0
- package/dts/chart/utils/path.d.ts.map +1 -1
- package/dts/chart/utils/point.d.ts +7 -0
- package/dts/chart/utils/point.d.ts.map +1 -1
- package/esm/chart/CartesianChart.js +145 -81
- package/esm/chart/Path.js +10 -7
- package/esm/chart/__stories__/CartesianChart.stories.js +10 -0
- package/esm/chart/__stories__/ChartAccessibility.stories.js +721 -0
- package/esm/chart/area/Area.js +19 -9
- package/esm/chart/area/AreaChart.js +11 -9
- package/esm/chart/area/DottedArea.js +11 -6
- package/esm/chart/area/GradientArea.js +11 -6
- package/esm/chart/area/SolidArea.js +3 -1
- package/esm/chart/area/__stories__/AreaChart.stories.js +47 -5
- package/esm/chart/axis/XAxis.js +14 -21
- package/esm/chart/axis/YAxis.js +4 -3
- package/esm/chart/axis/__stories__/Axis.stories.js +65 -48
- package/esm/chart/bar/Bar.js +9 -5
- package/esm/chart/bar/BarChart.js +34 -31
- package/esm/chart/bar/BarPlot.js +7 -5
- package/esm/chart/bar/BarStack.js +176 -36
- package/esm/chart/bar/BarStackGroup.js +37 -27
- package/esm/chart/bar/DefaultBar.js +24 -8
- package/esm/chart/bar/DefaultBarStack.js +24 -10
- package/esm/chart/bar/__stories__/BarChart.stories.js +105 -3
- package/esm/chart/gradient/Gradient.js +2 -1
- package/esm/chart/line/DottedLine.js +3 -1
- package/esm/chart/line/Line.js +32 -19
- package/esm/chart/line/LineChart.js +31 -9
- package/esm/chart/line/SolidLine.js +3 -1
- package/esm/chart/line/__stories__/LineChart.stories.js +115 -46
- package/esm/chart/point/Point.js +2 -1
- package/esm/chart/scrubber/DefaultScrubberBeacon.js +1 -1
- package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -10
- package/esm/chart/scrubber/Scrubber.js +47 -21
- package/esm/chart/scrubber/ScrubberAccessibilityView.js +177 -0
- package/esm/chart/scrubber/ScrubberBeaconGroup.js +24 -20
- package/esm/chart/scrubber/ScrubberProvider.js +29 -24
- package/esm/chart/scrubber/__stories__/Scrubber.stories.js +192 -6
- package/esm/chart/utils/axis.js +42 -14
- package/esm/chart/utils/bar.js +5 -4
- package/esm/chart/utils/chart.js +18 -5
- package/esm/chart/utils/context.js +7 -0
- package/esm/chart/utils/gradient.js +8 -4
- package/esm/chart/utils/path.js +90 -61
- package/esm/chart/utils/point.js +28 -18
- package/package.json +5 -5
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { memo, useCallback, useMemo } from 'react';
|
|
2
|
+
import { Pressable, StyleSheet, View } from 'react-native';
|
|
3
|
+
import { useScreenReaderStatus } from '@coinbase/cds-mobile/hooks/useScreenReaderStatus';
|
|
4
|
+
import { useCartesianChartContext } from '../ChartProvider';
|
|
5
|
+
import { useScrubberContext } from '../utils';
|
|
6
|
+
import { getPointOnSerializableScale } from '../utils/point';
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
const normalizeScrubberAccessibilityStep = function (step, defaultStep) {
|
|
9
|
+
if (defaultStep === void 0) {
|
|
10
|
+
defaultStep = 1;
|
|
11
|
+
}
|
|
12
|
+
const resolvedDefaultStep = Number.isFinite(defaultStep) ? Math.max(1, Math.floor(defaultStep)) : 1;
|
|
13
|
+
if (step === undefined || !Number.isFinite(step)) {
|
|
14
|
+
return resolvedDefaultStep;
|
|
15
|
+
}
|
|
16
|
+
return Math.max(1, Math.floor(step));
|
|
17
|
+
};
|
|
18
|
+
const getScrubberSampledIndices = (dataLength, step) => {
|
|
19
|
+
if (dataLength <= 0) return [];
|
|
20
|
+
const lastIndex = dataLength - 1;
|
|
21
|
+
if (lastIndex === 0) return [0];
|
|
22
|
+
const normalizedStep = Math.max(1, Math.floor(step));
|
|
23
|
+
const sampledIndices = [0];
|
|
24
|
+
for (let dataIndex = normalizedStep; dataIndex < lastIndex; dataIndex += normalizedStep) {
|
|
25
|
+
sampledIndices.push(dataIndex);
|
|
26
|
+
}
|
|
27
|
+
sampledIndices.push(lastIndex);
|
|
28
|
+
return sampledIndices;
|
|
29
|
+
};
|
|
30
|
+
const getCategoryValueForIndex = (index, scale, axis) => {
|
|
31
|
+
if (scale.type === 'band') {
|
|
32
|
+
return index;
|
|
33
|
+
}
|
|
34
|
+
const axisData = axis == null ? void 0 : axis.data;
|
|
35
|
+
if (axisData && Array.isArray(axisData) && typeof axisData[0] === 'number') {
|
|
36
|
+
var _numericData$index;
|
|
37
|
+
const numericData = axisData;
|
|
38
|
+
return (_numericData$index = numericData[index]) != null ? _numericData$index : index;
|
|
39
|
+
}
|
|
40
|
+
return index;
|
|
41
|
+
};
|
|
42
|
+
const getScrubberSegmentWeights = function (sampledIndices, dataLength, categoryScale, categoryAxis, drawingArea, orientation) {
|
|
43
|
+
if (orientation === void 0) {
|
|
44
|
+
orientation = 'horizontal';
|
|
45
|
+
}
|
|
46
|
+
const dimensionSize = orientation === 'horizontal' ? drawingArea.width : drawingArea.height;
|
|
47
|
+
const dimensionStart = orientation === 'horizontal' ? drawingArea.x : drawingArea.y;
|
|
48
|
+
const dimensionEnd = dimensionStart + dimensionSize;
|
|
49
|
+
if (sampledIndices.length === 0 || !categoryScale || !categoryAxis || dimensionSize <= 0) {
|
|
50
|
+
const segmentWeights = sampledIndices.map((index, position) => {
|
|
51
|
+
var _sampledIndices;
|
|
52
|
+
const nextIndex = (_sampledIndices = sampledIndices[position + 1]) != null ? _sampledIndices : dataLength;
|
|
53
|
+
return Math.max(1, nextIndex - index);
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
leading: 0,
|
|
57
|
+
segmentWeights,
|
|
58
|
+
trailing: 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (categoryScale.type === 'band') {
|
|
62
|
+
const bandScale = categoryScale;
|
|
63
|
+
const segmentWeights = [];
|
|
64
|
+
let leading = 0;
|
|
65
|
+
let trailing = 0;
|
|
66
|
+
for (let i = 0; i < sampledIndices.length; i++) {
|
|
67
|
+
const categoryValue = getCategoryValueForIndex(sampledIndices[i], categoryScale, categoryAxis);
|
|
68
|
+
const posStart = getPointOnSerializableScale(categoryValue, bandScale, 'stepStart');
|
|
69
|
+
const posEnd = getPointOnSerializableScale(categoryValue, bandScale, 'stepEnd');
|
|
70
|
+
segmentWeights.push(Math.max(1, Math.abs(posEnd - posStart)));
|
|
71
|
+
if (i === 0) {
|
|
72
|
+
leading = Math.max(0, Math.min(posStart, posEnd) - dimensionStart);
|
|
73
|
+
}
|
|
74
|
+
if (i === sampledIndices.length - 1) {
|
|
75
|
+
trailing = Math.max(0, dimensionEnd - Math.max(posStart, posEnd));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
leading,
|
|
80
|
+
segmentWeights,
|
|
81
|
+
trailing
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const segmentWeights = sampledIndices.map((index, position) => {
|
|
85
|
+
const prevIndex = position > 0 ? sampledIndices[position - 1] : -1;
|
|
86
|
+
const categoryValue = getCategoryValueForIndex(index, categoryScale, categoryAxis);
|
|
87
|
+
const posEnd = getPointOnSerializableScale(categoryValue, categoryScale);
|
|
88
|
+
const posStart = prevIndex < 0 ? dimensionStart : getPointOnSerializableScale(getCategoryValueForIndex(prevIndex, categoryScale, categoryAxis), categoryScale);
|
|
89
|
+
return Math.max(1, Math.abs(posEnd - posStart));
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
leading: 0,
|
|
93
|
+
segmentWeights,
|
|
94
|
+
trailing: 0
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
const styles = StyleSheet.create({
|
|
98
|
+
container: {
|
|
99
|
+
position: 'absolute'
|
|
100
|
+
},
|
|
101
|
+
segments: {
|
|
102
|
+
flex: 1
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
export const ScrubberAccessibilityView = /*#__PURE__*/memo(_ref => {
|
|
106
|
+
let {
|
|
107
|
+
accessibilityLabel,
|
|
108
|
+
accessibilityStep
|
|
109
|
+
} = _ref;
|
|
110
|
+
const isScreenReaderEnabled = useScreenReaderStatus();
|
|
111
|
+
const {
|
|
112
|
+
dataLength,
|
|
113
|
+
drawingArea,
|
|
114
|
+
layout,
|
|
115
|
+
getXAxis,
|
|
116
|
+
getYAxis,
|
|
117
|
+
getXSerializableScale,
|
|
118
|
+
getYSerializableScale
|
|
119
|
+
} = useCartesianChartContext();
|
|
120
|
+
const {
|
|
121
|
+
enableScrubbing
|
|
122
|
+
} = useScrubberContext();
|
|
123
|
+
const isHorizontalLayout = layout === 'horizontal';
|
|
124
|
+
const categoryAxis = useMemo(() => isHorizontalLayout ? getYAxis() : getXAxis(), [isHorizontalLayout, getXAxis, getYAxis]);
|
|
125
|
+
const categoryScale = useMemo(() => isHorizontalLayout ? getYSerializableScale() : getXSerializableScale(), [isHorizontalLayout, getXSerializableScale, getYSerializableScale]);
|
|
126
|
+
const resolvedStep = useMemo(() => normalizeScrubberAccessibilityStep(accessibilityStep), [accessibilityStep]);
|
|
127
|
+
const sampledIndices = useMemo(() => getScrubberSampledIndices(dataLength, resolvedStep), [dataLength, resolvedStep]);
|
|
128
|
+
const segmentOrientation = isHorizontalLayout ? 'vertical' : 'horizontal';
|
|
129
|
+
const {
|
|
130
|
+
leading,
|
|
131
|
+
segmentWeights,
|
|
132
|
+
trailing
|
|
133
|
+
} = useMemo(() => getScrubberSegmentWeights(sampledIndices, dataLength, categoryScale, categoryAxis, drawingArea, segmentOrientation), [sampledIndices, dataLength, categoryScale, categoryAxis, drawingArea, segmentOrientation]);
|
|
134
|
+
const sampledSegments = useMemo(() => {
|
|
135
|
+
if (accessibilityLabel === undefined) return [];
|
|
136
|
+
return sampledIndices.map((index, position) => {
|
|
137
|
+
var _segmentWeights$posit;
|
|
138
|
+
const weight = (_segmentWeights$posit = segmentWeights[position]) != null ? _segmentWeights$posit : 1;
|
|
139
|
+
const pointLabel = accessibilityLabel(index);
|
|
140
|
+
return {
|
|
141
|
+
index,
|
|
142
|
+
weight,
|
|
143
|
+
accessibilityLabel: pointLabel || "Data point " + (index + 1)
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}, [accessibilityLabel, sampledIndices, segmentWeights]);
|
|
147
|
+
const getSegmentStyle = useCallback(weight => ({
|
|
148
|
+
flex: weight
|
|
149
|
+
}), []);
|
|
150
|
+
const overlayStyle = useMemo(() => ({
|
|
151
|
+
left: drawingArea.x,
|
|
152
|
+
top: drawingArea.y,
|
|
153
|
+
width: drawingArea.width,
|
|
154
|
+
height: drawingArea.height
|
|
155
|
+
}), [drawingArea.x, drawingArea.y, drawingArea.width, drawingArea.height]);
|
|
156
|
+
const shouldHide = useMemo(() => !isScreenReaderEnabled || !enableScrubbing || !accessibilityLabel || dataLength <= 0 || drawingArea.width <= 0 || drawingArea.height <= 0 || sampledSegments.length === 0, [isScreenReaderEnabled, enableScrubbing, accessibilityLabel, dataLength, drawingArea.width, drawingArea.height, sampledSegments.length]);
|
|
157
|
+
if (shouldHide) return;
|
|
158
|
+
const segmentsFlexDirection = isHorizontalLayout ? 'column' : 'row';
|
|
159
|
+
return /*#__PURE__*/_jsx(View, {
|
|
160
|
+
pointerEvents: "box-none",
|
|
161
|
+
style: [styles.container, overlayStyle],
|
|
162
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
163
|
+
style: [styles.segments, {
|
|
164
|
+
flexDirection: segmentsFlexDirection
|
|
165
|
+
}],
|
|
166
|
+
children: [leading > 0 && /*#__PURE__*/_jsx(View, {
|
|
167
|
+
style: getSegmentStyle(leading)
|
|
168
|
+
}), sampledSegments.map(segment => /*#__PURE__*/_jsx(Pressable, {
|
|
169
|
+
accessible: true,
|
|
170
|
+
accessibilityLabel: segment.accessibilityLabel,
|
|
171
|
+
style: getSegmentStyle(segment.weight)
|
|
172
|
+
}, segment.index)), trailing > 0 && /*#__PURE__*/_jsx(View, {
|
|
173
|
+
style: getSegmentStyle(trailing)
|
|
174
|
+
})]
|
|
175
|
+
})
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -12,7 +12,7 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
|
|
|
12
12
|
let {
|
|
13
13
|
seriesId,
|
|
14
14
|
dataIndex,
|
|
15
|
-
|
|
15
|
+
dataIndexValue,
|
|
16
16
|
isIdle,
|
|
17
17
|
BeaconComponent,
|
|
18
18
|
idlePulse,
|
|
@@ -22,6 +22,7 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
|
|
|
22
22
|
stroke
|
|
23
23
|
} = _ref;
|
|
24
24
|
const {
|
|
25
|
+
layout,
|
|
25
26
|
getSeries,
|
|
26
27
|
getSeriesData,
|
|
27
28
|
getXScale,
|
|
@@ -49,10 +50,10 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
|
|
|
49
50
|
// Get scales for gradient evaluation
|
|
50
51
|
const gradientScale = useMemo(() => {
|
|
51
52
|
if (!gradient) return undefined;
|
|
52
|
-
const scale = gradient.axis === 'x' ? getXScale() : getYScale(series == null ? void 0 : series.yAxisId);
|
|
53
|
+
const scale = gradient.axis === 'x' ? getXScale(series == null ? void 0 : series.xAxisId) : getYScale(series == null ? void 0 : series.yAxisId);
|
|
53
54
|
if (!scale) return undefined;
|
|
54
55
|
return convertToSerializableScale(scale);
|
|
55
|
-
}, [gradient, getXScale, getYScale, series == null ? void 0 : series.yAxisId]);
|
|
56
|
+
}, [gradient, getXScale, getYScale, series == null ? void 0 : series.xAxisId, series == null ? void 0 : series.yAxisId]);
|
|
56
57
|
const gradientStops = useMemo(() => {
|
|
57
58
|
if (!gradient || !gradientScale) return undefined;
|
|
58
59
|
const domain = {
|
|
@@ -70,25 +71,25 @@ const BeaconWithData = /*#__PURE__*/memo(_ref => {
|
|
|
70
71
|
var _series$color;
|
|
71
72
|
if (gradient && gradientScale && gradientStops) {
|
|
72
73
|
var _gradient$axis;
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
74
|
+
const categoryAxisIsX = layout !== 'horizontal';
|
|
75
|
+
const gradientAxis = (_gradient$axis = gradient.axis) != null ? _gradient$axis : 'y';
|
|
76
|
+
const valueForAxis = gradientAxis === 'x' ? categoryAxisIsX ? dataIndexValue.value : dataY.value : categoryAxisIsX ? dataY.value : dataIndexValue.value;
|
|
77
|
+
const evaluatedColor = evaluateGradientAtValue(gradientStops, valueForAxis, gradientScale);
|
|
78
|
+
if (evaluatedColor) {
|
|
79
|
+
return evaluatedColor;
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// Fallback to series color
|
|
84
84
|
return (_series$color = series == null ? void 0 : series.color) != null ? _series$color : theme.color.fgPrimary;
|
|
85
|
-
}, [gradient, gradientScale, gradientStops,
|
|
85
|
+
}, [gradient, gradientScale, gradientStops, dataIndexValue, dataY, series == null ? void 0 : series.color, theme.color.fgPrimary, layout]);
|
|
86
|
+
const categoryAxisIsX = layout !== 'horizontal';
|
|
86
87
|
return /*#__PURE__*/_jsx(BeaconComponent, {
|
|
87
88
|
ref: beaconRef,
|
|
88
89
|
animate: animate,
|
|
89
90
|
color: color,
|
|
90
|
-
dataX:
|
|
91
|
-
dataY: dataY,
|
|
91
|
+
dataX: categoryAxisIsX ? dataIndexValue : dataY,
|
|
92
|
+
dataY: categoryAxisIsX ? dataY : dataIndexValue,
|
|
92
93
|
idlePulse: idlePulse,
|
|
93
94
|
isIdle: isIdle,
|
|
94
95
|
seriesId: seriesId,
|
|
@@ -109,12 +110,15 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
|
|
|
109
110
|
scrubberPosition
|
|
110
111
|
} = useScrubberContext();
|
|
111
112
|
const {
|
|
113
|
+
layout,
|
|
112
114
|
getXAxis,
|
|
115
|
+
getYAxis,
|
|
113
116
|
series,
|
|
114
117
|
dataLength,
|
|
115
118
|
animate
|
|
116
119
|
} = useCartesianChartContext();
|
|
117
|
-
const
|
|
120
|
+
const categoryAxisIsX = useMemo(() => layout !== 'horizontal', [layout]);
|
|
121
|
+
const indexAxis = useMemo(() => categoryAxisIsX ? getXAxis() : getYAxis(), [categoryAxisIsX, getXAxis, getYAxis]);
|
|
118
122
|
|
|
119
123
|
// Expose imperative handle with pulse method
|
|
120
124
|
useImperativeHandle(ref, () => ({
|
|
@@ -132,14 +136,14 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
|
|
|
132
136
|
var _scrubberPosition$val;
|
|
133
137
|
return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
|
|
134
138
|
}, [scrubberPosition, dataLength]);
|
|
135
|
-
const
|
|
136
|
-
// Convert index to actual
|
|
137
|
-
if (
|
|
138
|
-
const dataValue =
|
|
139
|
+
const dataIndexValue = useDerivedValue(() => {
|
|
140
|
+
// Convert index to actual category-axis value if axis has data.
|
|
141
|
+
if (indexAxis != null && indexAxis.data && Array.isArray(indexAxis.data) && indexAxis.data[dataIndex.value] !== undefined) {
|
|
142
|
+
const dataValue = indexAxis.data[dataIndex.value];
|
|
139
143
|
return typeof dataValue === 'string' ? dataIndex.value : dataValue;
|
|
140
144
|
}
|
|
141
145
|
return dataIndex.value;
|
|
142
|
-
}, [
|
|
146
|
+
}, [indexAxis, dataIndex]);
|
|
143
147
|
const isIdle = useDerivedValue(() => {
|
|
144
148
|
return scrubberPosition.value === undefined;
|
|
145
149
|
}, [scrubberPosition]);
|
|
@@ -155,7 +159,7 @@ export const ScrubberBeaconGroup = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_r
|
|
|
155
159
|
animate: animate,
|
|
156
160
|
beaconRef: createBeaconRef(s.id),
|
|
157
161
|
dataIndex: dataIndex,
|
|
158
|
-
|
|
162
|
+
dataIndexValue: dataIndexValue,
|
|
159
163
|
idlePulse: idlePulse,
|
|
160
164
|
isIdle: isIdle,
|
|
161
165
|
seriesId: s.id,
|
|
@@ -23,25 +23,29 @@ export const ScrubberProvider = _ref => {
|
|
|
23
23
|
throw new Error('ScrubberProvider must be used within a ChartContext');
|
|
24
24
|
}
|
|
25
25
|
const {
|
|
26
|
+
layout,
|
|
26
27
|
getXSerializableScale,
|
|
27
|
-
|
|
28
|
+
getYSerializableScale,
|
|
29
|
+
getXAxis,
|
|
30
|
+
getYAxis
|
|
28
31
|
} = chartContext;
|
|
29
32
|
const scrubberPosition = useSharedValue(undefined);
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
+
const categoryAxisIsX = useMemo(() => layout !== 'horizontal', [layout]);
|
|
34
|
+
const categoryAxis = useMemo(() => categoryAxisIsX ? getXAxis() : getYAxis(), [categoryAxisIsX, getXAxis, getYAxis]);
|
|
35
|
+
const categoryScale = useMemo(() => categoryAxisIsX ? getXSerializableScale() : getYSerializableScale(), [categoryAxisIsX, getXSerializableScale, getYSerializableScale]);
|
|
36
|
+
const getDataIndexFromPosition = useCallback(touchPosition => {
|
|
33
37
|
'worklet';
|
|
34
38
|
|
|
35
|
-
if (!
|
|
36
|
-
if (
|
|
37
|
-
const [domainMin, domainMax] =
|
|
39
|
+
if (!categoryScale || !categoryAxis) return 0;
|
|
40
|
+
if (categoryScale.type === 'band') {
|
|
41
|
+
const [domainMin, domainMax] = categoryScale.domain;
|
|
38
42
|
const categoryCount = domainMax - domainMin + 1;
|
|
39
43
|
let closestIndex = 0;
|
|
40
44
|
let closestDistance = Infinity;
|
|
41
45
|
for (let i = 0; i < categoryCount; i++) {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
const distance = Math.abs(
|
|
46
|
+
const categoryPos = getPointOnSerializableScale(i, categoryScale);
|
|
47
|
+
if (categoryPos !== undefined) {
|
|
48
|
+
const distance = Math.abs(touchPosition - categoryPos);
|
|
45
49
|
if (distance < closestDistance) {
|
|
46
50
|
closestDistance = distance;
|
|
47
51
|
closestIndex = i;
|
|
@@ -50,18 +54,17 @@ export const ScrubberProvider = _ref => {
|
|
|
50
54
|
}
|
|
51
55
|
return closestIndex;
|
|
52
56
|
} else {
|
|
53
|
-
// For numeric scales with axis data, find the nearest data point
|
|
54
|
-
const axisData =
|
|
57
|
+
// For numeric scales with axis data, find the nearest data point.
|
|
58
|
+
const axisData = categoryAxis.data;
|
|
55
59
|
if (axisData && Array.isArray(axisData) && typeof axisData[0] === 'number') {
|
|
56
|
-
// We have numeric axis data - find the closest data point
|
|
57
60
|
const numericData = axisData;
|
|
58
61
|
let closestIndex = 0;
|
|
59
62
|
let closestDistance = Infinity;
|
|
60
63
|
for (let i = 0; i < numericData.length; i++) {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
const distance = Math.abs(
|
|
64
|
+
const dataValue = numericData[i];
|
|
65
|
+
const categoryPos = getPointOnSerializableScale(dataValue, categoryScale);
|
|
66
|
+
if (categoryPos !== undefined) {
|
|
67
|
+
const distance = Math.abs(touchPosition - categoryPos);
|
|
65
68
|
if (distance < closestDistance) {
|
|
66
69
|
closestDistance = distance;
|
|
67
70
|
closestIndex = i;
|
|
@@ -71,13 +74,13 @@ export const ScrubberProvider = _ref => {
|
|
|
71
74
|
return closestIndex;
|
|
72
75
|
} else {
|
|
73
76
|
var _domain$min, _domain$max;
|
|
74
|
-
const
|
|
75
|
-
const dataIndex = Math.round(
|
|
76
|
-
const domain =
|
|
77
|
+
const dataValue = invertSerializableScale(touchPosition, categoryScale);
|
|
78
|
+
const dataIndex = Math.round(dataValue);
|
|
79
|
+
const domain = categoryAxis.domain;
|
|
77
80
|
return Math.max((_domain$min = domain.min) != null ? _domain$min : 0, Math.min(dataIndex, (_domain$max = domain.max) != null ? _domain$max : 0));
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
|
-
}, [
|
|
83
|
+
}, [categoryAxis, categoryScale]);
|
|
81
84
|
const handleStartEndHaptics = useCallback(() => {
|
|
82
85
|
void Haptics.lightImpact();
|
|
83
86
|
}, []);
|
|
@@ -95,13 +98,15 @@ export const ScrubberProvider = _ref => {
|
|
|
95
98
|
|
|
96
99
|
// Android does not trigger onUpdate when the gesture starts. This achieves consistent behavior across both iOS and Android
|
|
97
100
|
if (Platform.OS === 'android') {
|
|
98
|
-
const
|
|
101
|
+
const pointerPosition = categoryAxisIsX ? event.x : event.y;
|
|
102
|
+
const newScrubberPosition = getDataIndexFromPosition(pointerPosition);
|
|
99
103
|
if (newScrubberPosition !== scrubberPosition.value) {
|
|
100
104
|
scrubberPosition.value = newScrubberPosition;
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
}).onUpdate(function onUpdate(event) {
|
|
104
|
-
const
|
|
108
|
+
const pointerPosition = categoryAxisIsX ? event.x : event.y;
|
|
109
|
+
const newScrubberPosition = getDataIndexFromPosition(pointerPosition);
|
|
105
110
|
if (newScrubberPosition !== scrubberPosition.value) {
|
|
106
111
|
scrubberPosition.value = newScrubberPosition;
|
|
107
112
|
}
|
|
@@ -114,7 +119,7 @@ export const ScrubberProvider = _ref => {
|
|
|
114
119
|
if (enableScrubbing) {
|
|
115
120
|
scrubberPosition.value = undefined;
|
|
116
121
|
}
|
|
117
|
-
}), [allowOverflowGestures, handleStartEndHaptics,
|
|
122
|
+
}), [allowOverflowGestures, handleStartEndHaptics, getDataIndexFromPosition, categoryAxisIsX, scrubberPosition, enableScrubbing]);
|
|
118
123
|
const contextValue = useMemo(() => ({
|
|
119
124
|
enableScrubbing: !!enableScrubbing,
|
|
120
125
|
scrubberPosition
|