@coinbase/cds-mobile-visualization 3.4.0-beta.5 → 3.4.0-beta.7

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 (179) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dts/chart/CartesianChart.d.ts +57 -33
  3. package/dts/chart/CartesianChart.d.ts.map +1 -1
  4. package/dts/chart/ChartContextBridge.d.ts +28 -0
  5. package/dts/chart/ChartContextBridge.d.ts.map +1 -0
  6. package/dts/chart/Path.d.ts +77 -34
  7. package/dts/chart/Path.d.ts.map +1 -1
  8. package/dts/chart/PeriodSelector.d.ts +1 -1
  9. package/dts/chart/PeriodSelector.d.ts.map +1 -1
  10. package/dts/chart/area/Area.d.ts +42 -27
  11. package/dts/chart/area/Area.d.ts.map +1 -1
  12. package/dts/chart/area/AreaChart.d.ts +51 -10
  13. package/dts/chart/area/AreaChart.d.ts.map +1 -1
  14. package/dts/chart/area/DottedArea.d.ts +21 -2
  15. package/dts/chart/area/DottedArea.d.ts.map +1 -1
  16. package/dts/chart/area/GradientArea.d.ts +19 -13
  17. package/dts/chart/area/GradientArea.d.ts.map +1 -1
  18. package/dts/chart/area/SolidArea.d.ts +17 -2
  19. package/dts/chart/area/SolidArea.d.ts.map +1 -1
  20. package/dts/chart/axis/Axis.d.ts +68 -78
  21. package/dts/chart/axis/Axis.d.ts.map +1 -1
  22. package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
  23. package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
  24. package/dts/chart/axis/XAxis.d.ts +1 -1
  25. package/dts/chart/axis/XAxis.d.ts.map +1 -1
  26. package/dts/chart/axis/YAxis.d.ts +2 -2
  27. package/dts/chart/axis/YAxis.d.ts.map +1 -1
  28. package/dts/chart/axis/index.d.ts +1 -0
  29. package/dts/chart/axis/index.d.ts.map +1 -1
  30. package/dts/chart/bar/Bar.d.ts +16 -13
  31. package/dts/chart/bar/Bar.d.ts.map +1 -1
  32. package/dts/chart/bar/BarChart.d.ts +36 -20
  33. package/dts/chart/bar/BarChart.d.ts.map +1 -1
  34. package/dts/chart/bar/BarPlot.d.ts +2 -1
  35. package/dts/chart/bar/BarPlot.d.ts.map +1 -1
  36. package/dts/chart/bar/BarStack.d.ts +39 -48
  37. package/dts/chart/bar/BarStack.d.ts.map +1 -1
  38. package/dts/chart/bar/BarStackGroup.d.ts +1 -0
  39. package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
  40. package/dts/chart/bar/DefaultBar.d.ts +1 -1
  41. package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
  42. package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
  43. package/dts/chart/gradient/Gradient.d.ts +25 -0
  44. package/dts/chart/gradient/Gradient.d.ts.map +1 -0
  45. package/dts/chart/gradient/index.d.ts +2 -0
  46. package/dts/chart/gradient/index.d.ts.map +1 -0
  47. package/dts/chart/index.d.ts +3 -1
  48. package/dts/chart/index.d.ts.map +1 -1
  49. package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
  50. package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
  51. package/dts/chart/line/DottedLine.d.ts +13 -5
  52. package/dts/chart/line/DottedLine.d.ts.map +1 -1
  53. package/dts/chart/line/Line.d.ts +62 -25
  54. package/dts/chart/line/Line.d.ts.map +1 -1
  55. package/dts/chart/line/LineChart.d.ts +43 -9
  56. package/dts/chart/line/LineChart.d.ts.map +1 -1
  57. package/dts/chart/line/ReferenceLine.d.ts +68 -20
  58. package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
  59. package/dts/chart/line/SolidLine.d.ts +8 -5
  60. package/dts/chart/line/SolidLine.d.ts.map +1 -1
  61. package/dts/chart/line/index.d.ts +1 -1
  62. package/dts/chart/line/index.d.ts.map +1 -1
  63. package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
  64. package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
  65. package/dts/chart/point/Point.d.ts +120 -0
  66. package/dts/chart/point/Point.d.ts.map +1 -0
  67. package/dts/chart/point/index.d.ts +3 -0
  68. package/dts/chart/point/index.d.ts.map +1 -0
  69. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +8 -0
  70. package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
  71. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
  72. package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
  73. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
  74. package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
  75. package/dts/chart/scrubber/Scrubber.d.ts +172 -43
  76. package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
  77. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +44 -0
  78. package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
  79. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +31 -0
  80. package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
  81. package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
  82. package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
  83. package/dts/chart/scrubber/index.d.ts +3 -0
  84. package/dts/chart/scrubber/index.d.ts.map +1 -1
  85. package/dts/chart/text/ChartText.d.ts +151 -77
  86. package/dts/chart/text/ChartText.d.ts.map +1 -1
  87. package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
  88. package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
  89. package/dts/chart/text/index.d.ts +1 -1
  90. package/dts/chart/text/index.d.ts.map +1 -1
  91. package/dts/chart/utils/chart.d.ts +34 -7
  92. package/dts/chart/utils/chart.d.ts.map +1 -1
  93. package/dts/chart/utils/context.d.ts +28 -7
  94. package/dts/chart/utils/context.d.ts.map +1 -1
  95. package/dts/chart/utils/gradient.d.ts +117 -0
  96. package/dts/chart/utils/gradient.d.ts.map +1 -0
  97. package/dts/chart/utils/index.d.ts +3 -0
  98. package/dts/chart/utils/index.d.ts.map +1 -1
  99. package/dts/chart/utils/path.d.ts +53 -0
  100. package/dts/chart/utils/path.d.ts.map +1 -1
  101. package/dts/chart/utils/point.d.ts +60 -1
  102. package/dts/chart/utils/point.d.ts.map +1 -1
  103. package/dts/chart/utils/scale.d.ts +91 -0
  104. package/dts/chart/utils/scale.d.ts.map +1 -1
  105. package/dts/chart/utils/scrubber.d.ts +39 -0
  106. package/dts/chart/utils/scrubber.d.ts.map +1 -0
  107. package/dts/chart/utils/transition.d.ts +140 -0
  108. package/dts/chart/utils/transition.d.ts.map +1 -0
  109. package/esm/chart/CartesianChart.js +164 -70
  110. package/esm/chart/ChartContextBridge.js +148 -0
  111. package/esm/chart/Path.js +196 -113
  112. package/esm/chart/PeriodSelector.js +1 -1
  113. package/esm/chart/__stories__/CartesianChart.stories.js +371 -129
  114. package/esm/chart/__stories__/Chart.stories.js +2 -4
  115. package/esm/chart/area/Area.js +25 -35
  116. package/esm/chart/area/AreaChart.js +17 -12
  117. package/esm/chart/area/DottedArea.js +61 -109
  118. package/esm/chart/area/GradientArea.js +35 -91
  119. package/esm/chart/area/SolidArea.js +22 -8
  120. package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
  121. package/esm/chart/axis/Axis.js +2 -0
  122. package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
  123. package/esm/chart/axis/XAxis.js +62 -56
  124. package/esm/chart/axis/YAxis.js +58 -52
  125. package/esm/chart/axis/__stories__/Axis.stories.js +0 -1
  126. package/esm/chart/axis/index.js +1 -0
  127. package/esm/chart/bar/Bar.js +3 -1
  128. package/esm/chart/bar/BarChart.js +15 -37
  129. package/esm/chart/bar/BarPlot.js +41 -35
  130. package/esm/chart/bar/BarStack.js +75 -38
  131. package/esm/chart/bar/BarStackGroup.js +6 -16
  132. package/esm/chart/bar/DefaultBar.js +26 -48
  133. package/esm/chart/bar/DefaultBarStack.js +23 -58
  134. package/esm/chart/bar/__stories__/BarChart.stories.js +463 -77
  135. package/esm/chart/gradient/Gradient.js +53 -0
  136. package/esm/chart/gradient/index.js +1 -0
  137. package/esm/chart/index.js +3 -1
  138. package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
  139. package/esm/chart/line/DottedLine.js +29 -14
  140. package/esm/chart/line/Line.js +106 -67
  141. package/esm/chart/line/LineChart.js +20 -14
  142. package/esm/chart/line/ReferenceLine.js +80 -63
  143. package/esm/chart/line/SolidLine.js +25 -10
  144. package/esm/chart/line/__stories__/LineChart.stories.js +2098 -1975
  145. package/esm/chart/line/__stories__/ReferenceLine.stories.js +83 -28
  146. package/esm/chart/line/index.js +1 -1
  147. package/esm/chart/point/DefaultPointLabel.js +39 -0
  148. package/esm/chart/point/Point.js +188 -0
  149. package/esm/chart/point/index.js +2 -0
  150. package/esm/chart/scrubber/DefaultScrubberBeacon.js +179 -0
  151. package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
  152. package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
  153. package/esm/chart/scrubber/Scrubber.js +126 -146
  154. package/esm/chart/scrubber/ScrubberBeaconGroup.js +161 -0
  155. package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +185 -0
  156. package/esm/chart/scrubber/ScrubberProvider.js +46 -54
  157. package/esm/chart/scrubber/index.js +3 -1
  158. package/esm/chart/text/ChartText.js +242 -174
  159. package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
  160. package/esm/chart/text/index.js +1 -1
  161. package/esm/chart/utils/chart.js +44 -3
  162. package/esm/chart/utils/gradient.js +305 -0
  163. package/esm/chart/utils/index.js +3 -0
  164. package/esm/chart/utils/path.js +76 -8
  165. package/esm/chart/utils/point.js +116 -5
  166. package/esm/chart/utils/scale.js +230 -1
  167. package/esm/chart/utils/scrubber.js +139 -0
  168. package/esm/chart/utils/transition.js +214 -0
  169. package/package.json +7 -5
  170. package/dts/chart/Point.d.ts +0 -103
  171. package/dts/chart/Point.d.ts.map +0 -1
  172. package/dts/chart/line/GradientLine.d.ts +0 -45
  173. package/dts/chart/line/GradientLine.d.ts.map +0 -1
  174. package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
  175. package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
  176. package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
  177. package/esm/chart/Point.js +0 -111
  178. package/esm/chart/line/GradientLine.js +0 -62
  179. package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
@@ -1,905 +1,918 @@
1
- const _excluded = ["label"],
1
+ const _excluded = ["dataX", "dataY"],
2
2
  _excluded2 = ["label"],
3
3
  _excluded3 = ["style"],
4
- _excluded4 = ["dataX"];
5
- 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
+ _excluded4 = ["x", "y", "width", "height", "originY", "dataX"],
5
+ _excluded5 = ["seriesId", "color", "label"];
6
6
  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); }
