@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
@@ -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';
@@ -17,11 +18,15 @@ import { getLineData, unwrapAnimatedValue, useScrubberContext } from '../../util
17
18
  import { DefaultScrubberBeacon, DefaultScrubberBeaconLabel, DefaultScrubberLabel, Scrubber } from '..';
18
19
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
20
  const sampleData = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
21
+ const chartAccessibilityLabel = "Price chart with " + sampleData.length + " data points. Swipe to navigate.";
22
+ const getScrubberAccessibilityLabel = index => "Point " + (index + 1) + ": " + sampleData[index];
20
23
  const BasicScrubber = () => {
21
24
  return /*#__PURE__*/_jsx(LineChart, {
22
25
  enableScrubbing: true,
23
26
  showArea: true,
24
27
  showYAxis: true,
28
+ accessibilityLabel: chartAccessibilityLabel,
29
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
25
30
  height: 150,
26
31
  series: [{
27
32
  id: 'prices',
@@ -47,26 +52,35 @@ const BasicScrubber = () => {
47
52
  })
48
53
  });
49
54
  };
55
+ const seriesFilterData = {
56
+ top: [15, 28, 32, 44, 46, 36, 40, 45, 48, 38],
57
+ upperMiddle: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
58
+ lowerMiddle: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
59
+ bottom: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14]
60
+ };
50
61
  const SeriesFilter = () => {
62
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": top " + seriesFilterData.top[index] + ", lowerMiddle " + seriesFilterData.lowerMiddle[index], []);
51
63
  return /*#__PURE__*/_jsx(LineChart, {
52
64
  enableScrubbing: true,
65
+ accessibilityLabel: "Chart with multiple series. Swipe to navigate.",
66
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
53
67
  height: 150,
54
68
  series: [{
55
69
  id: 'top',
56
- data: [15, 28, 32, 44, 46, 36, 40, 45, 48, 38]
70
+ data: seriesFilterData.top
57
71
  }, {
58
72
  id: 'upperMiddle',
59
- data: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
73
+ data: seriesFilterData.upperMiddle,
60
74
  color: '#ef4444',
61
75
  type: 'dotted'
62
76
  }, {
63
77
  id: 'lowerMiddle',
64
- data: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
78
+ data: seriesFilterData.lowerMiddle,
65
79
  color: '#f59e0b',
66
80
  curve: 'natural'
67
81
  }, {
68
82
  id: 'bottom',
69
- data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14],
83
+ data: seriesFilterData.bottom,
70
84
  color: '#800080',
71
85
  curve: 'step',
72
86
  showArea: true
@@ -80,6 +94,8 @@ const WithLabels = () => {
80
94
  return /*#__PURE__*/_jsx(LineChart, {
81
95
  enableScrubbing: true,
82
96
  showArea: true,
97
+ accessibilityLabel: chartAccessibilityLabel,
98
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
83
99
  height: 150,
84
100
  series: [{
85
101
  id: 'prices',
@@ -96,6 +112,8 @@ const IdlePulse = () => {
96
112
  return /*#__PURE__*/_jsx(LineChart, {
97
113
  enableScrubbing: true,
98
114
  showArea: true,
115
+ accessibilityLabel: chartAccessibilityLabel,
116
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
99
117
  height: 150,
100
118
  series: [{
101
119
  id: 'prices',
@@ -114,6 +132,8 @@ const ImperativePulse = () => {
114
132
  children: [/*#__PURE__*/_jsx(LineChart, {
115
133
  enableScrubbing: true,
116
134
  showArea: true,
135
+ accessibilityLabel: chartAccessibilityLabel,
136
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
117
137
  height: 150,
118
138
  series: [{
119
139
  id: 'prices',
@@ -144,6 +164,8 @@ const BeaconStroke = () => {
144
164
  children: /*#__PURE__*/_jsx(LineChart, {
145
165
  enableScrubbing: true,
146
166
  showArea: true,
167
+ accessibilityLabel: chartAccessibilityLabel,
168
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
147
169
  height: 150,
148
170
  series: [{
149
171
  id: 'prices',
@@ -171,6 +193,8 @@ const CustomBeacon = () => {
171
193
  enableScrubbing: true,
172
194
  showArea: true,
173
195
  showYAxis: true,
196
+ accessibilityLabel: chartAccessibilityLabel,
197
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
174
198
  height: 150,
175
199
  series: [{
176
200
  id: 'prices',
@@ -248,7 +272,9 @@ const CustomBeaconLabel = () => {
248
272
  enableScrubbing: true,
249
273
  showArea: true,
250
274
  showYAxis: true,
275
+ accessibilityLabel: "Temperature chart with 6 data points. Swipe to navigate.",
251
276
  areaType: "dotted",
277
+ getScrubberAccessibilityLabel: index => "Point " + (index + 1) + ": " + [25, 30, 35, 45, 60, 100][index] + "\xB0F",
252
278
  height: 150,
253
279
  series: [{
254
280
  id: 'Boston',
@@ -371,7 +397,9 @@ const PercentageBeaconLabels = () => {
371
397
  children: /*#__PURE__*/_jsx(LineChart, {
372
398
  enableScrubbing: true,
373
399
  showArea: true,
400
+ accessibilityLabel: "NYC vs ATL comparison chart. Swipe to navigate.",
374
401
  areaType: "dotted",
402
+ getScrubberAccessibilityLabel: index => "Point " + (index + 1),
375
403
  height: 150,
376
404
  inset: {
377
405
  bottom: 8,
@@ -409,7 +437,9 @@ const PercentageBeaconLabels = () => {
409
437
  children: /*#__PURE__*/_jsx(LineChart, {
410
438
  enableScrubbing: true,
411
439
  showArea: true,
440
+ accessibilityLabel: "NYC vs ATL comparison chart. Swipe to navigate.",
412
441
  areaType: "dotted",
442
+ getScrubberAccessibilityLabel: index => "Point " + (index + 1),
413
443
  height: 150,
414
444
  inset: {
415
445
  bottom: 8,
@@ -448,6 +478,8 @@ const HideBeaconLabels = () => {
448
478
  enableScrubbing: true,
449
479
  legend: true,
450
480
  showArea: true,
481
+ accessibilityLabel: "Website visitors across 7 pages. Swipe to navigate.",
482
+ getScrubberAccessibilityLabel: index => "Page " + (index + 1) + ": " + [2400, 1398, 9800, 3908, 4800, 3800, 4300][index] + " views",
451
483
  height: 200,
452
484
  inset: {
453
485
  top: 60
@@ -474,6 +506,8 @@ const LabelElevated = () => {
474
506
  return /*#__PURE__*/_jsx(LineChart, {
475
507
  enableScrubbing: true,
476
508
  showArea: true,
509
+ accessibilityLabel: chartAccessibilityLabel,
510
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
477
511
  height: 200,
478
512
  inset: {
479
513
  top: 60
@@ -507,6 +541,8 @@ const CustomLabelComponent = () => {
507
541
  return /*#__PURE__*/_jsx(LineChart, {
508
542
  enableScrubbing: true,
509
543
  showArea: true,
544
+ accessibilityLabel: chartAccessibilityLabel,
545
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
510
546
  height: 200,
511
547
  inset: {
512
548
  top: 16,
@@ -522,11 +558,15 @@ const CustomLabelComponent = () => {
522
558
  })
523
559
  });
524
560
  };
561
+ const ethData = [5, 15, 18, 30, 65, 30, 15, 35, 15, 2, 45, 12, 15, 40];
525
562
  const LabelFonts = () => {
563
+ const getScrubberAccessibilityLabel = useCallback(index => "Day " + (index + 1) + ": BTC " + sampleData[index] + ", ETH " + ethData[index], []);
526
564
  return /*#__PURE__*/_jsx(LineChart, {
527
565
  enableScrubbing: true,
528
566
  showArea: true,
529
567
  showYAxis: true,
568
+ accessibilityLabel: "BTC and ETH comparison chart. Swipe to navigate.",
569
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
530
570
  height: 150,
531
571
  series: [{
532
572
  id: 'btc',
@@ -535,7 +575,7 @@ const LabelFonts = () => {
535
575
  color: assets.btc.color
536
576
  }, {
537
577
  id: 'eth',
538
- data: [5, 15, 18, 30, 65, 30, 15, 35, 15, 2, 45, 12, 15, 40],
578
+ data: ethData,
539
579
  label: 'ETH',
540
580
  color: assets.eth.color
541
581
  }],
@@ -555,6 +595,8 @@ const LabelBoundsInset = () => {
555
595
  children: [/*#__PURE__*/_jsx(LineChart, {
556
596
  enableScrubbing: true,
557
597
  showArea: true,
598
+ accessibilityLabel: chartAccessibilityLabel,
599
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
558
600
  height: 150,
559
601
  inset: {
560
602
  left: 0,
@@ -571,6 +613,8 @@ const LabelBoundsInset = () => {
571
613
  }), /*#__PURE__*/_jsx(LineChart, {
572
614
  enableScrubbing: true,
573
615
  showArea: true,
616
+ accessibilityLabel: chartAccessibilityLabel,
617
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
574
618
  height: 150,
575
619
  inset: {
576
620
  left: 0,
@@ -596,6 +640,8 @@ const CustomLine = () => {
596
640
  return /*#__PURE__*/_jsx(LineChart, {
597
641
  enableScrubbing: true,
598
642
  showArea: true,
643
+ accessibilityLabel: chartAccessibilityLabel,
644
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
599
645
  height: 150,
600
646
  series: [{
601
647
  id: 'prices',
@@ -628,6 +674,8 @@ const HiddenScrubberWhenIdle = () => {
628
674
  return /*#__PURE__*/_jsx(LineChart, {
629
675
  enableScrubbing: true,
630
676
  showArea: true,
677
+ accessibilityLabel: chartAccessibilityLabel,
678
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
631
679
  height: 150,
632
680
  series: [{
633
681
  id: 'prices',
@@ -644,6 +692,8 @@ const HideOverlay = () => {
644
692
  return /*#__PURE__*/_jsx(LineChart, {
645
693
  enableScrubbing: true,
646
694
  showArea: true,
695
+ accessibilityLabel: chartAccessibilityLabel,
696
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
647
697
  height: 150,
648
698
  series: [{
649
699
  id: 'prices',
@@ -654,6 +704,139 @@ const HideOverlay = () => {
654
704
  })
655
705
  });
656
706
  };
707
+ 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];
708
+ const matchupRedData = matchupBlueData.map(value => 100 - value);
709
+ const matchupTeamLabels = {
710
+ blue: 'BLUE',
711
+ red: 'RED'
712
+ };
713
+ const MatchupBeaconLabels = () => {
714
+ const theme = useTheme();
715
+ const MatchupScrubberBeaconLabel = /*#__PURE__*/memo(_ref7 => {
716
+ var _matchupTeamLabels$se;
717
+ let {
718
+ seriesId,
719
+ color
720
+ } = _ref7,
721
+ props = _objectWithoutPropertiesLoose(_ref7, _excluded3);
722
+ const {
723
+ getSeriesData,
724
+ series,
725
+ fontProvider
726
+ } = useCartesianChartContext();
727
+ const {
728
+ scrubberPosition
729
+ } = useScrubberContext();
730
+ const seriesData = useMemo(() => getLineData(getSeriesData(seriesId)), [getSeriesData, seriesId]);
731
+ const dataLength = useMemo(() => {
732
+ var _series$reduce3;
733
+ return (_series$reduce3 = series == null ? void 0 : series.reduce((max, currentSeries) => {
734
+ var _data$length3;
735
+ const data = getSeriesData(currentSeries.id);
736
+ return Math.max(max, (_data$length3 = data == null ? void 0 : data.length) != null ? _data$length3 : 0);
737
+ }, 0)) != null ? _series$reduce3 : 0;
738
+ }, [series, getSeriesData]);
739
+ const dataIndex = useDerivedValue(() => {
740
+ var _scrubberPosition$val3;
741
+ return (_scrubberPosition$val3 = scrubberPosition.value) != null ? _scrubberPosition$val3 : Math.max(0, dataLength - 1);
742
+ }, [scrubberPosition, dataLength]);
743
+ const teamLabel = (_matchupTeamLabels$se = matchupTeamLabels[seriesId]) != null ? _matchupTeamLabels$se : String(seriesId).toUpperCase();
744
+ const labelColor = color != null ? color : theme.color.fgPrimary;
745
+ const legalFontSize = theme.fontSize.legal;
746
+ const title3FontSize = theme.fontSize.title3;
747
+ const teamStyle = useMemo(() => ({
748
+ fontFamilies: ['Inter'],
749
+ fontSize: legalFontSize,
750
+ fontStyle: {
751
+ weight: FontWeight.Normal
752
+ },
753
+ color: Skia.Color(labelColor)
754
+ }), [labelColor, legalFontSize]);
755
+ const percentageStyle = useMemo(() => ({
756
+ fontFamilies: ['Inter'],
757
+ fontSize: title3FontSize,
758
+ fontStyle: {
759
+ weight: FontWeight.Bold
760
+ },
761
+ color: Skia.Color(labelColor)
762
+ }), [title3FontSize, labelColor]);
763
+ const matchupLabel = useDerivedValue(() => {
764
+ if (seriesData === undefined) {
765
+ return teamLabel;
766
+ }
767
+ const value = seriesData[dataIndex.value];
768
+ const builder = Skia.ParagraphBuilder.Make({
769
+ textAlign: TextAlign.Left
770
+ }, fontProvider);
771
+ builder.pushStyle(teamStyle);
772
+ builder.addText(teamLabel);
773
+ builder.addText('\n');
774
+ builder.pushStyle(percentageStyle);
775
+ builder.addText(value + "%");
776
+ const paragraph = builder.build();
777
+ paragraph.layout(240);
778
+ return paragraph;
779
+ }, [dataIndex, fontProvider, percentageStyle, seriesData, teamLabel, teamStyle]);
780
+ return /*#__PURE__*/_jsx(DefaultScrubberBeaconLabel, _extends({}, props, {
781
+ background: "transparent",
782
+ color: labelColor,
783
+ elevated: false,
784
+ inset: 0,
785
+ label: matchupLabel,
786
+ seriesId: seriesId
787
+ }));
788
+ });
789
+ const getScrubberAccessibilityLabel = useCallback(index => "Point " + (index + 1) + ": BLUE " + matchupBlueData[index] + "%, RED " + matchupRedData[index] + "%", []);
790
+ return /*#__PURE__*/_jsx(LineChart, {
791
+ enableScrubbing: true,
792
+ showArea: true,
793
+ accessibilityLabel: "BLUE vs RED matchup chart. Swipe to navigate.",
794
+ areaType: "dotted",
795
+ getScrubberAccessibilityLabel: getScrubberAccessibilityLabel,
796
+ height: 300,
797
+ inset: {
798
+ bottom: 8,
799
+ left: 8,
800
+ top: 8,
801
+ right: 0
802
+ },
803
+ series: [{
804
+ id: 'blue',
805
+ data: matchupBlueData,
806
+ color: "rgb(" + theme.spectrum.blue50 + ")",
807
+ label: 'BLUE'
808
+ }, {
809
+ id: 'red',
810
+ data: matchupRedData,
811
+ color: "rgb(" + theme.spectrum.red50 + ")",
812
+ label: 'RED'
813
+ }],
814
+ xAxis: {
815
+ range: _ref8 => {
816
+ let {
817
+ min,
818
+ max
819
+ } = _ref8;
820
+ return {
821
+ min,
822
+ max: max - 64
823
+ };
824
+ }
825
+ },
826
+ yAxis: {
827
+ domain: {
828
+ min: 0,
829
+ max: 100
830
+ }
831
+ },
832
+ children: /*#__PURE__*/_jsx(Scrubber, {
833
+ idlePulse: true,
834
+ BeaconLabelComponent: MatchupScrubberBeaconLabel,
835
+ beaconLabelHorizontalOffset: 16,
836
+ beaconLabelPreferredSide: "right"
837
+ })
838
+ });
839
+ };
657
840
  const ExampleNavigator = () => {
658
841
  const [currentIndex, setCurrentIndex] = useState(0);
659
842
  const examples = useMemo(() => [{
@@ -707,6 +890,9 @@ const ExampleNavigator = () => {
707
890
  }, {
708
891
  title: 'Hide Overlay',
709
892
  component: /*#__PURE__*/_jsx(HideOverlay, {})
893
+ }, {
894
+ title: 'Matchup Beacon Labels',
895
+ component: /*#__PURE__*/_jsx(MatchupBeaconLabels, {})
710
896
  }], []);
711
897
  const currentExample = examples[currentIndex];
712
898
  const isFirstExample = currentIndex === 0;
@@ -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 getAxisScale = _ref => {
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
- // Invert range for Y axis for SVG coordinate system
72
- if (type === 'y') {
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('When defining multiple axes, each must have a unique id. See https://cds.coinbase.com/components/charts/YAxis/#multiple-y-axes.');
150
+ throw new Error("When defining multiple " + axisName + ", each must have a unique id. See " + axisDocUrl + ".");
141
151
  }
142
- return axes.map(_ref3 => {
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
- } = _ref3,
146
- axis = _objectWithoutPropertiesLoose(_ref3, _excluded);
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 getAxisDomain = (axisParam, series, axisType) => {
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
- const seriesDomain = axisType === 'x' ? getChartDomain(series) : getChartRange(series);
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 = _ref4 => {
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
- } = _ref4;
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
@@ -6,7 +6,8 @@ import { defaultTransition } from './transition';
6
6
  /**
7
7
  * A bar-specific transition that extends Transition with stagger support.
8
8
  * When `staggerDelay` is provided, bars will animate with increasing delays
9
- * based on their horizontal position (leftmost starts first, rightmost last).
9
+ * based on their position along the category axis (vertical: left-to-right,
10
+ * horizontal: top-to-bottom).
10
11
  *
11
12
  * @example
12
13
  * // Bars stagger in from left to right over 250ms, each animating for 750ms
@@ -17,10 +18,10 @@ import { defaultTransition } from './transition';
17
18
  * Strips `staggerDelay` from a transition and computes a positional delay.
18
19
  *
19
20
  * @param transition - The transition config (may include staggerDelay)
20
- * @param normalizedX - The bar's normalized x position (0 = left edge, 1 = right edge)
21
+ * @param normalizedPosition - The bar's normalized position along the category axis (0–1)
21
22
  * @returns A standard Transition with computed delay
22
23
  */
23
- export const withStaggerDelayTransition = (transition, normalizedX) => {
24
+ export const withStaggerDelayTransition = (transition, normalizedPosition) => {
24
25
  var _baseTransition$delay;
25
26
  if (!transition) return null;
26
27
  const {
@@ -29,7 +30,7 @@ export const withStaggerDelayTransition = (transition, normalizedX) => {
29
30
  baseTransition = _objectWithoutPropertiesLoose(transition, _excluded);
30
31
  if (!staggerDelay) return transition;
31
32
  return _extends({}, baseTransition, {
32
- delay: ((_baseTransition$delay = baseTransition == null ? void 0 : baseTransition.delay) != null ? _baseTransition$delay : 0) + normalizedX * staggerDelay
33
+ delay: ((_baseTransition$delay = baseTransition == null ? void 0 : baseTransition.delay) != null ? _baseTransition$delay : 0) + normalizedPosition * staggerDelay
33
34
  });
34
35
  };
35
36
 
@@ -46,15 +46,16 @@ export const getChartDomain = (series, min, max) => {
46
46
  };
47
47
 
48
48
  /**
49
- * Creates a composite stack key that includes both stack ID and y-axis ID.
50
- * This ensures series with different y-scales don't get stacked together.
49
+ * Creates a composite stack key that includes stack ID and axis IDs.
50
+ * This ensures series with different scales don't get stacked together.
51
51
  */
52
52
  const createStackKey = series => {
53
53
  if (series.stackId === undefined) return undefined;
54
54
 
55
- // Include y-axis ID to prevent cross-scale stacking
55
+ // Include axis IDs to prevent cross-scale stacking
56
+ const xAxisId = series.xAxisId || 'default';
56
57
  const yAxisId = series.yAxisId || 'default';
57
- return series.stackId + ":" + yAxisId;
58
+ return series.stackId + ":" + xAxisId + ":" + yAxisId;
58
59
  };
59
60
 
60
61
  /**
@@ -225,12 +226,24 @@ export const getChartRange = (series, min, max) => {
225
226
  }
226
227
  return range;
227
228
  };
228
- export const defaultChartInset = {
229
+ export const defaultVerticalLayoutChartInset = {
229
230
  top: 32,
230
231
  left: 16,
231
232
  bottom: 16,
232
233
  right: 16
233
234
  };
235
+ export const defaultHorizontalLayoutChartInset = {
236
+ top: 16,
237
+ left: 16,
238
+ bottom: 16,
239
+ right: 48
240
+ };
241
+
242
+ /**
243
+ * @deprecated Use `defaultVerticalLayoutChartInset` for vertical layout charts or
244
+ * `defaultHorizontalLayoutChartInset` for horizontal layout charts.
245
+ */
246
+ export const defaultChartInset = defaultVerticalLayoutChartInset;
234
247
 
235
248
  /**
236
249
  * Normalize padding to include all sides with a value.
@@ -1,5 +1,12 @@
1
1
  import { createContext, useContext } from 'react';
2
2
 
3
+ /**
4
+ * Chart layout for Cartesian charts.
5
+ * Describes the direction bars/areas grow.
6
+ * - 'vertical': Bars grow vertically (up/down). X is category axis, Y is value axis.
7
+ * - 'horizontal': Bars grow horizontally (left/right). Y is category axis, X is value axis.
8
+ */
9
+
3
10
  /**
4
11
  * Context value for Cartesian (X/Y) coordinate charts.
5
12
  * Contains axis-specific methods and properties for rectangular coordinate systems.
@@ -262,9 +262,13 @@ export const getBaseline = function (axisBounds, baseline) {
262
262
  * @param fill - The color to use for the gradient
263
263
  * @param peakOpacity - Opacity at the peak of the gradient
264
264
  * @param baselineOpacity - Opacity at the baseline
265
- * @returns A gradient definition with y-axis stops in ascending order
265
+ * @param axis - The axis the gradient maps to ('y' for vertical, 'x' for horizontal layout)
266
+ * @returns A gradient definition with stops in ascending order
266
267
  */
267
- export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity) => {
268
+ export const createGradient = function (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity, axis) {
269
+ if (axis === void 0) {
270
+ axis = 'y';
271
+ }
268
272
  const {
269
273
  min,
270
274
  max
@@ -273,7 +277,7 @@ export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, bas
273
277
  const upperBound = Math.max(min, max);
274
278
  if (lowerBound < baselineValue && baselineValue < upperBound) {
275
279
  return {
276
- axis: 'y',
280
+ axis,
277
281
  stops: [{
278
282
  offset: lowerBound,
279
283
  color: fill,
@@ -291,7 +295,7 @@ export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, bas
291
295
  }
292
296
  const peakValue = Math.abs(min - baselineValue) > Math.abs(max - baselineValue) ? min : max;
293
297
  return {
294
- axis: 'y',
298
+ axis,
295
299
  stops: [{
296
300
  offset: peakValue,
297
301
  color: fill,