@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.
Files changed (106) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dts/chart/CartesianChart.d.ts +58 -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 +22 -8
  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/ScrubberAccessibilityView.d.ts +12 -0
  47. package/dts/chart/scrubber/ScrubberAccessibilityView.d.ts.map +1 -0
  48. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -1
  49. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  50. package/dts/chart/utils/axis.d.ts +20 -9
  51. package/dts/chart/utils/axis.d.ts.map +1 -1
  52. package/dts/chart/utils/bar.d.ts +4 -3
  53. package/dts/chart/utils/bar.d.ts.map +1 -1
  54. package/dts/chart/utils/chart.d.ts +13 -0
  55. package/dts/chart/utils/chart.d.ts.map +1 -1
  56. package/dts/chart/utils/context.d.ts +21 -6
  57. package/dts/chart/utils/context.d.ts.map +1 -1
  58. package/dts/chart/utils/gradient.d.ts +3 -1
  59. package/dts/chart/utils/gradient.d.ts.map +1 -1
  60. package/dts/chart/utils/path.d.ts +20 -0
  61. package/dts/chart/utils/path.d.ts.map +1 -1
  62. package/dts/chart/utils/point.d.ts +7 -0
  63. package/dts/chart/utils/point.d.ts.map +1 -1
  64. package/esm/chart/CartesianChart.js +145 -81
  65. package/esm/chart/Path.js +10 -7
  66. package/esm/chart/__stories__/CartesianChart.stories.js +10 -0
  67. package/esm/chart/__stories__/ChartAccessibility.stories.js +721 -0
  68. package/esm/chart/area/Area.js +19 -9
  69. package/esm/chart/area/AreaChart.js +11 -9
  70. package/esm/chart/area/DottedArea.js +11 -6
  71. package/esm/chart/area/GradientArea.js +11 -6
  72. package/esm/chart/area/SolidArea.js +3 -1
  73. package/esm/chart/area/__stories__/AreaChart.stories.js +47 -5
  74. package/esm/chart/axis/XAxis.js +14 -21
  75. package/esm/chart/axis/YAxis.js +4 -3
  76. package/esm/chart/axis/__stories__/Axis.stories.js +65 -48
  77. package/esm/chart/bar/Bar.js +9 -5
  78. package/esm/chart/bar/BarChart.js +34 -31
  79. package/esm/chart/bar/BarPlot.js +7 -5
  80. package/esm/chart/bar/BarStack.js +176 -36
  81. package/esm/chart/bar/BarStackGroup.js +37 -27
  82. package/esm/chart/bar/DefaultBar.js +24 -8
  83. package/esm/chart/bar/DefaultBarStack.js +24 -10
  84. package/esm/chart/bar/__stories__/BarChart.stories.js +105 -3
  85. package/esm/chart/gradient/Gradient.js +2 -1
  86. package/esm/chart/line/DottedLine.js +3 -1
  87. package/esm/chart/line/Line.js +32 -19
  88. package/esm/chart/line/LineChart.js +31 -9
  89. package/esm/chart/line/SolidLine.js +3 -1
  90. package/esm/chart/line/__stories__/LineChart.stories.js +115 -46
  91. package/esm/chart/point/Point.js +2 -1
  92. package/esm/chart/scrubber/DefaultScrubberBeacon.js +1 -1
  93. package/esm/chart/scrubber/DefaultScrubberLabel.js +26 -10
  94. package/esm/chart/scrubber/Scrubber.js +47 -21
  95. package/esm/chart/scrubber/ScrubberAccessibilityView.js +177 -0
  96. package/esm/chart/scrubber/ScrubberBeaconGroup.js +24 -20
  97. package/esm/chart/scrubber/ScrubberProvider.js +29 -24
  98. package/esm/chart/scrubber/__stories__/Scrubber.stories.js +192 -6
  99. package/esm/chart/utils/axis.js +42 -14
  100. package/esm/chart/utils/bar.js +5 -4
  101. package/esm/chart/utils/chart.js +18 -5
  102. package/esm/chart/utils/context.js +7 -0
  103. package/esm/chart/utils/gradient.js +8 -4
  104. package/esm/chart/utils/path.js +90 -61
  105. package/esm/chart/utils/point.js +28 -18
  106. package/package.json +5 -5