7
- import { forwardRef, memo, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react';
8
- import { Defs, LinearGradient, Stop } from 'react-native-svg';
7
+ 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; }
8
+ import { forwardRef, memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
9
+ import { useAnimatedReaction, useDerivedValue, useSharedValue, withDelay, withTiming } from 'react-native-reanimated';
9
10
  import { assets } from '@coinbase/cds-common/internal/data/assets';
10
11
  import { candles as btcCandles } from '@coinbase/cds-common/internal/data/candles';
11
12
  import { prices } from '@coinbase/cds-common/internal/data/prices';
12
13
  import { sparklineInteractiveData } from '@coinbase/cds-common/internal/visualizations/SparklineInteractiveData';
13
14
  import { useTabsContext } from '@coinbase/cds-common/tabs/TabsContext';
14
15
  import { useTheme } from '@coinbase/cds-mobile';
15
- import { Button } from '@coinbase/cds-mobile/buttons';
16
- import { Example, ExampleScreen } from '@coinbase/cds-mobile/examples/ExampleScreen';
16
+ import { Button, IconButton } from '@coinbase/cds-mobile/buttons';
17
+ import { ListCell } from '@coinbase/cds-mobile/cells';
18
+ import { ExampleScreen } from '@coinbase/cds-mobile/examples/ExampleScreen';
17
19
  import { Box, HStack, VStack } from '@coinbase/cds-mobile/layout';
18
- import { RemoteImage } from '@coinbase/cds-mobile/media';
20
+ import { Avatar, RemoteImage } from '@coinbase/cds-mobile/media';
19
21
  import { SectionHeader } from '@coinbase/cds-mobile/section-header/SectionHeader';
20
- import { SegmentedTabs } from '@coinbase/cds-mobile/tabs';
22
+ import { Pressable } from '@coinbase/cds-mobile/system';
21
23
  import { SegmentedTab } from '@coinbase/cds-mobile/tabs/SegmentedTab';
22
24
  import { TextLabel1 } from '@coinbase/cds-mobile/typography';
23
25
  import { Text } from '@coinbase/cds-mobile/typography/Text';
24
- import { Area, DottedArea, GradientArea } from '../../area';
25
- import { XAxis, YAxis } from '../../axis';
26
+ import { Circle, FontWeight, Group, Line as SkiaLine, Rect, Skia, TextAlign } from '@shopify/react-native-skia';
27
+ import { Area, DottedArea } from '../../area';
28
+ import { DefaultAxisTickLabel, XAxis, YAxis } from '../../axis';
29
+ import { BarPlot } from '../../bar';
26
30
  import { CartesianChart } from '../../CartesianChart';
27
31
  import { useCartesianChartContext } from '../../ChartProvider';
28
32
  import { PeriodSelector, PeriodSelectorActiveIndicator } from '../../PeriodSelector';
29
- import { Point } from '../../Point';
30
- import { Scrubber } from '../../scrubber';
31
- import { GradientLine, Line, LineChart, ReferenceLine } from '..';
33
+ import { Point } from '../../point';
34
+ import { DefaultScrubberBeacon, DefaultScrubberBeaconLabel, DefaultScrubberLabel, Scrubber } from '../../scrubber';
35
+ import { buildTransition, defaultTransition, getLineData, getPointOnSerializableScale, projectPointWithSerializableScale, unwrapAnimatedValue, useScrubberContext } from '../../utils';
36
+ import { DottedLine, Line, LineChart, ReferenceLine, SolidLine } from '..';
32
37
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
33
- const defaultChartHeight = 200;
34
- const formatChartDate = (timestamp, timeframe) => {
35
- const date = new Date(timestamp);
36
- switch (timeframe) {
37
- case 'hour':
38
- case '1H':
39
- return date.toLocaleString('en-US', {
40
- hour: 'numeric',
41
- minute: '2-digit',
42
- hour12: true
43
- });
44
- case 'day':
45
- case '1D':
46
- return date.toLocaleString('en-US', {
47
- month: 'short',
48
- day: 'numeric',
49
- hour: 'numeric',
50
- minute: '2-digit',
51
- hour12: true
52
- });
53
- case 'week':
54
- case 'month':
55
- case '1W':
56
- case '1M':
57
- return date.toLocaleDateString('en-US', {
58
- month: 'short',
59
- day: 'numeric',
60
- year: 'numeric'
61
- });
62
- case 'year':
63
- case 'all':
64
- case '1Y':
65
- case 'All':
66
- return date.toLocaleDateString('en-US', {
67
- month: 'short',
68
- year: 'numeric'
69
- });
70
- default:
71
- return date.toLocaleDateString('en-US');
72
- }
73
- };
74
- const calculateTrendData = (scrubberPosition, currentData, currentTimestamps, startPrice, currentPrice, activeTimeframe) => {
75
- if (scrubberPosition !== undefined) {
76
- // When hovering, show trend relative to START of time period (not previous point)
77
- const hoverIndex = scrubberPosition;
78
- const hoverPrice = currentData[hoverIndex];
79
- const hoverPriceChange = hoverPrice - startPrice; // Fixed: relative to start price
80
- const hoverTimestamp = currentTimestamps[hoverIndex];
81
- return {
82
- trendPrice: hoverPrice,
83
- trendPreviousPrice: startPrice,
84
- // Fixed: always use start price
85
- trendDirection: hoverPriceChange > 0 ? 'up' : hoverPriceChange < 0 ? 'down' : 'neutral',
86
- displayDate: formatChartDate(hoverTimestamp, activeTimeframe)
87
- };
88
- } else {
89
- // When not hovering, show current trend relative to start
90
- const latestTimestamp = currentTimestamps[currentTimestamps.length - 1];
91
- const priceChange = currentPrice - startPrice;
92
- return {
93
- trendPrice: currentPrice,
94
- trendPreviousPrice: startPrice,
95
- trendDirection: priceChange > 0 ? 'up' : priceChange < 0 ? 'down' : 'neutral',
96
- displayDate: formatChartDate(latestTimestamp, activeTimeframe)
97
- };
98
- }
99
- };
100
- export const BasicLineChart = () => {
101
- const chartData = [65, 78, 45, 88, 92, 73, 69];
38
+ function MultipleLine() {
39
+ const theme = useTheme();
40
+ const [scrubberPosition, setScrubberPosition] = useState();
41
+ const pages = useMemo(() => ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'], []);
42
+ const pageViews = useMemo(() => [2400, 1398, 9800, 3908, 4800, 3800, 4300], []);
43
+ const uniqueVisitors = useMemo(() => [4000, 3000, 2000, 2780, 1890, 2390, 3490], []);
44
+ const chartAccessibilityLabel = "Website visitors across " + pageViews.length + " pages.";
45
+ const scrubberAccessibilityLabel = useCallback(index => {
46
+ return pages[index] + " has " + pageViews[index] + " views and " + uniqueVisitors[index] + " unique visitors.";
47
+ }, [pages, pageViews, uniqueVisitors]);
48
+ const numberFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
49
+ maximumFractionDigits: 0
50
+ }).format(value), []);
51
+ const accessibilityLabel = useMemo(() => {
52
+ if (scrubberPosition !== undefined) {
53
+ return scrubberAccessibilityLabel(scrubberPosition);
54
+ }
55
+ return chartAccessibilityLabel;
56
+ }, [scrubberPosition, chartAccessibilityLabel, scrubberAccessibilityLabel]);
102
57
  return /*#__PURE__*/_jsx(LineChart, {
103
58
  enableScrubbing: true,
59
+ showArea: true,
60
+ showXAxis: true,
104
61
  showYAxis: true,
105
- height: defaultChartHeight,
106
- renderPoints: () => true,
62
+ accessibilityLabel: accessibilityLabel,
63
+ height: 200,
64
+ onScrubberPositionChange: setScrubberPosition,
107
65
  series: [{
108
- id: 'monthly-growth',
109
- data: chartData,
110
- label: 'Monthly Growth',
111
- color: '#2ca02c'
66
+ id: 'pageViews',
67
+ data: pageViews,
68
+ color: theme.color.accentBoldGreen,
69
+ // Label will render next to scrubber beacon
70
+ label: 'Page Views'
71
+ }, {
72
+ id: 'uniqueVisitors',
73
+ data: uniqueVisitors,
74
+ color: theme.color.accentBoldPurple,
75
+ label: 'Unique Visitors',
76
+ // Default area is gradient
77
+ areaType: 'dotted'
112
78
  }],
79
+ xAxis: {
80
+ // Used on the x-axis to provide context for each index from the series data array
81
+ data: pages
82
+ },
113
83
  yAxis: {
114
- requestedTickCount: 2,
115
- tickLabelFormatter: value => "$" + value,
116
- showGrid: true
84
+ showGrid: true,
85
+ tickLabelFormatter: numberFormatter
117
86
  },
118
87
  children: /*#__PURE__*/_jsx(Scrubber, {})
119
88
  });
120
- };
121
- export const BasicLineChartWithPoints = () => {
122
- const chartData = [65, 78, 45, 88, 92, 73, 69];
123
- return /*#__PURE__*/_jsxs(LineChart, {
89
+ }
90
+ function DataFormat() {
91
+ const [scrubberPosition, setScrubberPosition] = useState();
92
+ const yData = useMemo(() => [2, 5.5, 2, 8.5, 1.5, 5], []);
93
+ const xData = useMemo(() => [1, 2, 3, 5, 8, 10], []);
94
+ const chartAccessibilityLabel = "Chart with custom X and Y data. " + yData.length + " data points";
95
+ const scrubberAccessibilityLabel = useCallback(index => {
96
+ return "Point " + (index + 1) + ": X value " + xData[index] + ", Y value " + yData[index];
97
+ }, [xData, yData]);
98
+ const accessibilityLabel = useMemo(() => {
99
+ if (scrubberPosition !== undefined) {
100
+ return scrubberAccessibilityLabel(scrubberPosition);
101
+ }
102
+ return chartAccessibilityLabel;
103
+ }, [scrubberPosition, chartAccessibilityLabel, scrubberAccessibilityLabel]);
104
+ return /*#__PURE__*/_jsx(LineChart, {
105
+ enableScrubbing: true,
106
+ points: true,
107
+ showArea: true,
108
+ showXAxis: true,
124
109
  showYAxis: true,
125
- height: defaultChartHeight,
126
- renderPoints: () => true,
110
+ accessibilityLabel: accessibilityLabel,
111
+ curve: "natural",
112
+ height: 200,
113
+ inset: {
114
+ top: 16,
115
+ right: 16,
116
+ bottom: 0,
117
+ left: 0
118
+ },
119
+ onScrubberPositionChange: setScrubberPosition,
127
120
  series: [{
128
- id: 'monthly-growth',
129
- data: chartData,
130
- label: 'Monthly Growth',
131
- color: '#2ca02c'
121
+ id: 'line',
122
+ data: yData
132
123
  }],
124
+ xAxis: {
125
+ data: xData,
126
+ showLine: true,
127
+ showTickMarks: true,
128
+ showGrid: true
129
+ },
133
130
  yAxis: {
134
- requestedTickCount: 2,
135
- tickLabelFormatter: value => "$" + value,
131
+ domain: {
132
+ min: 0
133
+ },
134
+ position: 'left',
135
+ showLine: true,
136
+ showTickMarks: true,
136
137
  showGrid: true
137
138
  },
138
- children: [/*#__PURE__*/_jsx(Point, {
139
- dataX: 2,
140
- dataY: 60,
141
- fill: "purple",
142
- label: "hello world im on a point!",
143
- labelProps: {
144
- verticalAlignment: 'bottom',
145
- // why does this go in the opposite direction than what i would expect?
146
- dy: -16
139
+ children: /*#__PURE__*/_jsx(Scrubber, {
140
+ hideOverlay: true
141
+ })
142
+ });
143
+ }
144
+ function LiveUpdates() {
145
+ const scrubberRef = useRef(null);
146
+ const initialData = useMemo(() => {
147
+ return sparklineInteractiveData.hour.map(d => d.value);
148
+ }, []);
149
+ const [priceData, setPriceData] = useState(initialData);
150
+ const lastDataPointTimeRef = useRef(Date.now());
151
+ const updateCountRef = useRef(0);
152
+ const intervalSeconds = 3600 / initialData.length;
153
+ const maxPercentChange = Math.abs(initialData[initialData.length - 1] - initialData[0]) * 0.05;
154
+ useEffect(() => {
155
+ const priceUpdateInterval = setInterval(() => {
156
+ var _scrubberRef$current;
157
+ setPriceData(currentData => {
158
+ const newData = [...currentData];
159
+ const lastPrice = newData[newData.length - 1];
160
+ const priceChange = (Math.random() - 0.5) * maxPercentChange;
161
+ const newPrice = Math.round((lastPrice + priceChange) * 100) / 100;
162
+
163
+ // Check if we should roll over to a new data point
164
+ const currentTime = Date.now();
165
+ const timeSinceLastPoint = (currentTime - lastDataPointTimeRef.current) / 1000;
166
+ if (timeSinceLastPoint >= intervalSeconds) {
167
+ // Time for a new data point - remove first, add new at end
168
+ lastDataPointTimeRef.current = currentTime;
169
+ newData.shift(); // Remove oldest data point
170
+ newData.push(newPrice); // Add new data point
171
+ updateCountRef.current = 0;
172
+ } else {
173
+ // Just update the last data point
174
+ newData[newData.length - 1] = newPrice;
175
+ updateCountRef.current++;
176
+ }
177
+ return newData;
178
+ });
179
+
180
+ // Pulse the scrubber on each update
181
+ (_scrubberRef$current = scrubberRef.current) == null || _scrubberRef$current.pulse();
182
+ }, 2000 + Math.random() * 1000);
183
+ return () => clearInterval(priceUpdateInterval);
184
+ }, [intervalSeconds, maxPercentChange]);
185
+ return /*#__PURE__*/_jsx(LineChart, {
186
+ enableScrubbing: true,
187
+ showArea: true,
188
+ height: 200,
189
+ inset: {
190
+ right: 64
191
+ },
192
+ series: [{
193
+ id: 'btc',
194
+ data: priceData,
195
+ color: assets.btc.color
196
+ }],
197
+ children: /*#__PURE__*/_jsx(Scrubber, {
198
+ ref: scrubberRef
199
+ })
200
+ });
201
+ }
202
+ function MissingData() {
203
+ const theme = useTheme();
204
+ const pages = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'];
205
+ const pageViews = [2400, 1398, null, 3908, 4800, 3800, 4300];
206
+ const uniqueVisitors = [4000, 3000, null, 2780, 1890, 2390, 3490];
207
+ const numberFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
208
+ maximumFractionDigits: 0
209
+ }).format(value), []);
210
+ return /*#__PURE__*/_jsx(LineChart, {
211
+ enableScrubbing: true,
212
+ points: true,
213
+ showArea: true,
214
+ showXAxis: true,
215
+ showYAxis: true,
216
+ height: 200,
217
+ series: [{
218
+ id: 'pageViews',
219
+ data: pageViews,
220
+ color: theme.color.accentBoldGreen,
221
+ // Label will render next to scrubber beacon
222
+ label: 'Page Views',
223
+ connectNulls: true
224
+ }, {
225
+ id: 'uniqueVisitors',
226
+ data: uniqueVisitors,
227
+ color: theme.color.accentBoldPurple,
228
+ label: 'Unique Visitors'
229
+ }],
230
+ xAxis: {
231
+ // Used on the x-axis to provide context for each index from the series data array
232
+ data: pages
233
+ },
234
+ yAxis: {
235
+ showGrid: true,
236
+ tickLabelFormatter: numberFormatter
237
+ },
238
+ children: /*#__PURE__*/_jsx(Scrubber, {
239
+ overlayOffset: 6
240
+ })
241
+ });
242
+ }
243
+ function Interaction() {
244
+ const [scrubberPosition, setScrubberPosition] = useState();
245
+ return /*#__PURE__*/_jsxs(VStack, {
246
+ gap: 2,
247
+ children: [/*#__PURE__*/_jsx(Text, {
248
+ font: "label1",
249
+ children: scrubberPosition !== undefined ? "Scrubber position: " + scrubberPosition : 'Not scrubbing'
250
+ }), /*#__PURE__*/_jsx(LineChart, {
251
+ enableScrubbing: true,
252
+ showArea: true,
253
+ height: 200,
254
+ onScrubberPositionChange: setScrubberPosition,
255
+ series: [{
256
+ id: 'prices',
257
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
258
+ }],
259
+ children: /*#__PURE__*/_jsx(Scrubber, {})
260
+ })]
261
+ });
262
+ }
263
+ function Points() {
264
+ const theme = useTheme();
265
+ const keyMarketShiftIndices = [4, 6, 7, 9, 10];
266
+ const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
267
+ return /*#__PURE__*/_jsxs(CartesianChart, {
268
+ height: 200,
269
+ series: [{
270
+ id: 'prices',
271
+ data: data
272
+ }],
273
+ children: [/*#__PURE__*/_jsx(Area, {
274
+ fill: "rgb(" + theme.spectrum.blue5 + ")",
275
+ seriesId: "prices"
276
+ }), /*#__PURE__*/_jsx(Line, {
277
+ points: _ref => {
278
+ let {
279
+ dataX,
280
+ dataY
281
+ } = _ref,
282
+ props = _objectWithoutPropertiesLoose(_ref, _excluded);
283
+ return keyMarketShiftIndices.includes(dataX) ? _extends({}, props, {
284
+ strokeWidth: 2,
285
+ stroke: theme.color.bg,
286
+ radius: 5,
287
+ onClick: () => alert("You have clicked a key market shift at position " + (dataX + 1) + " with value " + dataY + "!"),
288
+ accessibilityLabel: "Key market shift point at position " + (dataX + 1) + ", value " + dataY + ". Click to view details."
289
+ }) : false;
147
290
  },
148
- onPress: () => console.log('clicked'),
149
- radius: 6,
150
- stroke: "purple",
151
- strokeWidth: 7
152
- }), /*#__PURE__*/_jsx(ReferenceLine, {
153
- dataX: 2,
154
- label: "testing 123",
155
- labelProps: {
156
- color: '#10b981',
157
- inset: 0,
158
- verticalAlignment: 'middle'
159
- }
160
- }), /*#__PURE__*/_jsx(ReferenceLine, {
161
- dataY: 60,
162
- label: "testing 123",
163
- labelProps: {
164
- horizontalAlignment: 'left'
165
- }
166
- }), /*#__PURE__*/_jsx(Point, {
167
- dataX: 5,
168
- dataY: 50,
169
- fill: "orange",
170
- radius: 5
291
+ seriesId: "prices"
171
292
  })]
172
293
  });
173
- };
174
- export const AssetPrice = () => {
175
- const pricePointsPerHour = 12;
176
- const currentHour = 14;
177
- const pricePointsToShow = currentHour * pricePointsPerHour;
178
- const parsedPrices = useMemo(() => prices.slice(0, pricePointsToShow).map(price => parseFloat(price)), [pricePointsToShow]);
179
- const [highlightedItemIndex, setHighlightedItemIndex] = useState(undefined);
180
- const isHovering = useMemo(() => typeof highlightedItemIndex === 'number' && highlightedItemIndex < pricePointsToShow, [highlightedItemIndex, pricePointsToShow]);
181
- const indexToTime = useCallback(index => {
182
- const date = new Date();
183
- date.setHours(0, 0, 0, 0);
184
- date.setMinutes(index * 5);
185
- return date.toLocaleTimeString('en-US', {
186
- hour: 'numeric',
187
- minute: '2-digit',
188
- hour12: true
189
- });
190
- }, []);
191
- const onScrubberPositionChange = useCallback(scrubberPosition => {
192
- setHighlightedItemIndex(scrubberPosition);
193
- }, []);
194
- const highlightedPrice = useMemo(() => {
195
- const price = isHovering && typeof highlightedItemIndex === 'number' ? prices[highlightedItemIndex] : prices[prices.length - 1];
196
- return new Intl.NumberFormat('en-US', {
294
+ }
295
+ function Transitions() {
296
+ const theme = useTheme();
297
+ const dataCount = 20;
298
+ const maxDataOffset = 15000;
299
+ const minStepOffset = 2500;
300
+ const maxStepOffset = 10000;
301
+ const domainLimit = 20000;
302
+ const updateInterval = 500;
303
+ const myTransitionConfig = {
304
+ type: 'spring',
305
+ stiffness: 700,
306
+ damping: 20
307
+ };
308
+ const negativeColor = "rgb(" + theme.spectrum.gray15 + ")";
309
+ const positiveColor = theme.color.fgPositive;
310
+ function generateNextValue(previousValue) {
311
+ const range = maxStepOffset - minStepOffset;
312
+ const offset = Math.random() * range + minStepOffset;
313
+ let direction;
314
+ if (previousValue >= maxDataOffset) {
315
+ direction = -1;
316
+ } else if (previousValue <= -maxDataOffset) {
317
+ direction = 1;
318
+ } else {
319
+ direction = Math.random() < 0.5 ? -1 : 1;
320
+ }
321
+ let newValue = previousValue + offset * direction;
322
+ newValue = Math.max(-maxDataOffset, Math.min(maxDataOffset, newValue));
323
+ return newValue;
324
+ }
325
+ function generateInitialData() {
326
+ const data = [];
327
+ let previousValue = Math.random() * 2 * maxDataOffset - maxDataOffset;
328
+ data.push(previousValue);
329
+ for (let i = 1; i < dataCount; i++) {
330
+ const newValue = generateNextValue(previousValue);
331
+ data.push(newValue);
332
+ previousValue = newValue;
333
+ }
334
+ return data;
335
+ }
336
+ const MyGradient = /*#__PURE__*/memo(props => {
337
+ const areaGradient = {
338
+ stops: _ref2 => {
339
+ let {
340
+ min,
341
+ max
342
+ } = _ref2;
343
+ return [{
344
+ offset: min,
345
+ color: negativeColor,
346
+ opacity: 1
347
+ }, {
348
+ offset: 0,
349
+ color: negativeColor,
350
+ opacity: 0
351
+ }, {
352
+ offset: 0,
353
+ color: positiveColor,
354
+ opacity: 0
355
+ }, {
356
+ offset: max,
357
+ color: positiveColor,
358
+ opacity: 1
359
+ }];
360
+ }
361
+ };
362
+ return /*#__PURE__*/_jsx(DottedArea, _extends({}, props, {
363
+ gradient: areaGradient
364
+ }));
365
+ });
366
+ function CustomTransitionsChart() {
367
+ const [data, setData] = useState(generateInitialData);
368
+ useEffect(() => {
369
+ const intervalId = setInterval(() => {
370
+ setData(currentData => {
371
+ var _currentData;
372
+ const lastValue = (_currentData = currentData[currentData.length - 1]) != null ? _currentData : 0;
373
+ const newValue = generateNextValue(lastValue);
374
+ return [...currentData.slice(1), newValue];
375
+ });
376
+ }, updateInterval);
377
+ return () => clearInterval(intervalId);
378
+ }, []);
379
+ const tickLabelFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
197
380
  style: 'currency',
198
- currency: 'USD'
199
- }).format(parseFloat(price));
200
- }, [highlightedItemIndex, isHovering]);
201
-
202
- // Calculate trend information
203
- const trendInfo = useMemo(() => {
204
- const currentPrice = isHovering && typeof highlightedItemIndex === 'number' ? parseFloat(prices[highlightedItemIndex]) : parseFloat(prices[prices.length - 1]);
205
- const startPrice = parseFloat(prices[0]);
206
- const priceChange = currentPrice - startPrice;
207
- const percentChange = priceChange / startPrice * 100;
208
- const trendDirection = priceChange > 0 ? 'up' : priceChange < 0 ? 'down' : 'neutral';
209
- const formattedPriceChange = new Intl.NumberFormat('en-US', {
381
+ currency: 'USD',
382
+ maximumFractionDigits: 0
383
+ }).format(value), []);
384
+ const valueAtIndexFormatter = useCallback(dataIndex => new Intl.NumberFormat('en-US', {
210
385
  style: 'currency',
211
386
  currency: 'USD'
212
- }).format(Math.abs(priceChange));
213
- const formattedPercentChange = Math.abs(percentChange).toFixed(2) + "%";
214
- return {
215
- direction: trendDirection,
216
- text: formattedPriceChange + " (" + formattedPercentChange + ")"
387
+ }).format(data[dataIndex]), [data]);
388
+ const lineGradient = {
389
+ stops: [{
390
+ offset: 0,
391
+ color: negativeColor
392
+ }, {
393
+ offset: 0,
394
+ color: positiveColor
395
+ }]
217
396
  };
218
- }, [highlightedItemIndex, isHovering]);
219
- return /*#__PURE__*/_jsx(VStack, {
220
- gap: 2,
221
- children: /*#__PURE__*/_jsxs(LineChart, {
222
- showArea: true,
223
- height: defaultChartHeight,
397
+ return /*#__PURE__*/_jsxs(CartesianChart, {
398
+ enableScrubbing: true,
399
+ height: 200,
224
400
  inset: {
225
- top: 4,
226
- bottom: 8,
227
- left: 0,
228
- right: 0
401
+ top: 32,
402
+ bottom: 32,
403
+ left: 16,
404
+ right: 16
229
405
  },
230
- onScrubberPositionChange: onScrubberPositionChange,
231
406
  series: [{
232
- id: 'price',
233
- data: parsedPrices,
234
- color: assets.btc.color
407
+ id: 'prices',
408
+ data: data,
409
+ gradient: lineGradient
235
410
  }],
236
- xAxis: {
411
+ yAxis: {
237
412
  domain: {
238
- min: 0,
239
- max: pricePointsPerHour * 24
413
+ min: -domainLimit,
414
+ max: domainLimit
240
415
  }
241
416
  },
242
- children: [/*#__PURE__*/_jsx(XAxis, {
243
- tickLabelFormatter: index => indexToTime(index).slice(0, -3),
244
- ticks: index => index % (12 * 6) === 0
245
- }), /*#__PURE__*/_jsx(ReferenceLine, {
246
- dataY: parsedPrices[0]
417
+ children: [/*#__PURE__*/_jsx(YAxis, {
418
+ showGrid: true,
419
+ requestedTickCount: 2,
420
+ tickLabelFormatter: tickLabelFormatter
421
+ }), /*#__PURE__*/_jsx(Line, {
422
+ showArea: true,
423
+ AreaComponent: MyGradient,
424
+ seriesId: "prices",
425
+ strokeWidth: 3,
426
+ transition: myTransitionConfig
427
+ }), /*#__PURE__*/_jsx(Scrubber, {
428
+ hideOverlay: true,
429
+ beaconTransitions: {
430
+ update: myTransitionConfig
431
+ },
432
+ label: valueAtIndexFormatter
247
433
  })]
248
- })
249
- });
250
- };
251
- export const LineStyles = () => {
252
- const topChartData = [15, 28, 32, 44, 46, 36, 40, 45, 48, 38];
253
- const upperMiddleChartData = [12, 23, 21, 29, 34, 28, 31, 38, 42, 35];
254
- const lowerMiddleChartData = [8, 15, 14, 25, 20, 18, 22, 28, 24, 30];
255
- const bottomChartData = [4, 8, 11, 15, 16, 14, 16, 10, 12, 14];
256
- return /*#__PURE__*/_jsxs(CartesianChart, {
257
- height: defaultChartHeight,
434
+ });
435
+ }
436
+ return /*#__PURE__*/_jsx(CustomTransitionsChart, {});
437
+ }
438
+ function BasicAccessible() {
439
+ const [scrubberPosition, setScrubberPosition] = useState();
440
+ const data = useMemo(() => [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58], []);
441
+
442
+ // Chart-level accessibility label provides overview
443
+ const chartAccessibilityLabel = useMemo(() => {
444
+ const currentPrice = data[data.length - 1];
445
+ return "Price chart showing trend over " + data.length + " data points. Current value: " + currentPrice + ". Use arrow keys to adjust view";
446
+ }, [data]);
447
+
448
+ // Scrubber-level accessibility label provides specific position info
449
+ const scrubberAccessibilityLabel = useCallback(index => {
450
+ return "Price at position " + (index + 1) + " of " + data.length + ": " + data[index];
451
+ }, [data]);
452
+ const accessibilityLabel = useMemo(() => {
453
+ if (scrubberPosition !== undefined) {
454
+ return scrubberAccessibilityLabel(scrubberPosition);
455
+ }
456
+ return chartAccessibilityLabel;
457
+ }, [scrubberPosition, chartAccessibilityLabel, scrubberAccessibilityLabel]);
458
+ return /*#__PURE__*/_jsx(LineChart, {
459
+ enableScrubbing: true,
460
+ showArea: true,
461
+ showYAxis: true,
462
+ accessibilityLabel: accessibilityLabel,
463
+ height: 200,
464
+ onScrubberPositionChange: setScrubberPosition,
258
465
  series: [{
259
- id: 'top',
260
- data: topChartData
261
- }, {
262
- id: 'upperMiddle',
263
- data: upperMiddleChartData,
264
- color: '#ef4444'
265
- }, {
266
- id: 'lowerMiddle',
267
- data: lowerMiddleChartData,
268
- color: '#f59e0b'
269
- }, {
270
- id: 'bottom',
271
- data: bottomChartData,
272
- color: '#800080'
466
+ id: 'prices',
467
+ data: data
273
468
  }],
274
- children: [/*#__PURE__*/_jsx(Line, {
275
- seriesId: "top"
276
- }), /*#__PURE__*/_jsx(Line, {
277
- seriesId: "upperMiddle",
278
- type: "dotted"
279
- }), /*#__PURE__*/_jsx(Line, {
280
- LineComponent: props => /*#__PURE__*/_jsx(GradientLine, _extends({}, props, {
281
- endColor: "#F7931A",
282
- startColor: "#E3D74D",
283
- strokeWidth: 4
284
- })),
285
- curve: "natural",
286
- seriesId: "lowerMiddle"
287
- }), /*#__PURE__*/_jsx(Line, {
288
- showArea: true,
289
- AreaComponent: DottedArea,
290
- curve: "step",
291
- seriesId: "bottom"
292
- })]
469
+ yAxis: {
470
+ showGrid: true
471
+ },
472
+ children: /*#__PURE__*/_jsx(Scrubber, {})
293
473
  });
294
- };
295
- export const ChartScale = () => {
296
- // Generate exponential growth data that benefits from log scaling
297
- const exponentialData = [1, 2, 4, 8, 15, 30, 65, 140, 280, 580, 1200, 2400, 4800, 9500, 19000, 38000, 75000, 150000];
298
- const scaleTypes = [{
299
- id: 'linear',
300
- label: 'Linear'
301
- }, {
302
- id: 'log',
303
- label: 'Log'
304
- }];
305
- const [selectedScaleType, setSelectedScaleType] = useState(scaleTypes[0]);
474
+ }
475
+ function Gradients() {
476
+ const theme = useTheme();
477
+ const spectrumColors = ['blue', 'green', 'orange', 'yellow', 'gray', 'indigo', 'pink', 'purple', 'red', 'teal', 'chartreuse'];
478
+ const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
479
+ const [currentSpectrumColor, setCurrentSpectrumColor] = useState('pink');
306
480
  return /*#__PURE__*/_jsxs(VStack, {
307
- gap: 3,
308
- children: [/*#__PURE__*/_jsx(VStack, {
309
- alignItems: "flex-end",
310
- gap: 2,
311
- children: /*#__PURE__*/_jsxs(HStack, {
312
- alignItems: "center",
313
- gap: 2,
314
- children: [/*#__PURE__*/_jsx(Text, {
315
- font: "label1",
316
- children: "Scale Type"
317
- }), /*#__PURE__*/_jsx(SegmentedTabs, {
318
- activeTab: selectedScaleType,
319
- onChange: setSelectedScaleType,
320
- tabs: scaleTypes
321
- })]
322
- })
481
+ gap: 2,
482
+ children: [/*#__PURE__*/_jsx(HStack, {
483
+ flexWrap: "wrap",
484
+ gap: 1,
485
+ justifyContent: "flex-end",
486
+ children: spectrumColors.map(color => /*#__PURE__*/_jsx(Pressable, {
487
+ accessibilityLabel: "Select " + color,
488
+ height: 16,
489
+ onPress: () => setCurrentSpectrumColor(color),
490
+ style: {
491
+ backgroundColor: "rgb(" + theme.spectrum[color + "20"] + ")",
492
+ borderColor: "rgb(" + theme.spectrum[color + "50"] + ")",
493
+ borderWidth: 2
494
+ },
495
+ width: 16
496
+ }, color))
323
497
  }), /*#__PURE__*/_jsx(LineChart, {
324
- showArea: true,
498
+ points: true,
325
499
  showYAxis: true,
326
- curve: "natural",
327
- height: defaultChartHeight,
500
+ height: 200,
328
501
  series: [{
329
- id: 'growth',
330
- data: exponentialData,
331
- color: '#10b981'
502
+ id: 'continuousGradient',
503
+ data: data,
504
+ gradient: {
505
+ stops: [{
506
+ offset: 0,
507
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "80"] + ")"
508
+ }, {
509
+ offset: Math.max(...data),
510
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "20"] + ")"
511
+ }]
512
+ }
513
+ }, {
514
+ id: 'discreteGradient',
515
+ data: data.map(d => d + 50),
516
+ // You can create a "discrete" gradient by having multiple stops at the same offset
517
+ gradient: {
518
+ stops: _ref3 => {
519
+ let {
520
+ min,
521
+ max
522
+ } = _ref3;
523
+ return [
524
+ // Allows a function which accepts min/max or direct array
525
+ {
526
+ offset: min,
527
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "80"] + ")"
528
+ }, {
529
+ offset: min + (max - min) / 3,
530
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "80"] + ")"
531
+ }, {
532
+ offset: min + (max - min) / 3,
533
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "50"] + ")"
534
+ }, {
535
+ offset: min + (max - min) / 3 * 2,
536
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "50"] + ")"
537
+ }, {
538
+ offset: min + (max - min) / 3 * 2,
539
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "20"] + ")"
540
+ }, {
541
+ offset: max,
542
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "20"] + ")"
543
+ }];
544
+ }
545
+ }
546
+ }, {
547
+ id: 'xAxisGradient',
548
+ data: data.map(d => d + 100),
549
+ gradient: {
550
+ // You can also configure by the x-axis.
551
+ axis: 'x',
552
+ stops: _ref4 => {
553
+ let {
554
+ min,
555
+ max
556
+ } = _ref4;
557
+ return [{
558
+ offset: min,
559
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "80"] + ")",
560
+ opacity: 0
561
+ }, {
562
+ offset: max,
563
+ color: "rgb(" + theme.spectrum[currentSpectrumColor + "20"] + ")",
564
+ opacity: 1
565
+ }];
566
+ }
567
+ }
332
568
  }],
569
+ strokeWidth: 4,
333
570
  yAxis: {
334
- scaleType: selectedScaleType == null ? void 0 : selectedScaleType.id,
335
- requestedTickCount: 5,
336
- tickLabelFormatter: value => value.toLocaleString(),
337
- showGrid: true,
338
- width: 70
571
+ showGrid: true
339
572
  }
340
573
  })]
341
574
  });
342
- };
343
- export const ColorShiftChart = () => {
575
+ }
576
+ function GainLossChart() {
344
577
  const theme = useTheme();
345
- const [activeTab, setActiveTab] = useState({
346
- id: '1H',
347
- label: '1H'
348
- });
349
- const tabConversion = {
350
- '1H': 'hour',
351
- '1D': 'day',
352
- '1W': 'week',
353
- '1M': 'month',
354
- '1Y': 'year',
355
- All: 'all'
356
- };
357
- const currentPriceData = activeTab ? sparklineInteractiveData[tabConversion[activeTab.id]] : sparklineInteractiveData.hour;
358
- const currentData = useMemo(() => [...currentPriceData.map(price => price.value)], [currentPriceData]);
359
- const currentTimestamps = useMemo(() => [...currentPriceData.map(price => price.date.toISOString())], [currentPriceData]);
360
- const startPrice = currentData[0];
361
- const currentPrice = currentData[currentData.length - 1];
362
- const priceChange = currentPrice - startPrice;
363
-
364
- // Determine colors based on trend
365
- const trendColor = useMemo(() => {
366
- return priceChange >= 0 ? theme.color.fgPositive : theme.color.fgNegative;
367
- }, [priceChange, theme.color.fgPositive, theme.color.fgNegative]);
368
- const activeBackground = useMemo(() => {
369
- return priceChange >= 0 ? 'bgPositiveWash' : 'bgNegativeWash';
370
- }, [priceChange]);
371
- const formatPrice = useCallback(price => {
372
- return "$" + price.toLocaleString('en-US', {
373
- minimumFractionDigits: 2,
374
- maximumFractionDigits: 2
375
- });
376
- }, []);
377
- const scrubberLabel = useCallback(index => {
378
- const timestamp = currentTimestamps[index];
379
- return formatChartDate(timestamp, (activeTab == null ? void 0 : activeTab.id) || '1H');
380
- }, [activeTab == null ? void 0 : activeTab.id, currentTimestamps]);
578
+ const data = useMemo(() => [-40, -28, -21, -5, 48, -5, -28, 2, -29, -46, 16, -30, -29, 8], []);
579
+ const negativeColor = "rgb(" + theme.spectrum.gray15 + ")";
580
+ const positiveColor = theme.color.fgPositive;
581
+ const tickLabelFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
582
+ style: 'currency',
583
+ currency: 'USD',
584
+ maximumFractionDigits: 0
585
+ }).format(value), []);
381
586
 
