@coinbase/cds-mobile-visualization 3.4.0-beta.21 → 3.4.0-beta.23
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 +14 -0
- package/dts/chart/CartesianChart.d.ts +39 -7
- package/dts/chart/CartesianChart.d.ts.map +1 -1
- package/dts/chart/Path.d.ts.map +1 -1
- package/dts/chart/PeriodSelector.d.ts +18 -6
- package/dts/chart/PeriodSelector.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 +33 -9
- 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 +8 -5
- 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/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 +6 -5
- 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/dts/chart/utils/transition.d.ts +7 -4
- package/dts/chart/utils/transition.d.ts.map +1 -1
- package/esm/chart/CartesianChart.js +107 -68
- package/esm/chart/Path.js +18 -14
- package/esm/chart/__stories__/ChartTransitions.stories.js +6 -10
- package/esm/chart/area/Area.js +19 -9
- package/esm/chart/area/AreaChart.js +18 -13
- package/esm/chart/area/DottedArea.js +23 -17
- package/esm/chart/area/GradientArea.js +11 -6
- package/esm/chart/area/SolidArea.js +3 -1
- package/esm/chart/area/__stories__/AreaChart.stories.js +30 -2
- package/esm/chart/axis/XAxis.js +14 -21
- package/esm/chart/axis/YAxis.js +4 -3
- 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 +99 -3
- package/esm/chart/gradient/Gradient.js +2 -1
- package/esm/chart/line/DottedLine.js +3 -1
- package/esm/chart/line/Line.js +36 -21
- package/esm/chart/line/LineChart.js +13 -11
- package/esm/chart/line/SolidLine.js +3 -1
- package/esm/chart/line/__stories__/LineChart.stories.js +31 -0
- 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/ScrubberBeaconGroup.js +24 -20
- package/esm/chart/scrubber/ScrubberProvider.js +29 -24
- package/esm/chart/scrubber/__stories__/Scrubber.stories.js +135 -1
- package/esm/chart/utils/axis.js +42 -14
- package/esm/chart/utils/bar.js +6 -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/esm/chart/utils/transition.js +28 -10
- package/package.json +5 -5
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
1
2
|
import React, { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo } from 'react';
|
|
2
3
|
import { runOnJS, useAnimatedReaction, useDerivedValue, useSharedValue } from 'react-native-reanimated';
|
|
3
4
|
import { useTheme } from '@coinbase/cds-mobile';
|
|
@@ -45,15 +46,19 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
45
46
|
scrubberPosition
|
|
46
47
|
} = useScrubberContext();
|
|
47
48
|
const {
|
|
49
|
+
layout,
|
|
48
50
|
getXSerializableScale,
|
|
51
|
+
getYSerializableScale,
|
|
49
52
|
getXAxis,
|
|
53
|
+
getYAxis,
|
|
50
54
|
series,
|
|
51
55
|
drawingArea,
|
|
52
56
|
animate,
|
|
53
57
|
dataLength
|
|
54
58
|
} = useCartesianChartContext();
|
|
55
|
-
const
|
|
56
|
-
const
|
|
59
|
+
const categoryAxisIsX = useMemo(() => layout !== 'horizontal', [layout]);
|
|
60
|
+
const indexAxis = useMemo(() => categoryAxisIsX ? getXAxis() : getYAxis(), [categoryAxisIsX, getXAxis, getYAxis]);
|
|
61
|
+
const indexScale = useMemo(() => categoryAxisIsX ? getXSerializableScale() : getYSerializableScale(), [categoryAxisIsX, getXSerializableScale, getYSerializableScale]);
|
|
57
62
|
|
|
58
63
|
// Animation state for delayed scrubber rendering (matches web timing)
|
|
59
64
|
const scrubberOpacity = useSharedValue(animate ? 0 : 1);
|
|
@@ -76,27 +81,43 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
76
81
|
var _scrubberPosition$val;
|
|
77
82
|
return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
|
|
78
83
|
}, [scrubberPosition, dataLength]);
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
const
|
|
82
|
-
return typeof
|
|
84
|
+
const dataValue = useDerivedValue(() => {
|
|
85
|
+
if (indexAxis != null && indexAxis.data && Array.isArray(indexAxis.data) && indexAxis.data[dataIndex.value] !== undefined) {
|
|
86
|
+
const axisValue = indexAxis.data[dataIndex.value];
|
|
87
|
+
return typeof axisValue === 'string' ? dataIndex.value : axisValue;
|
|
83
88
|
}
|
|
84
89
|
return dataIndex.value;
|
|
85
|
-
}, [
|
|
90
|
+
}, [indexAxis, dataIndex]);
|
|
86
91
|
const lineOpacity = useDerivedValue(() => {
|
|
87
92
|
return scrubberPosition.value !== undefined ? 1 : 0;
|
|
88
93
|
}, [scrubberPosition]);
|
|
89
94
|
const overlayOpacity = useDerivedValue(() => {
|
|
90
95
|
return scrubberPosition.value !== undefined ? 0.8 : 0;
|
|
91
96
|
}, [scrubberPosition]);
|
|
97
|
+
const pixelPosition = useDerivedValue(() => {
|
|
98
|
+
if (dataValue.value === undefined || !indexScale) return undefined;
|
|
99
|
+
return getPointOnSerializableScale(dataValue.value, indexScale);
|
|
100
|
+
}, [dataValue, indexScale]);
|
|
92
101
|
const overlayWidth = useDerivedValue(() => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
102
|
+
var _pixelPosition$value;
|
|
103
|
+
const pixel = (_pixelPosition$value = pixelPosition.value) != null ? _pixelPosition$value : 0;
|
|
104
|
+
return categoryAxisIsX ? drawingArea.x + drawingArea.width - pixel + overlayOffset : drawingArea.width + overlayOffset * 2;
|
|
105
|
+
}, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
|
|
106
|
+
const overlayHeight = useDerivedValue(() => {
|
|
107
|
+
var _pixelPosition$value2;
|
|
108
|
+
const pixel = (_pixelPosition$value2 = pixelPosition.value) != null ? _pixelPosition$value2 : 0;
|
|
109
|
+
return categoryAxisIsX ? drawingArea.height + overlayOffset * 2 : drawingArea.y + drawingArea.height - pixel + overlayOffset;
|
|
110
|
+
}, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
|
|
96
111
|
const overlayX = useDerivedValue(() => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
112
|
+
var _pixelPosition$value3;
|
|
113
|
+
const pixel = (_pixelPosition$value3 = pixelPosition.value) != null ? _pixelPosition$value3 : 0;
|
|
114
|
+
return categoryAxisIsX ? pixel : drawingArea.x - overlayOffset;
|
|
115
|
+
}, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
|
|
116
|
+
const overlayY = useDerivedValue(() => {
|
|
117
|
+
var _pixelPosition$value4;
|
|
118
|
+
const pixel = (_pixelPosition$value4 = pixelPosition.value) != null ? _pixelPosition$value4 : 0;
|
|
119
|
+
return categoryAxisIsX ? drawingArea.y - overlayOffset : pixel;
|
|
120
|
+
}, [pixelPosition, categoryAxisIsX, drawingArea, overlayOffset]);
|
|
100
121
|
const resolvedLabelValue = useSharedValue('');
|
|
101
122
|
const updateResolvedLabel = useCallback(index => {
|
|
102
123
|
if (!label) {
|
|
@@ -125,7 +146,8 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
125
146
|
color: s.color
|
|
126
147
|
}))) != null ? _series$filter$filter : [];
|
|
127
148
|
}, [series, filteredSeriesIds]);
|
|
128
|
-
const
|
|
149
|
+
const showBeaconLabels = !hideBeaconLabels && categoryAxisIsX && beaconLabels.length > 0;
|
|
150
|
+
const isReady = !!indexScale;
|
|
129
151
|
const groupEnterTransition = useMemo(() => getTransition(transitions == null ? void 0 : transitions.enter, animate, defaultAccessoryEnterTransition), [transitions == null ? void 0 : transitions.enter, animate]);
|
|
130
152
|
useEffect(() => {
|
|
131
153
|
if (animate && isReady) {
|
|
@@ -137,29 +159,33 @@ export const Scrubber = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) =>
|
|
|
137
159
|
opacity: scrubberOpacity,
|
|
138
160
|
children: [!hideOverlay && /*#__PURE__*/_jsx(Rect, {
|
|
139
161
|
color: theme.color.bg,
|
|
140
|
-
height:
|
|
162
|
+
height: overlayHeight,
|
|
141
163
|
opacity: overlayOpacity,
|
|
142
164
|
width: overlayWidth,
|
|
143
165
|
x: overlayX,
|
|
144
|
-
y:
|
|
145
|
-
}), !hideLine && /*#__PURE__*/_jsx(ReferenceLine, {
|
|
166
|
+
y: overlayY
|
|
167
|
+
}), !hideLine && /*#__PURE__*/_jsx(ReferenceLine, _extends({
|
|
146
168
|
LabelComponent: LabelComponent,
|
|
147
|
-
LineComponent: LineComponent
|
|
148
|
-
|
|
169
|
+
LineComponent: LineComponent
|
|
170
|
+
}, categoryAxisIsX ? {
|
|
171
|
+
dataX: dataValue
|
|
172
|
+
} : {
|
|
173
|
+
dataY: dataValue
|
|
174
|
+
}, {
|
|
149
175
|
label: resolvedLabelValue,
|
|
150
176
|
labelBoundsInset: labelBoundsInset,
|
|
151
177
|
labelElevated: labelElevated,
|
|
152
178
|
labelFont: labelFont,
|
|
153
179
|
opacity: lineOpacity,
|
|
154
180
|
stroke: lineStroke
|
|
155
|
-
}), /*#__PURE__*/_jsx(ScrubberBeaconGroup, {
|
|
181
|
+
})), /*#__PURE__*/_jsx(ScrubberBeaconGroup, {
|
|
156
182
|
ref: beaconGroupRef,
|
|
157
183
|
BeaconComponent: BeaconComponent,
|
|
158
184
|
idlePulse: idlePulse,
|
|
159
185
|
seriesIds: filteredSeriesIds,
|
|
160
186
|
stroke: beaconStroke,
|
|
161
187
|
transitions: transitions
|
|
162
|
-
}),
|
|
188
|
+
}), showBeaconLabels && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
|
|
163
189
|
BeaconLabelComponent: BeaconLabelComponent,
|
|
164
190
|
labelFont: beaconLabelFont,
|
|
165
191
|
labelHorizontalOffset: beaconLabelHorizontalOffset,
|
|
@@ -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
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const _excluded = ["seriesId", "color", "label"],
|
|
2
|
-
_excluded2 = ["seriesId", "color", "label"]
|
|
2
|
+
_excluded2 = ["seriesId", "color", "label"],
|
|
3
|
+
_excluded3 = ["seriesId", "color"];
|
|
3
4
|
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
4
5
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
5
6
|
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
|
@@ -654,6 +655,136 @@ const HideOverlay = () => {
|
|
|
654
655
|
})
|
|
655
656
|
});
|
|
656
657
|
};
|
|
658
|
+
const matchupBlueData = [47, 50, 51, 52, 53, 53, 53, 53, 52, 51, 51, 52, 53, 55, 57, 58, 59, 61, 63, 65, 64, 64, 64, 64, 64, 63, 63, 63, 64, 66, 68, 70, 71, 72, 74, 76, 76, 75, 74, 73, 74, 75, 75, 78];
|
|
659
|
+
const matchupRedData = matchupBlueData.map(value => 100 - value);
|
|
660
|
+
const matchupTeamLabels = {
|
|
661
|
+
blue: 'BLUE',
|
|
662
|
+
red: 'RED'
|
|
663
|
+
};
|
|
664
|
+
const MatchupBeaconLabels = () => {
|
|
665
|
+
const theme = useTheme();
|
|
666
|
+
const MatchupScrubberBeaconLabel = /*#__PURE__*/memo(_ref7 => {
|
|
667
|
+
var _matchupTeamLabels$se;
|
|
668
|
+
let {
|
|
669
|
+
seriesId,
|
|
670
|
+
color
|
|
671
|
+
} = _ref7,
|
|
672
|
+
props = _objectWithoutPropertiesLoose(_ref7, _excluded3);
|
|
673
|
+
const {
|
|
674
|
+
getSeriesData,
|
|
675
|
+
series,
|
|
676
|
+
fontProvider
|
|
677
|
+
} = useCartesianChartContext();
|
|
678
|
+
const {
|
|
679
|
+
scrubberPosition
|
|
680
|
+
} = useScrubberContext();
|
|
681
|
+
const seriesData = useMemo(() => getLineData(getSeriesData(seriesId)), [getSeriesData, seriesId]);
|
|
682
|
+
const dataLength = useMemo(() => {
|
|
683
|
+
var _series$reduce3;
|
|
684
|
+
return (_series$reduce3 = series == null ? void 0 : series.reduce((max, currentSeries) => {
|
|
685
|
+
var _data$length3;
|
|
686
|
+
const data = getSeriesData(currentSeries.id);
|
|
687
|
+
return Math.max(max, (_data$length3 = data == null ? void 0 : data.length) != null ? _data$length3 : 0);
|
|
688
|
+
}, 0)) != null ? _series$reduce3 : 0;
|
|
689
|
+
}, [series, getSeriesData]);
|
|
690
|
+
const dataIndex = useDerivedValue(() => {
|
|
691
|
+
var _scrubberPosition$val3;
|
|
692
|
+
return (_scrubberPosition$val3 = scrubberPosition.value) != null ? _scrubberPosition$val3 : Math.max(0, dataLength - 1);
|
|
693
|
+
}, [scrubberPosition, dataLength]);
|
|
694
|
+
const teamLabel = (_matchupTeamLabels$se = matchupTeamLabels[seriesId]) != null ? _matchupTeamLabels$se : String(seriesId).toUpperCase();
|
|
695
|
+
const labelColor = color != null ? color : theme.color.fgPrimary;
|
|
696
|
+
const legalFontSize = theme.fontSize.legal;
|
|
697
|
+
const title3FontSize = theme.fontSize.title3;
|
|
698
|
+
const teamStyle = useMemo(() => ({
|
|
699
|
+
fontFamilies: ['Inter'],
|
|
700
|
+
fontSize: legalFontSize,
|
|
701
|
+
fontStyle: {
|
|
702
|
+
weight: FontWeight.Normal
|
|
703
|
+
},
|
|
704
|
+
color: Skia.Color(labelColor)
|
|
705
|
+
}), [labelColor, legalFontSize]);
|
|
706
|
+
const percentageStyle = useMemo(() => ({
|
|
707
|
+
fontFamilies: ['Inter'],
|
|
708
|
+
fontSize: title3FontSize,
|
|
709
|
+
fontStyle: {
|
|
710
|
+
weight: FontWeight.Bold
|
|
711
|
+
},
|
|
712
|
+
color: Skia.Color(labelColor)
|
|
713
|
+
}), [title3FontSize, labelColor]);
|
|
714
|
+
const matchupLabel = useDerivedValue(() => {
|
|
715
|
+
if (seriesData === undefined) {
|
|
716
|
+
return teamLabel;
|
|
717
|
+
}
|
|
718
|
+
const value = seriesData[dataIndex.value];
|
|
719
|
+
const builder = Skia.ParagraphBuilder.Make({
|
|
720
|
+
textAlign: TextAlign.Left
|
|
721
|
+
}, fontProvider);
|
|
722
|
+
builder.pushStyle(teamStyle);
|
|
723
|
+
builder.addText(teamLabel);
|
|
724
|
+
builder.addText('\n');
|
|
725
|
+
builder.pushStyle(percentageStyle);
|
|
726
|
+
builder.addText(value + "%");
|
|
727
|
+
const paragraph = builder.build();
|
|
728
|
+
paragraph.layout(240);
|
|
729
|
+
return paragraph;
|
|
730
|
+
}, [dataIndex, fontProvider, percentageStyle, seriesData, teamLabel, teamStyle]);
|
|
731
|
+
return /*#__PURE__*/_jsx(DefaultScrubberBeaconLabel, _extends({}, props, {
|
|
732
|
+
background: "transparent",
|
|
733
|
+
color: labelColor,
|
|
734
|
+
elevated: false,
|
|
735
|
+
inset: 0,
|
|
736
|
+
label: matchupLabel,
|
|
737
|
+
seriesId: seriesId
|
|
738
|
+
}));
|
|
739
|
+
});
|
|
740
|
+
return /*#__PURE__*/_jsx(LineChart, {
|
|
741
|
+
enableScrubbing: true,
|
|
742
|
+
showArea: true,
|
|
743
|
+
areaType: "dotted",
|
|
744
|
+
height: 300,
|
|
745
|
+
inset: {
|
|
746
|
+
bottom: 8,
|
|
747
|
+
left: 8,
|
|
748
|
+
top: 8,
|
|
749
|
+
right: 0
|
|
750
|
+
},
|
|
751
|
+
series: [{
|
|
752
|
+
id: 'blue',
|
|
753
|
+
data: matchupBlueData,
|
|
754
|
+
color: "rgb(" + theme.spectrum.blue50 + ")",
|
|
755
|
+
label: 'BLUE'
|
|
756
|
+
}, {
|
|
757
|
+
id: 'red',
|
|
758
|
+
data: matchupRedData,
|
|
759
|
+
color: "rgb(" + theme.spectrum.red50 + ")",
|
|
760
|
+
label: 'RED'
|
|
761
|
+
}],
|
|
762
|
+
xAxis: {
|
|
763
|
+
range: _ref8 => {
|
|
764
|
+
let {
|
|
765
|
+
min,
|
|
766
|
+
max
|
|
767
|
+
} = _ref8;
|
|
768
|
+
return {
|
|
769
|
+
min,
|
|
770
|
+
max: max - 64
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
yAxis: {
|
|
775
|
+
domain: {
|
|
776
|
+
min: 0,
|
|
777
|
+
max: 100
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
children: /*#__PURE__*/_jsx(Scrubber, {
|
|
781
|
+
idlePulse: true,
|
|
782
|
+
BeaconLabelComponent: MatchupScrubberBeaconLabel,
|
|
783
|
+
beaconLabelHorizontalOffset: 16,
|
|
784
|
+
beaconLabelPreferredSide: "right"
|
|
785
|
+
})
|
|
786
|
+
});
|
|
787
|
+
};
|
|
657
788
|
const ExampleNavigator = () => {
|
|
658
789
|
const [currentIndex, setCurrentIndex] = useState(0);
|
|
659
790
|
const examples = useMemo(() => [{
|
|
@@ -707,6 +838,9 @@ const ExampleNavigator = () => {
|
|
|
707
838
|
}, {
|
|
708
839
|
title: 'Hide Overlay',
|
|
709
840
|
component: /*#__PURE__*/_jsx(HideOverlay, {})
|
|
841
|
+
}, {
|
|
842
|
+
title: 'Matchup Beacon Labels',
|
|
843
|
+
component: /*#__PURE__*/_jsx(MatchupBeaconLabels, {})
|
|
710
844
|
}], []);
|
|
711
845
|
const currentExample = examples[currentIndex];
|
|
712
846
|
const isFirstExample = currentIndex === 0;
|
package/esm/chart/utils/axis.js
CHANGED
|
@@ -47,7 +47,7 @@ export const toPointAnchor = placement => {
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Gets a D3 scale based on the axis configuration.
|
|
50
|
+
* Gets a D3 scale based on the cartesian axis configuration.
|
|
51
51
|
* Handles both numeric (linear/log) and categorical (band) scales.
|
|
52
52
|
*
|
|
53
53
|
* For numeric scales, the domain limit controls whether bounds are "nice" (human-friendly)
|
|
@@ -57,19 +57,27 @@ export const toPointAnchor = placement => {
|
|
|
57
57
|
* @returns The D3 scale function
|
|
58
58
|
* @throws An Error if bounds are invalid
|
|
59
59
|
*/
|
|
60
|
-
export const
|
|
60
|
+
export const getCartesianAxisScale = _ref => {
|
|
61
61
|
var _config$scaleType;
|
|
62
62
|
let {
|
|
63
63
|
config,
|
|
64
64
|
type,
|
|
65
65
|
range,
|
|
66
|
-
dataDomain
|
|
66
|
+
dataDomain,
|
|
67
|
+
layout = 'vertical'
|
|
67
68
|
} = _ref;
|
|
68
69
|
const scaleType = (_config$scaleType = config == null ? void 0 : config.scaleType) != null ? _config$scaleType : 'linear';
|
|
69
70
|
let adjustedRange = range;
|
|
70
71
|
|
|
71
|
-
//
|
|
72
|
-
|
|
72
|
+
// Determine if this axis needs range inversion for SVG coordinate system.
|
|
73
|
+
// SVG Y coordinates increase downward, so we need to invert for value axes
|
|
74
|
+
// where we want higher values at the top.
|
|
75
|
+
//
|
|
76
|
+
// For vertical layout: Y axis is the value axis -> invert (higher values at top)
|
|
77
|
+
// For horizontal layout: Y axis is the category axis -> don't invert (first category at top is natural)
|
|
78
|
+
// X axis never needs inversion (left-to-right is natural for both layouts)
|
|
79
|
+
const shouldInvertRange = type === 'y' && layout !== 'horizontal';
|
|
80
|
+
if (shouldInvertRange) {
|
|
73
81
|
adjustedRange = {
|
|
74
82
|
min: adjustedRange.max,
|
|
75
83
|
max: adjustedRange.min
|
|
@@ -121,6 +129,8 @@ export const getAxisConfig = function (type, axes, defaultId, defaultScaleType)
|
|
|
121
129
|
defaultScaleType = defaultAxisScaleType;
|
|
122
130
|
}
|
|
123
131
|
const defaultDomainLimit = type === 'x' ? 'strict' : 'nice';
|
|
132
|
+
const axisName = type === 'x' ? 'x-axis' : 'y-axis';
|
|
133
|
+
const axisDocUrl = type === 'x' ? 'https://cds.coinbase.com/components/charts/XAxis' : 'https://cds.coinbase.com/components/charts/YAxis';
|
|
124
134
|
if (!axes) {
|
|
125
135
|
return [{
|
|
126
136
|
id: defaultId,
|
|
@@ -137,16 +147,27 @@ export const getAxisConfig = function (type, axes, defaultId, defaultScaleType)
|
|
|
137
147
|
} = _ref2;
|
|
138
148
|
return id === undefined;
|
|
139
149
|
})) {
|
|
140
|
-
throw new Error(
|
|
150
|
+
throw new Error("When defining multiple " + axisName + ", each must have a unique id. See " + axisDocUrl + ".");
|
|
141
151
|
}
|
|
142
|
-
|
|
152
|
+
if (axesLength > 1) {
|
|
153
|
+
const ids = axes.map(_ref3 => {
|
|
154
|
+
let {
|
|
155
|
+
id
|
|
156
|
+
} = _ref3;
|
|
157
|
+
return id;
|
|
158
|
+
}).filter(id => id !== undefined);
|
|
159
|
+
if (new Set(ids).size !== ids.length) {
|
|
160
|
+
throw new Error("When defining multiple " + axisName + ", each must have a unique id. See " + axisDocUrl + ".");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return axes.map(_ref4 => {
|
|
143
164
|
let {
|
|
144
165
|
id
|
|
145
|
-
} =
|
|
146
|
-
axis = _objectWithoutPropertiesLoose(
|
|
166
|
+
} = _ref4,
|
|
167
|
+
axis = _objectWithoutPropertiesLoose(_ref4, _excluded);
|
|
147
168
|
return _extends({
|
|
148
169
|
// defaults the axis id if only a single axis is provided
|
|
149
|
-
id: axesLength > 1 ? id != null ? id : defaultAxisId : id,
|
|
170
|
+
id: axesLength > 1 ? id != null ? id : defaultAxisId : id != null ? id : defaultId,
|
|
150
171
|
scaleType: defaultScaleType,
|
|
151
172
|
domainLimit: defaultDomainLimit
|
|
152
173
|
}, axis);
|
|
@@ -168,10 +189,14 @@ export const getAxisConfig = function (type, axes, defaultId, defaultScaleType)
|
|
|
168
189
|
* @param axisParam - The axis configuration
|
|
169
190
|
* @param series - Array of series objects (for stacking support)
|
|
170
191
|
* @param axisType - Whether this is an 'x' or 'y' axis
|
|
192
|
+
* @param layout - The chart layout orientation
|
|
171
193
|
* @returns The calculated axis bounds
|
|
172
194
|
*/
|
|
173
|
-
export const
|
|
195
|
+
export const getCartesianAxisDomain = function (axisParam, series, axisType, layout) {
|
|
174
196
|
var _finalDomain$min, _finalDomain$max;
|
|
197
|
+
if (layout === void 0) {
|
|
198
|
+
layout = 'vertical';
|
|
199
|
+
}
|
|
175
200
|
let dataDomain = null;
|
|
176
201
|
if (axisParam.data && Array.isArray(axisParam.data) && axisParam.data.length > 0) {
|
|
177
202
|
const firstItem = axisParam.data[0];
|
|
@@ -193,7 +218,10 @@ export const getAxisDomain = (axisParam, series, axisType) => {
|
|
|
193
218
|
}
|
|
194
219
|
|
|
195
220
|
// Calculate domain from series data
|
|
196
|
-
|
|
221
|
+
// In vertical layout: X is category (index), Y is value (value)
|
|
222
|
+
// In horizontal layout: Y is category (index), X is value (value)
|
|
223
|
+
const isCategoryAxis = layout !== 'horizontal' && axisType === 'x' || layout === 'horizontal' && axisType === 'y';
|
|
224
|
+
const seriesDomain = isCategoryAxis ? getChartDomain(series) : getChartRange(series);
|
|
197
225
|
|
|
198
226
|
// If data sets the domain, use that instead of the series domain
|
|
199
227
|
const preferredDataDomain = dataDomain != null ? dataDomain : seriesDomain;
|
|
@@ -487,7 +515,7 @@ const generateEvenlyDistributedTicks = (scale, tickInterval, possibleTickValues,
|
|
|
487
515
|
* });
|
|
488
516
|
* // Returns tick positions centered in each selected band
|
|
489
517
|
*/
|
|
490
|
-
export const getAxisTicksData =
|
|
518
|
+
export const getAxisTicksData = _ref5 => {
|
|
491
519
|
var _options$anchor;
|
|
492
520
|
let {
|
|
493
521
|
ticks,
|
|
@@ -497,7 +525,7 @@ export const getAxisTicksData = _ref4 => {
|
|
|
497
525
|
possibleTickValues,
|
|
498
526
|
tickInterval,
|
|
499
527
|
options
|
|
500
|
-
} =
|
|
528
|
+
} = _ref5;
|
|
501
529
|
const anchor = (_options$anchor = options == null ? void 0 : options.anchor) != null ? _options$anchor : 'middle';
|
|
502
530
|
|
|
503
531
|
// Handle band scales
|