@@ -34,31 +34,24 @@ import { DottedLine, Line, LineChart, ReferenceLine, SolidLine } from '..';
34
34
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
35
35
  function MultipleLine() {
36
36
  const theme = useTheme();
37
- const [scrubberPosition, setScrubberPosition] = useState();
38
37
  const pages = useMemo(() => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], []);
39
38
  const pageViews = useMemo(() => [2400, 1398, 9800, 3908, 4800, 3800, 4300], []);
40
39
  const uniqueVisitors = useMemo(() => [4000, 3000, 2000, 2780, 1890, 2390, 3490], []);
41
40
  const chartAccessibilityLabel = "Website visitors across " + pageViews.length + " pages.";
42
- const scrubberAccessibilityLabel = useCallback(index => {
43
- return pages[index] + " has " + pageViews[index] + " views and " + uniqueVisitors[index] + " unique visitors.";
44
- }, [pages, pageViews, uniqueVisitors]);
41
+ const chartAccessibilityHint = 'Swipe left or right to hear details for each page.';
42
+ const getScrubberAccessibilityLabel = useCallback(index => pages[index] + " has " + pageViews[index] + " views and " + uniqueVisitors[index] + " unique visitors.", [pages, pageViews, uniqueVisitors]);
45
43
  const numberFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
46
44
  maximumFractionDigits: 0
47
45
  }).format(value), []);
