@coinbase/cds-mobile-visualization 3.4.0-beta.22 → 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.
Files changed (100) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dts/chart/CartesianChart.d.ts +39 -7
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/Path.d.ts.map +1 -1
  5. package/dts/chart/area/Area.d.ts +7 -0
  6. package/dts/chart/area/Area.d.ts.map +1 -1
  7. package/dts/chart/area/AreaChart.d.ts +5 -5
  8. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  9. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  10. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  11. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  12. package/dts/chart/axis/Axis.d.ts +3 -1
  13. package/dts/chart/axis/Axis.d.ts.map +1 -1
  14. package/dts/chart/axis/XAxis.d.ts +6 -0
  15. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  16. package/dts/chart/axis/YAxis.d.ts +1 -0
  17. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  18. package/dts/chart/bar/Bar.d.ts +4 -2
  19. package/dts/chart/bar/Bar.d.ts.map +1 -1
  20. package/dts/chart/bar/BarChart.d.ts +49 -9
  21. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  22. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  23. package/dts/chart/bar/BarStack.d.ts +30 -9
  24. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  25. package/dts/chart/bar/BarStackGroup.d.ts +1 -1
  26. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  27. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  28. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  29. package/dts/chart/gradient/Gradient.d.ts +5 -0
  30. package/dts/chart/gradient/Gradient.d.ts.map +1 -1
  31. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  32. package/dts/chart/line/Line.d.ts +7 -0
  33. package/dts/chart/line/Line.d.ts.map +1 -1
  34. package/dts/chart/line/LineChart.d.ts +5 -5
  35. package/dts/chart/line/LineChart.d.ts.map +1 -1
  36. package/dts/chart/line/ReferenceLine.d.ts +1 -0
  37. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  38. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  39. package/dts/chart/point/Point.d.ts +7 -0
  40. package/dts/chart/point/Point.d.ts.map +1 -1
  41. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -1
  42. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +2 -1
  43. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -1
  44. package/dts/chart/scrubber/Scrubber.d.ts +8 -0
  45. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  46. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -1
  47. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  48. package/dts/chart/utils/axis.d.ts +20 -9
  49. package/dts/chart/utils/axis.d.ts.map +1 -1
  50. package/dts/chart/utils/bar.d.ts +4 -3
  51. package/dts/chart/utils/bar.d.ts.map +1 -1
  52. package/dts/chart/utils/chart.d.ts +13 -0
  53. package/dts/chart/utils/chart.d.ts.map +1 -1
  54. package/dts/chart/utils/context.d.ts +21 -6
  55. package/dts/chart/utils/context.d.ts.map +1 -1
  56. package/dts/chart/utils/gradient.d.ts +3 -1
  57. package/dts/chart/utils/gradient.d.ts.map +1 -1
  58. package/dts/chart/utils/path.d.ts +20 -0
  59. package/dts/chart/utils/path.d.ts.map +1 -1
  60. package/dts/chart/utils/point.d.ts +7 -0
  61. package/dts/chart/utils/point.d.ts.map +1 -1
  62. package/esm/chart/CartesianChart.js +107 -68
  63. package/esm/chart/Path.js +10 -7
  64. package/esm/chart/area/Area.js +19 -9
  65. package/esm/chart/area/AreaChart.js +11 -9
  66. package/esm/chart/area/DottedArea.js +11 -6
  67. package/esm/chart/area/GradientArea.js +11 -6
  68. package/esm/chart/area/SolidArea.js +3 -1
  69. package/esm/chart/area/__stories__/AreaChart.stories.js +30 -2
  70. package/esm/chart/axis/XAxis.js +14 -21
  71. package/esm/chart/axis/YAxis.js +4 -3
  72. package/esm/chart/bar/Bar.js +9 -5
  73. package/esm/chart/bar/BarChart.js +34 -31
  74. package/esm/chart/bar/BarPlot.js +7 -5
  75. package/esm/chart/bar/BarStack.js +176 -36
  76. package/esm/chart/bar/BarStackGroup.js +37 -27
  77. package/esm/chart/bar/DefaultBar.js +24 -8
  78. package/esm/chart/bar/DefaultBarStack.js +24 -10
  79. package/esm/chart/bar/__stories__/BarChart.stories.js +99 -3
  80. package/esm/chart/gradient/Gradient.js +2 -1
  81. package/esm/chart/line/DottedLine.js +3 -1
  82. package/esm/chart/line/Line.js +32 -19
  83. package/esm/chart/line/LineChart.js +9 -8
  84. package/esm/chart/line/SolidLine.js +3 -1
  85. package/esm/chart/line/__stories__/LineChart.stories.js +31 -0
  86. package/esm/chart/point/Point.js +2 -1
  87. package/esm/chart/scrubber/DefaultScrubberBeacon.js +1 -1
  88. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -10
  89. package/esm/chart/scrubber/Scrubber.js +47 -21
  90. package/esm/chart/scrubber/ScrubberBeaconGroup.js +24 -20
  91. package/esm/chart/scrubber/ScrubberProvider.js +29 -24
  92. package/esm/chart/scrubber/__stories__/Scrubber.stories.js +135 -1
  93. package/esm/chart/utils/axis.js +42 -14
  94. package/esm/chart/utils/bar.js +5 -4
  95. package/esm/chart/utils/chart.js +18 -5
  96. package/esm/chart/utils/context.js +7 -0
  97. package/esm/chart/utils/gradient.js +8 -4
  98. package/esm/chart/utils/path.js +90 -61
  99. package/esm/chart/utils/point.js +28 -18
  100. package/package.json +5 -5