382
- // Custom tab component that uses the trend color for both active and inactive states
383
- const ColorShiftTab = useMemo(() => /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref, ref) => {
384
- let {
385
- label
386
- } = _ref,
387
- props = _objectWithoutPropertiesLoose(_ref, _excluded);
388
- return /*#__PURE__*/_jsx(SegmentedTab, _extends({
389
- ref: ref,
390
- activeColor: trendColor,
391
- color: trendColor,
392
- font: "label1",
393
- label: label
394
- }, props));
395
- })), [trendColor]);
396
- const tabs = useMemo(() => [{
397
- id: '1H',
398
- label: '1H'
399
- }, {
400
- id: '1D',
401
- label: '1D'
402
- }, {
403
- id: '1W',
404
- label: '1W'
405
- }, {
406
- id: '1M',
407
- label: '1M'
408
- }, {
409
- id: '1Y',
410
- label: '1Y'
411
- }, {
412
- id: 'All',
413
- label: 'All'
414
- }], []);
415
- return /*#__PURE__*/_jsx(Box, {
416
- style: {
417
- marginLeft: -16,
418
- marginRight: -16
419
- },
420
- children: /*#__PURE__*/_jsxs(VStack, {
421
- gap: 3,
422
- width: "100%",
423
- children: [/*#__PURE__*/_jsxs(LineChart, {
424
- enableScrubbing: true,
425
- showArea: true,
426
- showXAxis: true,
427
- height: defaultChartHeight,
428
- inset: {
429
- left: 0,
430
- right: 24,
431
- bottom: 0
432
- },
433
- series: [{
434
- id: 'price',
435
- data: currentData,
436
- color: trendColor
437
- }],
438
- children: [/*#__PURE__*/_jsx(Scrubber, {
439
- idlePulse: true,
440
- label: scrubberLabel
441
- }), /*#__PURE__*/_jsx(ReferenceLine, {
442
- dataY: startPrice,
443
- label: formatPrice(startPrice),
444
- labelProps: {
445
- horizontalAlignment: 'right',
446
- inset: 4,
447
- borderRadius: 4,
448
- dx: -8,
449
- color: theme.color.fgInverse,
450
- background: priceChange >= 0 ? theme.color.bgPositive : theme.color.bgNegative
451
- },
452
- stroke: priceChange >= 0 ? theme.color.bgPositive : theme.color.bgNegative
453
- })]
454
- }), /*#__PURE__*/_jsx(PeriodSelector, {
455
- TabComponent: ColorShiftTab,
456
- activeBackground: activeBackground,
457
- activeTab: activeTab,
458
- onChange: setActiveTab,
459
- tabs: tabs
460
- })]
461
- })
462
- });
463
- };
464
- export const PriceChart = () => {
465
- const {
466
- latestPrice,
467
- formattedPrice
468
- } = useMemo(() => {
469
- const latestPrice = sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
470
- return {
471
- latestPrice,
472
- formattedPrice: "$" + latestPrice.toLocaleString('en-US', {
473
- minimumFractionDigits: 2,
474
- maximumFractionDigits: 2
475
- })
476
- };
477
- }, []);
478
- const tabs = useMemo(() => [{
479
- id: '1H',
480
- label: '1H'
481
- }, {
482
- id: '1D',
483
- label: '1D'
484
- }, {
485
- id: '1W',
486
- label: '1W'
487
- }, {
488
- id: '1M',
489
- label: '1M'
490
- }, {
491
- id: '1Y',
492
- label: '1Y'
493
- }, {
494
- id: 'All',
495
- label: 'All'
496
- }], []);
497
- const [activeTab, setActiveTab] = useState(tabs[0]);
498
- const isLive = useMemo(() => (activeTab == null ? void 0 : activeTab.id) === '1H', [activeTab]);
499
- const activeBackground = useMemo(() => !isLive ? 'bgPrimaryWash' : 'bgNegativeWash', [isLive]);
500
- const [isHovering, setIsHovering] = useState(false);
501
- const tabConversion = {
502
- '1H': 'hour',
503
- '1D': 'day',
504
- '1W': 'week',
505
- '1M': 'month',
506
- '1Y': 'year',
507
- All: 'all'
587
+ // Line gradient: hard color change at 0 (full opacity for line)
588
+ const lineGradient = {
589
+ stops: [{
590
+ offset: 0,
591
+ color: negativeColor
592
+ }, {
593
+ offset: 0,
594
+ color: positiveColor
595
+ }]
508
596
  };
509
- const currentPriceData = activeTab ? sparklineInteractiveData[tabConversion[activeTab.id]] : sparklineInteractiveData.hour;
510
- const currentData = useMemo(() => [...currentPriceData.map(price => price.value)], [currentPriceData]);
511
- const currentTimestamps = useMemo(() => [...currentPriceData.map(price => price.date.toISOString())], [currentPriceData]);
512
- const startPrice = currentData[0];
513
- const {
514
- lowestPriceIndices,
515
- highestPriceIndices
516
- } = useMemo(() => {
517
- if (currentData.length === 0) {
518
- return {
519
- lowestPriceIndices: [],
520
- highestPriceIndices: [],
521
- minPrice: 0,
522
- maxPrice: 0
523
- };
524
- }
525
- let minPrice = currentData[0];
526
- let maxPrice = currentData[0];
527
-
528
- // First pass: find min and max values
529
- for (let i = 1; i < currentData.length; i++) {
530
- if (currentData[i] < minPrice) {
531
- minPrice = currentData[i];
532
- }
533
- if (currentData[i] > maxPrice) {
534
- maxPrice = currentData[i];
535
- }
536
- }
537
-
538
- // Second pass: find all indices where min and max occur
539
- const lowestPriceIndices = [];
540
- const highestPriceIndices = [];
541
- for (let i = 0; i < currentData.length; i++) {
542
- if (currentData[i] === minPrice) {
543
- lowestPriceIndices.push(i);
544
- }
545
- if (currentData[i] === maxPrice) {
546
- highestPriceIndices.push(i);
597
+ const GradientDottedArea = /*#__PURE__*/memo(props => /*#__PURE__*/_jsx(DottedArea, _extends({}, props, {
598
+ gradient: {
599
+ stops: _ref5 => {
600
+ let {
601
+ min,
602
+ max
603
+ } = _ref5;
604
+ return [{
605
+ offset: min,
606
+ color: negativeColor,
607
+ opacity: 0.4
608
+ }, {
609
+ offset: 0,
610
+ color: negativeColor,
611
+ opacity: 0
612
+ }, {
613
+ offset: 0,
614
+ color: positiveColor,
615
+ opacity: 0
616
+ }, {
617
+ offset: max,
618
+ color: positiveColor,
619
+ opacity: 0.4
620
+ }];
547
621
  }
548
622
  }
549
- return {
550
- lowestPriceIndices,
551
- highestPriceIndices,
552
- minPrice,
553
- maxPrice
554
- };
555
- }, [currentData]);
556
- const latestPriceCoords = useMemo(() => {
557
- if (currentData.length === 0) return {};
558
- return {
559
- x: currentData.length - 1,
560
- y: currentData[currentData.length - 1]
561
- };
562
- }, [currentData]);
563
- const onScrubberPositionChange = useCallback(item => {
564
- setIsHovering(item !== undefined);
565
- }, []);
566
- const {
567
- trendPrice,
568
- trendPreviousPrice,
569
- trendDirection
570
- } = useMemo(() => {
571
- return calculateTrendData(undefined, currentData, currentTimestamps, startPrice, latestPrice, (activeTab == null ? void 0 : activeTab.id) || '1H');
572
- }, [currentData, currentTimestamps, startPrice, latestPrice, activeTab]);
573
- const calculatedPriceChange = trendPrice - trendPreviousPrice;
574
- const calculatedPercentChange = calculatedPriceChange / trendPreviousPrice * 100;
575
- const formattedPriceChange = "$" + Math.abs(calculatedPriceChange).toLocaleString('en-US', {
576
- minimumFractionDigits: 2,
577
- maximumFractionDigits: 2
578
- }) + " (" + Math.abs(calculatedPercentChange).toLocaleString('en-US', {
579
- minimumFractionDigits: 2,
580
- maximumFractionDigits: 2
581
- }) + "%)";
582
- const scrubberLabel = useCallback(item => {
583
- if (item === undefined) return null;
584
- const timestamp = currentTimestamps[item];
585
- const price = currentData[item];
586
- const formattedPrice = price.toLocaleString('en-US', {
587
- minimumFractionDigits: 2,
588
- maximumFractionDigits: 2
589
- }) + ' USD';
590
- const formattedDate = formatChartDate(timestamp, (activeTab == null ? void 0 : activeTab.id) || '1H');
591
- return /*#__PURE__*/_jsxs(_Fragment, {
592
- children: [/*#__PURE__*/_jsx("tspan", {
593
- style: {
594
- fontWeight: 'bold',
595
- display: 'inline-block'
596
- },
597
- children: formattedPrice
598
- }), /*#__PURE__*/_jsxs("tspan", {
599
- style: {
600
- display: 'inline-block'
601
- },
602
- children: [" ", formattedDate]
603
- })]
604
- });
605
- }, [currentTimestamps, currentData, activeTab == null ? void 0 : activeTab.id]);
606
- const formatPrice = useCallback(value => {
607
- return "$" + value.toLocaleString('en-US', {
623
+ })));
624
+ return /*#__PURE__*/_jsxs(CartesianChart, {
625
+ enableScrubbing: true,
626
+ height: 200,
627
+ series: [{
628
+ id: 'prices',
629
+ data: data,
630
+ gradient: lineGradient
631
+ }],
632
+ children: [/*#__PURE__*/_jsx(YAxis, {
633
+ showGrid: true,
634
+ requestedTickCount: 2,
635
+ tickLabelFormatter: tickLabelFormatter
636
+ }), /*#__PURE__*/_jsx(Line, {
637
+ showArea: true,
638
+ AreaComponent: GradientDottedArea,
639
+ seriesId: "prices",
640
+ strokeWidth: 3
641
+ }), /*#__PURE__*/_jsx(Scrubber, {
642
+ hideOverlay: true
643
+ })]
644
+ });
645
+ }
646
+ function HighLowPrice() {
647
+ const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
648
+ const minPrice = Math.min(...data);
649
+ const maxPrice = Math.max(...data);
650
+ const minPriceIndex = data.indexOf(minPrice);
651
+ const maxPriceIndex = data.indexOf(maxPrice);
652
+ const formatPrice = useCallback(price => {
653
+ return "$" + price.toLocaleString('en-US', {
608
654
  minimumFractionDigits: 2,
609
655
  maximumFractionDigits: 2
610
656
  });
611
657
  }, []);
612
- return /*#__PURE__*/_jsxs(VStack, {
613
- gap: 3,
614
- width: "100%",
615
- children: [/*#__PURE__*/_jsx(LineChart, {
616
- showArea: true,
617
- height: defaultChartHeight,
618
- inset: {
619
- left: 0,
620
- right: 3,
621
- bottom: 3,
622
- top: 3
623
- },
624
- onScrubberPositionChange: onScrubberPositionChange,
625
- series: [{
626
- id: 'price',
627
- data: currentData,
628
- color: assets.eth.color,
629
- renderPoints: _ref2 => {
630
- let {
631
- dataX: index
632
- } = _ref2;
633
- if (highestPriceIndices.includes(index)) {
634
- return {
635
- opacity: 0,
636
- label: formatPrice(currentData[index]),
637
- labelProps: {
638
- position: 'top',
639
- dy: -16
640
- }
641
- };
642
- }
643
- if (lowestPriceIndices.includes(index)) {
644
- return {
645
- opacity: 0,
646
- label: formatPrice(currentData[index]),
647
- labelProps: {
648
- position: 'bottom',
649
- dy: 16
650
- }
651
- };
652
- }
653
- }
654
- }],
655
- yAxis: {
656
- domainLimit: 'strict'
657
- },
658
- children: /*#__PURE__*/_jsx(Scrubber, {})
659
- }), /*#__PURE__*/_jsx(PeriodSelector, {
660
- activeTab: activeTab,
661
- onChange: tab => setActiveTab(tab),
662
- tabs: tabs
658
+ return /*#__PURE__*/_jsxs(LineChart, {
659
+ showArea: true,
660
+ height: 200,
661
+ series: [{
662
+ id: 'prices',
663
+ data: data
664
+ }],
665
+ children: [/*#__PURE__*/_jsx(Point, {
666
+ dataX: minPriceIndex,
667
+ dataY: minPrice,
668
+ label: formatPrice(minPrice),
669
+ labelPosition: "bottom"
670
+ }), /*#__PURE__*/_jsx(Point, {
671
+ dataX: maxPriceIndex,
672
+ dataY: maxPrice,
673
+ label: formatPrice(maxPrice),
674
+ labelPosition: "top"
663
675
  })]
664
676
  });
665
- };
666
- export const ForecastChart = () => {
667
- const getDataFromSparkline = startDate => {
668
- const allData = sparklineInteractiveData.all;
669
- if (!allData || allData.length === 0) return [];
670
- const timelineData = allData.filter(point => point.date >= startDate);
671
- return timelineData.map(point => ({
672
- date: point.date,
673
- value: point.value
674
- }));
675
- };
676
- const historicalData = useMemo(() => getDataFromSparkline(new Date('2019-01-01')), []);
677
- const annualGrowthRate = 10;
678
- const generateForecastData = useCallback((lastDate, lastPrice, growthRate) => {
679
- const dailyGrowthRate = Math.pow(1 + growthRate / 100, 1 / 365) - 1;
680
- const forecastData = [];
681
- const fiveYearsFromNow = new Date(lastDate);
682
- fiveYearsFromNow.setFullYear(fiveYearsFromNow.getFullYear() + 5);
683
-
684
- // Generate daily forecast points for 5 years
685
- const currentDate = new Date(lastDate);
686
- let currentPrice = lastPrice;
687
- while (currentDate <= fiveYearsFromNow) {
688
- currentPrice = currentPrice * (1 + dailyGrowthRate * 10);
689
- forecastData.push({
690
- date: new Date(currentDate),
691
- value: Math.round(currentPrice)
692
- });
693
- currentDate.setDate(currentDate.getDate() + 10);
694
- }
695
- return forecastData;
696
- }, []);
697
- const forecastData = useMemo(() => {
698
- if (historicalData.length === 0) return [];
699
- const lastPoint = historicalData[historicalData.length - 1];
700
- return generateForecastData(lastPoint.date, lastPoint.value, annualGrowthRate);
701
- }, [generateForecastData, historicalData, annualGrowthRate]);
702
-
703
- // Combine all data points with dates converted to timestamps for x-axis
704
- const allDataPoints = useMemo(() => [...historicalData, ...forecastData], [historicalData, forecastData]);
705
- const formatPrice = useCallback(price => {
706
- return new Intl.NumberFormat('en-US', {
707
- style: 'currency',
708
- currency: 'USD',
709
- minimumFractionDigits: 0,
710
- maximumFractionDigits: 0
711
- }).format(price);
712
- }, []);
713
-
714
- // Format x-axis labels to show years
715
- const formatXAxisLabel = useCallback(timestamp => {
716
- return new Date(timestamp).getFullYear().toString();
717
- }, []);
677
+ }
678
+ function StylingScrubber() {
679
+ const theme = useTheme();
680
+ const pages = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'];
681
+ const pageViews = [2400, 1398, 9800, 3908, 4800, 3800, 4300];
682
+ const uniqueVisitors = [4000, 3000, 2000, 2780, 1890, 2390, 3490];
683
+ const numberFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
684
+ maximumFractionDigits: 0
685
+ }).format(value), []);
718
686
  return /*#__PURE__*/_jsx(LineChart, {
719
687
  enableScrubbing: true,
720
688
  showArea: true,
721
689
  showXAxis: true,
722
- areaType: "dotted",
723
- height: defaultChartHeight,
724
- inset: {
725
- top: 4,
726
- left: 0,
727
- right: 0,
728
- bottom: 0
729
- },
690
+ showYAxis: true,
691
+ height: 200,
730
692
  series: [{
731
- id: 'historical',
732
- data: historicalData.map(d => d.value),
733
- color: assets.btc.color
693
+ id: 'pageViews',
694
+ data: pageViews,
695
+ color: theme.color.accentBoldGreen,
696
+ // Label will render next to scrubber beacon
697
+ label: 'Page Views'
734
698
  }, {
735
- id: 'forecast',
736
- data: [...historicalData.map(d => null), ...forecastData.map(d => d.value)],
737
- color: assets.btc.color,
738
- type: 'dotted'
699
+ id: 'uniqueVisitors',
700
+ data: uniqueVisitors,
701
+ color: theme.color.accentBoldPurple,
702
+ label: 'Unique Visitors',
703
+ // Default area is gradient
704
+ areaType: 'dotted'
739
705
  }],
740
706
  xAxis: {
741
- data: allDataPoints.map(d => d.date.getTime()),
742
- tickLabelFormatter: formatXAxisLabel,
743
- tickInterval: 32
707
+ // Used on the x-axis to provide context for each index from the series data array
708
+ data: pages
744
709
  },
745
- children: /*#__PURE__*/_jsx(Scrubber, {})
746
- });
747
- };
748
- const PeriodSelectorExample = () => {
749
- const tabs = [{
750
- id: '1H',
751
- label: '1H'
752
- }, {
753
- id: '1D',
754
- label: '1D'
755
- }, {
756
- id: '1W',
757
- label: '1W'
758
- }, {
759
- id: '1M',
760
- label: '1M'
761
- }, {
762
- id: '1Y',
763
- label: '1Y'
764
- }, {
765
- id: 'All',
766
- label: 'All'
767
- }];
768
- const [activeTab, setActiveTab] = useState(tabs[0]);
769
- return /*#__PURE__*/_jsx(PeriodSelector, {
770
- activeTab: activeTab,
771
- onChange: tab => setActiveTab(tab),
772
- tabs: tabs
773
- });
774
- };
775
- const BTCTab = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref3, ref) => {
776
- let {
777
- label
778
- } = _ref3,
779
- props = _objectWithoutPropertiesLoose(_ref3, _excluded2);
780
- const {
781
- activeTab
782
- } = useTabsContext();
783
- const isActive = (activeTab == null ? void 0 : activeTab.id) === props.id;
784
- return /*#__PURE__*/_jsx(SegmentedTab, _extends({
785
- ref: ref,
786
- label: /*#__PURE__*/_jsx(TextLabel1, {
787
- style: {
788
- color: isActive ? assets.btc.color : undefined
789
- },
790
- children: label
710
+ yAxis: {
711
+ showGrid: true,
712
+ tickLabelFormatter: numberFormatter
713
+ },
714
+ children: /*#__PURE__*/_jsx(Scrubber, {
715
+ idlePulse: true,
716
+ LineComponent: SolidLine,
717
+ seriesIds: ['pageViews']
791
718
  })
792
- }, props));
793
- }));
794
- const BTCActiveIndicator = /*#__PURE__*/memo(_ref4 => {
795
- let {
796
- style
797
- } = _ref4,
798
- props = _objectWithoutPropertiesLoose(_ref4, _excluded3);
799
- return /*#__PURE__*/_jsx(PeriodSelectorActiveIndicator, _extends({}, props, {
800
- style: [style, {
801
- backgroundColor: assets.btc.color + "1A"
802
- }]
803
- }));
804
- });
805
- const AssetPriceDotted = () => {
806
- const currentPrice = sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
807
- const tabs = useMemo(() => [{
808
- id: 'hour',
809
- label: '1H'
810
- }, {
811
- id: 'day',
812
- label: '1D'
813
- }, {
814
- id: 'week',
815
- label: '1W'
816
- }, {
817
- id: 'month',
818
- label: '1M'
819
- }, {
820
- id: 'year',
821
- label: '1Y'
822
- }, {
823
- id: 'all',
824
- label: 'All'
825
- }], []);
826
- const [timePeriod, setTimePeriod] = useState(tabs[0]);
827
- const sparklineTimePeriodData = useMemo(() => {
828
- return sparklineInteractiveData[timePeriod.id];
829
- }, [timePeriod]);
830
- const sparklineTimePeriodDataValues = useMemo(() => {
831
- return sparklineTimePeriodData.map(d => d.value);
832
- }, [sparklineTimePeriodData]);
833
- const sparklineTimePeriodDataTimestamps = useMemo(() => {
834
- return sparklineTimePeriodData.map(d => d.date);
835
- }, [sparklineTimePeriodData]);
836
- const onPeriodChange = useCallback(period => {
837
- setTimePeriod(period || tabs[0]);
838
- }, [tabs, setTimePeriod]);
719
+ });
720
+ }
721
+ function Compact() {
722
+ const theme = useTheme();
723
+ const dimensions = {
724
+ width: 62,
725
+ height: 18
726
+ };
727
+ const sparklineData = prices.map(price => parseFloat(price)).filter((price, index) => index % 10 === 0);
728
+ const positiveFloor = Math.min(...sparklineData) - 10;
729
+ const negativeData = sparklineData.map(price => -1 * price).reverse();
730
+ const negativeCeiling = Math.max(...negativeData) + 10;
839
731
  const formatPrice = useCallback(price => {
840
- return new Intl.NumberFormat('en-US', {
841
- style: 'currency',
842
- currency: 'USD'
843
- }).format(price);
844
- }, []);
845
- const formatDate = useCallback(date => {
846
- const dayOfWeek = date.toLocaleDateString('en-US', {
847
- weekday: 'short'
732
+ return "$" + price.toLocaleString('en-US', {
733
+ minimumFractionDigits: 2,
734
+ maximumFractionDigits: 2
848
735
  });
849
- const monthDay = date.toLocaleDateString('en-US', {
850
- month: 'short',
851
- day: 'numeric'
736
+ }, []);
737
+ const CompactChart = /*#__PURE__*/memo(_ref6 => {
738
+ let {
739
+ data,
740
+ showArea,
741
+ color,
742
+ referenceY
743
+ } = _ref6;
744
+ return /*#__PURE__*/_jsx(Box, {
745
+ style: {
746
+ padding: 1
747
+ },
748
+ children: /*#__PURE__*/_jsx(LineChart, _extends({}, dimensions, {
749
+ enableScrubbing: false,
750
+ inset: 0,
751
+ series: [{
752
+ id: 'btc',
753
+ data,
754
+ color
755
+ }],
756
+ showArea: showArea,
757
+ children: /*#__PURE__*/_jsx(ReferenceLine, {
758
+ dataY: referenceY
759
+ })
760
+ }))
852
761
  });
853
- const time = date.toLocaleTimeString('en-US', {
854
- hour: 'numeric',
855
- minute: '2-digit',
856
- hour12: true
762
+ });
763
+ const ChartCell = /*#__PURE__*/memo(_ref7 => {
764
+ let {
765
+ data,
766
+ showArea,
767
+ color,
768
+ referenceY,
769
+ subdetail
770
+ } = _ref7;
771
+ return /*#__PURE__*/_jsx(ListCell, {
772
+ detail: formatPrice(parseFloat(prices[0])),
773
+ intermediary: /*#__PURE__*/_jsx(CompactChart, {
774
+ color: color,
775
+ data: data,
776
+ referenceY: referenceY,
777
+ showArea: showArea
778
+ }),
779
+ media: /*#__PURE__*/_jsx(Avatar, {
780
+ src: assets.btc.imageUrl
781
+ }),
782
+ onPress: () => console.log('clicked'),
783
+ spacingVariant: "condensed",
784
+ style: {
785
+ padding: 0
786
+ },
787
+ subdetail: subdetail
857
788
  });
858
- return dayOfWeek + ", " + monthDay + ", " + time;
789
+ });
790
+ return /*#__PURE__*/_jsxs(VStack, {
791
+ children: [/*#__PURE__*/_jsx(ChartCell, {
792
+ color: assets.btc.color,
793
+ data: sparklineData,
794
+ referenceY: parseFloat(prices[Math.floor(prices.length / 4)]),
795
+ subdetail: "-4.55%"
796
+ }), /*#__PURE__*/_jsx(ChartCell, {
797
+ showArea: true,
798
+ color: assets.btc.color,
799
+ data: sparklineData,
800
+ referenceY: parseFloat(prices[Math.floor(prices.length / 4)]),
801
+ subdetail: "-4.55%"
802
+ }), /*#__PURE__*/_jsx(ChartCell, {
803
+ showArea: true,
804
+ color: theme.color.fgPositive,
805
+ data: sparklineData,
806
+ referenceY: positiveFloor,
807
+ subdetail: "+0.25%"
808
+ }), /*#__PURE__*/_jsx(ChartCell, {
809
+ showArea: true,
810
+ color: theme.color.fgNegative,
811
+ data: negativeData,
812
+ referenceY: negativeCeiling,
813
+ subdetail: "-4.55%"
814
+ })]
815
+ });
816
+ }
817
+ function AssetPriceWithDottedArea() {
818
+ const fontMgr = useMemo(() => {
819
+ const fontProvider = Skia.TypefaceFontProvider.Make();
820
+ // Register system fonts if available, otherwise Skia will use defaults
821
+ return fontProvider;
859
822
  }, []);
860
- const scrubberLabel = useCallback(dataIndex => {
861
- const price = new Intl.NumberFormat('en-US', {
862
- minimumFractionDigits: 2,
863
- maximumFractionDigits: 2
864
- }).format(sparklineTimePeriodDataValues[dataIndex]);
865
- const date = formatDate(sparklineTimePeriodDataTimestamps[dataIndex]);
866
- return price + " USD " + date;
867
- }, [sparklineTimePeriodDataValues, formatDate, sparklineTimePeriodDataTimestamps]);
868
-
869
- // Chart overview accessibility label
870
- const chartOverviewLabel = useMemo(() => {
871
- if (sparklineTimePeriodData.length === 0) return '';
872
- const firstDate = sparklineTimePeriodData[0].date;
873
- const lastDate = sparklineTimePeriodData[sparklineTimePeriodData.length - 1].date;
874
- const currentYear = new Date().getFullYear();
875
- const shouldIncludeTime = timePeriod.id === 'hour' || timePeriod.id === 'day';
876
- const dateRangeOptions = _extends({
877
- month: 'long',
878
- day: 'numeric',
879
- year: firstDate.getFullYear() !== currentYear ? 'numeric' : undefined
880
- }, shouldIncludeTime && {
881
- hour: 'numeric',
882
- minute: '2-digit',
883
- hour12: true
884
- });
885
- const startDateStr = shouldIncludeTime ? firstDate.toLocaleString('en-US', dateRangeOptions) : firstDate.toLocaleDateString('en-US', dateRangeOptions);
886
- const endDateStr = shouldIncludeTime ? lastDate.toLocaleString('en-US', _extends({}, dateRangeOptions, {
887
- year: lastDate.getFullYear() !== currentYear ? 'numeric' : undefined
888
- })) : lastDate.toLocaleDateString('en-US', _extends({}, dateRangeOptions, {
889
- year: lastDate.getFullYear() !== currentYear ? 'numeric' : undefined
823
+ const BTCTab = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref8, ref) => {
824
+ let {
825
+ label
826
+ } = _ref8,
827
+ props = _objectWithoutPropertiesLoose(_ref8, _excluded2);
828
+ const {
829
+ activeTab
830
+ } = useTabsContext();
831
+ const isActive = (activeTab == null ? void 0 : activeTab.id) === props.id;
832
+ return /*#__PURE__*/_jsx(SegmentedTab, _extends({
833
+ ref: ref,
834
+ label: /*#__PURE__*/_jsx(TextLabel1, {
835
+ style: {
836
+ color: isActive ? assets.btc.color : undefined
837
+ },
838
+ children: label
839
+ })
840
+ }, props));
841
+ }));
842
+ const BTCActiveIndicator = /*#__PURE__*/memo(_ref9 => {
843
+ let {
844
+ style
845
+ } = _ref9,
846
+ props = _objectWithoutPropertiesLoose(_ref9, _excluded3);
847
+ return /*#__PURE__*/_jsx(PeriodSelectorActiveIndicator, _extends({}, props, {
848
+ style: [style, {
849
+ backgroundColor: assets.btc.color + "1A"
850
+ }]
890
851
  }));
891
- return "Price chart for Bitcoin, " + startDateStr + " to " + endDateStr + ". Swipe left or right to navigate data points.";
892
- }, [sparklineTimePeriodData, timePeriod.id]);
893
- return /*#__PURE__*/_jsx(Box, {
894
- accessibilityLabel: chartOverviewLabel,
895
- accessibilityLiveRegion: "polite",
896
- children: /*#__PURE__*/_jsxs(VStack, {
852
+ });
853
+ const AssetPriceDotted = /*#__PURE__*/memo(() => {
854
+ const theme = useTheme();
855
+ const currentPrice = sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
856
+ const tabs = useMemo(() => [{
857
+ id: 'hour',
858
+ label: '1H'
859
+ }, {
860
+ id: 'day',
861
+ label: '1D'
862
+ }, {
863
+ id: 'week',
864
+ label: '1W'
865
+ }, {
866
+ id: 'month',
867
+ label: '1M'
868
+ }, {
869
+ id: 'year',
870
+ label: '1Y'
871
+ }, {
872
+ id: 'all',
873
+ label: 'All'
874
+ }], []);
875
+ const [timePeriod, setTimePeriod] = useState(tabs[0]);
876
+ const sparklineTimePeriodData = useMemo(() => {
877
+ return sparklineInteractiveData[timePeriod.id];
878
+ }, [timePeriod]);
879
+ const sparklineTimePeriodDataValues = useMemo(() => {
880
+ return sparklineTimePeriodData.map(d => d.value);
881
+ }, [sparklineTimePeriodData]);
882
+ const sparklineTimePeriodDataTimestamps = useMemo(() => {
883
+ return sparklineTimePeriodData.map(d => d.date);
884
+ }, [sparklineTimePeriodData]);
885
+ const onPeriodChange = useCallback(period => {
886
+ setTimePeriod(period || tabs[0]);
887
+ }, [tabs, setTimePeriod]);
888
+ const priceFormatter = useMemo(() => new Intl.NumberFormat('en-US', {
889
+ style: 'currency',
890
+ currency: 'USD'
891
+ }), []);
892
+ const formatPrice = useCallback(price => {
893
+ return priceFormatter.format(price);
894
+ }, [priceFormatter]);
895
+ const formatDate = useCallback(date => {
896
+ const dayOfWeek = date.toLocaleDateString('en-US', {
897
+ weekday: 'short'
898
+ });
899
+ const monthDay = date.toLocaleDateString('en-US', {
900
+ month: 'short',
901
+ day: 'numeric'
902
+ });
903
+ const time = date.toLocaleTimeString('en-US', {
904
+ hour: 'numeric',
905
+ minute: '2-digit',
906
+ hour12: true
907
+ });
908
+ return dayOfWeek + ", " + monthDay + ", " + time;
909
+ }, []);
910
+ return /*#__PURE__*/_jsxs(VStack, {
897
911
  gap: 2,
898
912
  children: [/*#__PURE__*/_jsx(SectionHeader, {
899
- "aria-hidden": "true",
900
- balance: /*#__PURE__*/_jsxs(Text, {
913
+ balance: /*#__PURE__*/_jsx(Text, {
901
914
  font: "title2",
902
- children: [formatPrice(currentPrice), " ", sparklineTimePeriodDataValues.length]
915
+ children: formatPrice(currentPrice)
903
916
  }),
904
917
  end: /*#__PURE__*/_jsx(VStack, {
905
918
  justifyContent: "center",
@@ -909,7 +922,6 @@ const AssetPriceDotted = () => {
909
922
  source: assets.btc.imageUrl
910
923
  })
911
924
  }),
912
- padding: 0,
913
925
  title: /*#__PURE__*/_jsx(Text, {
914
926
  font: "title1",
915
927
  children: "Bitcoin"
@@ -917,9 +929,11 @@ const AssetPriceDotted = () => {
917
929
  }), /*#__PURE__*/_jsx(LineChart, {
918
930
  enableScrubbing: true,
919
931
  showArea: true,
920
- accessibilityLiveRegion: "polite",
921
932
  areaType: "dotted",
922
- height: defaultChartHeight,
933
+ height: 200,
934
+ inset: {
935
+ top: 52
936
+ },
923
937
  series: [{
924
938
  id: 'btc',
925
939
  data: sparklineTimePeriodDataValues,
@@ -927,42 +941,123 @@ const AssetPriceDotted = () => {
927
941
  }],
928
942
  children: /*#__PURE__*/_jsx(Scrubber, {
929
943
  idlePulse: true,
930
- label: scrubberLabel
944
+ labelElevated: true,
945
+ label: d => {
946
+ const date = formatDate(sparklineTimePeriodDataTimestamps[d]);
947
+ const price = formatPrice(sparklineTimePeriodDataValues[d]);
948
+ const regularStyle = {
949
+ fontFamilies: ['Inter'],
950
+ fontSize: 14,
951
+ fontStyle: {
952
+ weight: FontWeight.Normal
953
+ },
954
+ color: Skia.Color(theme.color.fgMuted)
955
+ };
956
+ const boldStyle = _extends({
957
+ fontFamilies: ['Inter']
958
+ }, regularStyle, {
959
+ fontStyle: {
960
+ weight: FontWeight.Bold
961
+ }
962
+ });
963
+
964
+ // 3. Use the ParagraphBuilder
965
+ const builder = Skia.ParagraphBuilder.Make({
966
+ textAlign: TextAlign.Left
967
+ }, fontMgr);
968
+ builder.pushStyle(boldStyle);
969
+ builder.addText(price);
970
+ builder.pushStyle(regularStyle);
971
+ builder.addText(" " + date);
972
+ const para = builder.build();
973
+ para.layout(512);
974
+ return para;
975
+ }
931
976
  })
932
977
  }), /*#__PURE__*/_jsx(PeriodSelector, {
933
978
  TabComponent: BTCTab,
934
979
  TabsActiveIndicatorComponent: BTCActiveIndicator,
935
- accessibilityLabel: "Select time period for chart",
936
980
  activeTab: timePeriod,
937
981
  onChange: onPeriodChange,
938
982
  tabs: tabs
939
983
  })]
940
- })
984
+ });
941
985
  });
942
- };
943
- const AssetPriceDottedNonMemoized = () => {
944
- const [scrubIndex, setScrubIndex] = useState(undefined);
945
- const currentPrice = sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
946
- const tabs = useMemo(() => [{
947
- id: 'hour',
948
- label: '1H'
949
- }, {
950
- id: 'day',
951
- label: '1D'
952
- }, {
953
- id: 'week',
954
- label: '1W'
955
- }, {
956
- id: 'month',
957
- label: '1M'
958
- }, {
959
- id: 'year',
960
- label: '1Y'
961
- }, {
962
- id: 'all',
963
- label: 'All'
964
- }], []);
965
- const [timePeriod, setTimePeriod] = useState(tabs[0]);
986
+ return /*#__PURE__*/_jsx(AssetPriceDotted, {});
987
+ }
988
+ const LegendDot = /*#__PURE__*/memo(props => {
989
+ return /*#__PURE__*/_jsx(Box, _extends({
990
+ borderRadius: 1000,
991
+ height: 10,
992
+ width: 10
993
+ }, props));
994
+ });
995
+ const LegendItem = /*#__PURE__*/memo(_ref0 => {
996
+ let {
997
+ color = assets.btc.color,
998
+ label,
999
+ value
1000
+ } = _ref0;
1001
+ return /*#__PURE__*/_jsxs(Box, {
1002
+ alignItems: "center",
1003
+ flexDirection: "row",
1004
+ gap: 0.5,
1005
+ children: [/*#__PURE__*/_jsx(LegendDot, {
1006
+ style: {
1007
+ backgroundColor: color
1008
+ }
1009
+ }), /*#__PURE__*/_jsx(Text, {
1010
+ font: "label2",
1011
+ children: label
1012
+ }), value && /*#__PURE__*/_jsx(Text, {
1013
+ color: "fgMuted",
1014
+ font: "label2",
1015
+ style: {
1016
+ fontWeight: 'bold'
1017
+ },
1018
+ children: value
1019
+ })]
1020
+ });
1021
+ });
1022
+ const PerformanceHeader = /*#__PURE__*/memo(_ref1 => {
1023
+ let {
1024
+ scrubberPosition,
1025
+ sparklineTimePeriodDataValues
1026
+ } = _ref1;
1027
+ const theme = useTheme();
1028
+ const formatPriceThousands = useCallback(price => {
1029
+ return new Intl.NumberFormat('en-US', {
1030
+ style: 'currency',
1031
+ currency: 'USD',
1032
+ minimumFractionDigits: 0,
1033
+ maximumFractionDigits: 0
1034
+ }).format(price / 1000) + "k";
1035
+ }, []);
1036
+ const shownPosition = scrubberPosition !== undefined ? scrubberPosition : sparklineTimePeriodDataValues.length - 1;
1037
+ return /*#__PURE__*/_jsxs(HStack, {
1038
+ gap: 1,
1039
+ paddingX: 1,
1040
+ children: [/*#__PURE__*/_jsx(LegendItem, {
1041
+ color: theme.color.fgPositive,
1042
+ label: "High Price",
1043
+ value: formatPriceThousands(sparklineTimePeriodDataValues[shownPosition] * 1.2)
1044
+ }), /*#__PURE__*/_jsx(LegendItem, {
1045
+ color: assets.btc.color,
1046
+ label: "Actual Price",
1047
+ value: formatPriceThousands(sparklineTimePeriodDataValues[shownPosition])
1048
+ }), /*#__PURE__*/_jsx(LegendItem, {
1049
+ color: theme.color.fgNegative,
1050
+ label: "Low Price",
1051
+ value: formatPriceThousands(sparklineTimePeriodDataValues[shownPosition] * 0.8)
1052
+ })]
1053
+ });
1054
+ });
1055
+ const PerformanceChart = /*#__PURE__*/memo(_ref10 => {
1056
+ let {
1057
+ timePeriod,
1058
+ onScrubberPositionChange
1059
+ } = _ref10;
1060
+ const theme = useTheme();
966
1061
  const sparklineTimePeriodData = useMemo(() => {
967
1062
  return sparklineInteractiveData[timePeriod.id];
968
1063
  }, [timePeriod]);
@@ -972,14 +1067,13 @@ const AssetPriceDottedNonMemoized = () => {
972
1067
  const sparklineTimePeriodDataTimestamps = useMemo(() => {
973
1068
  return sparklineTimePeriodData.map(d => d.date);
974
1069
  }, [sparklineTimePeriodData]);
975
- const onPeriodChange = useCallback(period => {
976
- setTimePeriod(period || tabs[0]);
977
- }, [tabs, setTimePeriod]);
978
- const formatPrice = useCallback(price => {
1070
+ const formatPriceThousands = useCallback(price => {
979
1071
  return new Intl.NumberFormat('en-US', {
980
1072
  style: 'currency',
981
- currency: 'USD'
982
- }).format(price);
1073
+ currency: 'USD',
1074
+ minimumFractionDigits: 0,
1075
+ maximumFractionDigits: 0
1076
+ }).format(price / 1000) + "k";
983
1077
  }, []);
984
1078
  const formatDate = useCallback(date => {
985
1079
  const dayOfWeek = date.toLocaleDateString('en-US', {
@@ -996,61 +1090,46 @@ const AssetPriceDottedNonMemoized = () => {
996
1090
  });
997
1091
  return dayOfWeek + ", " + monthDay + ", " + time;
998
1092
  }, []);
999
- const scrubberLabel = useMemo(() => {
1000
- if (scrubIndex === undefined) return null;
1001
- const price = new Intl.NumberFormat('en-US', {
1002
- minimumFractionDigits: 2,
1003
- maximumFractionDigits: 2
1004
- }).format(sparklineTimePeriodDataValues[scrubIndex]);
1005
- const date = formatDate(sparklineTimePeriodDataTimestamps[scrubIndex]);
1006
- return price + " USD " + date;
1007
- }, [scrubIndex, sparklineTimePeriodDataValues, formatDate, sparklineTimePeriodDataTimestamps]);
1008
- return /*#__PURE__*/_jsxs(VStack, {
1009
- gap: 2,
1010
- children: [/*#__PURE__*/_jsx(SectionHeader, {
1011
- balance: /*#__PURE__*/_jsxs(Text, {
1012
- font: "title2",
1013
- children: [formatPrice(currentPrice), " ", sparklineTimePeriodDataValues.length]
1014
- }),
1015
- end: /*#__PURE__*/_jsx(VStack, {
1016
- justifyContent: "center",
1017
- children: /*#__PURE__*/_jsx(RemoteImage, {
1018
- shape: "circle",
1019
- size: "xl",
1020
- source: assets.btc.imageUrl
1021
- })
1022
- }),
1023
- padding: 0,
1024
- title: /*#__PURE__*/_jsx(Text, {
1025
- font: "title1",
1026
- children: "Bitcoin"
1027
- })
1028
- }), /*#__PURE__*/_jsx(LineChart, {
1029
- enableScrubbing: true,
1030
- showArea: true,
1031
- areaType: "dotted",
1032
- height: defaultChartHeight,
1033
- onScrubberPositionChange: setScrubIndex,
1034
- series: [{
1035
- id: 'btc',
1036
- data: sparklineTimePeriodDataValues,
1037
- color: assets.btc.color
1038
- }],
1039
- children: /*#__PURE__*/_jsx(Scrubber, {
1040
- label: scrubberLabel
1041
- })
1042
- }), /*#__PURE__*/_jsx(PeriodSelector, {
1043
- TabComponent: BTCTab,
1044
- TabsActiveIndicatorComponent: BTCActiveIndicator,
1045
- activeTab: timePeriod,
1046
- onChange: onPeriodChange,
1047
- tabs: tabs
1048
- })]
1093
+ const getScrubberLabel = useCallback(d => formatDate(sparklineTimePeriodDataTimestamps[d]), [formatDate, sparklineTimePeriodDataTimestamps]);
1094
+ return /*#__PURE__*/_jsx(LineChart, {
1095
+ enableScrubbing: true,
1096
+ showArea: true,
1097
+ showYAxis: true,
1098
+ areaType: "dotted",
1099
+ height: 300,
1100
+ inset: {
1101
+ top: 52,
1102
+ left: 0,
1103
+ right: 0
1104
+ },
1105
+ onScrubberPositionChange: onScrubberPositionChange,
1106
+ series: [{
1107
+ id: 'high',
1108
+ data: sparklineTimePeriodDataValues.map(d => d * 1.2),
1109
+ color: theme.color.fgPositive,
1110
+ label: 'High Price'
1111
+ }, {
1112
+ id: 'btc',
1113
+ data: sparklineTimePeriodDataValues,
1114
+ color: assets.btc.color,
1115
+ label: 'Actual Price'
1116
+ }, {
1117
+ id: 'low',
1118
+ data: sparklineTimePeriodDataValues.map(d => d * 0.8),
1119
+ color: theme.color.fgNegative,
1120
+ label: 'Low Price'
1121
+ }],
1122
+ yAxis: {
1123
+ showGrid: true,
1124
+ tickLabelFormatter: formatPriceThousands
1125
+ },
1126
+ children: /*#__PURE__*/_jsx(Scrubber, {
1127
+ idlePulse: true,
1128
+ label: getScrubberLabel
1129
+ })
1049
1130
  });
1050
- };
1051
- const AssetPriceMultipleDotted = () => {
1052
- const [scrubIndex, setScrubIndex] = useState(undefined);
1053
- const currentPrice = sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
1131
+ });
1132
+ function Performance() {
1054
1133
  const tabs = useMemo(() => [{
1055
1134
  id: 'hour',
1056
1135
  label: '1H'
@@ -1071,288 +1150,662 @@ const AssetPriceMultipleDotted = () => {
1071
1150
  label: 'All'
1072
1151
  }], []);
1073
1152
  const [timePeriod, setTimePeriod] = useState(tabs[0]);
1153
+ const [scrubberPosition, setScrubberPosition] = useState();
1074
1154
  const sparklineTimePeriodData = useMemo(() => {
1075
1155
  return sparklineInteractiveData[timePeriod.id];
1076
1156
  }, [timePeriod]);
1077
1157
  const sparklineTimePeriodDataValues = useMemo(() => {
1078
1158
  return sparklineTimePeriodData.map(d => d.value);
1079
1159
  }, [sparklineTimePeriodData]);
1080
- const sparklineTimePeriodDataTimestamps = useMemo(() => {
1081
- return sparklineTimePeriodData.map(d => d.date);
1082
- }, [sparklineTimePeriodData]);
1083
1160
  const onPeriodChange = useCallback(period => {
1084
1161
  setTimePeriod(period || tabs[0]);
1085
- }, [tabs, setTimePeriod]);
1086
- const formatPrice = useCallback(price => {
1087
- return new Intl.NumberFormat('en-US', {
1088
- style: 'currency',
1089
- currency: 'USD'
1090
- }).format(price);
1091
- }, []);
1092
- const formatDate = useCallback(date => {
1093
- const dayOfWeek = date.toLocaleDateString('en-US', {
1094
- weekday: 'short'
1095
- });
1096
- const monthDay = date.toLocaleDateString('en-US', {
1097
- month: 'short',
1098
- day: 'numeric'
1099
- });
1100
- const time = date.toLocaleTimeString('en-US', {
1101
- hour: 'numeric',
1102
- minute: '2-digit',
1103
- hour12: true
1104
- });
1105
- return dayOfWeek + ", " + monthDay + ", " + time;
1106
- }, []);
1107
- const scrubberLabel = useMemo(() => {
1108
- if (scrubIndex === undefined) return null;
1109
- const price = new Intl.NumberFormat('en-US', {
1110
- minimumFractionDigits: 2,
1111
- maximumFractionDigits: 2
1112
- }).format(sparklineTimePeriodDataValues[scrubIndex]);
1113
- const date = formatDate(sparklineTimePeriodDataTimestamps[scrubIndex]);
1114
- return price + " USD " + date;
1115
- }, [scrubIndex, sparklineTimePeriodDataValues, formatDate, sparklineTimePeriodDataTimestamps]);
1162
+ }, [tabs]);
1116
1163
  return /*#__PURE__*/_jsxs(VStack, {
1117
1164
  gap: 2,
1118
- children: [/*#__PURE__*/_jsx(SectionHeader, {
1119
- balance: /*#__PURE__*/_jsxs(Text, {
1120
- font: "title2",
1121
- children: [formatPrice(currentPrice), " ", sparklineTimePeriodDataValues.length]
1122
- }),
1123
- end: /*#__PURE__*/_jsx(VStack, {
1124
- justifyContent: "center",
1125
- children: /*#__PURE__*/_jsx(RemoteImage, {
1126
- shape: "circle",
1127
- size: "xl",
1128
- source: assets.btc.imageUrl
1129
- })
1130
- }),
1131
- padding: 0,
1132
- title: /*#__PURE__*/_jsx(Text, {
1133
- font: "title1",
1134
- children: "Bitcoin"
1135
- })
1136
- }), /*#__PURE__*/_jsx(LineChart, {
1137
- enableScrubbing: true,
1138
- height: defaultChartHeight,
1139
- series: [{
1140
- id: 'btc',
1141
- data: sparklineTimePeriodDataValues,
1142
- color: assets.btc.color
1143
- }, {
1144
- id: 'eth',
1145
- data: sparklineTimePeriodDataValues.map(d => d * 0.75),
1146
- color: assets.eth.color
1147
- }, {
1148
- id: 'xrp',
1149
- data: sparklineTimePeriodDataValues.map(d => d * 0.5),
1150
- color: assets.xrp.color
1151
- }],
1152
- children: /*#__PURE__*/_jsx(Scrubber, {})
1165
+ style: {
1166
+ marginLeft: -8,
1167
+ marginRight: -8
1168
+ },
1169
+ children: [/*#__PURE__*/_jsx(PerformanceHeader, {
1170
+ scrubberPosition: scrubberPosition,
1171
+ sparklineTimePeriodDataValues: sparklineTimePeriodDataValues
1172
+ }), /*#__PURE__*/_jsx(PerformanceChart, {
1173
+ onScrubberPositionChange: setScrubberPosition,
1174
+ timePeriod: timePeriod
1153
1175
  }), /*#__PURE__*/_jsx(PeriodSelector, {
1154
- TabComponent: BTCTab,
1155
- TabsActiveIndicatorComponent: BTCActiveIndicator,
1156
1176
  activeTab: timePeriod,
1157
1177
  onChange: onPeriodChange,
1158
1178
  tabs: tabs
1159
1179
  })]
1160
1180
  });
1161
- };
1162
- const GainLossChart = () => {
1181
+ }
1182
+ const candlestickStockData = btcCandles.slice(0, 90).reverse();
1183
+ const CandlesticksHeader = /*#__PURE__*/memo(_ref11 => {
1184
+ let {
1185
+ currentIndex
1186
+ } = _ref11;
1187
+ const formatPrice = useCallback(price => {
1188
+ return new Intl.NumberFormat('en-US', {
1189
+ style: 'currency',
1190
+ currency: 'USD'
1191
+ }).format(parseFloat(price));
1192
+ }, []);
1193
+ const formatThousandsPriceNumber = useCallback(price => {
1194
+ const formattedPrice = new Intl.NumberFormat('en-US', {
1195
+ style: 'currency',
1196
+ currency: 'USD',
1197
+ minimumFractionDigits: 0,
1198
+ maximumFractionDigits: 0
1199
+ }).format(price / 1000);
1200
+ return formattedPrice + "k";
1201
+ }, []);
1202
+ const currentText = useMemo(() => {
1203
+ if (currentIndex !== undefined) {
1204
+ return "Open: " + formatThousandsPriceNumber(parseFloat(candlestickStockData[currentIndex].open)) + ", Close: " + formatThousandsPriceNumber(parseFloat(candlestickStockData[currentIndex].close)) + ", Volume: " + (parseFloat(candlestickStockData[currentIndex].volume) / 1000).toFixed(2) + "k";
1205
+ }
1206
+ return formatPrice(candlestickStockData[candlestickStockData.length - 1].close);
1207
+ }, [currentIndex, formatThousandsPriceNumber, formatPrice]);
1208
+ return /*#__PURE__*/_jsx(Text, {
1209
+ "aria-live": "polite",
1210
+ font: "headline",
1211
+ children: currentText
1212
+ });
1213
+ });
1214
+ const CandlesticksChart = /*#__PURE__*/memo(_ref12 => {
1215
+ let {
1216
+ infoTextId,
1217
+ onScrubberPositionChange
1218
+ } = _ref12;
1163
1219
  const theme = useTheme();
1164
- const gradientId = useId();
1165
- const data = [-40, -28, -21, -5, 48, -5, -28, 2, -29, -46, 16, -30, -29, 8];
1166
- const ChartDefs = _ref5 => {
1220
+ const min = useMemo(() => Math.min(...candlestickStockData.map(data => parseFloat(data.low))), []);
1221
+ const ThinSolidLine = /*#__PURE__*/memo(props => /*#__PURE__*/_jsx(SolidLine, _extends({}, props, {
1222
+ strokeWidth: 1
1223
+ })));
1224
+
1225
+ // Custom line component that renders a rect to highlight the entire bandwidth
1226
+ const BandwidthHighlight = /*#__PURE__*/memo(_ref13 => {
1227
+ let {
1228
+ stroke
1229
+ } = _ref13;
1230
+ const {
1231
+ getXSerializableScale,
1232
+ drawingArea
1233
+ } = useCartesianChartContext();
1234
+ const {
1235
+ scrubberPosition
1236
+ } = useScrubberContext();
1237
+ const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
1238
+ const rectWidth = useMemo(() => {
1239
+ if (xScale !== undefined && xScale.type === 'band') {
1240
+ return xScale.bandwidth;
1241
+ }
1242
+ return 0;
1243
+ }, [xScale]);
1244
+ const xPos = useDerivedValue(() => {
1245
+ const position = unwrapAnimatedValue(scrubberPosition);
1246
+ const xPos = position !== undefined && xScale ? getPointOnSerializableScale(position, xScale) : undefined;
1247
+ return xPos !== undefined ? xPos - rectWidth / 2 : 0;
1248
+ }, [scrubberPosition, xScale]);
1249
+ const opacity = useDerivedValue(() => xPos.value !== undefined ? 1 : 0, [xPos]);
1250
+ return /*#__PURE__*/_jsx(Rect, {
1251
+ color: stroke,
1252
+ height: drawingArea.height,
1253
+ opacity: opacity,
1254
+ width: rectWidth,
1255
+ x: xPos,
1256
+ y: drawingArea.y
1257
+ });
1258
+ });
1259
+ const candlesData = useMemo(() => candlestickStockData.map(data => [parseFloat(data.low), parseFloat(data.high)]), []);
1260
+ const CandlestickBarComponent = /*#__PURE__*/memo(_ref14 => {
1261
+ var _yScale, _yScale2;
1167
1262
  let {
1168
- threshold = 0
1169
- } = _ref5;
1263
+ x,
1264
+ y,
1265
+ width,
1266
+ height,
1267
+ dataX
1268
+ } = _ref14,
1269
+ props = _objectWithoutPropertiesLoose(_ref14, _excluded4);
1170
1270
  const {
1171
1271
  getYScale
1172
1272
  } = useCartesianChartContext();
1173
- // get the default y-axis scale
1174
1273
  const yScale = getYScale();
1175
- if (yScale) {
1176
- const domain = yScale.domain();
1177
- const range = yScale.range();
1178
- const baselinePercentage = (threshold - domain[0]) / (domain[1] - domain[0]) * 100;
1179
- const negativeColor = "rgb(" + theme.spectrum.gray20 + ")";
1180
- const positiveColor = theme.color.fgPositive;
1181
- return /*#__PURE__*/_jsxs(Defs, {
1182
- children: [/*#__PURE__*/_jsxs(LinearGradient, {
1183
- gradientUnits: "userSpaceOnUse",
1184
- id: gradientId + "-solid",
1185
- x1: "0%",
1186
- x2: "0%",
1187
- y1: range[0],
1188
- y2: range[1],
1189
- children: [/*#__PURE__*/_jsx(Stop, {
1190
- offset: "0%",
1191
- stopColor: negativeColor
1192
- }), /*#__PURE__*/_jsx(Stop, {
1193
- offset: baselinePercentage + "%",
1194
- stopColor: negativeColor
1195
- }), /*#__PURE__*/_jsx(Stop, {
1196
- offset: baselinePercentage + "%",
1197
- stopColor: positiveColor
1198
- }), /*#__PURE__*/_jsx(Stop, {
1199
- offset: "100%",
1200
- stopColor: positiveColor
1201
- })]
1202
- }), /*#__PURE__*/_jsxs(LinearGradient, {
1203
- gradientUnits: "userSpaceOnUse",
1204
- id: gradientId + "-gradient",
1205
- x1: "0%",
1206
- x2: "0%",
1207
- y1: range[0],
1208
- y2: range[1],
1209
- children: [/*#__PURE__*/_jsx(Stop, {
1210
- offset: "0%",
1211
- stopColor: negativeColor,
1212
- stopOpacity: 0.3
1213
- }), /*#__PURE__*/_jsx(Stop, {
1214
- offset: baselinePercentage + "%",
1215
- stopColor: negativeColor,
1216
- stopOpacity: 0
1217
- }), /*#__PURE__*/_jsx(Stop, {
1218
- offset: baselinePercentage + "%",
1219
- stopColor: positiveColor,
1220
- stopOpacity: 0
1221
- }), /*#__PURE__*/_jsx(Stop, {
1222
- offset: "100%",
1223
- stopColor: positiveColor,
1224
- stopOpacity: 0.3
1225
- })]
1226
- })]
1227
- });
1228
- }
1229
- return null;
1230
- };
1231
- const tickLabelFormatter = useCallback(value => new Intl.NumberFormat('en-US', {
1232
- style: 'currency',
1233
- currency: 'USD',
1234
- maximumFractionDigits: 0
1235
- }).format(value), []);
1236
- const solidColor = "url(#" + gradientId + "-solid)";
1274
+ const wickX = x + width / 2;
1275
+ const timePeriodValue = candlestickStockData[dataX];
1276
+ const open = parseFloat(timePeriodValue.open);
1277
+ const close = parseFloat(timePeriodValue.close);
1278
+ const bullish = open < close;
1279
+ const theme = useTheme();
1280
+ const color = bullish ? theme.color.fgPositive : theme.color.fgNegative;
1281
+ const openY = (_yScale = yScale == null ? void 0 : yScale(open)) != null ? _yScale : 0;
1282
+ const closeY = (_yScale2 = yScale == null ? void 0 : yScale(close)) != null ? _yScale2 : 0;
1283
+ const bodyHeight = Math.abs(openY - closeY);
1284
+ const bodyY = openY < closeY ? openY : closeY;
1285
+ return /*#__PURE__*/_jsxs(_Fragment, {
1286
+ children: [/*#__PURE__*/_jsx(SkiaLine, {
1287
+ color: color,
1288
+ p1: {
1289
+ x: wickX,
1290
+ y
1291
+ },
1292
+ p2: {
1293
+ x: wickX,
1294
+ y: y + height
1295
+ },
1296
+ strokeWidth: 1
1297
+ }), /*#__PURE__*/_jsx(Rect, {
1298
+ color: color,
1299
+ height: bodyHeight,
1300
+ width: width,
1301
+ x: x,
1302
+ y: bodyY
1303
+ })]
1304
+ });
1305
+ });
1306
+ const formatThousandsPriceNumber = useCallback(price => {
1307
+ const formattedPrice = new Intl.NumberFormat('en-US', {
1308
+ style: 'currency',
1309
+ currency: 'USD',
1310
+ minimumFractionDigits: 0,
1311
+ maximumFractionDigits: 0
1312
+ }).format(price / 1000);
1313
+ return formattedPrice + "k";
1314
+ }, []);
1315
+ const formatTime = useCallback(index => {
1316
+ if (index === null || index === undefined || index >= candlestickStockData.length) return '';
1317
+ const ts = parseInt(candlestickStockData[index].start);
1318
+ return new Date(ts * 1000).toLocaleDateString('en-US', {
1319
+ month: 'short',
1320
+ day: 'numeric'
1321
+ });
1322
+ }, []);
1237
1323
  return /*#__PURE__*/_jsxs(CartesianChart, {
1238
1324
  enableScrubbing: true,
1239
- height: defaultChartHeight,
1325
+ animate: false,
1326
+ "aria-labelledby": infoTextId,
1327
+ borderRadius: 0,
1328
+ height: 150,
1240
1329
  inset: {
1241
- top: 1.5,
1242
- bottom: 1.5,
1330
+ top: 8,
1331
+ bottom: 8,
1243
1332
  left: 0,
1244
1333
  right: 0
1245
1334
  },
1335
+ onScrubberPositionChange: onScrubberPositionChange,
1246
1336
  series: [{
1247
- id: 'prices',
1248
- data: data,
1249
- color: solidColor
1337
+ id: 'stock-prices',
1338
+ data: candlesData
1250
1339
  }],
1251
- children: [/*#__PURE__*/_jsx(ChartDefs, {}), /*#__PURE__*/_jsx(YAxis, {
1340
+ xAxis: {
1341
+ scaleType: 'band'
1342
+ },
1343
+ yAxis: {
1344
+ domain: {
1345
+ min
1346
+ }
1347
+ },
1348
+ children: [/*#__PURE__*/_jsx(XAxis, {
1349
+ tickLabelFormatter: formatTime
1350
+ }), /*#__PURE__*/_jsx(YAxis, {
1252
1351
  showGrid: true,
1253
- requestedTickCount: 2,
1254
- tickLabelFormatter: tickLabelFormatter
1255
- }), /*#__PURE__*/_jsx(Area, {
1256
- curve: "monotone",
1257
- fill: "url(#" + gradientId + "-gradient)",
1258
- seriesId: "prices"
1259
- }), /*#__PURE__*/_jsx(Line, {
1260
- curve: "monotone",
1261
- seriesId: "prices",
1262
- stroke: solidColor,
1263
- strokeWidth: 3
1352
+ GridLineComponent: ThinSolidLine,
1353
+ tickLabelFormatter: formatThousandsPriceNumber,
1354
+ width: 40
1264
1355
  }), /*#__PURE__*/_jsx(Scrubber, {
1265
- hideOverlay: true
1356
+ hideOverlay: true,
1357
+ LineComponent: BandwidthHighlight,
1358
+ lineStroke: theme.color.fgMuted,
1359
+ seriesIds: []
1360
+ }), /*#__PURE__*/_jsx(BarPlot, {
1361
+ BarComponent: CandlestickBarComponent,
1362
+ BarStackComponent: _ref15 => {
1363
+ let {
1364
+ children
1365
+ } = _ref15;
1366
+ return /*#__PURE__*/_jsx("g", {
1367
+ children: children
1368
+ });
1369
+ }
1370
+ })]
1371
+ });
1372
+ });
1373
+ function Candlesticks() {
1374
+ const infoTextId = useId();
1375
+ const [currentIndex, setCurrentIndex] = useState();
1376
+ return /*#__PURE__*/_jsxs(VStack, {
1377
+ gap: 2,
1378
+ children: [/*#__PURE__*/_jsx(CandlesticksHeader, {
1379
+ currentIndex: currentIndex
1380
+ }), /*#__PURE__*/_jsx(CandlesticksChart, {
1381
+ infoTextId: infoTextId,
1382
+ onScrubberPositionChange: setCurrentIndex
1266
1383
  })]
1267
1384
  });
1268
- };
1269
- const BitcoinChartWithScrubberBeacon = () => {
1385
+ }
1386
+ function MonotoneAssetPrice() {
1270
1387
  const theme = useTheme();
1271
- const prices = [...btcCandles].reverse().map(candle => parseFloat(candle.close));
1272
- const latestPrice = prices[prices.length - 1];
1273
- const formatPrice = price => {
1274
- return new Intl.NumberFormat('en-US', {
1275
- style: 'currency',
1276
- currency: 'USD'
1277
- }).format(price);
1278
- };
1279
- const formatPercentChange = price => {
1280
- return new Intl.NumberFormat('en-US', {
1281
- style: 'percent',
1282
- minimumFractionDigits: 2,
1283
- maximumFractionDigits: 2
1284
- }).format(price);
1285
- };
1286
- const percentChange = (latestPrice - prices[0]) / prices[0];
1287
- return /*#__PURE__*/_jsx(Box, {
1288
- borderRadius: 300,
1289
- overflow: "hidden",
1290
- style: {
1291
- backgroundColor: '#ED702F'
1292
- },
1293
- children: /*#__PURE__*/_jsxs(VStack, {
1294
- style: {
1295
- backgroundColor: 'rgba(0, 0, 0, 0.80)'
1388
+ const prices = sparklineInteractiveData.hour;
1389
+ const fontMgr = useMemo(() => {
1390
+ const fontProvider = Skia.TypefaceFontProvider.Make();
1391
+ // Register system fonts if available, otherwise Skia will use defaults
1392
+ return fontProvider;
1393
+ }, []);
1394
+ const priceFormatter = useMemo(() => new Intl.NumberFormat('en-US', {
1395
+ style: 'currency',
1396
+ currency: 'USD'
1397
+ }), []);
1398
+ const scrubberPriceFormatter = useMemo(() => new Intl.NumberFormat('en-US', {
1399
+ minimumFractionDigits: 2,
1400
+ maximumFractionDigits: 2
1401
+ }), []);
1402
+ const formatPrice = useCallback(price => {
1403
+ return priceFormatter.format(price);
1404
+ }, [priceFormatter]);
1405
+ const formatDate = useCallback(date => {
1406
+ const dayOfWeek = date.toLocaleDateString('en-US', {
1407
+ weekday: 'short'
1408
+ });
1409
+ const monthDay = date.toLocaleDateString('en-US', {
1410
+ month: 'short',
1411
+ day: 'numeric'
1412
+ });
1413
+ const time = date.toLocaleTimeString('en-US', {
1414
+ hour: 'numeric',
1415
+ minute: '2-digit',
1416
+ hour12: true
1417
+ });
1418
+ return dayOfWeek + ", " + monthDay + ", " + time;
1419
+ }, []);
1420
+ const scrubberLabel = useCallback(index => {
1421
+ const price = scrubberPriceFormatter.format(prices[index].value);
1422
+ const date = formatDate(prices[index].date);
1423
+ const regularStyle = {
1424
+ fontFamilies: ['Inter'],
1425
+ fontSize: 14,
1426
+ fontStyle: {
1427
+ weight: FontWeight.Normal
1296
1428
  },
1297
- children: [/*#__PURE__*/_jsxs(HStack, {
1298
- alignItems: "center",
1299
- gap: 2,
1300
- padding: 2,
1301
- paddingBottom: 0,
1302
- children: [/*#__PURE__*/_jsx(RemoteImage, {
1303
- shape: "circle",
1304
- size: "xxl",
1305
- source: assets.btc.imageUrl
1306
- }), /*#__PURE__*/_jsxs(VStack, {
1307
- flexGrow: 1,
1308
- gap: 0.25,
1309
- children: [/*#__PURE__*/_jsx(Text, {
1310
- font: "title1",
1311
- style: {
1312
- color: 'white'
1313
- },
1314
- children: "BTC"
1315
- }), /*#__PURE__*/_jsx(Text, {
1316
- color: "fgMuted",
1317
- font: "label1",
1318
- children: "Bitcoin"
1319
- })]
1320
- }), /*#__PURE__*/_jsxs(VStack, {
1321
- alignItems: "flex-end",
1322
- gap: 0.25,
1323
- children: [/*#__PURE__*/_jsx(Text, {
1324
- font: "title1",
1325
- style: {
1326
- color: 'white'
1327
- },
1328
- children: formatPrice(latestPrice)
1329
- }), /*#__PURE__*/_jsxs(Text, {
1330
- color: "fgPositive",
1331
- font: "label1",
1332
- children: ["+", formatPercentChange(percentChange)]
1333
- })]
1334
- })]
1335
- }), /*#__PURE__*/_jsx(LineChart, {
1336
- showArea: true,
1337
- height: 64,
1338
- inset: {
1339
- left: 0,
1340
- right: 3,
1341
- bottom: 0,
1342
- top: 2
1343
- },
1344
- series: [{
1345
- id: 'btcPrice',
1346
- data: prices,
1347
- color: assets.btc.color
1348
- }],
1349
- width: "100%",
1350
- children: /*#__PURE__*/_jsx(Scrubber, {})
1429
+ color: Skia.Color(theme.color.fgMuted)
1430
+ };
1431
+ const boldStyle = _extends({
1432
+ fontFamilies: ['Inter']
1433
+ }, regularStyle, {
1434
+ fontStyle: {
1435
+ weight: FontWeight.Bold
1436
+ }
1437
+ });
1438
+ const builder = Skia.ParagraphBuilder.Make({
1439
+ textAlign: TextAlign.Left
1440
+ }, fontMgr);
1441
+ builder.pushStyle(boldStyle);
1442
+ builder.addText(price + " USD");
1443
+ builder.pushStyle(regularStyle);
1444
+ builder.addText(" " + date);
1445
+ const para = builder.build();
1446
+ para.layout(512);
1447
+ return para;
1448
+ }, [scrubberPriceFormatter, prices, formatDate, theme.color.fgMuted, fontMgr]);
1449
+ const formatAxisLabelPrice = useCallback(price => {
1450
+ return formatPrice(price);
1451
+ }, [formatPrice]);
1452
+
1453
+ // Custom tick label component with offset positioning
1454
+ const CustomYAxisTickLabel = useCallback(props => /*#__PURE__*/_jsx(DefaultAxisTickLabel, _extends({}, props, {
1455
+ dx: 4,
1456
+ dy: -12,
1457
+ horizontalAlignment: "left"
1458
+ })), []);
1459
+ const CustomScrubberBeacon = /*#__PURE__*/memo(_ref16 => {
1460
+ let {
1461
+ dataX,
1462
+ dataY,
1463
+ seriesId,
1464
+ isIdle,
1465
+ animate = true
1466
+ } = _ref16;
1467
+ const {
1468
+ getSeries,
1469
+ getXSerializableScale,
1470
+ getYSerializableScale
1471
+ } = useCartesianChartContext();
1472
+ const targetSeries = useMemo(() => getSeries(seriesId), [getSeries, seriesId]);
1473
+ const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
1474
+ const yScale = useMemo(() => getYSerializableScale(targetSeries == null ? void 0 : targetSeries.yAxisId), [getYSerializableScale, targetSeries == null ? void 0 : targetSeries.yAxisId]);
1475
+ const animatedX = useSharedValue(0);
1476
+ const animatedY = useSharedValue(0);
1477
+
1478
+ // Calculate the target point position - project data to pixels
1479
+ const targetPoint = useDerivedValue(() => {
1480
+ if (!xScale || !yScale) return {
1481
+ x: 0,
1482
+ y: 0
1483
+ };
1484
+ return projectPointWithSerializableScale({
1485
+ x: unwrapAnimatedValue(dataX),
1486
+ y: unwrapAnimatedValue(dataY),
1487
+ xScale,
1488
+ yScale
1489
+ });
1490
+ }, [dataX, dataY, xScale, yScale]);
1491
+ useAnimatedReaction(() => {
1492
+ return {
1493
+ point: targetPoint.value,
1494
+ isIdle: unwrapAnimatedValue(isIdle)
1495
+ };
1496
+ }, (current, previous) => {
1497
+ // When animation is disabled, on initial render, or when we are starting,
1498
+ // continuing, or finishing scrubbing we should immediately transition
1499
+ if (!animate || previous === null || !previous.isIdle || !current.isIdle) {
1500
+ animatedX.value = current.point.x;
1501
+ animatedY.value = current.point.y;
1502
+ return;
1503
+ }
1504
+ animatedX.value = buildTransition(current.point.x, defaultTransition);
1505
+ animatedY.value = buildTransition(current.point.y, defaultTransition);
1506
+ }, [animate]);
1507
+
1508
+ // Create animated point using the animated values
1509
+ const animatedPoint = useDerivedValue(() => {
1510
+ return {
1511
+ x: animatedX.value,
1512
+ y: animatedY.value
1513
+ };
1514
+ }, [animatedX, animatedY]);
1515
+ return /*#__PURE__*/_jsxs(_Fragment, {
1516
+ children: [/*#__PURE__*/_jsx(Circle, {
1517
+ c: animatedPoint,
1518
+ color: theme.color.bg,
1519
+ r: 5
1520
+ }), /*#__PURE__*/_jsx(Circle, {
1521
+ c: animatedPoint,
1522
+ color: theme.color.fg,
1523
+ r: 5,
1524
+ strokeWidth: 3,
1525
+ style: "stroke"
1351
1526
  })]
1527
+ });
1528
+ });
1529
+ return /*#__PURE__*/_jsx(LineChart, {
1530
+ enableScrubbing: true,
1531
+ showYAxis: true,
1532
+ height: 200,
1533
+ inset: {
1534
+ top: 64
1535
+ },
1536
+ series: [{
1537
+ id: 'btc',
1538
+ data: prices.map(price => price.value),
1539
+ color: theme.color.fg,
1540
+ gradient: {
1541
+ axis: 'x',
1542
+ stops: _ref17 => {
1543
+ let {
1544
+ min
1545
+ } = _ref17;
1546
+ return [{
1547
+ offset: min,
1548
+ color: theme.color.fg,
1549
+ opacity: 0
1550
+ }, {
1551
+ offset: 32,
1552
+ color: theme.color.fg,
1553
+ opacity: 1
1554
+ }];
1555
+ }
1556
+ }
1557
+ }],
1558
+ xAxis: {
1559
+ range: _ref18 => {
1560
+ let {
1561
+ max
1562
+ } = _ref18;
1563
+ return {
1564
+ min: 96,
1565
+ max
1566
+ };
1567
+ }
1568
+ },
1569
+ yAxis: {
1570
+ position: 'left',
1571
+ width: 0,
1572
+ showGrid: true,
1573
+ tickLabelFormatter: formatAxisLabelPrice,
1574
+ TickLabelComponent: CustomYAxisTickLabel
1575
+ },
1576
+ children: /*#__PURE__*/_jsx(Scrubber, {
1577
+ hideOverlay: true,
1578
+ labelElevated: true,
1579
+ BeaconComponent: CustomScrubberBeacon,
1580
+ LineComponent: SolidLine,
1581
+ label: scrubberLabel
1352
1582
  })
1353
1583
  });
1354
- };
1355
- const ScrubberWithImperativeHandle = () => {
1584
+ }
1585
+ function ServiceAvailability() {
1586
+ const theme = useTheme();
1587
+ const availabilityEvents = useMemo(() => [{
1588
+ date: new Date('2022-01-01'),
1589
+ availability: 79
1590
+ }, {
1591
+ date: new Date('2022-01-03'),
1592
+ availability: 81
1593
+ }, {
1594
+ date: new Date('2022-01-04'),
1595
+ availability: 82
1596
+ }, {
1597
+ date: new Date('2022-01-06'),
1598
+ availability: 91
1599
+ }, {
1600
+ date: new Date('2022-01-07'),
1601
+ availability: 92
1602
+ }, {
1603
+ date: new Date('2022-01-10'),
1604
+ availability: 86
1605
+ }], []);
1606
+ return /*#__PURE__*/_jsxs(CartesianChart, {
1607
+ enableScrubbing: true,
1608
+ height: 200,
1609
+ series: [{
1610
+ id: 'availability',
1611
+ data: availabilityEvents.map(event => event.availability),
1612
+ gradient: {
1613
+ stops: _ref19 => {
1614
+ let {
1615
+ min,
1616
+ max
1617
+ } = _ref19;
1618
+ return [{
1619
+ offset: min,
1620
+ color: theme.color.fgNegative
1621
+ }, {
1622
+ offset: 85,
1623
+ color: theme.color.fgNegative
1624
+ }, {
1625
+ offset: 85,
1626
+ color: theme.color.fgWarning
1627
+ }, {
1628
+ offset: 90,
1629
+ color: theme.color.fgWarning
1630
+ }, {
1631
+ offset: 90,
1632
+ color: theme.color.fgPositive
1633
+ }, {
1634
+ offset: max,
1635
+ color: theme.color.fgPositive
1636
+ }];
1637
+ }
1638
+ }
1639
+ }],
1640
+ xAxis: {
1641
+ data: availabilityEvents.map(event => event.date.getTime())
1642
+ },
1643
+ yAxis: {
1644
+ domain: _ref20 => {
1645
+ let {
1646
+ min,
1647
+ max
1648
+ } = _ref20;
1649
+ return {
1650
+ min: Math.max(min - 2, 0),
1651
+ max: Math.min(max + 2, 100)
1652
+ };
1653
+ }
1654
+ },
1655
+ children: [/*#__PURE__*/_jsx(XAxis, {
1656
+ showGrid: true,
1657
+ showLine: true,
1658
+ showTickMarks: true,
1659
+ tickLabelFormatter: value => new Date(value).toLocaleDateString()
1660
+ }), /*#__PURE__*/_jsx(YAxis, {
1661
+ showGrid: true,
1662
+ showLine: true,
1663
+ showTickMarks: true,
1664
+ position: "left",
1665
+ tickLabelFormatter: value => value + "%"
1666
+ }), /*#__PURE__*/_jsx(Line, {
1667
+ curve: "stepAfter",
1668
+ points: props => _extends({}, props, {
1669
+ fill: theme.color.bg,
1670
+ stroke: props.fill
1671
+ }),
1672
+ seriesId: "availability"
1673
+ }), /*#__PURE__*/_jsx(Scrubber, {
1674
+ hideOverlay: true
1675
+ })]
1676
+ });
1677
+ }
1678
+ function ForecastAssetPrice() {
1679
+ const startYear = 2020;
1680
+ const data = [50, 45, 47, 46, 54, 54, 60, 61, 63, 66, 70];
1681
+ const currentIndex = 6;
1682
+ const strokeWidth = 3;
1683
+ // To prevent cutting off the edge of our lines
1684
+ const clipOffset = strokeWidth;
1685
+ const axisFormatter = useCallback(dataIndex => {
1686
+ return "" + (startYear + dataIndex);
1687
+ }, [startYear]);
1688
+ const HistoricalLineComponent = /*#__PURE__*/memo(props => {
1689
+ const {
1690
+ drawingArea,
1691
+ getXScale
1692
+ } = useCartesianChartContext();
1693
+ const xScale = getXScale();
1694
+ const historicalClipPath = useMemo(() => {
1695
+ if (!xScale || !drawingArea) return null;
1696
+ const currentX = xScale(currentIndex);
1697
+ if (currentX === undefined) return null;
1698
+
1699
+ // Create clip path for historical data (left side)
1700
+ const clip = Skia.Path.Make();
1701
+ clip.addRect({
1702
+ x: drawingArea.x - clipOffset,
1703
+ y: drawingArea.y - clipOffset,
1704
+ width: currentX + clipOffset - drawingArea.x,
1705
+ height: drawingArea.height + clipOffset * 2
1706
+ });
1707
+ return clip;
1708
+ }, [xScale, drawingArea]);
1709
+ if (!historicalClipPath) return null;
1710
+ return /*#__PURE__*/_jsx(Group, {
1711
+ clip: historicalClipPath,
1712
+ children: /*#__PURE__*/_jsx(SolidLine, _extends({
1713
+ strokeWidth: strokeWidth
1714
+ }, props))
1715
+ });
1716
+ });
1717
+
1718
+ // Since the solid and dotted line have different curves,
1719
+ // we need two separate line components. Otherwise we could
1720
+ // have one line component with SolidLine and DottedLine inside
1721
+ // of it and two clipPaths.
1722
+ const ForecastLineComponent = /*#__PURE__*/memo(props => {
1723
+ const {
1724
+ drawingArea,
1725
+ getXScale
1726
+ } = useCartesianChartContext();
1727
+ const xScale = getXScale();
1728
+ const forecastClipPath = useMemo(() => {
1729
+ if (!xScale || !drawingArea) return null;
1730
+ const currentX = xScale(currentIndex);
1731
+ if (currentX === undefined) return null;
1732
+
1733
+ // Create clip path for forecast data (right side)
1734
+ const clip = Skia.Path.Make();
1735
+ clip.addRect({
1736
+ x: currentX,
1737
+ y: drawingArea.y - clipOffset,
1738
+ width: drawingArea.x + drawingArea.width - currentX + clipOffset * 2,
1739
+ height: drawingArea.height + clipOffset * 2
1740
+ });
1741
+ return clip;
1742
+ }, [xScale, drawingArea]);
1743
+ if (!forecastClipPath) return null;
1744
+ return /*#__PURE__*/_jsx(Group, {
1745
+ clip: forecastClipPath,
1746
+ children: /*#__PURE__*/_jsx(DottedLine, _extends({
1747
+ dashIntervals: [0, strokeWidth * 2],
1748
+ strokeWidth: strokeWidth
1749
+ }, props))
1750
+ });
1751
+ });
1752
+ const CustomScrubber = /*#__PURE__*/memo(() => {
1753
+ const {
1754
+ scrubberPosition
1755
+ } = useScrubberContext();
1756
+ const idleScrubberOpacity = useDerivedValue(() => scrubberPosition.value === undefined ? 1 : 0, [scrubberPosition]);
1757
+ const scrubberOpacity = useDerivedValue(() => scrubberPosition.value !== undefined ? 1 : 0, [scrubberPosition]);
1758
+
1759
+ // Fade in animation for the Scrubber
1760
+ const fadeInOpacity = useSharedValue(0);
1761
+ useEffect(() => {
1762
+ fadeInOpacity.value = withDelay(350, withTiming(1, {
1763
+ duration: 150
1764
+ }));
1765
+ }, [fadeInOpacity]);
1766
+ return /*#__PURE__*/_jsxs(Group, {
1767
+ opacity: fadeInOpacity,
1768
+ children: [/*#__PURE__*/_jsx(Group, {
1769
+ opacity: scrubberOpacity,
1770
+ children: /*#__PURE__*/_jsx(Scrubber, {
1771
+ hideOverlay: true
1772
+ })
1773
+ }), /*#__PURE__*/_jsx(Group, {
1774
+ opacity: idleScrubberOpacity,
1775
+ children: /*#__PURE__*/_jsx(DefaultScrubberBeacon, {
1776
+ isIdle: true,
1777
+ dataX: currentIndex,
1778
+ dataY: data[currentIndex],
1779
+ seriesId: "price"
1780
+ })
1781
+ })]
1782
+ });
1783
+ });
1784
+ return /*#__PURE__*/_jsxs(CartesianChart, {
1785
+ enableScrubbing: true,
1786
+ height: 200,
1787
+ series: [{
1788
+ id: 'price',
1789
+ data,
1790
+ color: assets.btc.color
1791
+ }],
1792
+ children: [/*#__PURE__*/_jsx(Line, {
1793
+ LineComponent: HistoricalLineComponent,
1794
+ curve: "linear",
1795
+ seriesId: "price"
1796
+ }), /*#__PURE__*/_jsx(Line, {
1797
+ LineComponent: ForecastLineComponent,
1798
+ curve: "monotone",
1799
+ seriesId: "price",
1800
+ type: "dotted"
1801
+ }), /*#__PURE__*/_jsx(XAxis, {
1802
+ position: "bottom",
1803
+ requestedTickCount: 3,
1804
+ tickLabelFormatter: axisFormatter
1805
+ }), /*#__PURE__*/_jsx(CustomScrubber, {})]
1806
+ });
1807
+ }
1808
+ function ImperativeHandle() {
1356
1809
  const theme = useTheme();
1357
1810
  const scrubberRef = useRef(null);
1358
1811
  return /*#__PURE__*/_jsxs(VStack, {
@@ -1360,26 +1813,41 @@ const ScrubberWithImperativeHandle = () => {
1360
1813
  children: [/*#__PURE__*/_jsx(LineChart, {
1361
1814
  enableScrubbing: true,
1362
1815
  showYAxis: true,
1363
- height: defaultChartHeight,
1816
+ height: 250,
1364
1817
  series: [{
1365
1818
  id: 'priceA',
1366
1819
  data: [2400, 1398, 9800, 3908, 4800, 3800, 4300],
1367
- label: 'Page Views',
1368
- color: theme.color.accentBoldBlue,
1369
- curve: 'natural'
1820
+ label: 'Price A',
1821
+ color: theme.color.accentBoldBlue
1370
1822
  }, {
1371
1823
  id: 'priceB',
1372
1824
  data: [2000, 2491, 4501, 6049, 5019, 4930, 5910],
1373
- label: 'Unique Visitors G',
1374
- color: theme.color.accentBoldGreen,
1375
- curve: 'natural'
1825
+ label: 'Price B',
1826
+ color: theme.color.accentBoldGreen
1376
1827
  }, {
1377
1828
  id: 'priceC',
1378
1829
  data: [1000, 4910, 2300, 5910, 3940, 2940, 1940],
1379
- label: 'Unique Visitors P',
1380
- color: theme.color.accentBoldPurple,
1381
- curve: 'natural'
1830
+ label: 'Price C',
1831
+ color: theme.color.accentBoldPurple
1832
+ }, {
1833
+ id: 'priceD',
1834
+ data: [4810, 2030, 5810, 3940, 2940, 1940, 940],
1835
+ label: 'Price D',
1836
+ color: theme.color.accentBoldYellow
1382
1837
  }],
1838
+ xAxis: {
1839
+ // Give space for pulse animation
1840
+ range: _ref21 => {
1841
+ let {
1842
+ min,
1843
+ max
1844
+ } = _ref21;
1845
+ return {
1846
+ min,
1847
+ max: max - 8
1848
+ };
1849
+ }
1850
+ },
1383
1851
  yAxis: {
1384
1852
  domain: {
1385
1853
  min: 0
@@ -1392,857 +1860,512 @@ const ScrubberWithImperativeHandle = () => {
1392
1860
  })
1393
1861
  }), /*#__PURE__*/_jsx(Button, {
1394
1862
  onPress: () => {
1395
- var _scrubberRef$current;
1396
- return (_scrubberRef$current = scrubberRef.current) == null ? void 0 : _scrubberRef$current.pulse();
1863
+ var _scrubberRef$current2;
1864
+ return (_scrubberRef$current2 = scrubberRef.current) == null ? void 0 : _scrubberRef$current2.pulse();
1397
1865
  },
1398
- children: "Pulse Beacons"
1866
+ children: "Pulse"
1399
1867
  })]
1400
1868
  });
1401
- };
1402
- const BTCPriceChart = () => {
1403
- const tabs = [{
1404
- id: 'hour',
1405
- label: '1H'
1406
- }, {
1407
- id: 'day',
1408
- label: '1D'
1409
- }, {
1410
- id: 'week',
1411
- label: '1W'
1412
- }, {
1413
- id: 'month',
1414
- label: '1M'
1415
- }, {
1416
- id: 'year',
1417
- label: '1Y'
1418
- }, {
1419
- id: 'all',
1420
- label: 'All'
1421
- }];
1422
- const [activeTab, setActiveTab] = useState(tabs[0]);
1423
- const [highlightedItem, setHighlightedItem] = useState();
1424
- const currentPriceData = activeTab ? sparklineInteractiveData[activeTab.id] : sparklineInteractiveData.hour;
1425
- const currentData = useMemo(() => [...currentPriceData.map(price => price.value)], [currentPriceData]);
1426
- const currentTimestamps = useMemo(() => [...currentPriceData.map(price => price.date.toISOString())], [currentPriceData]);
1427
- const currentPrice = currentData[currentData.length - 1];
1428
- const startPrice = currentData[0];
1429
- const onScrubberPositionChange = useCallback(item => {
1430
- setHighlightedItem(item);
1431
- }, []);
1432
- const displayPrice = highlightedItem !== null && highlightedItem !== undefined ? currentData[highlightedItem] : currentPrice;
1433
- const btcAccentColor = '#F0A73C';
1434
- const {
1435
- displayDate
1436
- } = useMemo(() => {
1437
- return calculateTrendData(highlightedItem, currentData, currentTimestamps, startPrice, currentPrice, (activeTab == null ? void 0 : activeTab.id) || 'hour');
1438
- }, [highlightedItem, currentData, currentTimestamps, startPrice, currentPrice, activeTab]);
1439
- const formattedPrice = "$" + displayPrice.toLocaleString('en-US', {
1440
- minimumFractionDigits: 2,
1441
- maximumFractionDigits: 2
1869
+ }
1870
+ function CustomBeaconLabel() {
1871
+ const theme = useTheme();
1872
+ // This custom component label shows the percentage value of the data at the scrubber position.
1873
+ const MyScrubberBeaconLabel = /*#__PURE__*/memo(_ref22 => {
1874
+ let {
1875
+ seriesId,
1876
+ color,
1877
+ label
1878
+ } = _ref22,
1879
+ props = _objectWithoutPropertiesLoose(_ref22, _excluded5);
1880
+ const {
1881
+ getSeriesData,
1882
+ series
1883
+ } = useCartesianChartContext();
1884
+ const {
1885
+ scrubberPosition
1886
+ } = useScrubberContext();
1887
+ const seriesData = useMemo(() => getLineData(getSeriesData(seriesId)), [getSeriesData, seriesId]);
1888
+ const dataLength = useMemo(() => {
1889
+ var _series$reduce;
1890
+ return (_series$reduce = series == null ? void 0 : series.reduce((max, s) => {
1891
+ var _seriesData$length;
1892
+ const seriesData = getSeriesData(s.id);
1893
+ return Math.max(max, (_seriesData$length = seriesData == null ? void 0 : seriesData.length) != null ? _seriesData$length : 0);
1894
+ }, 0)) != null ? _series$reduce : 0;
1895
+ }, [series, getSeriesData]);
1896
+ const dataIndex = useDerivedValue(() => {
1897
+ var _scrubberPosition$val;
1898
+ return (_scrubberPosition$val = scrubberPosition.value) != null ? _scrubberPosition$val : Math.max(0, dataLength - 1);
1899
+ }, [scrubberPosition, dataLength]);
1900
+ const percentageLabel = useDerivedValue(() => {
1901
+ if (seriesData !== undefined) {
1902
+ const dataAtPosition = seriesData[dataIndex.value];
1903
+ return unwrapAnimatedValue(label) + " \xB7 " + dataAtPosition + "%";
1904
+ }
1905
+ return unwrapAnimatedValue(label);
1906
+ }, [label, seriesData, dataIndex]);
1907
+ return /*#__PURE__*/_jsx(DefaultScrubberBeaconLabel, _extends({}, props, {
1908
+ background: color,
1909
+ color: theme.color.bg,
1910
+ label: percentageLabel,
1911
+ seriesId: seriesId
1912
+ }));
1442
1913
  });
1443
- const AreaComponent = useMemo(() => props => /*#__PURE__*/_jsx(GradientArea, _extends({}, props, {
1444
- peakOpacity: 0.15
1445
- })), []);
1446
- return /*#__PURE__*/_jsx(Box, {
1447
- borderRadius: 300,
1448
- overflow: "hidden",
1449
- style: {
1450
- backgroundColor: btcAccentColor
1914
+ return /*#__PURE__*/_jsx(LineChart, {
1915
+ enableScrubbing: true,
1916
+ showArea: true,
1917
+ showYAxis: true,
1918
+ areaType: "dotted",
1919
+ height: 200,
1920
+ series: [{
1921
+ id: 'Boston',
1922
+ data: [25, 30, 35, 45, 60, 100],
1923
+ color: "rgb(" + theme.spectrum.green40 + ")",
1924
+ label: 'Boston'
1925
+ }, {
1926
+ id: 'Miami',
1927
+ data: [20, 25, 30, 35, 20, 0],
1928
+ color: "rgb(" + theme.spectrum.blue40 + ")",
1929
+ label: 'Miami'
1930
+ }, {
1931
+ id: 'Denver',
1932
+ data: [10, 15, 20, 25, 40, 0],
1933
+ color: "rgb(" + theme.spectrum.orange40 + ")",
1934
+ label: 'Denver'
1935
+ }, {
1936
+ id: 'Phoenix',
1937
+ data: [15, 10, 5, 0, 0, 0],
1938
+ color: "rgb(" + theme.spectrum.red40 + ")",
1939
+ label: 'Phoenix'
1940
+ }],
1941
+ yAxis: {
1942
+ showGrid: true
1451
1943
  },
1452
- width: "100%",
1453
- children: /*#__PURE__*/_jsxs(VStack, {
1454
- gap: 3,
1455
- width: "100%",
1456
- children: [/*#__PURE__*/_jsxs(HStack, {
1457
- alignItems: "flex-start",
1458
- gap: 3,
1459
- justifyContent: "space-between",
1460
- padding: 4,
1461
- children: [/*#__PURE__*/_jsxs(VStack, {
1462
- flexGrow: 1,
1463
- gap: 1,
1464
- children: [/*#__PURE__*/_jsx(Text, {
1465
- font: "title1",
1466
- children: "Coinbase Wrapped BTC"
1467
- }), /*#__PURE__*/_jsx(Text, {
1468
- font: "title2",
1469
- children: formattedPrice
1470
- })]
1471
- }), /*#__PURE__*/_jsx(VStack, {
1472
- justifyContent: "center",
1473
- children: /*#__PURE__*/_jsx(RemoteImage, {
1474
- shape: "circle",
1475
- size: "xxl",
1476
- source: assets.btc.imageUrl
1477
- })
1478
- })]
1479
- }), /*#__PURE__*/_jsxs(CartesianChart, {
1480
- enableScrubbing: true,
1481
- height: 200,
1482
- inset: {
1483
- bottom: 0,
1484
- right: 3,
1485
- left: 0,
1486
- top: 6
1487
- },
1488
- onScrubberPositionChange: onScrubberPositionChange,
1489
- series: [{
1490
- id: 'price',
1491
- data: currentData,
1492
- color: 'black'
1493
- }],
1494
- width: "100%",
1495
- children: [/*#__PURE__*/_jsx(Line, {
1496
- showArea: true,
1497
- AreaComponent: AreaComponent,
1498
- seriesId: "price",
1499
- strokeWidth: 3
1500
- }), /*#__PURE__*/_jsx(Scrubber, {
1501
- idlePulse: true,
1502
- label: displayDate,
1503
- labelProps: {
1504
- color: 'black'
1505
- },
1506
- lineStroke: "black"
1507
- })]
1508
- }), /*#__PURE__*/_jsx(Box, {
1509
- padding: 2,
1510
- children: /*#__PURE__*/_jsx(PeriodSelector, {
1511
- activeTab: activeTab,
1512
- onChange: tab => setActiveTab(tab),
1513
- tabs: tabs
1514
- })
1515
- })]
1944
+ children: /*#__PURE__*/_jsx(Scrubber, {
1945
+ BeaconLabelComponent: MyScrubberBeaconLabel
1516
1946
  })
1517
1947
  });
1518
- };
1519
- const LiveAssetPrice = () => {
1520
- const scrubberRef = useRef(null);
1521
- const initialData = useMemo(() => {
1522
- return sparklineInteractiveData.hour.map(d => d.value);
1523
- }, []);
1524
- const [priceData, setPriceData] = useState(initialData);
1525
- const lastDataPointTimeRef = useRef(Date.now());
1526
- const updateCountRef = useRef(0);
1527
- const intervalSeconds = 3600 / initialData.length;
1528
- const maxPercentChange = Math.abs(initialData[initialData.length - 1] - initialData[0]) * 0.05;
1529
- useEffect(() => {
1530
- const priceUpdateInterval = setInterval(() => {
1531
- var _scrubberRef$current2;
1532
- setPriceData(currentData => {
1533
- const newData = [...currentData];
1534
- const lastPrice = newData[newData.length - 1];
1535
- const priceChange = (Math.random() - 0.5) * maxPercentChange;
1536
- const newPrice = Math.round((lastPrice + priceChange) * 100) / 100;
1537
-
1538
- // Check if we should roll over to a new data point
1539
- const currentTime = Date.now();
1540
- const timeSinceLastPoint = (currentTime - lastDataPointTimeRef.current) / 1000;
1541
- if (timeSinceLastPoint >= intervalSeconds) {
1542
- // Time for a new data point - remove first, add new at end
1543
- lastDataPointTimeRef.current = currentTime;
1544
- newData.shift(); // Remove oldest data point
1545
- newData.push(newPrice); // Add new data point
1546
- updateCountRef.current = 0;
1547
- } else {
1548
- // Just update the last data point
1549
- newData[newData.length - 1] = newPrice;
1550
- updateCountRef.current++;
1551
- }
1552
- return newData;
1553
- });
1554
-
1555
- // Pulse the scrubber on each update
1556
- (_scrubberRef$current2 = scrubberRef.current) == null || _scrubberRef$current2.pulse();
1557
- }, 2000 + Math.random() * 1000);
1558
- return () => clearInterval(priceUpdateInterval);
1559
- }, [intervalSeconds, maxPercentChange]);
1948
+ }
1949
+ function CustomLabelComponent() {
1950
+ const CustomLabelComponent = /*#__PURE__*/memo(props => {
1951
+ const theme = useTheme();
1952
+ const {
1953
+ drawingArea
1954
+ } = useCartesianChartContext();
1955
+ if (!drawingArea) return;
1956
+ return /*#__PURE__*/_jsx(DefaultScrubberLabel, _extends({}, props, {
1957
+ elevated: true,
1958
+ background: theme.color.bgPrimary,
1959
+ color: theme.color.bgPrimaryWash,
1960
+ dy: 32,
1961
+ fontWeight: FontWeight.Bold,
1962
+ y: drawingArea.y + drawingArea.height
1963
+ }));
1964
+ });
1560
1965
  return /*#__PURE__*/_jsx(LineChart, {
1561
1966
  enableScrubbing: true,
1562
1967
  showArea: true,
1563
- height: defaultChartHeight,
1968
+ height: 200,
1969
+ inset: {
1970
+ top: 16,
1971
+ bottom: 64
1972
+ },
1564
1973
  series: [{
1565
- id: 'btc',
1566
- data: priceData,
1567
- color: assets.btc.color
1974
+ id: 'prices',
1975
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
1568
1976
  }],
1569
1977
  children: /*#__PURE__*/_jsx(Scrubber, {
1570
- ref: scrubberRef
1978
+ LabelComponent: CustomLabelComponent,
1979
+ label: dataIndex => "Day " + (dataIndex + 1)
1571
1980
  })
1572
1981
  });
1573
- };
1574
- const availabilityEvents = [{
1575
- date: new Date('2022-01-01'),
1576
- availability: 79
1577
- }, {
1578
- date: new Date('2022-01-03'),
1579
- availability: 81
1580
- }, {
1581
- date: new Date('2022-01-04'),
1582
- availability: 82
1583
- }, {
1584
- date: new Date('2022-01-06'),
1585
- availability: 91
1586
- }, {
1587
- date: new Date('2022-01-07'),
1588
- availability: 92
1589
- }, {
1590
- date: new Date('2022-01-10'),
1591
- availability: 86
1592
- }];
1593
-
1594
- // Generate prediction probability data for three candidates
1595
- const generatePredictionData = () => {
1596
- const now = new Date();
1597
- const baseTimestamp = now.getTime();
1598
-
1599
- // Generate data for different time periods
1600
- const generateForPeriod = (points, intervalMs) => {
1601
- return Array.from({
1602
- length: points
1603
- }, (_, i) => {
1604
- const timestamp = new Date(baseTimestamp - (points - 1 - i) * intervalMs);
1605
-
1606
- // Generate percentage values for three candidates that roughly sum to 100%
1607
- const candidate1 = 45 + Math.random() * 30; // 45-75%
1608
- const candidate2 = 15 + Math.random() * 20; // 15-35%
1609
- const candidate3 = Math.max(0, 100 - candidate1 - candidate2 - Math.random() * 5); // remainder
1982
+ }
1983
+ function HiddenScrubberWhenIdle() {
1984
+ const MyScrubberBeacon = /*#__PURE__*/memo(props => {
1985
+ const {
1986
+ scrubberPosition
1987
+ } = useScrubberContext();
1988
+ const beaconOpacity = useDerivedValue(() => scrubberPosition.value !== undefined ? 1 : 0, [scrubberPosition]);
1989
+ return /*#__PURE__*/_jsx(DefaultScrubberBeacon, _extends({}, props, {
1990
+ opacity: beaconOpacity
1991
+ }));
1992
+ });
1993
+ const MyScrubberBeaconLabel = /*#__PURE__*/memo(props => {
1994
+ const {
1995
+ scrubberPosition
1996
+ } = useScrubberContext();
1997
+ const labelOpacity = useDerivedValue(() => scrubberPosition.value !== undefined ? 1 : 0, [scrubberPosition]);
1998
+ return /*#__PURE__*/_jsx(DefaultScrubberBeaconLabel, _extends({}, props, {
1999
+ opacity: labelOpacity
2000
+ }));
2001
+ });
2002
+ return /*#__PURE__*/_jsx(LineChart, {
2003
+ enableScrubbing: true,
2004
+ showArea: true,
2005
+ height: 150,
2006
+ series: [{
2007
+ id: 'prices',
2008
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
2009
+ label: 'hello'
2010
+ }],
2011
+ children: /*#__PURE__*/_jsx(Scrubber, {
2012
+ BeaconComponent: MyScrubberBeacon,
2013
+ BeaconLabelComponent: MyScrubberBeaconLabel
2014
+ })
2015
+ });
2016
+ }
2017
+ function TwoLineScrubberLabel() {
2018
+ const theme = useTheme();
2019
+ const data = useMemo(() => [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58], []);
2020
+ const [alignment, setAlignment] = useState(TextAlign.Center);
2021
+ const fontMgr = useMemo(() => {
2022
+ const fontProvider = Skia.TypefaceFontProvider.Make();
2023
+ return fontProvider;
2024
+ }, []);
2025
+ const formatPrice = useCallback(price => {
2026
+ return new Intl.NumberFormat('en-US', {
2027
+ style: 'currency',
2028
+ currency: 'USD',
2029
+ minimumFractionDigits: 2,
2030
+ maximumFractionDigits: 2
2031
+ }).format(price);
2032
+ }, []);
2033
+ const scrubberLabel = useCallback(index => {
2034
+ const price = formatPrice(data[index] * 100);
2035
+ const day = "Day " + (index + 100);
2036
+ const priceStyle = {
2037
+ fontFamilies: ['Inter'],
2038
+ fontSize: 16,
2039
+ fontStyle: {
2040
+ weight: FontWeight.Bold
2041
+ },
2042
+ color: Skia.Color(theme.color.fg)
2043
+ };
2044
+ const dayStyle = {
2045
+ fontFamilies: ['Inter'],
2046
+ fontSize: 14,
2047
+ fontStyle: {
2048
+ weight: FontWeight.Normal
2049
+ },
2050
+ color: Skia.Color(theme.color.fgMuted)
2051
+ };
2052
+ const builder = Skia.ParagraphBuilder.Make({
2053
+ textAlign: alignment
2054
+ }, fontMgr);
2055
+ builder.pushStyle(priceStyle);
2056
+ builder.addText(price);
2057
+ builder.addText('\n');
2058
+ builder.pushStyle(dayStyle);
2059
+ builder.addText(day);
2060
+ const para = builder.build();
2061
+ // First layout with large width to get intrinsic size
2062
+ para.layout(384);
2063
+ return para;
2064
+ }, [data, formatPrice, theme.color.fg, theme.color.fgMuted, fontMgr, alignment]);
1610
2065
 
1611
- return {
1612
- date: timestamp,
1613
- candidate1: Math.round(candidate1 * 10) / 10,
1614
- candidate2: Math.round(candidate2 * 10) / 10,
1615
- candidate3: Math.round(candidate3 * 10) / 10
1616
- };
1617
- });
1618
- };
1619
- return {
1620
- hour: generateForPeriod(60, 60 * 1000),
1621
- // 60 points, 1 min apart
1622
- day: generateForPeriod(96, 15 * 60 * 1000),
1623
- // 96 points, 15 min apart
1624
- week: generateForPeriod(84, 2 * 60 * 60 * 1000),
1625
- // 84 points, 2 hours apart
1626
- month: generateForPeriod(120, 6 * 60 * 60 * 1000),
1627
- // 120 points, 6 hours apart
1628
- year: generateForPeriod(365, 24 * 60 * 60 * 1000),
1629
- // 365 points, 1 day apart
1630
- all: generateForPeriod(100, 7 * 24 * 60 * 60 * 1000) // 100 points, 1 week apart
1631
- };
1632
- };
1633
- const predictionData = generatePredictionData();
1634
- const PredictionLegend = /*#__PURE__*/memo(/*#__PURE__*/forwardRef((_ref6, ref) => {
1635
- let {
1636
- data,
1637
- colors
1638
- } = _ref6;
1639
- const [selectedIndex, setSelectedIndex] = useState(data.candidate1.length - 1);
1640
- useImperativeHandle(ref, () => ({
1641
- updateSelectedIndex: index => {
1642
- setSelectedIndex(index);
1643
- }
1644
- }));
1645
- const candidate1Value = Math.round(data.candidate1[selectedIndex]);
1646
- const candidate2Value = Math.round(data.candidate2[selectedIndex]);
1647
- const candidate3Value = Math.round(data.candidate3[selectedIndex]);
2066
+ // Custom scrubber label component that uses the selected alignment
2067
+ const AlignedScrubberLabel = /*#__PURE__*/memo(props => /*#__PURE__*/_jsx(DefaultScrubberLabel, _extends({}, props, {
2068
+ paragraphAlignment: alignment
2069
+ })));
1648
2070
  return /*#__PURE__*/_jsxs(VStack, {
1649
2071
  gap: 2,
1650
2072
  children: [/*#__PURE__*/_jsxs(HStack, {
1651
- alignItems: "center",
1652
- gap: 2,
1653
- children: [/*#__PURE__*/_jsx(Box, {
1654
- style: {
1655
- width: 12,
1656
- height: 12,
1657
- borderRadius: 6,
1658
- backgroundColor: colors.pink
1659
- }
1660
- }), /*#__PURE__*/_jsx(Text, {
1661
- font: "label2",
1662
- children: "Noah Wyle"
1663
- }), /*#__PURE__*/_jsxs(Text, {
1664
- font: "label1",
1665
- children: [candidate1Value, "%"]
1666
- })]
1667
- }), /*#__PURE__*/_jsxs(HStack, {
1668
- alignItems: "center",
1669
- gap: 2,
1670
- children: [/*#__PURE__*/_jsx(Box, {
1671
- style: {
1672
- width: 12,
1673
- height: 12,
1674
- borderRadius: 6,
1675
- backgroundColor: colors.teal
1676
- }
1677
- }), /*#__PURE__*/_jsx(Text, {
1678
- font: "label2",
1679
- children: "Adam Scott"
1680
- }), /*#__PURE__*/_jsxs(Text, {
1681
- font: "label1",
1682
- children: [candidate2Value, "%"]
1683
- })]
1684
- }), /*#__PURE__*/_jsxs(HStack, {
1685
- alignItems: "center",
1686
- gap: 2,
1687
- children: [/*#__PURE__*/_jsx(Box, {
1688
- style: {
1689
- width: 12,
1690
- height: 12,
1691
- borderRadius: 6,
1692
- backgroundColor: colors.green
1693
- }
1694
- }), /*#__PURE__*/_jsx(Text, {
1695
- font: "label2",
1696
- children: "Pedro Pascal"
1697
- }), /*#__PURE__*/_jsxs(Text, {
1698
- font: "label1",
1699
- children: [candidate3Value, "%"]
2073
+ gap: 1,
2074
+ children: [/*#__PURE__*/_jsx(Button, {
2075
+ compact: true,
2076
+ onPress: () => setAlignment(TextAlign.Left),
2077
+ variant: alignment === TextAlign.Left ? 'primary' : 'secondary',
2078
+ children: "Left"
2079
+ }), /*#__PURE__*/_jsx(Button, {
2080
+ compact: true,
2081
+ onPress: () => setAlignment(TextAlign.Center),
2082
+ variant: alignment === TextAlign.Center ? 'primary' : 'secondary',
2083
+ children: "Center"
2084
+ }), /*#__PURE__*/_jsx(Button, {
2085
+ compact: true,
2086
+ onPress: () => setAlignment(TextAlign.Right),
2087
+ variant: alignment === TextAlign.Right ? 'primary' : 'secondary',
2088
+ children: "Right"
1700
2089
  })]
2090
+ }), /*#__PURE__*/_jsx(LineChart, {
2091
+ enableScrubbing: true,
2092
+ showArea: true,
2093
+ height: 200,
2094
+ inset: {
2095
+ top: 64
2096
+ },
2097
+ series: [{
2098
+ id: 'prices',
2099
+ data: data,
2100
+ color: theme.color.accentBoldBlue
2101
+ }],
2102
+ children: /*#__PURE__*/_jsx(Scrubber, {
2103
+ idlePulse: true,
2104
+ labelElevated: true,
2105
+ LabelComponent: AlignedScrubberLabel,
2106
+ label: scrubberLabel
2107
+ })
1701
2108
  })]
1702
2109
  });
1703
- }));
1704
- const PredictionChart = () => {
2110
+ }
2111
+ function ExampleNavigator() {
1705
2112
  const theme = useTheme();
1706
-
1707
- // Ref for the legend component
1708
- const legendRef = useRef(null);
1709
-
1710
- // Define colors using spectrum
1711
- const colors = useMemo(() => ({
1712
- pink: "rgb(" + theme.spectrum.pink50 + ")",
1713
- teal: "rgb(" + theme.spectrum.teal50 + ")",
1714
- green: "rgb(" + theme.spectrum.green50 + ")"
1715
- }), [theme.spectrum]);
1716
- const tabs = useMemo(() => [{
1717
- id: 'hour',
1718
- label: '1H'
2113
+ const [currentIndex, setCurrentIndex] = useState(0);
2114
+ const examples = useMemo(() => [{
2115
+ title: 'Basic',
2116
+ component: /*#__PURE__*/_jsx(LineChart, {
2117
+ showArea: true,
2118
+ height: 200,
2119
+ series: [{
2120
+ id: 'prices',
2121
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
2122
+ }]
2123
+ })
1719
2124
  }, {
1720
- id: 'day',
1721
- label: '1D'
2125
+ title: 'Imperative Handle',
2126
+ component: /*#__PURE__*/_jsx(ImperativeHandle, {})
1722
2127
  }, {
1723
- id: 'week',
1724
- label: '1W'
2128
+ title: 'Multiple Lines',
2129
+ component: /*#__PURE__*/_jsx(MultipleLine, {})
1725
2130
  }, {
1726
- id: 'month',
1727
- label: '1M'
2131
+ title: 'Data Format',
2132
+ component: /*#__PURE__*/_jsx(DataFormat, {})
1728
2133
  }, {
1729
- id: 'year',
1730
- label: '1Y'
2134
+ title: 'Live Updates',
2135
+ component: /*#__PURE__*/_jsx(LiveUpdates, {})
1731
2136
  }, {
1732
- id: 'all',
1733
- label: 'All'
1734
- }], []);
1735
- const [timePeriod, setTimePeriod] = useState(tabs[0]);
1736
- const periodData = useMemo(() => {
1737
- return predictionData[timePeriod.id];
1738
- }, [timePeriod]);
1739
- const candidate1Data = useMemo(() => periodData.map(d => d.candidate1), [periodData]);
1740
- const candidate2Data = useMemo(() => periodData.map(d => d.candidate2), [periodData]);
1741
- const candidate3Data = useMemo(() => periodData.map(d => d.candidate3), [periodData]);
1742
- const timestamps = useMemo(() => periodData.map(d => d.date), [periodData]);
1743
-
1744
- // Data object for the legend
1745
- const legendData = useMemo(() => ({
1746
- candidate1: candidate1Data,
1747
- candidate2: candidate2Data,
1748
- candidate3: candidate3Data
1749
- }), [candidate1Data, candidate2Data, candidate3Data]);
1750
-
1751
- // Update legend via imperative ref when scrubber position changes
1752
- const onScrubberPositionChange = useCallback(dataIndex => {
1753
- var _legendRef$current;
1754
- const idx = dataIndex != null ? dataIndex : candidate1Data.length - 1;
1755
- (_legendRef$current = legendRef.current) == null || _legendRef$current.updateSelectedIndex(idx);
1756
- }, [candidate1Data.length]);
1757
-
1758
- // Update legend when data length changes
1759
- useEffect(() => {
1760
- var _legendRef$current2;
1761
- (_legendRef$current2 = legendRef.current) == null || _legendRef$current2.updateSelectedIndex(candidate1Data.length - 1);
1762
- }, [candidate1Data.length]);
1763
- const onPeriodChange = useCallback(period => {
1764
- setTimePeriod(period || tabs[0]);
1765
- }, [tabs]);
1766
- const scrubberLabel = useCallback(dataIndex => {
1767
- const date = timestamps[dataIndex];
1768
- const currentYear = new Date().getFullYear();
1769
- const shouldIncludeTime = timePeriod.id === 'hour' || timePeriod.id === 'day';
1770
- const dateOptions = _extends({
1771
- weekday: 'short',
1772
- month: 'short',
1773
- day: 'numeric',
1774
- year: date.getFullYear() !== currentYear ? 'numeric' : undefined
1775
- }, shouldIncludeTime && {
1776
- hour: 'numeric',
1777
- minute: '2-digit',
1778
- hour12: true
1779
- });
1780
- const dateStr = shouldIncludeTime ? date.toLocaleString('en-US', dateOptions) : date.toLocaleDateString('en-US', dateOptions);
1781
- return dateStr;
1782
- }, [timestamps, timePeriod.id]);
1783
- const chartOverviewLabel = useMemo(() => {
1784
- if (periodData.length === 0) return '';
1785
- const firstDate = periodData[0].date;
1786
- const lastDate = periodData[periodData.length - 1].date;
1787
- const currentYear = new Date().getFullYear();
1788
- const shouldIncludeTime = timePeriod.id === 'hour' || timePeriod.id === 'day';
1789
- const dateRangeOptions = _extends({
1790
- month: 'long',
1791
- day: 'numeric',
1792
- year: firstDate.getFullYear() !== currentYear ? 'numeric' : undefined
1793
- }, shouldIncludeTime && {
1794
- hour: 'numeric',
1795
- minute: '2-digit',
1796
- hour12: true
1797
- });
1798
- const startDateStr = shouldIncludeTime ? firstDate.toLocaleString('en-US', dateRangeOptions) : firstDate.toLocaleDateString('en-US', dateRangeOptions);
1799
- const endDateStr = shouldIncludeTime ? lastDate.toLocaleString('en-US', _extends({}, dateRangeOptions, {
1800
- year: lastDate.getFullYear() !== currentYear ? 'numeric' : undefined
1801
- })) : lastDate.toLocaleDateString('en-US', _extends({}, dateRangeOptions, {
1802
- year: lastDate.getFullYear() !== currentYear ? 'numeric' : undefined
1803
- }));
1804
- return "Prediction chart, " + startDateStr + " to " + endDateStr + ". Swipe left or right to navigate data points.";
1805
- }, [periodData, timePeriod.id]);
1806
- return /*#__PURE__*/_jsx(Box, {
1807
- accessibilityLabel: chartOverviewLabel,
1808
- accessibilityLiveRegion: "polite",
2137
+ title: 'Missing Data',
2138
+ component: /*#__PURE__*/_jsx(MissingData, {})
2139
+ }, {
2140
+ title: 'Empty State',
2141
+ component: /*#__PURE__*/_jsx(LineChart, {
2142
+ height: 200,
2143
+ series: [{
2144
+ id: 'line',
2145
+ color: "rgb(" + theme.spectrum.gray50 + ")",
2146
+ data: [1, 1],
2147
+ showArea: true
2148
+ }],
2149
+ yAxis: {
2150
+ domain: {
2151
+ min: -1,
2152
+ max: 3
2153
+ }
2154
+ }
2155
+ })
2156
+ }, {
2157
+ title: 'Scales',
2158
+ component: /*#__PURE__*/_jsx(LineChart, {
2159
+ showArea: true,
2160
+ showYAxis: true,
2161
+ height: 200,
2162
+ series: [{
2163
+ id: 'prices',
2164
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
2165
+ }],
2166
+ yAxis: {
2167
+ scaleType: 'log',
2168
+ showGrid: true,
2169
+ ticks: [1, 10, 100]
2170
+ }
2171
+ })
2172
+ }, {
2173
+ title: 'Interaction',
2174
+ component: /*#__PURE__*/_jsx(Interaction, {})
2175
+ }, {
2176
+ title: 'Points',
2177
+ component: /*#__PURE__*/_jsx(Points, {})
2178
+ }, {
2179
+ title: 'Transitions',
2180
+ component: /*#__PURE__*/_jsx(Transitions, {})
2181
+ }, {
2182
+ title: 'Basic Accessible',
2183
+ component: /*#__PURE__*/_jsx(BasicAccessible, {})
2184
+ }, {
2185
+ title: 'Styling Axes',
2186
+ component: /*#__PURE__*/_jsx(LineChart, {
2187
+ showArea: true,
2188
+ showXAxis: true,
2189
+ showYAxis: true,
2190
+ height: 200,
2191
+ series: [{
2192
+ id: 'prices',
2193
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
2194
+ }],
2195
+ xAxis: {
2196
+ showGrid: true,
2197
+ showLine: true,
2198
+ showTickMarks: true,
2199
+ tickLabelFormatter: dataX => "Day " + dataX
2200
+ },
2201
+ yAxis: {
2202
+ showGrid: true,
2203
+ showLine: true,
2204
+ showTickMarks: true
2205
+ }
2206
+ })
2207
+ }, {
2208
+ title: 'Gradients',
2209
+ component: /*#__PURE__*/_jsx(Gradients, {})
2210
+ }, {
2211
+ title: 'Gain/Loss',
2212
+ component: /*#__PURE__*/_jsx(GainLossChart, {})
2213
+ }, {
2214
+ title: 'Styling Lines',
2215
+ component: /*#__PURE__*/_jsx(LineChart, {
2216
+ height: 200,
2217
+ series: [{
2218
+ id: 'top',
2219
+ data: [15, 28, 32, 44, 46, 36, 40, 45, 48, 38]
2220
+ }, {
2221
+ id: 'upperMiddle',
2222
+ data: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
2223
+ color: '#ef4444',
2224
+ type: 'dotted'
2225
+ }, {
2226
+ id: 'lowerMiddle',
2227
+ data: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
2228
+ color: '#f59e0b',
2229
+ curve: 'natural',
2230
+ gradient: {
2231
+ axis: 'x',
2232
+ stops: [{
2233
+ offset: 0,
2234
+ color: '#E3D74D'
2235
+ }, {
2236
+ offset: 9,
2237
+ color: '#F7931A'
2238
+ }]
2239
+ },
2240
+ strokeWidth: 6
2241
+ }, {
2242
+ id: 'bottom',
2243
+ data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14],
2244
+ color: '#800080',
2245
+ curve: 'step',
2246
+ AreaComponent: DottedArea,
2247
+ showArea: true
2248
+ }]
2249
+ })
2250
+ }, {
2251
+ title: 'Styling Reference Lines',
2252
+ component: /*#__PURE__*/_jsxs(LineChart, {
2253
+ enableScrubbing: true,
2254
+ showArea: true,
2255
+ height: 200,
2256
+ series: [{
2257
+ id: 'prices',
2258
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
2259
+ color: theme.color.fgPositive
2260
+ }],
2261
+ xAxis: {
2262
+ // Give space before the end of the chart for the scrubber
2263
+ range: _ref23 => {
2264
+ let {
2265
+ min,
2266
+ max
2267
+ } = _ref23;
2268
+ return {
2269
+ min,
2270
+ max: max - 24
2271
+ };
2272
+ }
2273
+ },
2274
+ children: [/*#__PURE__*/_jsx(ReferenceLine, {
2275
+ LineComponent: props => /*#__PURE__*/_jsx(DottedLine, _extends({}, props, {
2276
+ dashIntervals: [0, 16],
2277
+ strokeWidth: 3
2278
+ })),
2279
+ dataY: 10,
2280
+ stroke: theme.color.fg
2281
+ }), /*#__PURE__*/_jsx(Scrubber, {})]
2282
+ })
2283
+ }, {
2284
+ title: 'High/Low Price',
2285
+ component: /*#__PURE__*/_jsx(HighLowPrice, {})
2286
+ }, {
2287
+ title: 'Styling Scrubber',
2288
+ component: /*#__PURE__*/_jsx(StylingScrubber, {})
2289
+ }, {
2290
+ title: 'Compact',
2291
+ component: /*#__PURE__*/_jsx(Compact, {})
2292
+ }, {
2293
+ title: 'Asset Price With Dotted Area',
2294
+ component: /*#__PURE__*/_jsx(AssetPriceWithDottedArea, {})
2295
+ }, {
2296
+ title: 'Performance',
2297
+ component: /*#__PURE__*/_jsx(Performance, {})
2298
+ }, {
2299
+ title: 'Candlesticks',
2300
+ component: /*#__PURE__*/_jsx(Candlesticks, {})
2301
+ }, {
2302
+ title: 'Monotone Asset Price',
2303
+ component: /*#__PURE__*/_jsx(MonotoneAssetPrice, {})
2304
+ }, {
2305
+ title: 'Service Availability',
2306
+ component: /*#__PURE__*/_jsx(ServiceAvailability, {})
2307
+ }, {
2308
+ title: 'Forecast Asset Price',
2309
+ component: /*#__PURE__*/_jsx(ForecastAssetPrice, {})
2310
+ }, {
2311
+ title: 'Custom Beacon Label',
2312
+ component: /*#__PURE__*/_jsx(CustomBeaconLabel, {})
2313
+ }, {
2314
+ title: 'Custom Label Component',
2315
+ component: /*#__PURE__*/_jsx(CustomLabelComponent, {})
2316
+ }, {
2317
+ title: 'Hidden Scrubber When Idle',
2318
+ component: /*#__PURE__*/_jsx(HiddenScrubberWhenIdle, {})
2319
+ }, {
2320
+ title: 'Two-Line Scrubber Label',
2321
+ component: /*#__PURE__*/_jsx(TwoLineScrubberLabel, {})
2322
+ }], [theme.color.fg, theme.color.fgPositive, theme.spectrum.gray50]);
2323
+ const currentExample = examples[currentIndex];
2324
+ const isFirstExample = currentIndex === 0;
2325
+ const isLastExample = currentIndex === examples.length - 1;
2326
+ const handlePrevious = useCallback(() => {
2327
+ setCurrentIndex(prev => Math.max(0, prev - 1));
2328
+ }, []);
2329
+ const handleNext = useCallback(() => {
2330
+ setCurrentIndex(prev => Math.min(examples.length - 1, prev + 1));
2331
+ }, [examples.length]);
2332
+ return /*#__PURE__*/_jsx(ExampleScreen, {
1809
2333
  children: /*#__PURE__*/_jsxs(VStack, {
1810
2334
  gap: 4,
1811
- children: [/*#__PURE__*/_jsx(PredictionLegend, {
1812
- ref: legendRef,
1813
- colors: colors,
1814
- data: legendData
2335
+ children: [/*#__PURE__*/_jsxs(HStack, {
2336
+ alignItems: "center",
2337
+ justifyContent: "space-between",
2338
+ padding: 2,
2339
+ children: [/*#__PURE__*/_jsx(IconButton, {
2340
+ accessibilityHint: "Navigate to previous example",
2341
+ accessibilityLabel: "Previous",
2342
+ disabled: isFirstExample,
2343
+ name: "arrowLeft",
2344
+ onPress: handlePrevious,
2345
+ variant: "secondary"
2346
+ }), /*#__PURE__*/_jsxs(VStack, {
2347
+ alignItems: "center",
2348
+ gap: 1,
2349
+ children: [/*#__PURE__*/_jsx(Text, {
2350
+ font: "title3",
2351
+ children: currentExample.title
2352
+ }), /*#__PURE__*/_jsxs(TextLabel1, {
2353
+ color: "fgMuted",
2354
+ children: [currentIndex + 1, " / ", examples.length]
2355
+ })]
2356
+ }), /*#__PURE__*/_jsx(IconButton, {
2357
+ accessibilityHint: "Navigate to next example",
2358
+ accessibilityLabel: "Next",
2359
+ disabled: isLastExample,
2360
+ name: "arrowRight",
2361
+ onPress: handleNext,
2362
+ variant: "secondary"
2363
+ })]
1815
2364
  }), /*#__PURE__*/_jsx(Box, {
1816
- style: {
1817
- marginLeft: -16,
1818
- marginRight: -16
1819
- },
1820
- children: /*#__PURE__*/_jsx(LineChart, {
1821
- enableScrubbing: true,
1822
- showYAxis: true,
1823
- accessibilityLiveRegion: "polite",
1824
- height: defaultChartHeight,
1825
- inset: {
1826
- left: 0
1827
- },
1828
- onScrubberPositionChange: onScrubberPositionChange,
1829
- series: [{
1830
- id: 'candidate1',
1831
- data: candidate1Data,
1832
- color: colors.pink
1833
- }, {
1834
- id: 'candidate2',
1835
- data: candidate2Data,
1836
- color: colors.teal
1837
- }, {
1838
- id: 'candidate3',
1839
- data: candidate3Data,
1840
- color: colors.green
1841
- }],
1842
- xAxis: {
1843
- range: _ref7 => {
1844
- let {
1845
- min,
1846
- max
1847
- } = _ref7;
1848
- return {
1849
- min,
1850
- max: max - 32
1851
- };
1852
- }
1853
- },
1854
- yAxis: {
1855
- domain: {
1856
- min: 0,
1857
- max: 100
1858
- },
1859
- tickLabelFormatter: value => value + "%",
1860
- requestedTickCount: 5,
1861
- showGrid: true
1862
- },
1863
- children: /*#__PURE__*/_jsx(Scrubber, {
1864
- idlePulse: true,
1865
- label: scrubberLabel
1866
- })
1867
- })
1868
- }), /*#__PURE__*/_jsx(PeriodSelector, {
1869
- accessibilityLabel: "Select time period for prediction chart",
1870
- activeTab: timePeriod,
1871
- onChange: onPeriodChange,
1872
- tabs: tabs
2365
+ padding: 1,
2366
+ children: currentExample.component
1873
2367
  })]
1874
2368
  })
1875
2369
  });
1876
- };
1877
- const AvailabilityChart = () => {
1878
- const theme = useTheme();
1879
- const [scrubIndex, setScrubIndex] = useState();
1880
- const ChartDefs = /*#__PURE__*/memo(_ref8 => {
1881
- var _rangeBounds$min, _rangeBounds$max, _yScale, _yScale2, _yScale3, _yScale4;
1882
- let {
1883
- yellowThresholdPercentage = 85,
1884
- greenThresholdPercentage = 90
1885
- } = _ref8;
1886
- const {
1887
- getYScale,
1888
- getYAxis
1889
- } = useCartesianChartContext();
1890
- const yScale = getYScale();
1891
- const yAxis = getYAxis();
1892
- if (!yScale) return null;
1893
- const rangeBounds = yAxis == null ? void 0 : yAxis.domain;
1894
- const rangeMin = (_rangeBounds$min = rangeBounds == null ? void 0 : rangeBounds.min) != null ? _rangeBounds$min : 0;
1895
- const rangeMax = (_rangeBounds$max = rangeBounds == null ? void 0 : rangeBounds.max) != null ? _rangeBounds$max : 100;
1896
-
1897
- // Calculate the Y positions in the chart coordinate system
1898
- const yellowThresholdY = (_yScale = yScale(yellowThresholdPercentage)) != null ? _yScale : 0;
1899
- const greenThresholdY = (_yScale2 = yScale(greenThresholdPercentage)) != null ? _yScale2 : 0;
1900
- const minY = (_yScale3 = yScale(rangeMax)) != null ? _yScale3 : 0; // Top of chart (max value)
1901
- const maxY = (_yScale4 = yScale(rangeMin)) != null ? _yScale4 : 0; // Bottom of chart (min value)
1902
-
1903
- // Calculate percentages based on actual chart positions
1904
- const yellowThreshold = (yellowThresholdY - minY) / (maxY - minY) * 100;
1905
- const greenThreshold = (greenThresholdY - minY) / (maxY - minY) * 100;
1906
- return /*#__PURE__*/_jsx(Defs, {
1907
- children: /*#__PURE__*/_jsxs(LinearGradient, {
1908
- gradientUnits: "userSpaceOnUse",
1909
- id: "availabilityGradient",
1910
- x1: "0%",
1911
- x2: "0%",
1912
- y1: minY,
1913
- y2: maxY,
1914
- children: [/*#__PURE__*/_jsx(Stop, {
1915
- offset: "0%",
1916
- stopColor: theme.color.fgPositive
1917
- }), /*#__PURE__*/_jsx(Stop, {
1918
- offset: greenThreshold + "%",
1919
- stopColor: theme.color.fgPositive
1920
- }), /*#__PURE__*/_jsx(Stop, {
1921
- offset: greenThreshold + "%",
1922
- stopColor: theme.color.fgWarning
1923
- }), /*#__PURE__*/_jsx(Stop, {
1924
- offset: yellowThreshold + "%",
1925
- stopColor: theme.color.fgWarning
1926
- }), /*#__PURE__*/_jsx(Stop, {
1927
- offset: yellowThreshold + "%",
1928
- stopColor: theme.color.fgNegative
1929
- }), /*#__PURE__*/_jsx(Stop, {
1930
- offset: "100%",
1931
- stopColor: theme.color.fgNegative
1932
- })]
1933
- })
1934
- });
1935
- });
1936
- return /*#__PURE__*/_jsxs(CartesianChart, {
1937
- enableScrubbing: true,
1938
- height: defaultChartHeight,
1939
- onScrubberPositionChange: setScrubIndex,
1940
- series: [{
1941
- id: 'availability',
1942
- data: availabilityEvents.map(event => event.availability),
1943
- color: 'url(#availabilityGradient)'
1944
- }],
1945
- xAxis: {
1946
- data: availabilityEvents.map(event => event.date.getTime())
1947
- },
1948
- yAxis: {
1949
- domain: _ref9 => {
1950
- let {
1951
- min,
1952
- max
1953
- } = _ref9;
1954
- return {
1955
- min: Math.max(min - 2, 0),
1956
- max: Math.min(max + 2, 100)
1957
- };
1958
- }
1959
- },
1960
- children: [/*#__PURE__*/_jsx(ChartDefs, {}), /*#__PURE__*/_jsx(XAxis, {
1961
- showGrid: true,
1962
- showLine: true,
1963
- showTickMarks: true,
1964
- tickLabelFormatter: value => new Date(value).toLocaleDateString()
1965
- }), /*#__PURE__*/_jsx(YAxis, {
1966
- showGrid: true,
1967
- showLine: true,
1968
- showTickMarks: true,
1969
- position: "left",
1970
- tickLabelFormatter: value => value + "%"
1971
- }), /*#__PURE__*/_jsx(Line, {
1972
- curve: "stepAfter",
1973
- renderPoints: () => ({
1974
- fill: theme.color.bg,
1975
- stroke: 'url(#availabilityGradient)',
1976
- strokeWidth: 2
1977
- }),
1978
- seriesId: "availability"
1979
- }), /*#__PURE__*/_jsx(Scrubber, {
1980
- overlayOffset: 10
1981
- })]
1982
- });
1983
- };
1984
- const sampleData = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
1985
- const LineChartStories = () => {
1986
- const theme = useTheme();
1987
- return /*#__PURE__*/_jsxs(ExampleScreen, {
1988
- children: [/*#__PURE__*/_jsx(Example, {
1989
- title: "Basic",
1990
- children: /*#__PURE__*/_jsx(LineChart, {
1991
- enableScrubbing: true,
1992
- showArea: true,
1993
- showYAxis: true,
1994
- curve: "monotone",
1995
- height: defaultChartHeight,
1996
- series: [{
1997
- id: 'prices',
1998
- data: sampleData
1999
- }],
2000
- yAxis: {
2001
- showGrid: true
2002
- },
2003
- children: /*#__PURE__*/_jsx(Scrubber, {})
2004
- })
2005
- }), /*#__PURE__*/_jsx(Example, {
2006
- title: "Simple",
2007
- children: /*#__PURE__*/_jsx(LineChart, {
2008
- curve: "monotone",
2009
- height: defaultChartHeight,
2010
- series: [{
2011
- id: 'prices',
2012
- data: sampleData
2013
- }]
2014
- })
2015
- }), /*#__PURE__*/_jsx(Example, {
2016
- title: "Gain/Loss",
2017
- children: /*#__PURE__*/_jsx(GainLossChart, {})
2018
- }), /*#__PURE__*/_jsx(Example, {
2019
- title: "BTC Price Chart",
2020
- children: /*#__PURE__*/_jsx(BTCPriceChart, {})
2021
- }), /*#__PURE__*/_jsx(Example, {
2022
- title: "Price Chart",
2023
- children: /*#__PURE__*/_jsx(PriceChart, {})
2024
- }), /*#__PURE__*/_jsx(Example, {
2025
- title: "Asset Price Dotted",
2026
- children: /*#__PURE__*/_jsx(AssetPriceDotted, {})
2027
- }), /*#__PURE__*/_jsx(Example, {
2028
- title: "Multiple Series",
2029
- children: /*#__PURE__*/_jsx(LineChart, {
2030
- enableScrubbing: true,
2031
- showXAxis: true,
2032
- showYAxis: true,
2033
- height: defaultChartHeight,
2034
- series: [{
2035
- id: 'pageViews',
2036
- data: [2400, 1398, 9800, 3908, 4800, 3800, 4300],
2037
- label: 'Page Views',
2038
- color: theme.color.accentBoldBlue,
2039
- curve: 'natural'
2040
- }, {
2041
- id: 'uniqueVisitors',
2042
- data: [4000, 3000, 2000, 2780, 1890, 2390, 3490],
2043
- label: 'Unique Visitors',
2044
- color: theme.color.accentBoldGreen,
2045
- curve: 'natural'
2046
- }],
2047
- xAxis: {
2048
- data: ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'],
2049
- scaleType: 'band'
2050
- },
2051
- yAxis: {
2052
- domain: {
2053
- min: 0
2054
- },
2055
- showGrid: true,
2056
- tickLabelFormatter: value => value.toLocaleString()
2057
- },
2058
- children: /*#__PURE__*/_jsx(Scrubber, {})
2059
- })
2060
- }), /*#__PURE__*/_jsx(Example, {
2061
- title: "Points",
2062
- children: /*#__PURE__*/_jsxs(CartesianChart, {
2063
- height: defaultChartHeight,
2064
- series: [{
2065
- id: 'prices',
2066
- data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]
2067
- }],
2068
- children: [/*#__PURE__*/_jsx(Area, {
2069
- curve: "monotone",
2070
- fill: "rgb(" + theme.spectrum.blue5 + ")",
2071
- seriesId: "prices"
2072
- }), /*#__PURE__*/_jsx(Line, {
2073
- curve: "monotone",
2074
- renderPoints: _ref0 => {
2075
- let {
2076
- dataX
2077
- } = _ref0,
2078
- props = _objectWithoutPropertiesLoose(_ref0, _excluded4);
2079
- return [4, 6, 7, 9, 10].includes(dataX) ? _extends({}, props, {
2080
- strokeWidth: 2,
2081
- stroke: theme.color.bg,
2082
- radius: 5,
2083
- onClick: () => alert('You have clicked a key market shift!')
2084
- }) : false;
2085
- },
2086
- seriesId: "prices"
2087
- })]
2088
- })
2089
- }), /*#__PURE__*/_jsx(Example, {
2090
- title: "Data Formats",
2091
- children: /*#__PURE__*/_jsx(LineChart, {
2092
- enableScrubbing: true,
2093
- showArea: true,
2094
- showXAxis: true,
2095
- showYAxis: true,
2096
- curve: "natural",
2097
- height: defaultChartHeight,
2098
- renderPoints: () => true,
2099
- series: [{
2100
- id: 'line',
2101
- data: [2, 5.5, 2, 8.5, 1.5, 5]
2102
- }],
2103
- xAxis: {
2104
- data: [1, 2, 3, 5, 8, 10],
2105
- showLine: true,
2106
- showTickMarks: true,
2107
- showGrid: true
2108
- },
2109
- yAxis: {
2110
- domain: {
2111
- min: 0
2112
- },
2113
- position: 'left',
2114
- showLine: true,
2115
- showTickMarks: true,
2116
- showGrid: true
2117
- },
2118
- children: /*#__PURE__*/_jsx(Scrubber, {})
2119
- })
2120
- }), /*#__PURE__*/_jsx(Example, {
2121
- title: "Bitcoin Chart with Scrubber Beacon",
2122
- children: /*#__PURE__*/_jsx(BitcoinChartWithScrubberBeacon, {})
2123
- }), /*#__PURE__*/_jsx(Example, {
2124
- title: "Scrubber with Imperative Handle",
2125
- children: /*#__PURE__*/_jsx(ScrubberWithImperativeHandle, {})
2126
- }), /*#__PURE__*/_jsx(Example, {
2127
- title: "Asset Price",
2128
- children: /*#__PURE__*/_jsx(AssetPrice, {})
2129
- }), /*#__PURE__*/_jsx(Example, {
2130
- title: "Line Styles",
2131
- children: /*#__PURE__*/_jsx(LineStyles, {})
2132
- }), /*#__PURE__*/_jsx(Example, {
2133
- title: "Chart Scale",
2134
- children: /*#__PURE__*/_jsx(ChartScale, {})
2135
- }), /*#__PURE__*/_jsx(Example, {
2136
- title: "Color Shift Chart",
2137
- children: /*#__PURE__*/_jsx(ColorShiftChart, {})
2138
- }), /*#__PURE__*/_jsx(Example, {
2139
- title: "Price Chart",
2140
- children: /*#__PURE__*/_jsx(PriceChart, {})
2141
- }), /*#__PURE__*/_jsx(Example, {
2142
- title: "Forecast Chart",
2143
- children: /*#__PURE__*/_jsx(ForecastChart, {})
2144
- }), /*#__PURE__*/_jsx(Example, {
2145
- title: "Period Selector",
2146
- children: /*#__PURE__*/_jsx(PeriodSelectorExample, {})
2147
- }), /*#__PURE__*/_jsx(Example, {
2148
- title: "Live Asset Price",
2149
- children: /*#__PURE__*/_jsx(LiveAssetPrice, {})
2150
- }), /*#__PURE__*/_jsx(Example, {
2151
- title: "Availability Chart",
2152
- children: /*#__PURE__*/_jsx(AvailabilityChart, {})
2153
- })]
2154
- });
2155
- };
2156
- const AssetPriceScreen = () => {
2157
- return /*#__PURE__*/_jsxs(ExampleScreen, {
2158
- children: [/*#__PURE__*/_jsx(Example, {
2159
- title: "Scrubber with Imperative Handle",
2160
- children: /*#__PURE__*/_jsx(ScrubberWithImperativeHandle, {})
2161
- }), /*#__PURE__*/_jsx(Example, {
2162
- title: "Basic",
2163
- children: /*#__PURE__*/_jsx(LineChart, {
2164
- enableScrubbing: true,
2165
- showArea: true,
2166
- showYAxis: true,
2167
- curve: "monotone",
2168
- height: defaultChartHeight,
2169
- series: [{
2170
- id: 'prices',
2171
- data: sampleData
2172
- }],
2173
- yAxis: {
2174
- showGrid: true
2175
- },
2176
- children: /*#__PURE__*/_jsx(Scrubber, {})
2177
- })
2178
- }), /*#__PURE__*/_jsx(Example, {
2179
- title: "Simple",
2180
- children: /*#__PURE__*/_jsx(LineChart, {
2181
- curve: "monotone",
2182
- height: defaultChartHeight,
2183
- series: [{
2184
- id: 'prices',
2185
- data: sampleData
2186
- }]
2187
- })
2188
- }), /*#__PURE__*/_jsx(Example, {
2189
- title: "Data Formats",
2190
- children: /*#__PURE__*/_jsx(LineChart, {
2191
- enableScrubbing: true,
2192
- showArea: true,
2193
- showXAxis: true,
2194
- showYAxis: true,
2195
- curve: "natural",
2196
- height: defaultChartHeight,
2197
- series: [{
2198
- id: 'line',
2199
- data: [2, 5.5, 2, 8.5, 1.5, 5]
2200
- }],
2201
- xAxis: {
2202
- data: [1, 2, 3, 5, 8, 10],
2203
- showLine: true,
2204
- showTickMarks: true,
2205
- showGrid: true
2206
- },
2207
- yAxis: {
2208
- domain: {
2209
- min: 0
2210
- },
2211
- position: 'left',
2212
- showLine: true,
2213
- showTickMarks: true,
2214
- showGrid: true
2215
- },
2216
- children: /*#__PURE__*/_jsx(Scrubber, {})
2217
- })
2218
- }), /*#__PURE__*/_jsx(Example, {
2219
- title: "Color Shift Chart",
2220
- children: /*#__PURE__*/_jsx(ColorShiftChart, {})
2221
- }), /*#__PURE__*/_jsx(Example, {
2222
- title: "Asset Price Dotted",
2223
- children: /*#__PURE__*/_jsx(AssetPriceDotted, {})
2224
- }), /*#__PURE__*/_jsx(Example, {
2225
- title: "Asset Price Multiple Dotted",
2226
- children: /*#__PURE__*/_jsx(AssetPriceMultipleDotted, {})
2227
- }), /*#__PURE__*/_jsx(Example, {
2228
- title: "Asset Price Dotted (Old)",
2229
- children: /*#__PURE__*/_jsx(AssetPriceDottedNonMemoized, {})
2230
- }), /*#__PURE__*/_jsx(Example, {
2231
- title: "BTC Price Chart",
2232
- children: /*#__PURE__*/_jsx(BTCPriceChart, {})
2233
- }), /*#__PURE__*/_jsx(Example, {
2234
- title: "Gain/Loss",
2235
- children: /*#__PURE__*/_jsx(GainLossChart, {})
2236
- }), /*#__PURE__*/_jsx(Example, {
2237
- title: "Live Asset Price",
2238
- children: /*#__PURE__*/_jsx(LiveAssetPrice, {})
2239
- }), /*#__PURE__*/_jsx(Example, {
2240
- title: "Prediction Chart",
2241
- children: /*#__PURE__*/_jsx(PredictionChart, {})
2242
- }), /*#__PURE__*/_jsx(Example, {
2243
- title: "Availability Chart",
2244
- children: /*#__PURE__*/_jsx(AvailabilityChart, {})
2245
- })]
2246
- });
2247
- };
2248
- export default AssetPriceScreen;
2370
+ }
2371
+ export default ExampleNavigator;