48
- const accessibilityLabel = useMemo(() => {
49
- if (scrubberPosition !== undefined) {
50
- return scrubberAccessibilityLabel(scrubberPosition);
51
- }
52
- return chartAccessibilityLabel;
53
- }, [scrubberPosition, chartAccessibilityLabel, scrubberAccessibilityLabel]);
54
46
  return /*#__PURE__*/_jsx(LineChart, {
55
47
  enableScrubbing: true,
56
48
  showArea: true,
57
49
  showXAxis: true,
58
50
  showYAxis: true,
59
- accessibilityLabel: accessibilityLabel,
51
+ accessibilityHint: chartAccessibilityHint,
52
+ accessibilityLabel: chartAccessibilityLabel + " " + chartAccessibilityHint,
53
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
60
54
  height: 200,
61
- onScrubberPositionChange: setScrubberPosition,
62
55
  series: [{
63
56
  id: 'pageViews',
64
57
  data: pageViews,
@@ -85,27 +78,19 @@ function MultipleLine() {
85
78
  });
86
79
  }
87
80
  function DataFormat() {
88
- const [scrubberPosition, setScrubberPosition] = useState();
89
81
  const yData = useMemo(() => [2, 5.5, 2, 8.5, 1.5, 5], []);
90
82
  const xData = useMemo(() => [1, 2, 3, 5, 8, 10], []);
91
83
  const chartAccessibilityLabel = "Chart with custom X and Y data. " + yData.length + " data points";
92
- const scrubberAccessibilityLabel = useCallback(index => {
93
- return "Point " + (index + 1) + ": X value " + xData[index] + ", Y value " + yData[index];
94
- }, [xData, yData]);
95
- const accessibilityLabel = useMemo(() => {
96
- if (scrubberPosition !== undefined) {
97
- return scrubberAccessibilityLabel(scrubberPosition);
98
- }
99
- return chartAccessibilityLabel;
100
- }, [scrubberPosition, chartAccessibilityLabel, scrubberAccessibilityLabel]);
84
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": X value " + xData[index] + ", Y value " + yData[index], [xData, yData]);
101
85
  return /*#__PURE__*/_jsx(LineChart, {
102
86
  enableScrubbing: true,
103
87
  points: true,
104
88
  showArea: true,
105
89
  showXAxis: true,
106
90
  showYAxis: true,
107
- accessibilityLabel: accessibilityLabel,
91
+ accessibilityLabel: chartAccessibilityLabel,
108
92
  curve: "natural",
93
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
109
94
  height: 200,
110
95
  inset: {
111
96
  top: 16,
@@ -113,7 +98,6 @@ function DataFormat() {
113
98
  bottom: 0,
114
99
  left: 0
115
100
  },
116
- onScrubberPositionChange: setScrubberPosition,
117
101
  series: [{
118
102
  id: 'line',
119
103
  data: yData
@@ -144,6 +128,8 @@ function LiveUpdates() {
144
128
  return sparklineInteractiveData.hour.map(d => d.value);
145
129
  }, []);
146
130
  const [priceData, setPriceData] = useState(initialData);
131
+ const chartAccessibilityLabel = "Live price chart with " + priceData.length + " data points.";
132
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": " + priceData[index], [priceData]);
147
133
  const lastDataPointTimeRef = useRef(Date.now());
148
134
  const updateCountRef = useRef(0);
149
135
  const intervalSeconds = 3600 / initialData.length;
@@ -182,6 +168,8 @@ function LiveUpdates() {
182
168
  return /*#__PURE__*/_jsx(LineChart, {
183
169
  enableScrubbing: true,
184
170
  showArea: true,
171
+ accessibilityLabel: chartAccessibilityLabel,
172
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
185
173
  height: 200,
186
174
  inset: {
187
175
  right: 64
@@ -198,9 +186,17 @@ function LiveUpdates() {
198
186
  }
199
187
  function MissingData() {
200
188
  const theme = useTheme();
201
- const pages = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'];
202
- const pageViews = [2400, 1398, null, 3908, 4800, 3800, 4300];
203
- const uniqueVisitors = [4000, 3000, null, 2780, 1890, 2390, 3490];
189
+ const pages = useMemo(() => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], []);
190
+ const pageViews = useMemo(() => [2400, 1398, null, 3908, 4800, 3800, 4300], []);
191
+ const uniqueVisitors = useMemo(() => [4000, 3000, null, 2780, 1890, 2390, 3490], []);
192
+ const chartAccessibilityLabel = "Website visitors across " + pages.length + " pages. Some data points are missing.";
193
+ const getScrubberAccessibilityLabel = useCallback(index => {
194
+ const pv = pageViews[index];
195
+ const uv = uniqueVisitors[index];
196
+ const pvStr = pv != null ? pv : 'no data';
197
+ const uvStr = uv != null ? uv : 'no data';
198
+ return pages[index] + ": " + pvStr + " views, " + uvStr + " unique visitors.";
199
+ }, [pages, pageViews, uniqueVisitors]);
204
200
  const numberFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
205
201
  maximumFractionDigits: 0
206
202
  }).format(value), []);
@@ -210,6 +206,8 @@ function MissingData() {
210
206
  showArea: true,
211
207
  showXAxis: true,
212
208
  showYAxis: true,
209
+ accessibilityLabel: chartAccessibilityLabel,
210
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
213
211
  height: 200,
214
212
  series: [{
215
213
  id: 'pageViews',
@@ -239,6 +237,9 @@ function MissingData() {
239
237
  }
240
238
  function Interaction() {
241
239
  const [scrubberPosition, setScrubberPosition] = useState();
240
+ const data = useMemo(() => [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58], []);
241
+ const chartAccessibilityLabel = "Price chart with " + data.length + " data points. Swipe to navigate.";
242
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": " + data[index], [data]);
242
243
  return /*#__PURE__*/_jsxs(VStack, {
243
244
  gap: 2,
244
245
  children: [/*#__PURE__*/_jsx(Text, {
@@ -247,11 +248,13 @@ function Interaction() {
247
248
  }), /*#__PURE__*/_jsx(LineChart, {
248
249
  enableScrubbing: true,
249
250
  showArea: true,
251
+ accessibilityLabel: chartAccessibilityLabel,
252
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
250
253
  height: 200,
251
254
  onScrubberPositionChange: setScrubberPosition,
252
255
  series: [{
253
256
  id: 'prices',
254
- data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
257
+ data
255
258
  }],
256
259
  children: /*#__PURE__*/_jsx(Scrubber, {})
257
260
  })]
@@ -391,8 +394,12 @@ function Transitions() {
391
394
  color: positiveColor
392
395
  }]
393
396
  };
397
+ const chartAccessibilityLabel = "Price chart with " + data.length + " data points. Swipe to navigate.";
398
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": " + valueAtIndexFormatter(index), [valueAtIndexFormatter]);
394
399
  return /*#__PURE__*/_jsxs(CartesianChart, {
395
400
  enableScrubbing: true,
401
+ accessibilityLabel: chartAccessibilityLabel,
402
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
396
403
  height: 200,
397
404
  inset: {
398
405
  top: 32,
@@ -433,7 +440,6 @@ function Transitions() {
433
440
  return /*#__PURE__*/_jsx(CustomTransitionsChart, {});
434
441
  }
435
442
  function BasicAccessible() {
436
- const [scrubberPosition, setScrubberPosition] = useState();
437
443
  const data = useMemo(() => [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58], []);
438
444
 
439
445
  // Chart-level accessibility label provides overview
@@ -441,24 +447,14 @@ function BasicAccessible() {
441
447
  const currentPrice = data[data.length - 1];
442
448
  return "Price chart showing trend over " + data.length + " data points. Current value: " + currentPrice + ". Use arrow keys to adjust view";
443
449
  }, [data]);
444
-
445
- // Scrubber-level accessibility label provides specific position info
446
- const scrubberAccessibilityLabel = useCallback(index => {
447
- return "Price at position " + (index + 1) + " of " + data.length + ": " + data[index];
448
- }, [data]);
449
- const accessibilityLabel = useMemo(() => {
450
- if (scrubberPosition !== undefined) {
451
- return scrubberAccessibilityLabel(scrubberPosition);
452
- }
453
- return chartAccessibilityLabel;
454
- }, [scrubberPosition, chartAccessibilityLabel, scrubberAccessibilityLabel]);
450
+ const getScrubberAccessibilityLabel = useCallback(index => "Price at position " + (index + 1) + " of " + data.length + ": " + data[index], [data]);
455
451
  return /*#__PURE__*/_jsx(LineChart, {
456
452
  enableScrubbing: true,
457
453
  showArea: true,
458
454
  showYAxis: true,
459
- accessibilityLabel: accessibilityLabel,
455
+ accessibilityLabel: chartAccessibilityLabel,
456
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
460
457
  height: 200,
461
- onScrubberPositionChange: setScrubberPosition,
462
458
  series: [{
463
459
  id: 'prices',
464
460
  data: data
@@ -618,8 +614,12 @@ function GainLossChart() {
618
614
  }
619
615
  }
620
616
  })));
617
+ const chartAccessibilityLabel = "Price chart with " + data.length + " data points. Swipe to navigate.";
618
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": " + tickLabelFormatter(data[index]), [data, tickLabelFormatter]);
621
619
  return /*#__PURE__*/_jsxs(CartesianChart, {
622
620
  enableScrubbing: true,
621
+ accessibilityLabel: chartAccessibilityLabel,
622
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
623
623
  height: 200,
624
624
  series: [{
625
625
  id: 'prices',
@@ -674,9 +674,11 @@ function HighLowPrice() {
674
674
  }
675
675
  function StylingScrubber() {
676
676
  const theme = useTheme();
677
- const pages = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'];
678
- const pageViews = [2400, 1398, 9800, 3908, 4800, 3800, 4300];
679
- const uniqueVisitors = [4000, 3000, 2000, 2780, 1890, 2390, 3490];
677
+ const pages = useMemo(() => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], []);
678
+ const pageViews = useMemo(() => [2400, 1398, 9800, 3908, 4800, 3800, 4300], []);
679
+ const uniqueVisitors = useMemo(() => [4000, 3000, 2000, 2780, 1890, 2390, 3490], []);
680
+ const chartAccessibilityLabel = "Website visitors across " + pageViews.length + " pages.";
681
+ const getScrubberAccessibilityLabel = useCallback(index => pages[index] + ": " + pageViews[index] + " views, " + uniqueVisitors[index] + " unique visitors.", [pages, pageViews, uniqueVisitors]);
680
682
  const numberFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
681
683
  maximumFractionDigits: 0
682
684
  }).format(value), []);
@@ -685,6 +687,8 @@ function StylingScrubber() {
685
687
  showArea: true,
686
688
  showXAxis: true,
687
689
  showYAxis: true,
690
+ accessibilityLabel: chartAccessibilityLabel,
691
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
688
692
  height: 200,
689
693
  series: [{
690
694
  id: 'pageViews',
@@ -905,6 +909,12 @@ function AssetPriceWithDottedArea() {
905
909
  });
906
910
  return dayOfWeek + ", " + monthDay + ", " + time;
907
911
  }, []);
912
+ const chartAccessibilityLabel = "Bitcoin price chart for " + timePeriod.label + " period. Current price: " + formatPrice(currentPrice) + ".";
913
+ const getScrubberAccessibilityLabel = useCallback(index => {
914
+ const price = formatPrice(sparklineTimePeriodDataValues[index]);
915
+ const date = formatDate(sparklineTimePeriodDataTimestamps[index]);
916
+ return price + " " + date;
917
+ }, [formatDate, formatPrice, sparklineTimePeriodDataTimestamps, sparklineTimePeriodDataValues]);
908
918
  return /*#__PURE__*/_jsxs(VStack, {
909
919
  gap: 2,
910
920
  children: [/*#__PURE__*/_jsx(SectionHeader, {
@@ -927,7 +937,9 @@ function AssetPriceWithDottedArea() {
927
937
  }), /*#__PURE__*/_jsx(LineChart, {
928
938
  enableScrubbing: true,
929
939
  showArea: true,
940
+ accessibilityLabel: chartAccessibilityLabel,
930
941
  areaType: "dotted",
942
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
931
943
  height: 200,
932
944
  inset: {
933
945
  top: 52
@@ -1089,11 +1101,19 @@ const PerformanceChart = /*#__PURE__*/memo(_ref10 => {
1089
1101
  return dayOfWeek + ", " + monthDay + ", " + time;
1090
1102
  }, []);
1091
1103
  const getScrubberLabel = useCallback(d => formatDate(sparklineTimePeriodDataTimestamps[d]), [formatDate, sparklineTimePeriodDataTimestamps]);
1104
+ const chartAccessibilityLabel = "Bitcoin price chart with high, actual, and low series. " + sparklineTimePeriodDataValues.length + " data points. Swipe to navigate.";
1105
+ const getScrubberAccessibilityLabel = useCallback(index => {
1106
+ const price = formatPriceThousands(sparklineTimePeriodDataValues[index]);
1107
+ const date = formatDate(sparklineTimePeriodDataTimestamps[index]);
1108
+ return "Point " + (index + 1) + ": " + price + ", " + date;
1109
+ }, [formatDate, formatPriceThousands, sparklineTimePeriodDataTimestamps, sparklineTimePeriodDataValues]);
1092
1110
  return /*#__PURE__*/_jsx(LineChart, {
1093
1111
  enableScrubbing: true,
1094
1112
  showArea: true,
1095
1113
  showYAxis: true,
1114
+ accessibilityLabel: chartAccessibilityLabel,
1096
1115
  areaType: "dotted",
1116
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
1097
1117
  height: 300,
1098
1118
  inset: {
1099
1119
  top: 52,
@@ -1250,6 +1270,12 @@ function MonotoneAssetPrice() {
1250
1270
  dy: -12,
1251
1271
  horizontalAlignment: "left"
1252
1272
  })), []);
1273
+ const chartAccessibilityLabel = "Price chart with " + prices.length + " data points. Swipe to navigate.";
1274
+ const getScrubberAccessibilityLabel = useCallback(index => {
1275
+ const price = scrubberPriceFormatter.format(prices[index].value);
1276
+ const date = formatDate(prices[index].date);
1277
+ return price + " USD " + date;
1278
+ }, [formatDate, prices, scrubberPriceFormatter]);
1253
1279
  const CustomScrubberBeacon = /*#__PURE__*/memo(_ref11 => {
1254
1280
  let {
1255
1281
  dataX,
@@ -1323,6 +1349,8 @@ function MonotoneAssetPrice() {
1323
1349
  return /*#__PURE__*/_jsx(LineChart, {
1324
1350
  enableScrubbing: true,
1325
1351
  showYAxis: true,
1352
+ accessibilityLabel: chartAccessibilityLabel,
1353
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
1326
1354
  height: 200,
1327
1355
  inset: {
1328
1356
  top: 64
@@ -1397,8 +1425,12 @@ function ServiceAvailability() {
1397
1425
  date: new Date('2022-01-10'),
1398
1426
  availability: 86
1399
1427
  }], []);
1428
+ const chartAccessibilityLabel = "Service availability chart with " + availabilityEvents.length + " data points. Swipe to navigate.";
1429
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": " + availabilityEvents[index].availability + "% availability on " + availabilityEvents[index].date.toLocaleDateString(), [availabilityEvents]);
1400
1430
  return /*#__PURE__*/_jsxs(CartesianChart, {
1401
1431
  enableScrubbing: true,
1432
+ accessibilityLabel: chartAccessibilityLabel,
1433
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
1402
1434
  height: 200,
1403
1435
  series: [{
1404
1436
  id: 'availability',
@@ -1471,7 +1503,7 @@ function ServiceAvailability() {
1471
1503
  }
1472
1504
  function ForecastAssetPrice() {
1473
1505
  const startYear = 2020;
1474
- const data = [50, 45, 47, 46, 54, 54, 60, 61, 63, 66, 70];
1506
+ const data = useMemo(() => [50, 45, 47, 46, 54, 54, 60, 61, 63, 66, 70], []);
1475
1507
  const currentIndex = 6;
1476
1508
  const strokeWidth = 3;
1477
1509
  // To prevent cutting off the edge of our lines
@@ -1575,8 +1607,12 @@ function ForecastAssetPrice() {
1575
1607
  })]
1576
1608
  });
1577
1609
  });
1610
+ const chartAccessibilityLabel = "Forecast chart with " + data.length + " data points. Swipe to navigate.";
1611
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": " + axisFormatter(index) + ", value " + data[index], [axisFormatter, data]);
1578
1612
  return /*#__PURE__*/_jsxs(CartesianChart, {
1579
1613
  enableScrubbing: true,
1614
+ accessibilityLabel: chartAccessibilityLabel,
1615
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
1580
1616
  height: 200,
1581
1617
  series: [{
1582
1618
  id: 'price',
@@ -1703,6 +1739,34 @@ function DataCardWithLineChart() {
1703
1739
  })]
1704
1740
  });
1705
1741
  }
1742
+ function HorizontalLayoutLineChart() {
1743
+ const symbols = ['BTC', 'ETH', 'SOL', 'DOGE', 'ADA'];
1744
+ const allocations = [72, 46, 33, 21, 14];
1745
+ return /*#__PURE__*/_jsx(LineChart, {
1746
+ points: true,
1747
+ showArea: true,
1748
+ showXAxis: true,
1749
+ showYAxis: true,
1750
+ height: 240,
1751
+ layout: "horizontal",
1752
+ series: [{
1753
+ id: 'allocations',
1754
+ data: allocations,
1755
+ color: assets.btc.color
1756
+ }],
1757
+ xAxis: {
1758
+ domain: {
1759
+ min: 0,
1760
+ max: 80
1761
+ },
1762
+ tickLabelFormatter: value => value + "%"
1763
+ },
1764
+ yAxis: {
1765
+ data: symbols,
1766
+ scaleType: 'band'
1767
+ }
1768
+ });
1769
+ }
1706
1770
  function ExampleNavigator() {
1707
1771
  const theme = useTheme();
1708
1772
  const [currentIndex, setCurrentIndex] = useState(0);
@@ -1716,6 +1780,9 @@ function ExampleNavigator() {
1716
1780
  data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
1717
1781
  }]
1718
1782
  })
1783
+ }, {
1784
+ title: 'Horizontal Layout',
1785
+ component: /*#__PURE__*/_jsx(HorizontalLayoutLineChart, {})
1719
1786
  }, {
1720
1787
  title: 'Multiple Lines',
1721
1788
  component: /*#__PURE__*/_jsx(MultipleLine, {})
@@ -1844,6 +1911,8 @@ function ExampleNavigator() {
1844
1911
  component: /*#__PURE__*/_jsxs(LineChart, {
1845
1912
  enableScrubbing: true,
1846
1913
  showArea: true,
1914
+ accessibilityLabel: "Price chart with reference line. 14 data points. Swipe to navigate.",
1915
+ getScrubberAccessibilityLabel: index => "Point " + (index + 1),
1847
1916
  height: 200,
1848
1917
  series: [{
1849
1918
  id: 'prices',
@@ -16,6 +16,7 @@ export const Point = /*#__PURE__*/memo(_ref => {
16
16
  let {
17
17
  dataX,
18
18
  dataY,
19
+ xAxisId,
19
20
  yAxisId,
20
21
  fill: fillProp,
21
22
  radius = 5,
@@ -41,7 +42,7 @@ export const Point = /*#__PURE__*/memo(_ref => {
41
42
  drawingArea
42
43
  } = useCartesianChartContext();
43
44
  const animate = animateProp != null ? animateProp : animationEnabled;
44
- const xScale = getXScale();
45
+ const xScale = getXScale(xAxisId);
45
46
  const yScale = getYScale(yAxisId);
46
47
  const shouldAnimate = animate != null ? animate : false;
47
48
  const updateTransition = useMemo(() => getTransition((transitions == null ? void 0 : transitions.update) !== undefined ? transitions.update : transition, animate, defaultTransition), [animate, transitions == null ? void 0 : transitions.update, transition]);
@@ -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,