@@ -42,7 +42,7 @@ export const DefaultScrubberBeacon = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((
42
42
  drawingArea
43
43
  } = useCartesianChartContext();
44
44
  const targetSeries = useMemo(() => getSeries(seriesId), [getSeries, seriesId]);
45
- const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
45
+ const xScale = useMemo(() => getXSerializableScale(targetSeries == null ? void 0 : targetSeries.xAxisId), [getXSerializableScale, targetSeries == null ? void 0 : targetSeries.xAxisId]);
46
46
  const yScale = useMemo(() => getYSerializableScale(targetSeries == null ? void 0 : targetSeries.yAxisId), [getYSerializableScale, targetSeries == null ? void 0 : targetSeries.yAxisId]);
47
47
  const color = useMemo(() => {
48
48
  var _ref2;
@@ -1,28 +1,44 @@
1
- const _excluded = ["verticalAlignment", "dy", "boundsInset"];
1
+ const _excluded = ["dx", "dy"];
2
2
  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); }
3
3
  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
- import { memo } from 'react';
4
+ import { memo, useMemo } from 'react';
5
5
  import { useCartesianChartContext } from '../ChartProvider';
6
6
  import { DefaultReferenceLineLabel } from '../line';
7
7
  import { jsx as _jsx } from "react/jsx-runtime";
8
8
  /**
9
9
  * DefaultScrubberLabel is the default label component for the scrubber line.
10
10
  * It will automatically add padding around the label when elevated to fit within chart bounds to prevent shadow from being cutoff.
11
- * It will also center the label vertically with the top available area.
11
+ * In vertical layout, it positions the label above the scrubber line.
12
+ * In horizontal layout, it centers the label in the chart's right inset.
12
13
  */
13
14
  export const DefaultScrubberLabel = /*#__PURE__*/memo(_ref => {
14
15
  let {
15
- verticalAlignment = 'middle',
16
- dy,
17
- boundsInset
16
+ dx: dxProp,
17
+ dy: dyProp
18
18
  } = _ref,
19
19
  props = _objectWithoutPropertiesLoose(_ref, _excluded);
20
20
  const {
21
- drawingArea
21
+ drawingArea,
22
+ layout,
23
+ width: chartWidth
22
24
  } = useCartesianChartContext();
25
+ const isHorizontalLayout = layout === 'horizontal';
26
+ const dx = useMemo(() => {
27
+ if (dxProp !== undefined) return dxProp;
28
+ if (isHorizontalLayout) {
29
+ const drawingAreaEnd = drawingArea.x + drawingArea.width;
30
+ const rightOffset = chartWidth - drawingAreaEnd;
31
+ return rightOffset / 2;
32
+ }
33
+ return 0;
34
+ }, [drawingArea.width, drawingArea.x, dxProp, isHorizontalLayout, chartWidth]);
35
+ const dy = useMemo(() => {
36
+ if (dyProp !== undefined) return dyProp;
37
+ if (isHorizontalLayout) return 0;
38
+ return -0.5 * drawingArea.y;
39
+ }, [dyProp, isHorizontalLayout, drawingArea.y]);
23
40
  return /*#__PURE__*/_jsx(DefaultReferenceLineLabel, _extends({
24
- boundsInset: boundsInset,
25
- dy: dy != null ? dy : -0.5 * drawingArea.y,
26
- verticalAlignment: verticalAlignment
41
+ dx: dx,
42
+ dy: dy
27
43
  }, props));
28
44
  });
@@ -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 xAxis = useMemo(() => getXAxis(), [getXAxis]);
56
- const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
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 dataX = useDerivedValue(() => {
80
- if (xAxis != null && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex.value] !== undefined) {
81
- const dataValue = xAxis.data[dataIndex.value];
82
- return typeof dataValue === 'string' ? dataIndex.value : dataValue;
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
- }, [xAxis, dataIndex]);
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
- const pixelX = dataX.value !== undefined && xScale ? getPointOnSerializableScale(dataX.value, xScale) : 0;
94
- return drawingArea.x + drawingArea.width - pixelX + overlayOffset;
95
- }, [dataX, xScale]);
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
- const xValue = dataX.value !== undefined && xScale ? getPointOnSerializableScale(dataX.value, xScale) : 0;
98
- return xValue;
99
- }, [dataX, xScale]);
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 isReady = !!xScale;
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: drawingArea.height + overlayOffset * 2,
162
+ height: overlayHeight,
141
163
  opacity: overlayOpacity,
142
164
  width: overlayWidth,
143
165
  x: overlayX,
144
- y: drawingArea.y - overlayOffset
145
- }), !hideLine && /*#__PURE__*/_jsx(ReferenceLine, {
166
+ y: overlayY
167
+ }), !hideLine && /*#__PURE__*/_jsx(ReferenceLine, _extends({
146
168
  LabelComponent: LabelComponent,
147
- LineComponent: LineComponent,
148
- dataX: dataX,
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
- }), !hideBeaconLabels && beaconLabels.length > 0 && /*#__PURE__*/_jsx(ScrubberBeaconLabelGroup, {
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
- dataX,
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 axis = (_gradient$axis = gradient.axis) != null ? _gradient$axis : 'y';
74
- const dataValue = axis === 'x' ? dataX.value : dataY.value;
75
- if (dataValue !== undefined) {
76
- const evaluatedColor = evaluateGradientAtValue(gradientStops, dataValue, gradientScale);
77
- if (evaluatedColor) {
78
- return evaluatedColor;
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, dataX, dataY, series == null ? void 0 : series.color, theme.color.fgPrimary]);
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: 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 xAxis = useMemo(() => getXAxis(), [getXAxis]);
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 dataX = useDerivedValue(() => {
136
- // Convert index to actual x value if axis has data
137
- if (xAxis != null && xAxis.data && Array.isArray(xAxis.data) && xAxis.data[dataIndex.value] !== undefined) {
138
- const dataValue = xAxis.data[dataIndex.value];
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
- }, [xAxis, dataIndex]);
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
- dataX: dataX,
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
- getXAxis
28
+ getYSerializableScale,
29
+ getXAxis,
30
+ getYAxis
28
31
  } = chartContext;
29
32
  const scrubberPosition = useSharedValue(undefined);
30
- const xAxis = useMemo(() => getXAxis(), [getXAxis]);
31
- const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
32
- const getDataIndexFromX = useCallback(touchX => {
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 (!xScale || !xAxis) return 0;
36
- if (xScale.type === 'band') {
37
- const [domainMin, domainMax] = xScale.domain;
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 xPos = getPointOnSerializableScale(i, xScale);
43
- if (xPos !== undefined) {
44
- const distance = Math.abs(touchX - xPos);
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 = xAxis.data;
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 xValue = numericData[i];
62
- const xPos = getPointOnSerializableScale(xValue, xScale);
63
- if (xPos !== undefined) {
64
- const distance = Math.abs(touchX - xPos);
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 xValue = invertSerializableScale(touchX, xScale);
75
- const dataIndex = Math.round(xValue);
76
- const domain = xAxis.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
- }, [xAxis, xScale]);
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 newScrubberPosition = getDataIndexFromX(event.x);
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 newScrubberPosition = getDataIndexFromX(event.x);
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, getDataIndexFromX, scrubberPosition, enableScrubbing]);
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;