@coinbase/cds-mcp-server 8.21.8 → 8.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/mcp-docs/mobile/components/AreaChart.txt +39 -37
- package/mcp-docs/mobile/components/Avatar.txt +18 -18
- package/mcp-docs/mobile/components/AvatarButton.txt +19 -19
- package/mcp-docs/mobile/components/Banner.txt +62 -23
- package/mcp-docs/mobile/components/BarChart.txt +37 -35
- package/mcp-docs/mobile/components/Box.txt +18 -18
- package/mcp-docs/mobile/components/BrowserBar.txt +18 -18
- package/mcp-docs/mobile/components/Button.txt +19 -19
- package/mcp-docs/mobile/components/Carousel.txt +18 -18
- package/mcp-docs/mobile/components/CartesianChart.txt +75 -44
- package/mcp-docs/mobile/components/CheckboxCell.txt +19 -19
- package/mcp-docs/mobile/components/Chip.txt +20 -20
- package/mcp-docs/mobile/components/Coachmark.txt +18 -18
- package/mcp-docs/mobile/components/ContentCard.txt +18 -18
- package/mcp-docs/mobile/components/ContentCardBody.txt +18 -18
- package/mcp-docs/mobile/components/ContentCardFooter.txt +18 -18
- package/mcp-docs/mobile/components/ContentCardHeader.txt +18 -18
- package/mcp-docs/mobile/components/ContentCell.txt +18 -18
- package/mcp-docs/mobile/components/ControlGroup.txt +18 -18
- package/mcp-docs/mobile/components/DatePicker.txt +1 -1
- package/mcp-docs/mobile/components/Divider.txt +18 -18
- package/mcp-docs/mobile/components/DotCount.txt +1 -1
- package/mcp-docs/mobile/components/DotSymbol.txt +2 -2
- package/mcp-docs/mobile/components/Fallback.txt +18 -18
- package/mcp-docs/mobile/components/HStack.txt +18 -18
- package/mcp-docs/mobile/components/Icon.txt +6 -0
- package/mcp-docs/mobile/components/IconButton.txt +19 -19
- package/mcp-docs/mobile/components/InputChip.txt +20 -20
- package/mcp-docs/mobile/components/Interactable.txt +19 -19
- package/mcp-docs/mobile/components/LineChart.txt +1608 -898
- package/mcp-docs/mobile/components/Link.txt +18 -18
- package/mcp-docs/mobile/components/ListCell.txt +37 -19
- package/mcp-docs/mobile/components/Lottie.txt +18 -18
- package/mcp-docs/mobile/components/MediaChip.txt +20 -20
- package/mcp-docs/mobile/components/MultiContentModule.txt +18 -18
- package/mcp-docs/mobile/components/NavigationTitle.txt +18 -18
- package/mcp-docs/mobile/components/NavigationTitleSelect.txt +18 -18
- package/mcp-docs/mobile/components/Numpad.txt +18 -18
- package/mcp-docs/mobile/components/Overlay.txt +18 -18
- package/mcp-docs/mobile/components/PageFooter.txt +17 -17
- package/mcp-docs/mobile/components/PageHeader.txt +17 -17
- package/mcp-docs/mobile/components/PeriodSelector.txt +26 -26
- package/mcp-docs/mobile/components/Point.txt +203 -98
- package/mcp-docs/mobile/components/Pressable.txt +19 -19
- package/mcp-docs/mobile/components/ProgressBar.txt +1 -1
- package/mcp-docs/mobile/components/ProgressBarWithFixedLabels.txt +1 -1
- package/mcp-docs/mobile/components/ProgressBarWithFloatLabel.txt +1 -1
- package/mcp-docs/mobile/components/ProgressCircle.txt +1 -1
- package/mcp-docs/mobile/components/RadioCell.txt +19 -19
- package/mcp-docs/mobile/components/ReferenceLine.txt +197 -54
- package/mcp-docs/mobile/components/RollingNumber.txt +18 -18
- package/mcp-docs/mobile/components/Scrubber.txt +597 -79
- package/mcp-docs/mobile/components/SegmentedTabs.txt +18 -18
- package/mcp-docs/mobile/components/SelectAlpha.txt +1 -1
- package/mcp-docs/mobile/components/SelectChip.txt +20 -20
- package/mcp-docs/mobile/components/SlideButton.txt +19 -19
- package/mcp-docs/mobile/components/Spacer.txt +6 -6
- package/mcp-docs/mobile/components/SparklineInteractive.txt +3 -3
- package/mcp-docs/mobile/components/Spinner.txt +1 -1
- package/mcp-docs/mobile/components/Stepper.txt +18 -18
- package/mcp-docs/mobile/components/TabLabel.txt +18 -18
- package/mcp-docs/mobile/components/TabNavigation.txt +18 -18
- package/mcp-docs/mobile/components/TabbedChips.txt +18 -18
- package/mcp-docs/mobile/components/TabbedChipsAlpha.txt +1 -1
- package/mcp-docs/mobile/components/Tabs.txt +18 -18
- package/mcp-docs/mobile/components/Tag.txt +18 -18
- package/mcp-docs/mobile/components/Text.txt +18 -18
- package/mcp-docs/mobile/components/Toast.txt +18 -18
- package/mcp-docs/mobile/components/Tooltip.txt +17 -1
- package/mcp-docs/mobile/components/TopNavBar.txt +18 -18
- package/mcp-docs/mobile/components/VStack.txt +18 -18
- package/mcp-docs/mobile/components/XAxis.txt +86 -24
- package/mcp-docs/mobile/components/YAxis.txt +75 -17
- package/mcp-docs/mobile/routes.txt +1 -1
- package/mcp-docs/web/components/AreaChart.txt +523 -301
- package/mcp-docs/web/components/Avatar.txt +27 -27
- package/mcp-docs/web/components/AvatarButton.txt +28 -28
- package/mcp-docs/web/components/Banner.txt +71 -32
- package/mcp-docs/web/components/BarChart.txt +182 -313
- package/mcp-docs/web/components/Box.txt +28 -28
- package/mcp-docs/web/components/Button.txt +28 -28
- package/mcp-docs/web/components/Calendar.txt +27 -27
- package/mcp-docs/web/components/Carousel.txt +27 -27
- package/mcp-docs/web/components/CartesianChart.txt +62 -309
- package/mcp-docs/web/components/CheckboxCell.txt +25 -25
- package/mcp-docs/web/components/Chip.txt +27 -27
- package/mcp-docs/web/components/Coachmark.txt +27 -27
- package/mcp-docs/web/components/ContainedAssetCard.txt +27 -27
- package/mcp-docs/web/components/ContentCard.txt +28 -28
- package/mcp-docs/web/components/ContentCardBody.txt +28 -28
- package/mcp-docs/web/components/ContentCardFooter.txt +28 -28
- package/mcp-docs/web/components/ContentCardHeader.txt +28 -28
- package/mcp-docs/web/components/ContentCell.txt +28 -28
- package/mcp-docs/web/components/ControlGroup.txt +27 -27
- package/mcp-docs/web/components/Divider.txt +27 -27
- package/mcp-docs/web/components/Fallback.txt +28 -28
- package/mcp-docs/web/components/FloatingAssetCard.txt +27 -27
- package/mcp-docs/web/components/Grid.txt +28 -28
- package/mcp-docs/web/components/GridColumn.txt +27 -27
- package/mcp-docs/web/components/HStack.txt +28 -28
- package/mcp-docs/web/components/Icon.txt +27 -27
- package/mcp-docs/web/components/IconButton.txt +28 -28
- package/mcp-docs/web/components/InputChip.txt +27 -27
- package/mcp-docs/web/components/Interactable.txt +28 -28
- package/mcp-docs/web/components/LineChart.txt +1598 -1116
- package/mcp-docs/web/components/Link.txt +28 -28
- package/mcp-docs/web/components/ListCell.txt +48 -30
- package/mcp-docs/web/components/Lottie.txt +27 -27
- package/mcp-docs/web/components/MediaChip.txt +27 -27
- package/mcp-docs/web/components/Modal.txt +27 -27
- package/mcp-docs/web/components/ModalBody.txt +27 -27
- package/mcp-docs/web/components/ModalFooter.txt +27 -27
- package/mcp-docs/web/components/ModalHeader.txt +27 -27
- package/mcp-docs/web/components/MultiContentModule.txt +28 -28
- package/mcp-docs/web/components/NavigationBar.txt +5 -5
- package/mcp-docs/web/components/NudgeCard.txt +27 -27
- package/mcp-docs/web/components/Overlay.txt +27 -27
- package/mcp-docs/web/components/PageFooter.txt +26 -26
- package/mcp-docs/web/components/PageHeader.txt +26 -26
- package/mcp-docs/web/components/Pagination.txt +27 -27
- package/mcp-docs/web/components/PeriodSelector.txt +49 -49
- package/mcp-docs/web/components/Point.txt +228 -79
- package/mcp-docs/web/components/Pressable.txt +28 -28
- package/mcp-docs/web/components/RadioCell.txt +25 -25
- package/mcp-docs/web/components/ReferenceLine.txt +208 -60
- package/mcp-docs/web/components/RemoteImage.txt +26 -26
- package/mcp-docs/web/components/RollingNumber.txt +28 -28
- package/mcp-docs/web/components/Scrubber.txt +463 -68
- package/mcp-docs/web/components/SectionHeader.txt +27 -27
- package/mcp-docs/web/components/SegmentedTabs.txt +27 -27
- package/mcp-docs/web/components/SelectChip.txt +27 -27
- package/mcp-docs/web/components/SelectOption.txt +27 -27
- package/mcp-docs/web/components/Sidebar.txt +27 -27
- package/mcp-docs/web/components/SidebarItem.txt +27 -27
- package/mcp-docs/web/components/Spacer.txt +34 -34
- package/mcp-docs/web/components/SparklineInteractive.txt +1 -1
- package/mcp-docs/web/components/Spinner.txt +27 -27
- package/mcp-docs/web/components/Stepper.txt +27 -27
- package/mcp-docs/web/components/TabLabel.txt +27 -27
- package/mcp-docs/web/components/TabNavigation.txt +26 -26
- package/mcp-docs/web/components/TabbedChips.txt +26 -26
- package/mcp-docs/web/components/TabbedChipsAlpha.txt +1 -1
- package/mcp-docs/web/components/Tabs.txt +27 -27
- package/mcp-docs/web/components/Tag.txt +27 -27
- package/mcp-docs/web/components/Text.txt +28 -28
- package/mcp-docs/web/components/TileButton.txt +28 -28
- package/mcp-docs/web/components/Toast.txt +27 -27
- package/mcp-docs/web/components/Tooltip.txt +17 -1
- package/mcp-docs/web/components/VStack.txt +28 -28
- package/mcp-docs/web/components/XAxis.txt +86 -22
- package/mcp-docs/web/components/YAxis.txt +133 -89
- package/package.json +1 -1
|
@@ -10,311 +10,325 @@ import { LineChart } from '@coinbase/cds-mobile-visualization'
|
|
|
10
10
|
|
|
11
11
|
## Examples
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
LineChart is a wrapper for [CartesianChart](/components/graphs/CartesianChart) that makes it easy to create standard line charts, supporting a single x/y axis pair. Charts are built using `@shopify/react-native-skia`.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
function BasicExample() {
|
|
17
|
-
const [scrubIndex, setScrubIndex] = useState(undefined);
|
|
18
|
-
const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
|
|
15
|
+
### Setup
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
if (scrubIndex === undefined) return undefined;
|
|
22
|
-
return `Value: ${data[scrubIndex]} at index ${scrubIndex}`;
|
|
23
|
-
}, [scrubIndex, data]);
|
|
17
|
+
Before using LineChart, you need to wrap your app with `ChartBridgeProvider`. This enables charts to access CDS theming and other React contexts within the Skia renderer. See [CartesianChart](/components/graphs/CartesianChart/#setup) for details.
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
<LineChart
|
|
27
|
-
enableScrubbing
|
|
28
|
-
onScrubberPositionChange={setScrubIndex}
|
|
29
|
-
height={150}
|
|
30
|
-
series={[
|
|
31
|
-
{
|
|
32
|
-
id: 'prices',
|
|
33
|
-
data: data,
|
|
34
|
-
},
|
|
35
|
-
]}
|
|
36
|
-
curve="monotone"
|
|
37
|
-
showYAxis
|
|
38
|
-
showArea
|
|
39
|
-
yAxis={{
|
|
40
|
-
showGrid: true,
|
|
41
|
-
}}
|
|
42
|
-
accessibilityLabel={accessibilityLabel}
|
|
43
|
-
>
|
|
44
|
-
<Scrubber />
|
|
45
|
-
</LineChart>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
```
|
|
19
|
+
### Basics
|
|
49
20
|
|
|
50
|
-
|
|
21
|
+
The only prop required is `series`, which takes an array of series objects. Each series object needs an `id` and a `data` array of numbers.
|
|
51
22
|
|
|
52
23
|
```jsx
|
|
53
24
|
<LineChart
|
|
54
|
-
|
|
25
|
+
showArea
|
|
26
|
+
height={200}
|
|
55
27
|
series={[
|
|
56
28
|
{
|
|
57
29
|
id: 'prices',
|
|
58
30
|
data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
|
|
59
31
|
},
|
|
60
32
|
]}
|
|
61
|
-
curve="monotone"
|
|
62
33
|
/>
|
|
63
34
|
```
|
|
64
35
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
You can specify the dimensions of the chart to make it more compact.
|
|
36
|
+
LineChart also supports multiple lines, interaction, and axes.
|
|
37
|
+
Other props, such as `areaType` can be applied to the chart as a whole or per series.
|
|
68
38
|
|
|
69
39
|
```jsx
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
40
|
+
<LineChart
|
|
41
|
+
enableScrubbing
|
|
42
|
+
showArea
|
|
43
|
+
series={[
|
|
44
|
+
{
|
|
45
|
+
id: 'pageViews',
|
|
46
|
+
data: [2400, 1398, 9800, 3908, 4800, 3800, 4300],
|
|
47
|
+
color: theme.color.accentBoldGreen,
|
|
48
|
+
// Label will render next to scrubber beacon
|
|
49
|
+
label: 'Page Views',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'uniqueVisitors',
|
|
53
|
+
data: [4000, 3000, 2000, 2780, 1890, 2390, 3490],
|
|
54
|
+
color: theme.color.accentBoldPurple,
|
|
55
|
+
label: 'Unique Visitors',
|
|
56
|
+
// Default area is gradient
|
|
57
|
+
areaType: 'dotted',
|
|
58
|
+
},
|
|
59
|
+
]}
|
|
60
|
+
xAxis={{
|
|
61
|
+
// Used on the x-axis to provide context for each index from the series data array
|
|
62
|
+
data: ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'],
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<Scrubber />
|
|
66
|
+
</LineChart>
|
|
67
|
+
```
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
.map((price) => parseFloat(price))
|
|
76
|
-
.filter((price, index) => index % 10 === 0);
|
|
77
|
-
const positiveFloor = Math.min(...sparklineData) - 10;
|
|
69
|
+
### Data
|
|
78
70
|
|
|
79
|
-
|
|
80
|
-
const negativeCeiling = Math.max(...negativeData) + 10;
|
|
71
|
+
The data array for each series defines the y values for that series. You can adjust the y values for a series of data by setting the `data` prop on the xAxis.
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
```jsx
|
|
74
|
+
const yData = [2, 5.5, 2, 8.5, 1.5, 5];
|
|
75
|
+
const xData = [1, 2, 3, 5, 8, 10];
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<LineChart
|
|
79
|
+
enableScrubbing
|
|
80
|
+
showArea
|
|
81
|
+
series={[
|
|
82
|
+
{
|
|
83
|
+
id: 'line',
|
|
84
|
+
data: yData,
|
|
85
|
+
},
|
|
86
|
+
]}
|
|
87
|
+
xAxis={{ data: xData, showLine: true, showTickMarks: true, showGrid: true }}
|
|
88
|
+
yAxis={{
|
|
89
|
+
domain: { min: 0 },
|
|
90
|
+
position: 'left',
|
|
91
|
+
showLine: true,
|
|
92
|
+
showTickMarks: true,
|
|
93
|
+
showGrid: true,
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<Scrubber />
|
|
97
|
+
</LineChart>
|
|
98
|
+
);
|
|
99
|
+
```
|
|
88
100
|
|
|
89
|
-
|
|
90
|
-
<Box style={{ padding: 1 }}>
|
|
91
|
-
<LineChart
|
|
92
|
-
{...dimensions}
|
|
93
|
-
enableScrubbing={false}
|
|
94
|
-
overflow="visible"
|
|
95
|
-
inset={0}
|
|
96
|
-
showArea={showArea}
|
|
97
|
-
series={[
|
|
98
|
-
{
|
|
99
|
-
id: 'btc',
|
|
100
|
-
data,
|
|
101
|
-
color,
|
|
102
|
-
},
|
|
103
|
-
]}
|
|
104
|
-
>
|
|
105
|
-
<ReferenceLine dataY={referenceY} />
|
|
106
|
-
</LineChart>
|
|
107
|
-
</Box>
|
|
108
|
-
));
|
|
101
|
+
#### Live Updates
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<ListCell
|
|
113
|
-
detail={formatPrice(parseFloat(prices[0]))}
|
|
114
|
-
intermediary={
|
|
115
|
-
<CompactChart data={data} showArea={showArea} color={color} referenceY={referenceY} />
|
|
116
|
-
}
|
|
117
|
-
media={<CellMedia source={assets.btc.imageUrl} title="BTC" type="image" />}
|
|
118
|
-
onClick={() => console.log('clicked')}
|
|
119
|
-
subdetail={subdetail}
|
|
120
|
-
title={isPhone ? undefined : assets.btc.name}
|
|
121
|
-
variant={variant}
|
|
122
|
-
style={{ padding: 0 }}
|
|
123
|
-
/>
|
|
124
|
-
);
|
|
125
|
-
});
|
|
103
|
+
You can change the data passed in via `series` prop to update the chart.
|
|
126
104
|
|
|
127
|
-
|
|
128
|
-
<VStack gap={2}>
|
|
129
|
-
<ChartCell
|
|
130
|
-
data={sparklineData}
|
|
131
|
-
color={assets.btc.color}
|
|
132
|
-
referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
|
|
133
|
-
subdetail="-4.55%"
|
|
134
|
-
variant="negative"
|
|
135
|
-
/>
|
|
136
|
-
<ChartCell
|
|
137
|
-
data={sparklineData}
|
|
138
|
-
showArea
|
|
139
|
-
color={assets.btc.color}
|
|
140
|
-
referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
|
|
141
|
-
subdetail="-4.55%"
|
|
142
|
-
variant="negative"
|
|
143
|
-
/>
|
|
144
|
-
<ChartCell
|
|
145
|
-
data={sparklineData}
|
|
146
|
-
showArea
|
|
147
|
-
color={theme.color.fgPositive}
|
|
148
|
-
referenceY={positiveFloor}
|
|
149
|
-
subdetail="+0.25%"
|
|
150
|
-
variant="positive"
|
|
151
|
-
/>
|
|
152
|
-
<ChartCell
|
|
153
|
-
data={negativeData}
|
|
154
|
-
showArea
|
|
155
|
-
color={theme.color.fgNegative}
|
|
156
|
-
referenceY={negativeCeiling}
|
|
157
|
-
subdetail="-4.55%"
|
|
158
|
-
variant="negative"
|
|
159
|
-
/>
|
|
160
|
-
</VStack>
|
|
161
|
-
);
|
|
162
|
-
};
|
|
163
|
-
```
|
|
105
|
+
You can also use the `useRef` hook to reference the scrubber and pulse it on each update.
|
|
164
106
|
|
|
165
|
-
|
|
107
|
+
```jsx
|
|
108
|
+
function LiveUpdates() {
|
|
109
|
+
const scrubberRef = useRef < ScrubberRef > null;
|
|
166
110
|
|
|
167
|
-
|
|
111
|
+
const initialData = useMemo(() => {
|
|
112
|
+
return sparklineInteractiveData.hour.map((d) => d.value);
|
|
113
|
+
}, []);
|
|
168
114
|
|
|
169
|
-
|
|
170
|
-
function GainLossChart() {
|
|
171
|
-
const theme = useTheme();
|
|
172
|
-
const gradientId = useId();
|
|
115
|
+
const [priceData, setPriceData] = useState(initialData);
|
|
173
116
|
|
|
174
|
-
const
|
|
117
|
+
const lastDataPointTimeRef = useRef(Date.now());
|
|
118
|
+
const updateCountRef = useRef(0);
|
|
175
119
|
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
style: 'currency',
|
|
180
|
-
currency: 'USD',
|
|
181
|
-
maximumFractionDigits: 0,
|
|
182
|
-
}).format(value),
|
|
183
|
-
[],
|
|
184
|
-
);
|
|
120
|
+
const intervalSeconds = 3600 / initialData.length;
|
|
121
|
+
|
|
122
|
+
const maxPercentChange = Math.abs(initialData[initialData.length - 1] - initialData[0]) * 0.05;
|
|
185
123
|
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const priceUpdateInterval = setInterval(
|
|
126
|
+
() => {
|
|
127
|
+
setPriceData((currentData) => {
|
|
128
|
+
const newData = [...currentData];
|
|
129
|
+
const lastPrice = newData[newData.length - 1];
|
|
190
130
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const range = yScale.range();
|
|
131
|
+
const priceChange = (Math.random() - 0.5) * maxPercentChange;
|
|
132
|
+
const newPrice = Math.round((lastPrice + priceChange) * 100) / 100;
|
|
194
133
|
|
|
195
|
-
|
|
134
|
+
// Check if we should roll over to a new data point
|
|
135
|
+
const currentTime = Date.now();
|
|
136
|
+
const timeSinceLastPoint = (currentTime - lastDataPointTimeRef.current) / 1000;
|
|
196
137
|
|
|
197
|
-
|
|
198
|
-
|
|
138
|
+
if (timeSinceLastPoint >= intervalSeconds) {
|
|
139
|
+
// Time for a new data point - remove first, add new at end
|
|
140
|
+
lastDataPointTimeRef.current = currentTime;
|
|
141
|
+
newData.shift(); // Remove oldest data point
|
|
142
|
+
newData.push(newPrice); // Add new data point
|
|
143
|
+
updateCountRef.current = 0;
|
|
144
|
+
} else {
|
|
145
|
+
// Just update the last data point
|
|
146
|
+
newData[newData.length - 1] = newPrice;
|
|
147
|
+
updateCountRef.current++;
|
|
148
|
+
}
|
|
199
149
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
<LinearGradient
|
|
203
|
-
gradientUnits="userSpaceOnUse"
|
|
204
|
-
id={`${gradientId}-solid`}
|
|
205
|
-
x1="0%"
|
|
206
|
-
x2="0%"
|
|
207
|
-
y1={range[0]}
|
|
208
|
-
y2={range[1]}
|
|
209
|
-
>
|
|
210
|
-
<Stop offset="0%" stopColor={negativeColor} />
|
|
211
|
-
<Stop offset={`${baselinePercentage}%`} stopColor={negativeColor} />
|
|
212
|
-
<Stop offset={`${baselinePercentage}%`} stopColor={positiveColor} />
|
|
213
|
-
<Stop offset="100%" stopColor={positiveColor} />
|
|
214
|
-
</LinearGradient>
|
|
215
|
-
<LinearGradient
|
|
216
|
-
gradientUnits="userSpaceOnUse"
|
|
217
|
-
id={`${gradientId}-gradient`}
|
|
218
|
-
x1="0%"
|
|
219
|
-
x2="0%"
|
|
220
|
-
y1={range[0]}
|
|
221
|
-
y2={range[1]}
|
|
222
|
-
>
|
|
223
|
-
<Stop offset="0%" stopColor={negativeColor} stopOpacity={0.3} />
|
|
224
|
-
<Stop offset={`${baselinePercentage}%`} stopColor={negativeColor} stopOpacity={0} />
|
|
225
|
-
<Stop offset={`${baselinePercentage}%`} stopColor={positiveColor} stopOpacity={0} />
|
|
226
|
-
<Stop offset="100%" stopColor={positiveColor} stopOpacity={0.3} />
|
|
227
|
-
</LinearGradient>
|
|
228
|
-
</Defs>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
150
|
+
return newData;
|
|
151
|
+
});
|
|
231
152
|
|
|
232
|
-
|
|
233
|
-
|
|
153
|
+
// Pulse the scrubber on each update
|
|
154
|
+
scrubberRef.current?.pulse();
|
|
155
|
+
},
|
|
156
|
+
2000 + Math.random() * 1000,
|
|
157
|
+
);
|
|
234
158
|
|
|
235
|
-
|
|
159
|
+
return () => clearInterval(priceUpdateInterval);
|
|
160
|
+
}, [intervalSeconds, maxPercentChange]);
|
|
236
161
|
|
|
237
162
|
return (
|
|
238
|
-
<
|
|
163
|
+
<LineChart
|
|
239
164
|
enableScrubbing
|
|
240
|
-
|
|
165
|
+
showArea
|
|
166
|
+
height={200}
|
|
167
|
+
inset={{ right: 64 }}
|
|
241
168
|
series={[
|
|
242
169
|
{
|
|
243
|
-
id: '
|
|
244
|
-
data:
|
|
245
|
-
color:
|
|
170
|
+
id: 'btc',
|
|
171
|
+
data: priceData,
|
|
172
|
+
color: assets.btc.color,
|
|
246
173
|
},
|
|
247
174
|
]}
|
|
248
|
-
padding={{ top: 1.5, bottom: 1.5, left: 2, right: 0 }}
|
|
249
175
|
>
|
|
250
|
-
<
|
|
251
|
-
|
|
252
|
-
<Area seriesId="prices" curve="monotone" fill={`url(#${gradientId}-gradient)`} />
|
|
253
|
-
<Line strokeWidth={3} curve="monotone" seriesId="prices" stroke={solidColor} />
|
|
254
|
-
<Scrubber hideOverlay />
|
|
255
|
-
</CartesianChart>
|
|
176
|
+
<Scrubber ref={scrubberRef} labelElevated />
|
|
177
|
+
</LineChart>
|
|
256
178
|
);
|
|
257
179
|
}
|
|
258
180
|
```
|
|
259
181
|
|
|
260
|
-
|
|
182
|
+
#### Missing Data
|
|
261
183
|
|
|
262
|
-
|
|
184
|
+
By default, null values in data create gaps in a line. Use `connectNulls` to skip null values and draw a continuous line.
|
|
185
|
+
Note that scrubber beacons and points are still only shown at non-null data values.
|
|
263
186
|
|
|
264
187
|
```jsx
|
|
265
|
-
function
|
|
188
|
+
function MissingData() {
|
|
266
189
|
const theme = useTheme();
|
|
267
|
-
const [
|
|
190
|
+
const pages = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'];
|
|
191
|
+
const pageViews = [2400, 1398, null, 3908, 4800, 3800, 4300];
|
|
192
|
+
const uniqueVisitors = [4000, 3000, null, 2780, 1890, 2390, 3490];
|
|
268
193
|
|
|
269
|
-
const
|
|
270
|
-
|
|
194
|
+
const numberFormatter = useCallback(
|
|
195
|
+
(value: number) => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value),
|
|
196
|
+
[],
|
|
197
|
+
);
|
|
271
198
|
|
|
272
199
|
return (
|
|
273
200
|
<LineChart
|
|
274
201
|
enableScrubbing
|
|
275
|
-
|
|
202
|
+
showArea
|
|
203
|
+
showXAxis
|
|
204
|
+
showYAxis
|
|
205
|
+
height={200}
|
|
206
|
+
// You can render points at every valid data point by always returning true
|
|
207
|
+
points
|
|
276
208
|
series={[
|
|
277
209
|
{
|
|
278
|
-
id: '
|
|
279
|
-
data:
|
|
280
|
-
|
|
281
|
-
|
|
210
|
+
id: 'pageViews',
|
|
211
|
+
data: pageViews,
|
|
212
|
+
color: theme.color.accentBoldGreen,
|
|
213
|
+
// Label will render next to scrubber beacon
|
|
214
|
+
label: 'Page Views',
|
|
215
|
+
connectNulls: true,
|
|
282
216
|
},
|
|
283
217
|
{
|
|
284
|
-
id: '
|
|
285
|
-
data:
|
|
286
|
-
|
|
287
|
-
|
|
218
|
+
id: 'uniqueVisitors',
|
|
219
|
+
data: uniqueVisitors,
|
|
220
|
+
color: theme.color.accentBoldPurple,
|
|
221
|
+
label: 'Unique Visitors',
|
|
288
222
|
},
|
|
289
223
|
]}
|
|
290
|
-
|
|
224
|
+
xAxis={{
|
|
225
|
+
// Used on the x-axis to provide context for each index from the series data array
|
|
226
|
+
data: pages,
|
|
227
|
+
}}
|
|
291
228
|
yAxis={{
|
|
292
|
-
domain: {
|
|
293
|
-
min: 0,
|
|
294
|
-
},
|
|
295
229
|
showGrid: true,
|
|
230
|
+
tickLabelFormatter: numberFormatter,
|
|
296
231
|
}}
|
|
297
|
-
curve="monotone"
|
|
298
232
|
>
|
|
299
|
-
|
|
233
|
+
{/* We can offset the overlay to account for the points being drawn on the lines */}
|
|
234
|
+
<Scrubber overlayOffset={6} />
|
|
300
235
|
</LineChart>
|
|
301
236
|
);
|
|
302
237
|
}
|
|
303
238
|
```
|
|
304
239
|
|
|
305
|
-
|
|
240
|
+
##### Empty State
|
|
241
|
+
|
|
242
|
+
```jsx
|
|
243
|
+
function EmptyState() {
|
|
244
|
+
const theme = useTheme();
|
|
245
|
+
return (
|
|
246
|
+
<LineChart
|
|
247
|
+
height={200}
|
|
248
|
+
series={[
|
|
249
|
+
{
|
|
250
|
+
id: 'line',
|
|
251
|
+
color: `rgb(${theme.spectrum.gray50})`,
|
|
252
|
+
data: [1, 1],
|
|
253
|
+
showArea: true,
|
|
254
|
+
},
|
|
255
|
+
]}
|
|
256
|
+
yAxis={{ domain: { min: -1, max: 3 } }}
|
|
257
|
+
/>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Scales
|
|
263
|
+
|
|
264
|
+
LineChart uses `linear` scaling on axes by default, but you can also use other types, such as `log`. See [XAxis](/components/graphs/XAxis) and [YAxis](/components/graphs/YAxis) for more information.
|
|
265
|
+
|
|
266
|
+
```jsx
|
|
267
|
+
<LineChart
|
|
268
|
+
showArea
|
|
269
|
+
showYAxis
|
|
270
|
+
height={200}
|
|
271
|
+
series={[
|
|
272
|
+
{
|
|
273
|
+
id: 'prices',
|
|
274
|
+
data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
|
|
275
|
+
},
|
|
276
|
+
]}
|
|
277
|
+
yAxis={{
|
|
278
|
+
scaleType: 'log',
|
|
279
|
+
showGrid: true,
|
|
280
|
+
ticks: [1, 10, 100],
|
|
281
|
+
}}
|
|
282
|
+
/>
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Interaction
|
|
286
|
+
|
|
287
|
+
Charts have built in functionality enabled through scrubbing, which can be used by setting `enableScrubbing` to true. You can listen to value changes through `onScrubberPositionChange`. Adding `Scrubber` to LineChart showcases the current scrubber position.
|
|
288
|
+
|
|
289
|
+
```jsx
|
|
290
|
+
function Interaction() {
|
|
291
|
+
const [scrubberPosition, setScrubberPosition] = useState<number | undefined>();
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<VStack gap={2}>
|
|
295
|
+
<Text font="label1">
|
|
296
|
+
{scrubberPosition !== undefined
|
|
297
|
+
? `Scrubber position: ${scrubberPosition}`
|
|
298
|
+
: 'Not scrubbing'}
|
|
299
|
+
</Text>
|
|
300
|
+
<LineChart
|
|
301
|
+
enableScrubbing
|
|
302
|
+
showArea
|
|
303
|
+
height={200}
|
|
304
|
+
onScrubberPositionChange={setScrubberPosition}
|
|
305
|
+
series={[
|
|
306
|
+
{
|
|
307
|
+
id: 'prices',
|
|
308
|
+
data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
|
|
309
|
+
},
|
|
310
|
+
]}
|
|
311
|
+
>
|
|
312
|
+
<Scrubber />
|
|
313
|
+
</LineChart>
|
|
314
|
+
</VStack>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### Points
|
|
306
320
|
|
|
307
|
-
You can use
|
|
321
|
+
You can use `points` from LineChart to render instances of [Point](/components/graphs/Point) at specific data locations with custom styling.
|
|
308
322
|
|
|
309
323
|
```jsx
|
|
310
|
-
function
|
|
324
|
+
function Points() {
|
|
311
325
|
const theme = useTheme();
|
|
312
326
|
const keyMarketShiftIndices = [4, 6, 7, 9, 10];
|
|
313
327
|
const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
|
|
314
328
|
|
|
315
329
|
return (
|
|
316
330
|
<CartesianChart
|
|
317
|
-
height={
|
|
331
|
+
height={200}
|
|
318
332
|
series={[
|
|
319
333
|
{
|
|
320
334
|
id: 'prices',
|
|
@@ -322,385 +336,1171 @@ function PointsChart() {
|
|
|
322
336
|
},
|
|
323
337
|
]}
|
|
324
338
|
>
|
|
325
|
-
<Area
|
|
339
|
+
<Area fill={`rgb(${theme.spectrum.blue5})`} seriesId="prices" />
|
|
326
340
|
<Line
|
|
327
|
-
|
|
328
|
-
renderPoints={({ dataX, dataY, ...props }) =>
|
|
341
|
+
points={({ dataX, ...props }) =>
|
|
329
342
|
keyMarketShiftIndices.includes(dataX)
|
|
330
343
|
? {
|
|
331
344
|
...props,
|
|
332
345
|
strokeWidth: 2,
|
|
333
346
|
stroke: theme.color.bg,
|
|
334
347
|
radius: 5,
|
|
335
|
-
onClick: () =>
|
|
336
|
-
alert(
|
|
337
|
-
`You have clicked a key market shift at position ${dataX + 1} with value ${dataY}!`,
|
|
338
|
-
),
|
|
339
|
-
accessibilityLabel: `Key market shift point at position ${dataX + 1}, value ${dataY}. Click to view details.`,
|
|
340
348
|
}
|
|
341
349
|
: false
|
|
342
350
|
}
|
|
343
|
-
|
|
351
|
+
seriesId="prices"
|
|
344
352
|
/>
|
|
345
353
|
</CartesianChart>
|
|
346
354
|
);
|
|
347
355
|
}
|
|
348
356
|
```
|
|
349
357
|
|
|
350
|
-
|
|
358
|
+
#### Performance
|
|
351
359
|
|
|
352
|
-
|
|
360
|
+
Renders are done on JS thread, other code is in UI
|
|
353
361
|
|
|
354
362
|
```jsx
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
363
|
+
|
|
364
|
+
function Performance() {
|
|
365
|
+
const tabs = useMemo(
|
|
366
|
+
() => [
|
|
367
|
+
{ id: 'hour', label: '1H' },
|
|
368
|
+
{ id: 'day', label: '1D' },
|
|
369
|
+
{ id: 'week', label: '1W' },
|
|
370
|
+
{ id: 'month', label: '1M' },
|
|
371
|
+
{ id: 'year', label: '1Y' },
|
|
372
|
+
{ id: 'all', label: 'All' },
|
|
373
|
+
],
|
|
374
|
+
[],
|
|
375
|
+
);
|
|
376
|
+
const [timePeriod, setTimePeriod] = useState<TabValue>(tabs[0]);
|
|
377
|
+
const [scrubberPosition, setScrubberPosition] = useState<number | undefined>();
|
|
378
|
+
|
|
379
|
+
const sparklineTimePeriodData = useMemo(() => {
|
|
380
|
+
return sparklineInteractiveData[timePeriod.id as keyof typeof sparklineInteractiveData];
|
|
381
|
+
}, [timePeriod]);
|
|
382
|
+
|
|
383
|
+
const sparklineTimePeriodDataValues = useMemo(() => {
|
|
384
|
+
return sparklineTimePeriodData.map((d) => d.value);
|
|
385
|
+
}, [sparklineTimePeriodData]);
|
|
386
|
+
|
|
387
|
+
const onPeriodChange = useCallback(
|
|
388
|
+
(period: TabValue | null) => {
|
|
389
|
+
setTimePeriod(period || tabs[0]);
|
|
390
|
+
},
|
|
391
|
+
[tabs],
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<VStack gap={2} style={{ marginLeft: -8, marginRight: -8 }}>
|
|
396
|
+
<PerformanceHeader
|
|
397
|
+
scrubberPosition={scrubberPosition}
|
|
398
|
+
sparklineTimePeriodDataValues={sparklineTimePeriodDataValues}
|
|
399
|
+
/>
|
|
400
|
+
<PerformanceChart onScrubberPositionChange={setScrubberPosition} timePeriod={timePeriod} />
|
|
401
|
+
<PeriodSelector activeTab={timePeriod} onChange={onPeriodChange} tabs={tabs} />
|
|
402
|
+
</VStack>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const PerformanceHeader = memo(
|
|
407
|
+
({
|
|
408
|
+
scrubberPosition,
|
|
409
|
+
sparklineTimePeriodDataValues,
|
|
410
|
+
}: {
|
|
411
|
+
scrubberPosition: number | undefined;
|
|
412
|
+
sparklineTimePeriodDataValues: number[];
|
|
413
|
+
}) => {
|
|
414
|
+
const theme = useTheme();
|
|
415
|
+
|
|
416
|
+
const formatPriceThousands = useCallback((price: number) => {
|
|
417
|
+
return `${new Intl.NumberFormat('en-US', {
|
|
418
|
+
style: 'currency',
|
|
419
|
+
currency: 'USD',
|
|
420
|
+
minimumFractionDigits: 0,
|
|
421
|
+
maximumFractionDigits: 0,
|
|
422
|
+
}).format(price / 1000)}k`;
|
|
423
|
+
}, []);
|
|
424
|
+
|
|
425
|
+
const shownPosition =
|
|
426
|
+
scrubberPosition !== undefined ? scrubberPosition : sparklineTimePeriodDataValues.length - 1;
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<HStack gap={1} paddingX={1}>
|
|
430
|
+
<LegendItem
|
|
431
|
+
color={theme.color.fgPositive}
|
|
432
|
+
label="High Price"
|
|
433
|
+
value={formatPriceThousands(sparklineTimePeriodDataValues[shownPosition] * 1.2)}
|
|
434
|
+
/>
|
|
435
|
+
<LegendItem
|
|
436
|
+
color={assets.btc.color}
|
|
437
|
+
label="Actual Price"
|
|
438
|
+
value={formatPriceThousands(sparklineTimePeriodDataValues[shownPosition])}
|
|
439
|
+
/>
|
|
440
|
+
<LegendItem
|
|
441
|
+
color={theme.color.fgNegative}
|
|
442
|
+
label="Low Price"
|
|
443
|
+
value={formatPriceThousands(sparklineTimePeriodDataValues[shownPosition] * 0.8)}
|
|
444
|
+
/>
|
|
445
|
+
</HStack>
|
|
446
|
+
);
|
|
447
|
+
},
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const PerformanceChart = memo(
|
|
451
|
+
({
|
|
452
|
+
timePeriod,
|
|
453
|
+
onScrubberPositionChange,
|
|
454
|
+
}: {
|
|
455
|
+
timePeriod: TabValue;
|
|
456
|
+
onScrubberPositionChange: (position: number | undefined) => void;
|
|
457
|
+
}) => {
|
|
458
|
+
const theme = useTheme();
|
|
459
|
+
|
|
460
|
+
const sparklineTimePeriodData = useMemo(() => {
|
|
461
|
+
return sparklineInteractiveData[timePeriod.id as keyof typeof sparklineInteractiveData];
|
|
462
|
+
}, [timePeriod]);
|
|
463
|
+
|
|
464
|
+
const sparklineTimePeriodDataValues = useMemo(() => {
|
|
465
|
+
return sparklineTimePeriodData.map((d) => d.value);
|
|
466
|
+
}, [sparklineTimePeriodData]);
|
|
467
|
+
|
|
468
|
+
const sparklineTimePeriodDataTimestamps = useMemo(() => {
|
|
469
|
+
return sparklineTimePeriodData.map((d) => d.date);
|
|
470
|
+
}, [sparklineTimePeriodData]);
|
|
471
|
+
|
|
472
|
+
const formatPriceThousands = useCallback((price: number) => {
|
|
473
|
+
return `${new Intl.NumberFormat('en-US', {
|
|
474
|
+
style: 'currency',
|
|
475
|
+
currency: 'USD',
|
|
476
|
+
minimumFractionDigits: 0,
|
|
477
|
+
maximumFractionDigits: 0,
|
|
478
|
+
}).format(price / 1000)}k`;
|
|
479
|
+
}, []);
|
|
480
|
+
|
|
481
|
+
const formatDate = useCallback((date: Date) => {
|
|
482
|
+
const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
|
|
483
|
+
|
|
484
|
+
const monthDay = date.toLocaleDateString('en-US', {
|
|
485
|
+
month: 'short',
|
|
486
|
+
day: 'numeric',
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const time = date.toLocaleTimeString('en-US', {
|
|
490
|
+
hour: 'numeric',
|
|
491
|
+
minute: '2-digit',
|
|
492
|
+
hour12: true,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
return `${dayOfWeek}, ${monthDay}, ${time}`;
|
|
496
|
+
}, []);
|
|
497
|
+
|
|
498
|
+
const getScrubberLabel = useCallback(
|
|
499
|
+
(d: number) => formatDate(sparklineTimePeriodDataTimestamps[d]),
|
|
500
|
+
[formatDate, sparklineTimePeriodDataTimestamps],
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<LineChart
|
|
505
|
+
enableScrubbing
|
|
506
|
+
showArea
|
|
507
|
+
showYAxis
|
|
508
|
+
areaType="dotted"
|
|
509
|
+
height={300}
|
|
510
|
+
inset={{ top: 52, left: 0, right: 0 }}
|
|
511
|
+
onScrubberPositionChange={onScrubberPositionChange}
|
|
512
|
+
series={[
|
|
513
|
+
{
|
|
514
|
+
id: 'high',
|
|
515
|
+
data: sparklineTimePeriodDataValues.map((d) => d * 1.2),
|
|
516
|
+
color: theme.color.fgPositive,
|
|
517
|
+
label: 'High Price',
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
id: 'btc',
|
|
521
|
+
data: sparklineTimePeriodDataValues,
|
|
522
|
+
color: assets.btc.color,
|
|
523
|
+
label: 'Actual Price',
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
id: 'low',
|
|
527
|
+
data: sparklineTimePeriodDataValues.map((d) => d * 0.8),
|
|
528
|
+
color: theme.color.fgNegative,
|
|
529
|
+
label: 'Low Price',
|
|
530
|
+
},
|
|
531
|
+
]}
|
|
532
|
+
xAxis={{ range: ({ min, max }) => ({ min, max: max - 16 }) }}
|
|
533
|
+
yAxis={{ showGrid: true, tickLabelFormatter: formatPriceThousands }}
|
|
534
|
+
>
|
|
535
|
+
<Scrubber idlePulse label={getScrubberLabel} />
|
|
536
|
+
</LineChart>
|
|
537
|
+
);
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
#### Gestures
|
|
543
|
+
|
|
544
|
+
By default, charts will not track gestures that go outside of the chart bounds. You can allow overflow gestures by setting `allowOverflowGestures` to `true`.
|
|
375
545
|
|
|
376
546
|
```jsx
|
|
377
547
|
<LineChart
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
{
|
|
385
|
-
id: 'upperMiddle',
|
|
386
|
-
data: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
|
|
387
|
-
color: '#ef4444',
|
|
388
|
-
type: 'dotted',
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
id: 'lowerMiddle',
|
|
392
|
-
data: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
|
|
393
|
-
color: '#f59e0b',
|
|
394
|
-
curve: 'natural',
|
|
395
|
-
LineComponent: (props) => (
|
|
396
|
-
<GradientLine {...props} endColor="#F7931A" startColor="#E3D74D" strokeWidth={4} />
|
|
397
|
-
),
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
id: 'bottom',
|
|
401
|
-
data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14],
|
|
402
|
-
color: '#800080',
|
|
403
|
-
curve: 'step',
|
|
404
|
-
AreaComponent: DottedArea,
|
|
405
|
-
showArea: true,
|
|
406
|
-
},
|
|
407
|
-
]}
|
|
408
|
-
/>
|
|
548
|
+
enableScrubbing
|
|
549
|
+
allowOverflowGestures
|
|
550
|
+
...
|
|
551
|
+
>
|
|
552
|
+
...
|
|
553
|
+
</LineChart>
|
|
409
554
|
```
|
|
410
555
|
|
|
411
|
-
###
|
|
556
|
+
### Animations
|
|
557
|
+
|
|
558
|
+
You can configure chart transitions using `transition` on LineChart and `beaconTransitions` on [Scrubber](/components/graphs/Scrubber). You can also disable animations by setting the `animate` on LineChart to `false`.
|
|
412
559
|
|
|
413
560
|
```jsx
|
|
414
|
-
function
|
|
415
|
-
const
|
|
416
|
-
const
|
|
561
|
+
function Transitions() {
|
|
562
|
+
const theme = useTheme();
|
|
563
|
+
const dataCount = 20;
|
|
564
|
+
const maxDataOffset = 15000;
|
|
565
|
+
const minStepOffset = 2500;
|
|
566
|
+
const maxStepOffset = 10000;
|
|
567
|
+
const domainLimit = 20000;
|
|
568
|
+
const updateInterval = 500;
|
|
569
|
+
|
|
570
|
+
const myTransitionConfig: Transition = { type: 'spring', stiffness: 700, damping: 20 };
|
|
571
|
+
const negativeColor = `rgb(${theme.spectrum.gray15})`;
|
|
572
|
+
const positiveColor = theme.color.fgPositive;
|
|
573
|
+
|
|
574
|
+
function generateNextValue(previousValue: number) {
|
|
575
|
+
const range = maxStepOffset - minStepOffset;
|
|
576
|
+
const offset = Math.random() * range + minStepOffset;
|
|
577
|
+
|
|
578
|
+
let direction;
|
|
579
|
+
if (previousValue >= maxDataOffset) {
|
|
580
|
+
direction = -1;
|
|
581
|
+
} else if (previousValue <= -maxDataOffset) {
|
|
582
|
+
direction = 1;
|
|
583
|
+
} else {
|
|
584
|
+
direction = Math.random() < 0.5 ? -1 : 1;
|
|
585
|
+
}
|
|
417
586
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
587
|
+
let newValue = previousValue + offset * direction;
|
|
588
|
+
newValue = Math.max(-maxDataOffset, Math.min(maxDataOffset, newValue));
|
|
589
|
+
return newValue;
|
|
590
|
+
}
|
|
421
591
|
|
|
422
|
-
|
|
592
|
+
function generateInitialData() {
|
|
593
|
+
const data = [];
|
|
423
594
|
|
|
424
|
-
|
|
425
|
-
|
|
595
|
+
let previousValue = Math.random() * 2 * maxDataOffset - maxDataOffset;
|
|
596
|
+
data.push(previousValue);
|
|
426
597
|
|
|
427
|
-
|
|
598
|
+
for (let i = 1; i < dataCount; i++) {
|
|
599
|
+
const newValue = generateNextValue(previousValue);
|
|
600
|
+
data.push(newValue);
|
|
601
|
+
previousValue = newValue;
|
|
602
|
+
}
|
|
428
603
|
|
|
429
|
-
|
|
604
|
+
return data;
|
|
605
|
+
}
|
|
430
606
|
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
() =>
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
607
|
+
const MyGradient = memo((props: DottedAreaProps) => {
|
|
608
|
+
const areaGradient = {
|
|
609
|
+
stops: ({ min, max }: AxisBounds) => [
|
|
610
|
+
{ offset: min, color: negativeColor, opacity: 1 },
|
|
611
|
+
{ offset: 0, color: negativeColor, opacity: 0 },
|
|
612
|
+
{ offset: 0, color: positiveColor, opacity: 0 },
|
|
613
|
+
{ offset: max, color: positiveColor, opacity: 1 },
|
|
614
|
+
],
|
|
615
|
+
};
|
|
437
616
|
|
|
438
|
-
|
|
439
|
-
|
|
617
|
+
return <DottedArea {...props} gradient={areaGradient} />;
|
|
618
|
+
});
|
|
440
619
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const timeSinceLastPoint = (currentTime - lastDataPointTimeRef.current) / 1000;
|
|
620
|
+
function CustomTransitionsChart() {
|
|
621
|
+
const [data, setData] = useState(generateInitialData);
|
|
444
622
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
updateCountRef.current = 0;
|
|
451
|
-
} else {
|
|
452
|
-
// Just update the last data point
|
|
453
|
-
newData[newData.length - 1] = newPrice;
|
|
454
|
-
updateCountRef.current++;
|
|
455
|
-
}
|
|
623
|
+
useEffect(() => {
|
|
624
|
+
const intervalId = setInterval(() => {
|
|
625
|
+
setData((currentData) => {
|
|
626
|
+
const lastValue = currentData[currentData.length - 1] ?? 0;
|
|
627
|
+
const newValue = generateNextValue(lastValue);
|
|
456
628
|
|
|
457
|
-
return
|
|
629
|
+
return [...currentData.slice(1), newValue];
|
|
458
630
|
});
|
|
631
|
+
}, updateInterval);
|
|
459
632
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
633
|
+
return () => clearInterval(intervalId);
|
|
634
|
+
}, []);
|
|
635
|
+
|
|
636
|
+
const tickLabelFormatter = useCallback(
|
|
637
|
+
(value: number) =>
|
|
638
|
+
new Intl.NumberFormat('en-US', {
|
|
639
|
+
style: 'currency',
|
|
640
|
+
currency: 'USD',
|
|
641
|
+
maximumFractionDigits: 0,
|
|
642
|
+
}).format(value),
|
|
643
|
+
[],
|
|
464
644
|
);
|
|
465
645
|
|
|
466
|
-
|
|
467
|
-
|
|
646
|
+
const valueAtIndexFormatter = useCallback(
|
|
647
|
+
(dataIndex: number) =>
|
|
648
|
+
new Intl.NumberFormat('en-US', {
|
|
649
|
+
style: 'currency',
|
|
650
|
+
currency: 'USD',
|
|
651
|
+
}).format(data[dataIndex]),
|
|
652
|
+
[data],
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
const lineGradient = {
|
|
656
|
+
stops: [
|
|
657
|
+
{ offset: 0, color: negativeColor },
|
|
658
|
+
{ offset: 0, color: positiveColor },
|
|
659
|
+
],
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
return (
|
|
663
|
+
<CartesianChart
|
|
664
|
+
enableScrubbing
|
|
665
|
+
height={200}
|
|
666
|
+
inset={{ top: 32, bottom: 32, left: 16, right: 16 }}
|
|
667
|
+
series={[
|
|
668
|
+
{
|
|
669
|
+
id: 'prices',
|
|
670
|
+
data: data,
|
|
671
|
+
gradient: lineGradient,
|
|
672
|
+
},
|
|
673
|
+
]}
|
|
674
|
+
yAxis={{ domain: { min: -domainLimit, max: domainLimit } }}
|
|
675
|
+
>
|
|
676
|
+
<YAxis showGrid requestedTickCount={2} tickLabelFormatter={tickLabelFormatter} />
|
|
677
|
+
<Line
|
|
678
|
+
showArea
|
|
679
|
+
AreaComponent={MyGradient}
|
|
680
|
+
seriesId="prices"
|
|
681
|
+
strokeWidth={3}
|
|
682
|
+
transition={myTransitionConfig}
|
|
683
|
+
/>
|
|
684
|
+
<Scrubber
|
|
685
|
+
hideOverlay
|
|
686
|
+
beaconTransitions={{ update: myTransitionConfig }}
|
|
687
|
+
label={valueAtIndexFormatter}
|
|
688
|
+
/>
|
|
689
|
+
</CartesianChart>
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return <CustomTransitionsChart />;
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### Accessibility
|
|
698
|
+
|
|
699
|
+
You can use `accessibilityLabel` on the chart to provide a descriptive label.
|
|
700
|
+
|
|
701
|
+
```jsx
|
|
702
|
+
function BasicAccessible() {
|
|
703
|
+
const [scrubberPosition, setScrubberPosition] = useState<number | undefined>();
|
|
704
|
+
const data = useMemo(() => [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58], []);
|
|
705
|
+
|
|
706
|
+
// Chart-level accessibility label provides overview
|
|
707
|
+
const chartAccessibilityLabel = useMemo(() => {
|
|
708
|
+
const currentPrice = data[data.length - 1];
|
|
709
|
+
return `Price chart showing trend over ${data.length} data points. Current value: ${currentPrice}. Use arrow keys to adjust view`;
|
|
710
|
+
}, [data]);
|
|
711
|
+
|
|
712
|
+
// Scrubber-level accessibility label provides specific position info
|
|
713
|
+
const scrubberAccessibilityLabel = useCallback(
|
|
714
|
+
(index: number) => {
|
|
715
|
+
return `Price at position ${index + 1} of ${data.length}: ${data[index]}`;
|
|
716
|
+
},
|
|
717
|
+
[data],
|
|
718
|
+
);
|
|
468
719
|
|
|
469
720
|
const accessibilityLabel = useMemo(() => {
|
|
470
|
-
if (
|
|
471
|
-
return
|
|
472
|
-
|
|
473
|
-
return
|
|
474
|
-
}, [
|
|
721
|
+
if (scrubberPosition !== undefined) {
|
|
722
|
+
return scrubberAccessibilityLabel(scrubberPosition);
|
|
723
|
+
}
|
|
724
|
+
return chartAccessibilityLabel;
|
|
725
|
+
}, [scrubberPosition, chartAccessibilityLabel, scrubberAccessibilityLabel]);
|
|
475
726
|
|
|
476
727
|
return (
|
|
477
728
|
<LineChart
|
|
478
729
|
enableScrubbing
|
|
479
|
-
onScrubberPositionChange={setScrubIndex}
|
|
480
730
|
showArea
|
|
481
|
-
|
|
731
|
+
showYAxis
|
|
732
|
+
accessibilityLabel={accessibilityLabel}
|
|
733
|
+
height={200}
|
|
734
|
+
onScrubberPositionChange={setScrubberPosition}
|
|
482
735
|
series={[
|
|
483
736
|
{
|
|
484
|
-
id: '
|
|
485
|
-
data:
|
|
486
|
-
color: assets.btc.color,
|
|
737
|
+
id: 'prices',
|
|
738
|
+
data: data,
|
|
487
739
|
},
|
|
488
740
|
]}
|
|
489
|
-
|
|
490
|
-
|
|
741
|
+
yAxis={{
|
|
742
|
+
showGrid: true,
|
|
743
|
+
}}
|
|
491
744
|
>
|
|
492
|
-
<Scrubber
|
|
745
|
+
<Scrubber />
|
|
493
746
|
</LineChart>
|
|
494
747
|
);
|
|
495
748
|
}
|
|
496
749
|
```
|
|
497
750
|
|
|
498
|
-
###
|
|
751
|
+
### Styling
|
|
752
|
+
|
|
753
|
+
#### Axes
|
|
499
754
|
|
|
500
|
-
|
|
755
|
+
Using `showXAxis` and `showYAxis` allows you to display the axes. For more information, such as adjusting domain and range, see [XAxis](/components/graphs/XAxis) and [YAxis](/components/graphs/YAxis).
|
|
501
756
|
|
|
502
757
|
```jsx
|
|
503
|
-
|
|
504
|
-
|
|
758
|
+
<LineChart
|
|
759
|
+
showArea
|
|
760
|
+
showXAxis
|
|
761
|
+
showYAxis
|
|
762
|
+
height={200}
|
|
763
|
+
series={[
|
|
764
|
+
{
|
|
765
|
+
id: 'prices',
|
|
766
|
+
data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
|
|
767
|
+
},
|
|
768
|
+
]}
|
|
769
|
+
xAxis={{
|
|
770
|
+
showGrid: true,
|
|
771
|
+
showLine: true,
|
|
772
|
+
showTickMarks: true,
|
|
773
|
+
tickLabelFormatter: (dataX: number) => `Day ${dataX}`,
|
|
774
|
+
}}
|
|
775
|
+
yAxis={{
|
|
776
|
+
showGrid: true,
|
|
777
|
+
showLine: true,
|
|
778
|
+
showTickMarks: true,
|
|
779
|
+
}}
|
|
780
|
+
/>
|
|
781
|
+
```
|
|
505
782
|
|
|
506
|
-
|
|
507
|
-
const xData = [1, 2, 3, 5, 8, 10];
|
|
783
|
+
#### Fonts
|
|
508
784
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
785
|
+
By default, charts will use the default font of the system. You can use `fontFamily` at the chart level to customize this. For more, see [Skia's documentation on fonts](https://shopify.github.io/react-native-skia/docs/text/paragraph/#fonts).
|
|
786
|
+
|
|
787
|
+
```jsx
|
|
788
|
+
<LineChart
|
|
789
|
+
fontFamilies={["Coinbase Sans"]}
|
|
790
|
+
...
|
|
791
|
+
>
|
|
792
|
+
...
|
|
793
|
+
</LineChart>
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
You can also use `fontProvider` along with `useFonts` from Skia if you need to load a custom font.
|
|
797
|
+
|
|
798
|
+
```jsx
|
|
799
|
+
const fontProvider = useFonts({
|
|
800
|
+
MyCustomFontFamily: [
|
|
801
|
+
require("./MyCustomFont-Regular.ttf"),
|
|
802
|
+
require("./MyCustomFont-Bold.ttf"),
|
|
803
|
+
],
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
return (
|
|
807
|
+
<LineChart
|
|
808
|
+
fontFamilies={["MyCustomFontFamily"]}
|
|
809
|
+
fontProvider={fontProvider}
|
|
810
|
+
...
|
|
811
|
+
>
|
|
812
|
+
...
|
|
813
|
+
</LineChart>
|
|
814
|
+
);
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
#### Gradients
|
|
818
|
+
|
|
819
|
+
Gradients can be applied to the y-axis (default) or x-axis. Each stop requires an `offset`, which is based on the data within the x/y scale and `color`, with an optional `opacity` (defaults to 1).
|
|
820
|
+
|
|
821
|
+
Values in between stops will be interpolated smoothly using [srgb color space](https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperty).
|
|
822
|
+
|
|
823
|
+
```jsx
|
|
824
|
+
function Gradients() {
|
|
825
|
+
const theme = useTheme();
|
|
826
|
+
const spectrumColors: ThemeVars.SpectrumHue[] = [
|
|
827
|
+
'blue',
|
|
828
|
+
'green',
|
|
829
|
+
'orange',
|
|
830
|
+
'yellow',
|
|
831
|
+
'gray',
|
|
832
|
+
'indigo',
|
|
833
|
+
'pink',
|
|
834
|
+
'purple',
|
|
835
|
+
'red',
|
|
836
|
+
'teal',
|
|
837
|
+
'chartreuse',
|
|
838
|
+
];
|
|
839
|
+
const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
|
|
840
|
+
|
|
841
|
+
const [currentSpectrumColor, setCurrentSpectrumColor] = useState<ThemeVars.SpectrumHue>('pink');
|
|
513
842
|
|
|
514
843
|
return (
|
|
515
|
-
<
|
|
844
|
+
<VStack gap={2}>
|
|
845
|
+
<HStack flexWrap="wrap" gap={1} justifyContent="flex-end">
|
|
846
|
+
{spectrumColors.map((color) => (
|
|
847
|
+
<Pressable
|
|
848
|
+
key={color}
|
|
849
|
+
accessibilityLabel={`Select ${color}`}
|
|
850
|
+
height={16}
|
|
851
|
+
onPress={() => setCurrentSpectrumColor(color)}
|
|
852
|
+
style={{
|
|
853
|
+
backgroundColor: `rgb(${theme.spectrum[`${color}20`]})`,
|
|
854
|
+
borderColor: `rgb(${theme.spectrum[`${color}50`]})`,
|
|
855
|
+
borderWidth: 2,
|
|
856
|
+
}}
|
|
857
|
+
width={16}
|
|
858
|
+
/>
|
|
859
|
+
))}
|
|
860
|
+
</HStack>
|
|
861
|
+
<LineChart
|
|
862
|
+
showYAxis
|
|
863
|
+
height={200}
|
|
864
|
+
points
|
|
865
|
+
series={[
|
|
866
|
+
{
|
|
867
|
+
id: 'continuousGradient',
|
|
868
|
+
data: data,
|
|
869
|
+
gradient: {
|
|
870
|
+
stops: [
|
|
871
|
+
{ offset: 0, color: `rgb(${theme.spectrum[`${currentSpectrumColor}80`]})` },
|
|
872
|
+
{
|
|
873
|
+
offset: Math.max(...data),
|
|
874
|
+
color: `rgb(${theme.spectrum[`${currentSpectrumColor}20`]})`,
|
|
875
|
+
},
|
|
876
|
+
],
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
id: 'discreteGradient',
|
|
881
|
+
data: data.map((d) => d + 50),
|
|
882
|
+
// You can create a "discrete" gradient by having multiple stops at the same offset
|
|
883
|
+
gradient: {
|
|
884
|
+
stops: ({ min, max }) => [
|
|
885
|
+
// Allows a function which accepts min/max or direct array
|
|
886
|
+
{ offset: min, color: `rgb(${theme.spectrum[`${currentSpectrumColor}80`]})` },
|
|
887
|
+
{
|
|
888
|
+
offset: min + (max - min) / 3,
|
|
889
|
+
color: `rgb(${theme.spectrum[`${currentSpectrumColor}80`]})`,
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
offset: min + (max - min) / 3,
|
|
893
|
+
color: `rgb(${theme.spectrum[`${currentSpectrumColor}50`]})`,
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
offset: min + ((max - min) / 3) * 2,
|
|
897
|
+
color: `rgb(${theme.spectrum[`${currentSpectrumColor}50`]})`,
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
offset: min + ((max - min) / 3) * 2,
|
|
901
|
+
color: `rgb(${theme.spectrum[`${currentSpectrumColor}20`]})`,
|
|
902
|
+
},
|
|
903
|
+
{ offset: max, color: `rgb(${theme.spectrum[`${currentSpectrumColor}20`]})` },
|
|
904
|
+
],
|
|
905
|
+
},
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
id: 'xAxisGradient',
|
|
909
|
+
data: data.map((d) => d + 100),
|
|
910
|
+
gradient: {
|
|
911
|
+
// You can also configure by the x-axis.
|
|
912
|
+
axis: 'x',
|
|
913
|
+
stops: ({ min, max }) => [
|
|
914
|
+
{
|
|
915
|
+
offset: min,
|
|
916
|
+
color: `rgb(${theme.spectrum[`${currentSpectrumColor}80`]})`,
|
|
917
|
+
opacity: 0,
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
offset: max,
|
|
921
|
+
color: `rgb(${theme.spectrum[`${currentSpectrumColor}20`]})`,
|
|
922
|
+
opacity: 1,
|
|
923
|
+
},
|
|
924
|
+
],
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
]}
|
|
928
|
+
strokeWidth={4}
|
|
929
|
+
yAxis={{
|
|
930
|
+
showGrid: true,
|
|
931
|
+
}}
|
|
932
|
+
/>
|
|
933
|
+
</VStack>
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
You can even pass in a separate gradient for your `Line` and `Area` components.
|
|
939
|
+
|
|
940
|
+
```jsx
|
|
941
|
+
function GainLossChart() {
|
|
942
|
+
const theme = useTheme();
|
|
943
|
+
const data = useMemo(() => [-40, -28, -21, -5, 48, -5, -28, 2, -29, -46, 16, -30, -29, 8], []);
|
|
944
|
+
const negativeColor = `rgb(${theme.spectrum.gray15})`;
|
|
945
|
+
const positiveColor = theme.color.fgPositive;
|
|
946
|
+
|
|
947
|
+
const tickLabelFormatter = useCallback(
|
|
948
|
+
(value: number) =>
|
|
949
|
+
new Intl.NumberFormat('en-US', {
|
|
950
|
+
style: 'currency',
|
|
951
|
+
currency: 'USD',
|
|
952
|
+
maximumFractionDigits: 0,
|
|
953
|
+
}).format(value),
|
|
954
|
+
[],
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
// Line gradient: hard color change at 0 (full opacity for line)
|
|
958
|
+
const lineGradient = {
|
|
959
|
+
stops: [
|
|
960
|
+
{ offset: 0, color: negativeColor },
|
|
961
|
+
{ offset: 0, color: positiveColor },
|
|
962
|
+
],
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
const GradientDottedArea = memo((props: DottedAreaProps) => (
|
|
966
|
+
<DottedArea
|
|
967
|
+
{...props}
|
|
968
|
+
gradient={{
|
|
969
|
+
stops: ({ min, max }) => [
|
|
970
|
+
{ offset: min, color: negativeColor, opacity: 0.4 },
|
|
971
|
+
{ offset: 0, color: negativeColor, opacity: 0 },
|
|
972
|
+
{ offset: 0, color: positiveColor, opacity: 0 },
|
|
973
|
+
{ offset: max, color: positiveColor, opacity: 0.4 },
|
|
974
|
+
],
|
|
975
|
+
}}
|
|
976
|
+
/>
|
|
977
|
+
));
|
|
978
|
+
|
|
979
|
+
return (
|
|
980
|
+
<CartesianChart
|
|
516
981
|
enableScrubbing
|
|
517
|
-
|
|
982
|
+
height={200}
|
|
518
983
|
series={[
|
|
519
984
|
{
|
|
520
|
-
id: '
|
|
521
|
-
data:
|
|
985
|
+
id: 'prices',
|
|
986
|
+
data: data,
|
|
987
|
+
gradient: lineGradient,
|
|
522
988
|
},
|
|
523
989
|
]}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
renderPoints={() => true}
|
|
527
|
-
curve="natural"
|
|
528
|
-
showXAxis
|
|
529
|
-
xAxis={{ data: xData, showLine: true, showTickMarks: true, showGrid: true }}
|
|
530
|
-
showYAxis
|
|
531
|
-
yAxis={{
|
|
532
|
-
domain: { min: 0 },
|
|
533
|
-
position: 'left',
|
|
534
|
-
showLine: true,
|
|
535
|
-
showTickMarks: true,
|
|
536
|
-
showGrid: true,
|
|
990
|
+
xAxis={{
|
|
991
|
+
range: ({ min, max }) => ({ min, max: max - 16 }),
|
|
537
992
|
}}
|
|
538
|
-
inset={{ top: 16, right: 16, bottom: 0, left: 0 }}
|
|
539
|
-
accessibilityLabel={accessibilityLabel}
|
|
540
993
|
>
|
|
994
|
+
<YAxis showGrid requestedTickCount={2} tickLabelFormatter={tickLabelFormatter} />
|
|
995
|
+
<Line showArea AreaComponent={GradientDottedArea} seriesId="prices" strokeWidth={3} />
|
|
541
996
|
<Scrubber hideOverlay />
|
|
542
|
-
</
|
|
997
|
+
</CartesianChart>
|
|
543
998
|
);
|
|
544
999
|
}
|
|
545
1000
|
```
|
|
546
1001
|
|
|
547
|
-
|
|
1002
|
+
#### Lines
|
|
1003
|
+
|
|
1004
|
+
You can customize lines by placing props in `LineChart` or at each individual series. Lines can have a `type` of `solid` or `dotted`. They can optionally show an area underneath them (using `showArea`).
|
|
1005
|
+
|
|
1006
|
+
```jsx
|
|
1007
|
+
<LineChart
|
|
1008
|
+
height={200}
|
|
1009
|
+
series={[
|
|
1010
|
+
{
|
|
1011
|
+
id: 'top',
|
|
1012
|
+
data: [15, 28, 32, 44, 46, 36, 40, 45, 48, 38],
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
id: 'upperMiddle',
|
|
1016
|
+
data: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
|
|
1017
|
+
color: '#ef4444',
|
|
1018
|
+
type: 'dotted',
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
id: 'lowerMiddle',
|
|
1022
|
+
data: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
|
|
1023
|
+
color: '#f59e0b',
|
|
1024
|
+
curve: 'natural',
|
|
1025
|
+
gradient: {
|
|
1026
|
+
axis: 'x',
|
|
1027
|
+
stops: [
|
|
1028
|
+
{ offset: 0, color: '#E3D74D' },
|
|
1029
|
+
{ offset: 9, color: '#F7931A' },
|
|
1030
|
+
],
|
|
1031
|
+
},
|
|
1032
|
+
strokeWidth: 6,
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
id: 'bottom',
|
|
1036
|
+
data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14],
|
|
1037
|
+
color: '#800080',
|
|
1038
|
+
curve: 'step',
|
|
1039
|
+
AreaComponent: DottedArea,
|
|
1040
|
+
showArea: true,
|
|
1041
|
+
},
|
|
1042
|
+
]}
|
|
1043
|
+
/>
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
You can also add instances of [ReferenceLine](/components/graphs/ReferenceLine) to your LineChart to highlight a specific x or y value.
|
|
1047
|
+
|
|
1048
|
+
```jsx
|
|
1049
|
+
<LineChart
|
|
1050
|
+
enableScrubbing
|
|
1051
|
+
showArea
|
|
1052
|
+
height={200}
|
|
1053
|
+
series={[
|
|
1054
|
+
{
|
|
1055
|
+
id: 'prices',
|
|
1056
|
+
data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
|
|
1057
|
+
color: theme.color.fgPositive,
|
|
1058
|
+
},
|
|
1059
|
+
]}
|
|
1060
|
+
xAxis={{
|
|
1061
|
+
// Give space before the end of the chart for the scrubber
|
|
1062
|
+
range: ({ min, max }) => ({ min, max: max - 24 }),
|
|
1063
|
+
}}
|
|
1064
|
+
>
|
|
1065
|
+
<ReferenceLine
|
|
1066
|
+
LineComponent={(props) => <DottedLine {...props} dashIntervals={[0, 16]} strokeWidth={3} />}
|
|
1067
|
+
dataY={10}
|
|
1068
|
+
stroke={theme.color.fg}
|
|
1069
|
+
/>
|
|
1070
|
+
<Scrubber />
|
|
1071
|
+
</LineChart>
|
|
1072
|
+
```
|
|
548
1073
|
|
|
549
|
-
|
|
1074
|
+
#### Points
|
|
1075
|
+
|
|
1076
|
+
You can also add instances of [Point](/components/graphs/Point) directly inside of a LineChart.
|
|
550
1077
|
|
|
551
1078
|
```jsx
|
|
552
|
-
function
|
|
553
|
-
const [scrubIndex, setScrubIndex] = useState(undefined);
|
|
1079
|
+
function HighLowPrice() {
|
|
554
1080
|
const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
|
|
1081
|
+
const minPrice = Math.min(...data);
|
|
1082
|
+
const maxPrice = Math.max(...data);
|
|
555
1083
|
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
return
|
|
561
|
-
|
|
1084
|
+
const minPriceIndex = data.indexOf(minPrice);
|
|
1085
|
+
const maxPriceIndex = data.indexOf(maxPrice);
|
|
1086
|
+
|
|
1087
|
+
const formatPrice = useCallback((price: number) => {
|
|
1088
|
+
return `$${price.toLocaleString('en-US', {
|
|
1089
|
+
minimumFractionDigits: 2,
|
|
1090
|
+
maximumFractionDigits: 2,
|
|
1091
|
+
})}`;
|
|
1092
|
+
}, []);
|
|
562
1093
|
|
|
563
1094
|
return (
|
|
564
1095
|
<LineChart
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
height={150}
|
|
1096
|
+
showArea
|
|
1097
|
+
height={200}
|
|
568
1098
|
series={[
|
|
569
1099
|
{
|
|
570
1100
|
id: 'prices',
|
|
571
1101
|
data: data,
|
|
572
1102
|
},
|
|
573
1103
|
]}
|
|
574
|
-
|
|
575
|
-
|
|
1104
|
+
>
|
|
1105
|
+
<Point
|
|
1106
|
+
dataX={minPriceIndex}
|
|
1107
|
+
dataY={minPrice}
|
|
1108
|
+
label={formatPrice(minPrice)}
|
|
1109
|
+
labelPosition="bottom"
|
|
1110
|
+
/>
|
|
1111
|
+
<Point
|
|
1112
|
+
dataX={maxPriceIndex}
|
|
1113
|
+
dataY={maxPrice}
|
|
1114
|
+
label={formatPrice(maxPrice)}
|
|
1115
|
+
labelPosition="top"
|
|
1116
|
+
/>
|
|
1117
|
+
</LineChart>
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
#### Scrubber
|
|
1123
|
+
|
|
1124
|
+
When using [Scrubber](/components/graphs/Scrubber) with series that have labels, labels will automatically render to the side of the scrubber beacon.
|
|
1125
|
+
|
|
1126
|
+
You can customize the line used for and which series will render a scrubber beacon.
|
|
1127
|
+
|
|
1128
|
+
You can have scrubber beacon's pulse by either adding `idlePulse` to Scrubber or use Scrubber's ref to dynamically pulse.
|
|
1129
|
+
|
|
1130
|
+
```jsx
|
|
1131
|
+
function StylingScrubber() {
|
|
1132
|
+
const theme = useTheme();
|
|
1133
|
+
const pages = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G'];
|
|
1134
|
+
const pageViews = [2400, 1398, 9800, 3908, 4800, 3800, 4300];
|
|
1135
|
+
const uniqueVisitors = [4000, 3000, 2000, 2780, 1890, 2390, 3490];
|
|
1136
|
+
|
|
1137
|
+
const numberFormatter = useCallback(
|
|
1138
|
+
(value: number) => new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format(value),
|
|
1139
|
+
[],
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
return (
|
|
1143
|
+
<LineChart
|
|
1144
|
+
enableScrubbing
|
|
576
1145
|
showArea
|
|
1146
|
+
showXAxis
|
|
1147
|
+
showYAxis
|
|
1148
|
+
height={200}
|
|
1149
|
+
series={[
|
|
1150
|
+
{
|
|
1151
|
+
id: 'pageViews',
|
|
1152
|
+
data: pageViews,
|
|
1153
|
+
color: theme.color.accentBoldGreen,
|
|
1154
|
+
// Label will render next to scrubber beacon
|
|
1155
|
+
label: 'Page Views',
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
id: 'uniqueVisitors',
|
|
1159
|
+
data: uniqueVisitors,
|
|
1160
|
+
color: theme.color.accentBoldPurple,
|
|
1161
|
+
label: 'Unique Visitors',
|
|
1162
|
+
// Default area is gradient
|
|
1163
|
+
areaType: 'dotted',
|
|
1164
|
+
},
|
|
1165
|
+
]}
|
|
1166
|
+
xAxis={{
|
|
1167
|
+
// Used on the x-axis to provide context for each index from the series data array
|
|
1168
|
+
data: pages,
|
|
1169
|
+
}}
|
|
577
1170
|
yAxis={{
|
|
578
1171
|
showGrid: true,
|
|
1172
|
+
tickLabelFormatter: numberFormatter,
|
|
579
1173
|
}}
|
|
580
|
-
accessibilityLabel={accessibilityLabel}
|
|
581
1174
|
>
|
|
582
|
-
<Scrubber />
|
|
1175
|
+
<Scrubber idlePulse LineComponent={SolidLine} seriesIds={['pageViews']} />
|
|
583
1176
|
</LineChart>
|
|
584
1177
|
);
|
|
585
1178
|
}
|
|
586
1179
|
```
|
|
587
1180
|
|
|
588
|
-
|
|
1181
|
+
#### Sizing
|
|
1182
|
+
|
|
1183
|
+
Charts by default take up `100%` of the `width` and `height` available, but can be customized as any other component.
|
|
1184
|
+
|
|
1185
|
+
##### Compact
|
|
1186
|
+
|
|
1187
|
+
You can also have charts in a compact form.
|
|
589
1188
|
|
|
590
1189
|
```jsx
|
|
591
|
-
function
|
|
592
|
-
const
|
|
593
|
-
const
|
|
594
|
-
const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
|
|
1190
|
+
function Compact() {
|
|
1191
|
+
const theme = useTheme();
|
|
1192
|
+
const dimensions = { width: 62, height: 18 };
|
|
595
1193
|
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
return `Value: ${data[scrubIndex]} at position ${scrubIndex + 1}`;
|
|
601
|
-
}, [scrubIndex, data]);
|
|
1194
|
+
const sparklineData = prices
|
|
1195
|
+
.map((price) => parseFloat(price))
|
|
1196
|
+
.filter((price, index) => index % 10 === 0);
|
|
1197
|
+
const positiveFloor = Math.min(...sparklineData) - 10;
|
|
602
1198
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
1199
|
+
const negativeData = sparklineData.map((price) => -1 * price).reverse();
|
|
1200
|
+
const negativeCeiling = Math.max(...negativeData) + 10;
|
|
1201
|
+
|
|
1202
|
+
const formatPrice = useCallback((price: number) => {
|
|
1203
|
+
return `$${price.toLocaleString('en-US', {
|
|
1204
|
+
minimumFractionDigits: 2,
|
|
1205
|
+
maximumFractionDigits: 2,
|
|
1206
|
+
})}`;
|
|
1207
|
+
}, []);
|
|
1208
|
+
|
|
1209
|
+
type CompactChartProps = {
|
|
1210
|
+
data: number[];
|
|
1211
|
+
showArea?: boolean;
|
|
1212
|
+
color?: string;
|
|
1213
|
+
referenceY: number;
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
const CompactChart = memo(({ data, showArea, color, referenceY }: CompactChartProps) => (
|
|
1217
|
+
<Box style={{ padding: 1 }}>
|
|
608
1218
|
<LineChart
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1219
|
+
{...dimensions}
|
|
1220
|
+
enableScrubbing={false}
|
|
1221
|
+
inset={0}
|
|
612
1222
|
series={[
|
|
613
1223
|
{
|
|
614
|
-
id: '
|
|
615
|
-
data
|
|
1224
|
+
id: 'btc',
|
|
1225
|
+
data,
|
|
1226
|
+
color,
|
|
616
1227
|
},
|
|
617
1228
|
]}
|
|
618
|
-
|
|
619
|
-
showYAxis
|
|
620
|
-
showArea
|
|
621
|
-
yAxis={{
|
|
622
|
-
showGrid: true,
|
|
623
|
-
}}
|
|
624
|
-
aria-labelledby={headerId}
|
|
1229
|
+
showArea={showArea}
|
|
625
1230
|
>
|
|
626
|
-
<
|
|
1231
|
+
<ReferenceLine dataY={referenceY} />
|
|
627
1232
|
</LineChart>
|
|
1233
|
+
</Box>
|
|
1234
|
+
));
|
|
1235
|
+
|
|
1236
|
+
const ChartCell = memo(
|
|
1237
|
+
({
|
|
1238
|
+
data,
|
|
1239
|
+
showArea,
|
|
1240
|
+
color,
|
|
1241
|
+
referenceY,
|
|
1242
|
+
subdetail,
|
|
1243
|
+
}: CompactChartProps & { subdetail: string }) => {
|
|
1244
|
+
return (
|
|
1245
|
+
<ListCell
|
|
1246
|
+
detail={formatPrice(parseFloat(prices[0]))}
|
|
1247
|
+
intermediary={
|
|
1248
|
+
<CompactChart color={color} data={data} referenceY={referenceY} showArea={showArea} />
|
|
1249
|
+
}
|
|
1250
|
+
media={<Avatar src={assets.btc.imageUrl} />}
|
|
1251
|
+
onPress={() => console.log('clicked')}
|
|
1252
|
+
spacingVariant="condensed"
|
|
1253
|
+
style={{ padding: 0 }}
|
|
1254
|
+
subdetail={subdetail}
|
|
1255
|
+
/>
|
|
1256
|
+
);
|
|
1257
|
+
},
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
return (
|
|
1261
|
+
<VStack>
|
|
1262
|
+
<ChartCell
|
|
1263
|
+
color={assets.btc.color}
|
|
1264
|
+
data={sparklineData}
|
|
1265
|
+
referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
|
|
1266
|
+
subdetail="-4.55%"
|
|
1267
|
+
/>
|
|
1268
|
+
<ChartCell
|
|
1269
|
+
showArea
|
|
1270
|
+
color={assets.btc.color}
|
|
1271
|
+
data={sparklineData}
|
|
1272
|
+
referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
|
|
1273
|
+
subdetail="-4.55%"
|
|
1274
|
+
/>
|
|
1275
|
+
<ChartCell
|
|
1276
|
+
showArea
|
|
1277
|
+
color={theme.color.fgPositive}
|
|
1278
|
+
data={sparklineData}
|
|
1279
|
+
referenceY={positiveFloor}
|
|
1280
|
+
subdetail="+0.25%"
|
|
1281
|
+
/>
|
|
1282
|
+
<ChartCell
|
|
1283
|
+
showArea
|
|
1284
|
+
color={theme.color.fgNegative}
|
|
1285
|
+
data={negativeData}
|
|
1286
|
+
referenceY={negativeCeiling}
|
|
1287
|
+
subdetail="-4.55%"
|
|
1288
|
+
/>
|
|
628
1289
|
</VStack>
|
|
629
1290
|
);
|
|
630
1291
|
}
|
|
631
1292
|
```
|
|
632
1293
|
|
|
633
|
-
###
|
|
1294
|
+
### Composed Examples
|
|
634
1295
|
|
|
635
1296
|
#### Asset Price with Dotted Area
|
|
636
1297
|
|
|
1298
|
+
You can use [PeriodSelector](/components/graphs/PeriodSelector) to have a chart where the user can select a time period and the chart automatically animates.
|
|
1299
|
+
|
|
637
1300
|
```jsx
|
|
638
1301
|
function AssetPriceWithDottedArea() {
|
|
1302
|
+
const fontMgr = useMemo(() => {
|
|
1303
|
+
const fontProvider = Skia.TypefaceFontProvider.Make();
|
|
1304
|
+
// Register system fonts if available, otherwise Skia will use defaults
|
|
1305
|
+
return fontProvider;
|
|
1306
|
+
}, []);
|
|
1307
|
+
|
|
639
1308
|
const BTCTab: TabComponent = memo(
|
|
640
|
-
forwardRef(
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const isActive = activeTab?.id === props.id;
|
|
644
|
-
|
|
645
|
-
return (
|
|
646
|
-
<SegmentedTab
|
|
647
|
-
ref={ref}
|
|
648
|
-
label={
|
|
649
|
-
<TextLabel1
|
|
650
|
-
style={{
|
|
651
|
-
transition: 'color 0.2s ease',
|
|
652
|
-
color: isActive ? assets.btc.color : undefined,
|
|
653
|
-
}}
|
|
654
|
-
>
|
|
655
|
-
{label}
|
|
656
|
-
</TextLabel1>
|
|
657
|
-
}
|
|
658
|
-
{...props}
|
|
659
|
-
/>
|
|
660
|
-
);
|
|
661
|
-
},
|
|
662
|
-
),
|
|
663
|
-
);
|
|
1309
|
+
forwardRef(({ label, ...props }: SegmentedTabProps, ref: React.ForwardedRef<View>) => {
|
|
1310
|
+
const { activeTab } = useTabsContext();
|
|
1311
|
+
const isActive = activeTab?.id === props.id;
|
|
664
1312
|
|
|
665
|
-
|
|
1313
|
+
return (
|
|
1314
|
+
<SegmentedTab
|
|
1315
|
+
ref={ref}
|
|
1316
|
+
label={
|
|
1317
|
+
<TextLabel1
|
|
1318
|
+
style={{
|
|
1319
|
+
color: isActive ? assets.btc.color : undefined,
|
|
1320
|
+
}}
|
|
1321
|
+
>
|
|
1322
|
+
{label}
|
|
1323
|
+
</TextLabel1>
|
|
1324
|
+
}
|
|
1325
|
+
{...props}
|
|
1326
|
+
/>
|
|
1327
|
+
);
|
|
1328
|
+
}),
|
|
1329
|
+
);
|
|
1330
|
+
const BTCActiveIndicator = memo(({ style, ...props }: TabsActiveIndicatorProps) => (
|
|
666
1331
|
<PeriodSelectorActiveIndicator
|
|
667
1332
|
{...props}
|
|
668
|
-
style={
|
|
1333
|
+
style={[style, { backgroundColor: `${assets.btc.color}1A` }]}
|
|
669
1334
|
/>
|
|
670
1335
|
));
|
|
671
1336
|
|
|
672
|
-
const AssetPriceDotted = memo(() => {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1337
|
+
const AssetPriceDotted = memo(() => {
|
|
1338
|
+
const theme = useTheme();
|
|
1339
|
+
const currentPrice =
|
|
1340
|
+
sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
|
|
1341
|
+
const tabs = useMemo(
|
|
1342
|
+
() => [
|
|
1343
|
+
{ id: 'hour', label: '1H' },
|
|
1344
|
+
{ id: 'day', label: '1D' },
|
|
1345
|
+
{ id: 'week', label: '1W' },
|
|
1346
|
+
{ id: 'month', label: '1M' },
|
|
1347
|
+
{ id: 'year', label: '1Y' },
|
|
1348
|
+
{ id: 'all', label: 'All' },
|
|
1349
|
+
],
|
|
1350
|
+
[],
|
|
1351
|
+
);
|
|
1352
|
+
const [timePeriod, setTimePeriod] = useState<TabValue>(tabs[0]);
|
|
1353
|
+
|
|
1354
|
+
const sparklineTimePeriodData = useMemo(() => {
|
|
1355
|
+
return sparklineInteractiveData[timePeriod.id as keyof typeof sparklineInteractiveData];
|
|
1356
|
+
}, [timePeriod]);
|
|
1357
|
+
|
|
1358
|
+
const sparklineTimePeriodDataValues = useMemo(() => {
|
|
1359
|
+
return sparklineTimePeriodData.map((d) => d.value);
|
|
1360
|
+
}, [sparklineTimePeriodData]);
|
|
1361
|
+
|
|
1362
|
+
const sparklineTimePeriodDataTimestamps = useMemo(() => {
|
|
1363
|
+
return sparklineTimePeriodData.map((d) => d.date);
|
|
1364
|
+
}, [sparklineTimePeriodData]);
|
|
1365
|
+
|
|
1366
|
+
const onPeriodChange = useCallback(
|
|
1367
|
+
(period: TabValue | null) => {
|
|
1368
|
+
setTimePeriod(period || tabs[0]);
|
|
1369
|
+
},
|
|
1370
|
+
[tabs, setTimePeriod],
|
|
1371
|
+
);
|
|
1372
|
+
|
|
1373
|
+
const priceFormatter = useMemo(
|
|
1374
|
+
() =>
|
|
1375
|
+
new Intl.NumberFormat('en-US', {
|
|
1376
|
+
style: 'currency',
|
|
1377
|
+
currency: 'USD',
|
|
1378
|
+
}),
|
|
1379
|
+
[],
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
const formatPrice = useCallback(
|
|
1383
|
+
(price: number) => {
|
|
1384
|
+
return priceFormatter.format(price);
|
|
1385
|
+
},
|
|
1386
|
+
[priceFormatter],
|
|
1387
|
+
);
|
|
1388
|
+
|
|
1389
|
+
const formatDate = useCallback((date: Date) => {
|
|
1390
|
+
const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
|
|
1391
|
+
|
|
1392
|
+
const monthDay = date.toLocaleDateString('en-US', {
|
|
1393
|
+
month: 'short',
|
|
1394
|
+
day: 'numeric',
|
|
1395
|
+
});
|
|
1396
|
+
|
|
1397
|
+
const time = date.toLocaleTimeString('en-US', {
|
|
1398
|
+
hour: 'numeric',
|
|
1399
|
+
minute: '2-digit',
|
|
1400
|
+
hour12: true,
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
return `${dayOfWeek}, ${monthDay}, ${time}`;
|
|
1404
|
+
}, []);
|
|
1405
|
+
|
|
1406
|
+
return (
|
|
1407
|
+
<VStack gap={2}>
|
|
1408
|
+
<SectionHeader
|
|
1409
|
+
balance={<Text font="title2">{formatPrice(currentPrice)}</Text>}
|
|
1410
|
+
end={
|
|
1411
|
+
<VStack justifyContent="center">
|
|
1412
|
+
<RemoteImage shape="circle" size="xl" source={assets.btc.imageUrl} />
|
|
1413
|
+
</VStack>
|
|
1414
|
+
}
|
|
1415
|
+
title={<Text font="title1">Bitcoin</Text>}
|
|
1416
|
+
/>
|
|
1417
|
+
<LineChart
|
|
1418
|
+
enableScrubbing
|
|
1419
|
+
showArea
|
|
1420
|
+
areaType="dotted"
|
|
1421
|
+
height={200}
|
|
1422
|
+
inset={{ top: 52 }}
|
|
1423
|
+
series={[
|
|
1424
|
+
{
|
|
1425
|
+
id: 'btc',
|
|
1426
|
+
data: sparklineTimePeriodDataValues,
|
|
1427
|
+
color: assets.btc.color,
|
|
1428
|
+
},
|
|
1429
|
+
]}
|
|
1430
|
+
>
|
|
1431
|
+
<Scrubber
|
|
1432
|
+
idlePulse
|
|
1433
|
+
label={(d: number) => {
|
|
1434
|
+
const date = formatDate(sparklineTimePeriodDataTimestamps[d]);
|
|
1435
|
+
const price = formatPrice(sparklineTimePeriodDataValues[d]);
|
|
1436
|
+
|
|
1437
|
+
const regularStyle: SkTextStyle = {
|
|
1438
|
+
fontFamilies: ['Inter'],
|
|
1439
|
+
fontSize: 14,
|
|
1440
|
+
fontStyle: {
|
|
1441
|
+
weight: FontWeight.Normal,
|
|
1442
|
+
},
|
|
1443
|
+
color: Skia.Color(theme.color.fgMuted),
|
|
1444
|
+
};
|
|
1445
|
+
|
|
1446
|
+
const boldStyle: SkTextStyle = {
|
|
1447
|
+
fontFamilies: ['Inter'],
|
|
1448
|
+
...regularStyle,
|
|
1449
|
+
fontStyle: {
|
|
1450
|
+
weight: FontWeight.Bold,
|
|
1451
|
+
},
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
// 3. Use the ParagraphBuilder
|
|
1455
|
+
const builder = Skia.ParagraphBuilder.Make(
|
|
1456
|
+
{
|
|
1457
|
+
textAlign: TextAlign.Left,
|
|
1458
|
+
},
|
|
1459
|
+
fontMgr,
|
|
1460
|
+
);
|
|
1461
|
+
|
|
1462
|
+
builder.pushStyle(boldStyle);
|
|
1463
|
+
builder.addText(price);
|
|
1464
|
+
|
|
1465
|
+
builder.pushStyle(regularStyle);
|
|
1466
|
+
builder.addText(` ${date}`);
|
|
1467
|
+
|
|
1468
|
+
const para = builder.build();
|
|
1469
|
+
para.layout(512);
|
|
1470
|
+
return para;
|
|
1471
|
+
}}
|
|
1472
|
+
labelElevated
|
|
1473
|
+
/>
|
|
1474
|
+
</LineChart>
|
|
1475
|
+
<PeriodSelector
|
|
1476
|
+
TabComponent={BTCTab}
|
|
1477
|
+
TabsActiveIndicatorComponent={BTCActiveIndicator}
|
|
1478
|
+
activeTab={timePeriod}
|
|
1479
|
+
onChange={onPeriodChange}
|
|
1480
|
+
tabs={tabs}
|
|
1481
|
+
/>
|
|
1482
|
+
</VStack>
|
|
1483
|
+
);
|
|
1484
|
+
});
|
|
1485
|
+
|
|
1486
|
+
return <AssetPriceDotted />;
|
|
1487
|
+
}
|
|
1488
|
+
```
|
|
685
1489
|
|
|
686
|
-
|
|
687
|
-
return sparklineInteractiveData[timePeriod.id as keyof typeof sparklineInteractiveData];
|
|
688
|
-
}, [timePeriod]);
|
|
1490
|
+
#### Monotone Asset Price
|
|
689
1491
|
|
|
690
|
-
|
|
691
|
-
return sparklineTimePeriodData.map((d) => d.value);
|
|
692
|
-
}, [sparklineTimePeriodData]);
|
|
1492
|
+
You can adjust [YAxis](/components/graphs/YAxis) and [Scrubber](/components/graphs/Scrubber) to have a chart where the y-axis is overlaid and the beacon is inverted in style.
|
|
693
1493
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1494
|
+
```jsx
|
|
1495
|
+
function MonotoneAssetPrice() {
|
|
1496
|
+
const theme = useTheme();
|
|
1497
|
+
const prices = sparklineInteractiveData.hour;
|
|
697
1498
|
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
);
|
|
1499
|
+
const fontMgr = useMemo(() => {
|
|
1500
|
+
const fontProvider = Skia.TypefaceFontProvider.Make();
|
|
1501
|
+
// Register system fonts if available, otherwise Skia will use defaults
|
|
1502
|
+
return fontProvider;
|
|
1503
|
+
}, []);
|
|
704
1504
|
|
|
705
1505
|
const priceFormatter = useMemo(
|
|
706
1506
|
() =>
|
|
@@ -720,9 +1520,12 @@ const BTCActiveIndicator = memo(({ style, ...props }: TabsActiveIndicatorProps)
|
|
|
720
1520
|
[],
|
|
721
1521
|
);
|
|
722
1522
|
|
|
723
|
-
const formatPrice = useCallback(
|
|
724
|
-
|
|
725
|
-
|
|
1523
|
+
const formatPrice = useCallback(
|
|
1524
|
+
(price: number) => {
|
|
1525
|
+
return priceFormatter.format(price);
|
|
1526
|
+
},
|
|
1527
|
+
[priceFormatter],
|
|
1528
|
+
);
|
|
726
1529
|
|
|
727
1530
|
const formatDate = useCallback((date: Date) => {
|
|
728
1531
|
const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
|
|
@@ -743,351 +1546,198 @@ const BTCActiveIndicator = memo(({ style, ...props }: TabsActiveIndicatorProps)
|
|
|
743
1546
|
|
|
744
1547
|
const scrubberLabel = useCallback(
|
|
745
1548
|
(index: number) => {
|
|
746
|
-
const price = scrubberPriceFormatter.format(
|
|
747
|
-
const date = formatDate(
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
1549
|
+
const price = scrubberPriceFormatter.format(prices[index].value);
|
|
1550
|
+
const date = formatDate(prices[index].date);
|
|
1551
|
+
|
|
1552
|
+
const regularStyle: SkTextStyle = {
|
|
1553
|
+
fontFamilies: ['Inter'],
|
|
1554
|
+
fontSize: 14,
|
|
1555
|
+
fontStyle: {
|
|
1556
|
+
weight: FontWeight.Normal,
|
|
1557
|
+
},
|
|
1558
|
+
color: Skia.Color(theme.color.fgMuted),
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
const boldStyle: SkTextStyle = {
|
|
1562
|
+
fontFamilies: ['Inter'],
|
|
1563
|
+
...regularStyle,
|
|
1564
|
+
fontStyle: {
|
|
1565
|
+
weight: FontWeight.Bold,
|
|
1566
|
+
},
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
const builder = Skia.ParagraphBuilder.Make(
|
|
1570
|
+
{
|
|
1571
|
+
textAlign: TextAlign.Left,
|
|
1572
|
+
},
|
|
1573
|
+
fontMgr,
|
|
752
1574
|
);
|
|
1575
|
+
|
|
1576
|
+
builder.pushStyle(boldStyle);
|
|
1577
|
+
builder.addText(`${price} USD`);
|
|
1578
|
+
|
|
1579
|
+
builder.pushStyle(regularStyle);
|
|
1580
|
+
builder.addText(` ${date}`);
|
|
1581
|
+
|
|
1582
|
+
const para = builder.build();
|
|
1583
|
+
para.layout(512);
|
|
1584
|
+
return para;
|
|
753
1585
|
},
|
|
754
|
-
[scrubberPriceFormatter,
|
|
1586
|
+
[scrubberPriceFormatter, prices, formatDate, theme.color.fgMuted, fontMgr],
|
|
755
1587
|
);
|
|
756
1588
|
|
|
757
|
-
const
|
|
758
|
-
(
|
|
759
|
-
|
|
760
|
-
const date = formatDate(sparklineTimePeriodDataTimestamps[index]);
|
|
761
|
-
return `${price} USD ${date}`;
|
|
1589
|
+
const formatAxisLabelPrice = useCallback(
|
|
1590
|
+
(price: number) => {
|
|
1591
|
+
return formatPrice(price);
|
|
762
1592
|
},
|
|
763
|
-
[
|
|
1593
|
+
[formatPrice],
|
|
764
1594
|
);
|
|
765
1595
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
title={<Text font="title1">Bitcoin</Text>}
|
|
771
|
-
balance={<Text font="title2">{formatPrice(currentPrice)}</Text>}
|
|
772
|
-
end={
|
|
773
|
-
<VStack justifyContent="center">
|
|
774
|
-
<RemoteImage source={assets.btc.imageUrl} size="xl" shape="circle" />
|
|
775
|
-
</VStack>
|
|
776
|
-
}
|
|
777
|
-
/>
|
|
778
|
-
<LineChart
|
|
779
|
-
overflow="visible"
|
|
780
|
-
enableScrubbing
|
|
781
|
-
onScrubberPositionChange={setScrubIndex}
|
|
782
|
-
series={[
|
|
783
|
-
{
|
|
784
|
-
id: 'btc',
|
|
785
|
-
data: sparklineTimePeriodDataValues,
|
|
786
|
-
color: assets.btc.color,
|
|
787
|
-
},
|
|
788
|
-
]}
|
|
789
|
-
showArea
|
|
790
|
-
areaType="dotted"
|
|
791
|
-
height={150}
|
|
792
|
-
style={{ outlineColor: assets.btc.color }}
|
|
793
|
-
accessibilityLabel={scrubberLabel}
|
|
794
|
-
padding={{ left: 2, right: 2 }}
|
|
795
|
-
>
|
|
796
|
-
<Scrubber label={scrubberLabel} labelProps={{ elevation: 1 }} idlePulse />
|
|
797
|
-
</LineChart>
|
|
798
|
-
<PeriodSelector
|
|
799
|
-
TabComponent={BTCTab}
|
|
800
|
-
TabsActiveIndicatorComponent={BTCActiveIndicator}
|
|
801
|
-
tabs={tabs}
|
|
802
|
-
activeTab={timePeriod}
|
|
803
|
-
onChange={onPeriodChange}
|
|
804
|
-
/>
|
|
805
|
-
</VStack>
|
|
806
|
-
)});
|
|
807
|
-
|
|
808
|
-
return <AssetPriceDotted />;
|
|
809
|
-
};
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
#### Forecast Asset Price
|
|
813
|
-
|
|
814
|
-
```jsx
|
|
815
|
-
function ForecastAssetPrice() {
|
|
816
|
-
const ForecastAreaComponent = memo(
|
|
817
|
-
(props: AreaComponentProps) => (
|
|
818
|
-
<DottedArea {...props} peakOpacity={0.4} baselineOpacity={0.4} />
|
|
819
|
-
),
|
|
1596
|
+
// Custom tick label component with offset positioning
|
|
1597
|
+
const CustomYAxisTickLabel = useCallback(
|
|
1598
|
+
(props: any) => <DefaultAxisTickLabel {...props} dx={4} dy={-12} horizontalAlignment="left" />,
|
|
1599
|
+
[],
|
|
820
1600
|
);
|
|
821
1601
|
|
|
822
|
-
const
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
const allData = sparklineInteractiveData.all;
|
|
827
|
-
if (!allData || allData.length === 0) return [];
|
|
828
|
-
|
|
829
|
-
const timelineData = allData.filter((point) => point.date >= startDate);
|
|
830
|
-
|
|
831
|
-
return timelineData.map((point) => ({
|
|
832
|
-
date: point.date,
|
|
833
|
-
value: point.value,
|
|
834
|
-
}));
|
|
835
|
-
}, []);
|
|
836
|
-
|
|
837
|
-
const historicalData = useMemo(() => getDataFromSparkline(new Date('2019-01-01')), [getDataFromSparkline]);
|
|
838
|
-
|
|
839
|
-
const annualGrowthRate = 10;
|
|
840
|
-
|
|
841
|
-
const generateForecastData = useCallback(
|
|
842
|
-
(lastDate: Date, lastPrice: number, growthRate: number) => {
|
|
843
|
-
const dailyGrowthRate = Math.pow(1 + growthRate / 100, 1 / 365) - 1;
|
|
844
|
-
const forecastData = [];
|
|
845
|
-
const fiveYearsFromNow = new Date(lastDate);
|
|
846
|
-
fiveYearsFromNow.setFullYear(fiveYearsFromNow.getFullYear() + 5);
|
|
847
|
-
|
|
848
|
-
// Generate daily forecast points for 5 years
|
|
849
|
-
const currentDate = new Date(lastDate);
|
|
850
|
-
let currentPrice = lastPrice;
|
|
851
|
-
|
|
852
|
-
while (currentDate <= fiveYearsFromNow) {
|
|
853
|
-
currentPrice = currentPrice * (1 + dailyGrowthRate * 10);
|
|
854
|
-
forecastData.push({
|
|
855
|
-
date: new Date(currentDate),
|
|
856
|
-
value: Math.round(currentPrice),
|
|
857
|
-
});
|
|
858
|
-
currentDate.setDate(currentDate.getDate() + 10);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
return forecastData;
|
|
862
|
-
},
|
|
863
|
-
[],
|
|
864
|
-
);
|
|
865
|
-
|
|
866
|
-
const priceFormatter = useMemo(
|
|
867
|
-
() =>
|
|
868
|
-
new Intl.NumberFormat('en-US', {
|
|
869
|
-
minimumFractionDigits: 2,
|
|
870
|
-
maximumFractionDigits: 2,
|
|
871
|
-
}),
|
|
872
|
-
[],
|
|
873
|
-
);
|
|
874
|
-
|
|
875
|
-
const formatDate = useCallback((date: Date) => {
|
|
876
|
-
const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
|
|
877
|
-
|
|
878
|
-
const monthDay = date.toLocaleDateString('en-US', {
|
|
879
|
-
month: 'short',
|
|
880
|
-
day: 'numeric',
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
const time = date.toLocaleTimeString('en-US', {
|
|
884
|
-
hour: 'numeric',
|
|
885
|
-
minute: '2-digit',
|
|
886
|
-
hour12: true,
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
return `${dayOfWeek}, ${monthDay}, ${time}`;
|
|
890
|
-
}, []);
|
|
891
|
-
|
|
892
|
-
const forecastData = useMemo(() => {
|
|
893
|
-
if (historicalData.length === 0) return [];
|
|
894
|
-
const lastPoint = historicalData[historicalData.length - 1];
|
|
895
|
-
return generateForecastData(lastPoint.date, lastPoint.value, annualGrowthRate);
|
|
896
|
-
}, [generateForecastData, historicalData, annualGrowthRate]);
|
|
1602
|
+
const CustomScrubberBeacon = memo(
|
|
1603
|
+
({ dataX, dataY, seriesId, isIdle, animate = true }: ScrubberBeaconProps) => {
|
|
1604
|
+
const { getSeries, getXSerializableScale, getYSerializableScale } =
|
|
1605
|
+
useCartesianChartContext();
|
|
897
1606
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1607
|
+
const targetSeries = useMemo(() => getSeries(seriesId), [getSeries, seriesId]);
|
|
1608
|
+
const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
|
|
1609
|
+
const yScale = useMemo(
|
|
1610
|
+
() => getYSerializableScale(targetSeries?.yAxisId),
|
|
1611
|
+
[getYSerializableScale, targetSeries?.yAxisId],
|
|
1612
|
+
);
|
|
903
1613
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1614
|
+
const animatedX = useSharedValue(0);
|
|
1615
|
+
const animatedY = useSharedValue(0);
|
|
1616
|
+
|
|
1617
|
+
// Calculate the target point position - project data to pixels
|
|
1618
|
+
const targetPoint = useDerivedValue(() => {
|
|
1619
|
+
if (!xScale || !yScale) return { x: 0, y: 0 };
|
|
1620
|
+
return projectPointWithSerializableScale({
|
|
1621
|
+
x: unwrapAnimatedValue(dataX),
|
|
1622
|
+
y: unwrapAnimatedValue(dataY),
|
|
1623
|
+
xScale,
|
|
1624
|
+
yScale,
|
|
1625
|
+
});
|
|
1626
|
+
}, [dataX, dataY, xScale, yScale]);
|
|
908
1627
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1628
|
+
useAnimatedReaction(
|
|
1629
|
+
() => {
|
|
1630
|
+
return { point: targetPoint.value, isIdle: unwrapAnimatedValue(isIdle) };
|
|
1631
|
+
},
|
|
1632
|
+
(current, previous) => {
|
|
1633
|
+
// When animation is disabled, on initial render, or when we are starting,
|
|
1634
|
+
// continuing, or finishing scrubbing we should immediately transition
|
|
1635
|
+
if (!animate || previous === null || !previous.isIdle || !current.isIdle) {
|
|
1636
|
+
animatedX.value = current.point.x;
|
|
1637
|
+
animatedY.value = current.point.y;
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
913
1640
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1641
|
+
animatedX.value = buildTransition(current.point.x, defaultTransition);
|
|
1642
|
+
animatedY.value = buildTransition(current.point.y, defaultTransition);
|
|
1643
|
+
},
|
|
1644
|
+
[animate],
|
|
1645
|
+
);
|
|
918
1646
|
|
|
919
|
-
|
|
920
|
-
(
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
return (
|
|
924
|
-
<>
|
|
925
|
-
<tspan style={{ fontWeight: 'bold' }}>{price} USD</tspan> {date}
|
|
926
|
-
</>
|
|
927
|
-
);
|
|
928
|
-
},
|
|
929
|
-
[priceFormatter, allDataPoints, formatDate],
|
|
930
|
-
);
|
|
1647
|
+
// Create animated point using the animated values
|
|
1648
|
+
const animatedPoint = useDerivedValue(() => {
|
|
1649
|
+
return { x: animatedX.value, y: animatedY.value };
|
|
1650
|
+
}, [animatedX, animatedY]);
|
|
931
1651
|
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1652
|
+
return (
|
|
1653
|
+
<>
|
|
1654
|
+
<Circle c={animatedPoint} color={theme.color.bg} r={5} />
|
|
1655
|
+
<Circle c={animatedPoint} color={theme.color.fg} r={5} strokeWidth={3} style="stroke" />
|
|
1656
|
+
</>
|
|
1657
|
+
);
|
|
1658
|
+
},
|
|
1659
|
+
);
|
|
940
1660
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
id: 'historical',
|
|
959
|
-
data: historicalDataValues,
|
|
960
|
-
color: assets.btc.color,
|
|
961
|
-
},
|
|
962
|
-
{
|
|
963
|
-
id: 'forecast',
|
|
964
|
-
data: forecastDataValues,
|
|
965
|
-
color: assets.btc.color,
|
|
966
|
-
type: 'dotted',
|
|
967
|
-
},
|
|
968
|
-
]}
|
|
969
|
-
xAxis={{
|
|
970
|
-
data: xAxisData,
|
|
971
|
-
tickLabelFormatter: (value: number) => {
|
|
972
|
-
return new Date(value).toLocaleDateString('en-US', {
|
|
973
|
-
month: 'numeric',
|
|
974
|
-
year: 'numeric',
|
|
975
|
-
});
|
|
1661
|
+
return (
|
|
1662
|
+
<LineChart
|
|
1663
|
+
enableScrubbing
|
|
1664
|
+
showYAxis
|
|
1665
|
+
height={200}
|
|
1666
|
+
inset={{ top: 64 }}
|
|
1667
|
+
series={[
|
|
1668
|
+
{
|
|
1669
|
+
id: 'btc',
|
|
1670
|
+
data: prices.map((price) => price.value),
|
|
1671
|
+
color: theme.color.fg,
|
|
1672
|
+
gradient: {
|
|
1673
|
+
axis: 'x',
|
|
1674
|
+
stops: ({ min }) => [
|
|
1675
|
+
{ offset: min, color: theme.color.fg, opacity: 0 },
|
|
1676
|
+
{ offset: 32, color: theme.color.fg, opacity: 1 },
|
|
1677
|
+
],
|
|
976
1678
|
},
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
1679
|
+
},
|
|
1680
|
+
]}
|
|
1681
|
+
xAxis={{
|
|
1682
|
+
range: ({ max }) => ({ min: 96, max }),
|
|
1683
|
+
}}
|
|
1684
|
+
yAxis={{
|
|
1685
|
+
position: 'left',
|
|
1686
|
+
width: 0,
|
|
1687
|
+
showGrid: true,
|
|
1688
|
+
tickLabelFormatter: formatAxisLabelPrice,
|
|
1689
|
+
TickLabelComponent: CustomYAxisTickLabel,
|
|
1690
|
+
}}
|
|
1691
|
+
>
|
|
1692
|
+
<Scrubber
|
|
1693
|
+
labelElevated
|
|
1694
|
+
hideOverlay
|
|
1695
|
+
BeaconComponent={CustomScrubberBeacon}
|
|
1696
|
+
LineComponent={SolidLine}
|
|
1697
|
+
label={scrubberLabel}
|
|
1698
|
+
/>
|
|
1699
|
+
</LineChart>
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
989
1702
|
```
|
|
990
1703
|
|
|
991
|
-
#### Availability
|
|
1704
|
+
#### Service Availability
|
|
1705
|
+
|
|
1706
|
+
You can have irregular data points by passing in `data` to `xAxis`.
|
|
992
1707
|
|
|
993
1708
|
```jsx
|
|
994
|
-
function
|
|
1709
|
+
function ServiceAvailability() {
|
|
995
1710
|
const theme = useTheme();
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
date: new Date('2022-01-
|
|
1001
|
-
availability:
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
{
|
|
1008
|
-
date: new Date('2022-01-04'),
|
|
1009
|
-
availability: 82,
|
|
1010
|
-
},
|
|
1011
|
-
{
|
|
1012
|
-
date: new Date('2022-01-06'),
|
|
1013
|
-
availability: 91,
|
|
1014
|
-
},
|
|
1015
|
-
{
|
|
1016
|
-
date: new Date('2022-01-07'),
|
|
1017
|
-
availability: 92,
|
|
1018
|
-
},
|
|
1019
|
-
{
|
|
1020
|
-
date: new Date('2022-01-10'),
|
|
1021
|
-
availability: 86,
|
|
1022
|
-
},
|
|
1023
|
-
];
|
|
1024
|
-
|
|
1025
|
-
const accessibilityLabel = useMemo(() => {
|
|
1026
|
-
if (scrubIndex === undefined) return undefined;
|
|
1027
|
-
const event = availabilityEvents[scrubIndex];
|
|
1028
|
-
const formattedDate = event.date.toLocaleDateString('en-US', {
|
|
1029
|
-
weekday: 'short',
|
|
1030
|
-
month: 'short',
|
|
1031
|
-
day: 'numeric',
|
|
1032
|
-
year: 'numeric',
|
|
1033
|
-
});
|
|
1034
|
-
const status =
|
|
1035
|
-
event.availability >= 90 ? 'Good' : event.availability >= 85 ? 'Warning' : 'Critical';
|
|
1036
|
-
return `${formattedDate}: Availability ${event.availability}% - Status: ${status}`;
|
|
1037
|
-
}, [scrubIndex, availabilityEvents]);
|
|
1038
|
-
|
|
1039
|
-
const ChartDefs = memo(({ yellowThresholdPercentage = 85, greenThresholdPercentage = 90 }) => {
|
|
1040
|
-
const { drawingArea, height, series, getYScale, getYAxis } = useCartesianChartContext();
|
|
1041
|
-
const yScale = getYScale();
|
|
1042
|
-
const yAxis = getYAxis();
|
|
1043
|
-
|
|
1044
|
-
if (!series || !drawingArea || !yScale) return null;
|
|
1045
|
-
|
|
1046
|
-
const rangeBounds = yAxis?.domain;
|
|
1047
|
-
const rangeMin = rangeBounds?.min ?? 0;
|
|
1048
|
-
const rangeMax = rangeBounds?.max ?? 100;
|
|
1049
|
-
|
|
1050
|
-
// Calculate the Y positions in the chart coordinate system
|
|
1051
|
-
const yellowThresholdY = yScale(yellowThresholdPercentage) ?? 0;
|
|
1052
|
-
const greenThresholdY = yScale(greenThresholdPercentage) ?? 0;
|
|
1053
|
-
const minY = yScale(rangeMax) ?? 0; // Top of chart (max value)
|
|
1054
|
-
const maxY = yScale(rangeMin) ?? drawingArea.height; // Bottom of chart (min value)
|
|
1055
|
-
|
|
1056
|
-
// Calculate percentages based on actual chart positions
|
|
1057
|
-
const yellowThreshold = ((yellowThresholdY - minY) / (maxY - minY)) * 100;
|
|
1058
|
-
const greenThreshold = ((greenThresholdY - minY) / (maxY - minY)) * 100;
|
|
1059
|
-
|
|
1060
|
-
return (
|
|
1061
|
-
<Defs>
|
|
1062
|
-
<LinearGradient
|
|
1063
|
-
gradientUnits="userSpaceOnUse"
|
|
1064
|
-
id="availabilityGradient"
|
|
1065
|
-
x1="0%"
|
|
1066
|
-
x2="0%"
|
|
1067
|
-
y1={minY}
|
|
1068
|
-
y2={maxY}
|
|
1069
|
-
>
|
|
1070
|
-
<stop offset="0%" stopColor={theme.color.fgPositive} />
|
|
1071
|
-
<stop offset={`${greenThreshold}%`} stopColor={theme.color.fgPositive} />
|
|
1072
|
-
<stop offset={`${greenThreshold}%`} stopColor={theme.color.fgWarning} />
|
|
1073
|
-
<stop offset={`${yellowThreshold}%`} stopColor={theme.color.fgWarning} />
|
|
1074
|
-
<stop offset={`${yellowThreshold}%`} stopColor={theme.color.fgNegative} />
|
|
1075
|
-
<stop offset="100%" stopColor={theme.color.fgNegative} />
|
|
1076
|
-
</LinearGradient>
|
|
1077
|
-
</Defs>
|
|
1078
|
-
);
|
|
1079
|
-
});
|
|
1711
|
+
const availabilityEvents = useMemo(
|
|
1712
|
+
() => [
|
|
1713
|
+
{ date: new Date('2022-01-01'), availability: 79 },
|
|
1714
|
+
{ date: new Date('2022-01-03'), availability: 81 },
|
|
1715
|
+
{ date: new Date('2022-01-04'), availability: 82 },
|
|
1716
|
+
{ date: new Date('2022-01-06'), availability: 91 },
|
|
1717
|
+
{ date: new Date('2022-01-07'), availability: 92 },
|
|
1718
|
+
{ date: new Date('2022-01-10'), availability: 86 },
|
|
1719
|
+
],
|
|
1720
|
+
[],
|
|
1721
|
+
);
|
|
1080
1722
|
|
|
1081
1723
|
return (
|
|
1082
1724
|
<CartesianChart
|
|
1083
1725
|
enableScrubbing
|
|
1084
|
-
|
|
1085
|
-
height={150}
|
|
1726
|
+
height={200}
|
|
1086
1727
|
series={[
|
|
1087
1728
|
{
|
|
1088
1729
|
id: 'availability',
|
|
1089
1730
|
data: availabilityEvents.map((event) => event.availability),
|
|
1090
|
-
|
|
1731
|
+
gradient: {
|
|
1732
|
+
stops: ({ min, max }) => [
|
|
1733
|
+
{ offset: min, color: theme.color.fgNegative },
|
|
1734
|
+
{ offset: 85, color: theme.color.fgNegative },
|
|
1735
|
+
{ offset: 85, color: theme.color.fgWarning },
|
|
1736
|
+
{ offset: 90, color: theme.color.fgWarning },
|
|
1737
|
+
{ offset: 90, color: theme.color.fgPositive },
|
|
1738
|
+
{ offset: max, color: theme.color.fgPositive },
|
|
1739
|
+
],
|
|
1740
|
+
},
|
|
1091
1741
|
},
|
|
1092
1742
|
]}
|
|
1093
1743
|
xAxis={{
|
|
@@ -1096,10 +1746,7 @@ function AvailabilityChart() {
|
|
|
1096
1746
|
yAxis={{
|
|
1097
1747
|
domain: ({ min, max }) => ({ min: Math.max(min - 2, 0), max: Math.min(max + 2, 100) }),
|
|
1098
1748
|
}}
|
|
1099
|
-
padding={{ left: 2, right: 2 }}
|
|
1100
|
-
accessibilityLabel={accessibilityLabel}
|
|
1101
1749
|
>
|
|
1102
|
-
<ChartDefs />
|
|
1103
1750
|
<XAxis
|
|
1104
1751
|
showGrid
|
|
1105
1752
|
showLine
|
|
@@ -1115,10 +1762,10 @@ function AvailabilityChart() {
|
|
|
1115
1762
|
/>
|
|
1116
1763
|
<Line
|
|
1117
1764
|
curve="stepAfter"
|
|
1118
|
-
|
|
1765
|
+
points={(props) => ({
|
|
1766
|
+
...props,
|
|
1119
1767
|
fill: theme.color.bg,
|
|
1120
|
-
stroke:
|
|
1121
|
-
strokeWidth: 2,
|
|
1768
|
+
stroke: props.fill,
|
|
1122
1769
|
})}
|
|
1123
1770
|
seriesId="availability"
|
|
1124
1771
|
/>
|
|
@@ -1128,81 +1775,139 @@ function AvailabilityChart() {
|
|
|
1128
1775
|
}
|
|
1129
1776
|
```
|
|
1130
1777
|
|
|
1131
|
-
#### Asset Price
|
|
1778
|
+
#### Forecast Asset Price
|
|
1132
1779
|
|
|
1133
|
-
You can
|
|
1780
|
+
You can combine multiple lines within a series to change styles dynamically.
|
|
1134
1781
|
|
|
1135
1782
|
```jsx
|
|
1136
|
-
function
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
const formatPrice = (price: number) => {
|
|
1142
|
-
return new Intl.NumberFormat('en-US', {
|
|
1143
|
-
style: 'currency',
|
|
1144
|
-
currency: 'USD',
|
|
1145
|
-
}).format(price);
|
|
1146
|
-
};
|
|
1783
|
+
function ForecastAssetPrice() {
|
|
1784
|
+
const startYear = 2020;
|
|
1785
|
+
const data = [50, 45, 47, 46, 54, 54, 60, 61, 63, 66, 70];
|
|
1786
|
+
const currentIndex = 6;
|
|
1147
1787
|
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1788
|
+
const strokeWidth = 3;
|
|
1789
|
+
// To prevent cutting off the edge of our lines
|
|
1790
|
+
const clipOffset = strokeWidth;
|
|
1791
|
+
|
|
1792
|
+
const axisFormatter = useCallback(
|
|
1793
|
+
(dataIndex: number) => {
|
|
1794
|
+
return `${startYear + dataIndex}`;
|
|
1795
|
+
},
|
|
1796
|
+
[startYear],
|
|
1797
|
+
);
|
|
1798
|
+
|
|
1799
|
+
const HistoricalLineComponent = memo((props: SolidLineProps) => {
|
|
1800
|
+
const { drawingArea, getXScale } = useCartesianChartContext();
|
|
1801
|
+
const xScale = getXScale();
|
|
1802
|
+
|
|
1803
|
+
const historicalClipPath = useMemo(() => {
|
|
1804
|
+
if (!xScale || !drawingArea) return null;
|
|
1805
|
+
|
|
1806
|
+
const currentX = xScale(currentIndex);
|
|
1807
|
+
if (currentX === undefined) return null;
|
|
1808
|
+
|
|
1809
|
+
// Create clip path for historical data (left side)
|
|
1810
|
+
const clip = Skia.Path.Make();
|
|
1811
|
+
clip.addRect({
|
|
1812
|
+
x: drawingArea.x - clipOffset,
|
|
1813
|
+
y: drawingArea.y - clipOffset,
|
|
1814
|
+
width: currentX + clipOffset - drawingArea.x,
|
|
1815
|
+
height: drawingArea.height + clipOffset * 2,
|
|
1816
|
+
});
|
|
1817
|
+
return clip;
|
|
1818
|
+
}, [xScale, drawingArea]);
|
|
1819
|
+
|
|
1820
|
+
if (!historicalClipPath) return null;
|
|
1821
|
+
|
|
1822
|
+
return (
|
|
1823
|
+
<Group clip={historicalClipPath}>
|
|
1824
|
+
<SolidLine strokeWidth={strokeWidth} {...props} />
|
|
1825
|
+
</Group>
|
|
1826
|
+
);
|
|
1827
|
+
});
|
|
1828
|
+
|
|
1829
|
+
// Since the solid and dotted line have different curves,
|
|
1830
|
+
// we need two separate line components. Otherwise we could
|
|
1831
|
+
// have one line component with SolidLine and DottedLine inside
|
|
1832
|
+
// of it and two clipPaths.
|
|
1833
|
+
const ForecastLineComponent = memo((props: DottedLineProps) => {
|
|
1834
|
+
const { drawingArea, getXScale } = useCartesianChartContext();
|
|
1835
|
+
const xScale = getXScale();
|
|
1836
|
+
|
|
1837
|
+
const forecastClipPath = useMemo(() => {
|
|
1838
|
+
if (!xScale || !drawingArea) return null;
|
|
1839
|
+
|
|
1840
|
+
const currentX = xScale(currentIndex);
|
|
1841
|
+
if (currentX === undefined) return null;
|
|
1842
|
+
|
|
1843
|
+
// Create clip path for forecast data (right side)
|
|
1844
|
+
const clip = Skia.Path.Make();
|
|
1845
|
+
clip.addRect({
|
|
1846
|
+
x: currentX,
|
|
1847
|
+
y: drawingArea.y - clipOffset,
|
|
1848
|
+
width: drawingArea.x + drawingArea.width - currentX + clipOffset * 2,
|
|
1849
|
+
height: drawingArea.height + clipOffset * 2,
|
|
1850
|
+
});
|
|
1851
|
+
return clip;
|
|
1852
|
+
}, [xScale, drawingArea]);
|
|
1155
1853
|
|
|
1156
|
-
|
|
1854
|
+
if (!forecastClipPath) return null;
|
|
1855
|
+
|
|
1856
|
+
return (
|
|
1857
|
+
<Group clip={forecastClipPath}>
|
|
1858
|
+
<DottedLine dashIntervals={[0, strokeWidth * 2]} strokeWidth={strokeWidth} {...props} />
|
|
1859
|
+
</Group>
|
|
1860
|
+
);
|
|
1861
|
+
});
|
|
1862
|
+
const CustomScrubber = memo(() => {
|
|
1863
|
+
const { scrubberPosition } = useScrubberContext();
|
|
1864
|
+
|
|
1865
|
+
const idleScrubberOpacity = useDerivedValue(
|
|
1866
|
+
() => (scrubberPosition.value === undefined ? 1 : 0),
|
|
1867
|
+
[scrubberPosition],
|
|
1868
|
+
);
|
|
1869
|
+
const scrubberOpacity = useDerivedValue(
|
|
1870
|
+
() => (scrubberPosition.value !== undefined ? 1 : 0),
|
|
1871
|
+
[scrubberPosition],
|
|
1872
|
+
);
|
|
1873
|
+
|
|
1874
|
+
// Fade in animation for the Scrubber
|
|
1875
|
+
const fadeInOpacity = useSharedValue(0);
|
|
1876
|
+
|
|
1877
|
+
useEffect(() => {
|
|
1878
|
+
fadeInOpacity.value = withDelay(350, withTiming(1, { duration: 150 }));
|
|
1879
|
+
}, [fadeInOpacity]);
|
|
1880
|
+
|
|
1881
|
+
return (
|
|
1882
|
+
<Group opacity={fadeInOpacity}>
|
|
1883
|
+
<Group opacity={scrubberOpacity}>
|
|
1884
|
+
<Scrubber hideOverlay />
|
|
1885
|
+
</Group>
|
|
1886
|
+
<Group opacity={idleScrubberOpacity}>
|
|
1887
|
+
<DefaultScrubberBeacon
|
|
1888
|
+
isIdle
|
|
1889
|
+
dataX={currentIndex}
|
|
1890
|
+
dataY={data[currentIndex]}
|
|
1891
|
+
seriesId="price"
|
|
1892
|
+
/>
|
|
1893
|
+
</Group>
|
|
1894
|
+
</Group>
|
|
1895
|
+
);
|
|
1896
|
+
});
|
|
1157
1897
|
|
|
1158
1898
|
return (
|
|
1159
|
-
<
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
}}
|
|
1164
|
-
borderRadius={300}
|
|
1165
|
-
gap={2}
|
|
1166
|
-
padding={2}
|
|
1167
|
-
paddingBottom={0}
|
|
1168
|
-
overflow="hidden"
|
|
1899
|
+
<CartesianChart
|
|
1900
|
+
enableScrubbing
|
|
1901
|
+
height={200}
|
|
1902
|
+
series={[{ id: 'price', data, color: assets.btc.color }]}
|
|
1169
1903
|
>
|
|
1170
|
-
<
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
</Text>
|
|
1176
|
-
<Text font="label1" color="fgPositive" accessibilityLabel={`Up ${formatPercentChange(percentChange)}`}>
|
|
1177
|
-
+{formatPercentChange(percentChange)}
|
|
1178
|
-
</Text>
|
|
1179
|
-
</VStack>
|
|
1180
|
-
</HStack>
|
|
1181
|
-
<div
|
|
1182
|
-
style={{
|
|
1183
|
-
marginLeft: `calc(-1 * ${theme.space[2]})`,
|
|
1184
|
-
marginRight: `calc(-1 * ${theme.space[2]})`,
|
|
1185
|
-
}}
|
|
1186
|
-
>
|
|
1187
|
-
<LineChart
|
|
1188
|
-
inset={{ left: 0, right: 18, bottom: 0, top: 0 }}
|
|
1189
|
-
series={[
|
|
1190
|
-
{
|
|
1191
|
-
id: 'btcPrice',
|
|
1192
|
-
data: prices,
|
|
1193
|
-
color: assets.btc.color,
|
|
1194
|
-
},
|
|
1195
|
-
]}
|
|
1196
|
-
showArea
|
|
1197
|
-
width="100%"
|
|
1198
|
-
height={92}
|
|
1199
|
-
>
|
|
1200
|
-
<Scrubber idlePulse styles={{ beacon: { stroke: 'white' } }} />
|
|
1201
|
-
</LineChart>
|
|
1202
|
-
</div>
|
|
1203
|
-
</VStack>
|
|
1904
|
+
<Line LineComponent={HistoricalLineComponent} curve="linear" seriesId="price" />
|
|
1905
|
+
<Line LineComponent={ForecastLineComponent} curve="monotone" seriesId="price" type="dotted" />
|
|
1906
|
+
<XAxis position="bottom" requestedTickCount={3} tickLabelFormatter={axisFormatter} />
|
|
1907
|
+
<CustomScrubber />
|
|
1908
|
+
</CartesianChart>
|
|
1204
1909
|
);
|
|
1205
|
-
}
|
|
1910
|
+
}
|
|
1206
1911
|
```
|
|
1207
1912
|
|
|
1208
1913
|
## Props
|
|
@@ -1211,7 +1916,7 @@ function BitcoinChartWithScrubberBeacon() {
|
|
|
1211
1916
|
| --- | --- | --- | --- | --- |
|
|
1212
1917
|
| `AreaComponent` | `AreaComponent` | No | `-` | Custom component to render line area fill. |
|
|
1213
1918
|
| `LineComponent` | `LineComponent` | No | `-` | Component to render the line. Takes precedence over the type prop if provided. |
|
|
1214
|
-
| `alignContent` | `flex-start \| flex-end \| center \|
|
|
1919
|
+
| `alignContent` | `flex-start \| flex-end \| center \| space-between \| space-around \| space-evenly \| stretch` | No | `-` | - |
|
|
1215
1920
|
| `alignItems` | `flex-start \| flex-end \| center \| stretch \| baseline` | No | `-` | - |
|
|
1216
1921
|
| `alignSelf` | `auto \| FlexAlignType` | No | `-` | - |
|
|
1217
1922
|
| `allowOverflowGestures` | `boolean` | No | `-` | Allows continuous gestures on the chart to continue outside the bounds of the chart element. |
|
|
@@ -1240,10 +1945,11 @@ function BitcoinChartWithScrubberBeacon() {
|
|
|
1240
1945
|
| `borderedVertical` | `boolean` | No | `-` | Add a border to the top and bottom sides of the box. |
|
|
1241
1946
|
| `bottom` | `string \| number` | No | `-` | - |
|
|
1242
1947
|
| `color` | `currentColor \| fg \| fgMuted \| fgInverse \| fgPrimary \| fgWarning \| fgPositive \| fgNegative \| bg \| bgAlternate \| bgInverse \| bgOverlay \| bgElevation1 \| bgElevation2 \| bgPrimary \| bgPrimaryWash \| bgSecondary \| bgTertiary \| bgSecondaryWash \| bgNegative \| bgNegativeWash \| bgPositive \| bgPositiveWash \| bgWarning \| bgWarningWash \| bgLine \| bgLineHeavy \| bgLineInverse \| bgLinePrimary \| bgLinePrimarySubtle \| accentSubtleRed \| accentBoldRed \| accentSubtleGreen \| accentBoldGreen \| accentSubtleBlue \| accentBoldBlue \| accentSubtlePurple \| accentBoldPurple \| accentSubtleYellow \| accentBoldYellow \| accentSubtleGray \| accentBoldGray \| transparent` | No | `-` | - |
|
|
1243
|
-
| `columnGap` | `0 \| 1 \| 2 \|
|
|
1244
|
-
| `
|
|
1948
|
+
| `columnGap` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
1949
|
+
| `connectNulls` | `boolean` | No | `-` | When true, the area is connected across null values. |
|
|
1950
|
+
| `curve` | `bump \| catmullRom \| linear \| linearClosed \| monotone \| natural \| step \| stepBefore \| stepAfter` | No | `'bump'` | The curve interpolation method to use for the line. |
|
|
1245
1951
|
| `dangerouslySetBackground` | `string` | No | `-` | - |
|
|
1246
|
-
| `display` | `
|
|
1952
|
+
| `display` | `none \| flex` | No | `-` | - |
|
|
1247
1953
|
| `elevation` | `0 \| 1 \| 2` | No | `-` | Determines box shadow styles. Parent should have overflow set to visible to ensure styles are not clipped. |
|
|
1248
1954
|
| `enableScrubbing` | `boolean` | No | `-` | Enables scrubbing interactions. When true, allows scrubbing and makes scrubber components interactive. |
|
|
1249
1955
|
| `flexBasis` | `string \| number` | No | `-` | - |
|
|
@@ -1252,23 +1958,24 @@ function BitcoinChartWithScrubberBeacon() {
|
|
|
1252
1958
|
| `flexShrink` | `number` | No | `-` | - |
|
|
1253
1959
|
| `flexWrap` | `wrap \| nowrap \| wrap-reverse` | No | `-` | - |
|
|
1254
1960
|
| `font` | `inherit \| FontFamily` | No | `-` | - |
|
|
1255
|
-
| `
|
|
1256
|
-
| `
|
|
1961
|
+
| `fontFamilies` | `string[]` | No | `-` | Default font families to use within ChartText. If not provided, will be the default for the system. |
|
|
1962
|
+
| `fontProvider` | `SkTypefaceFontProvider` | No | `-` | Skia font provider to allow for custom fonts. If not provided, the only available fonts will be those defined by the system. |
|
|
1963
|
+
| `fontSize` | `FontSize \| inherit` | No | `-` | - |
|
|
1257
1964
|
| `fontWeight` | `inherit \| FontWeight` | No | `-` | - |
|
|
1258
|
-
| `gap` | `0 \| 1 \| 2 \|
|
|
1259
|
-
| `height` | `string \| number` | No | `-` |
|
|
1965
|
+
| `gap` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
1966
|
+
| `height` | `string \| number` | No | `-` | - |
|
|
1260
1967
|
| `inset` | `number \| Partial<ChartInset>` | No | `-` | Inset around the entire chart (outside the axes). |
|
|
1261
1968
|
| `justifyContent` | `flex-start \| flex-end \| center \| space-between \| space-around \| space-evenly` | No | `-` | - |
|
|
1262
1969
|
| `key` | `Key \| null` | No | `-` | - |
|
|
1263
1970
|
| `left` | `string \| number` | No | `-` | - |
|
|
1264
1971
|
| `lineHeight` | `inherit \| LineHeight` | No | `-` | - |
|
|
1265
|
-
| `margin` | `0 \| -1 \| -2 \| -
|
|
1266
|
-
| `marginBottom` | `0 \| -1 \| -2 \| -
|
|
1267
|
-
| `marginEnd` | `0 \| -1 \| -2 \| -
|
|
1268
|
-
| `marginStart` | `0 \| -1 \| -2 \| -
|
|
1269
|
-
| `marginTop` | `0 \| -1 \| -2 \| -
|
|
1270
|
-
| `marginX` | `0 \| -1 \| -2 \| -
|
|
1271
|
-
| `marginY` | `0 \| -1 \| -2 \| -
|
|
1972
|
+
| `margin` | `0 \| -1 \| -2 \| -0.25 \| -0.5 \| -0.75 \| -1.5 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10` | No | `-` | - |
|
|
1973
|
+
| `marginBottom` | `0 \| -1 \| -2 \| -0.25 \| -0.5 \| -0.75 \| -1.5 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10` | No | `-` | - |
|
|
1974
|
+
| `marginEnd` | `0 \| -1 \| -2 \| -0.25 \| -0.5 \| -0.75 \| -1.5 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10` | No | `-` | - |
|
|
1975
|
+
| `marginStart` | `0 \| -1 \| -2 \| -0.25 \| -0.5 \| -0.75 \| -1.5 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10` | No | `-` | - |
|
|
1976
|
+
| `marginTop` | `0 \| -1 \| -2 \| -0.25 \| -0.5 \| -0.75 \| -1.5 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10` | No | `-` | - |
|
|
1977
|
+
| `marginX` | `0 \| -1 \| -2 \| -0.25 \| -0.5 \| -0.75 \| -1.5 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10` | No | `-` | - |
|
|
1978
|
+
| `marginY` | `0 \| -1 \| -2 \| -0.25 \| -0.5 \| -0.75 \| -1.5 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10` | No | `-` | - |
|
|
1272
1979
|
| `maxHeight` | `string \| number` | No | `-` | - |
|
|
1273
1980
|
| `maxWidth` | `string \| number` | No | `-` | - |
|
|
1274
1981
|
| `minHeight` | `string \| number` | No | `-` | - |
|
|
@@ -1286,39 +1993,42 @@ function BitcoinChartWithScrubberBeacon() {
|
|
|
1286
1993
|
| `onPointerUp` | `((event: PointerEvent) => void)` | No | `-` | - |
|
|
1287
1994
|
| `onPointerUpCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
|
|
1288
1995
|
| `onScrubberPositionChange` | `((index: number) => void) \| undefined` | No | `-` | Callback fired when the scrubber position changes. Receives the dataIndex of the scrubber or undefined when not scrubbing. |
|
|
1289
|
-
| `opacity` | `number \| AnimatedNode` | No |
|
|
1996
|
+
| `opacity` | `number \| AnimatedNode & number` | No | `1` | Opacity of the lines stroke. Will also be applied to points and area fill. |
|
|
1290
1997
|
| `overflow` | `visible \| hidden \| scroll` | No | `-` | - |
|
|
1291
|
-
| `padding` | `0 \| 1 \| 2 \|
|
|
1292
|
-
| `paddingBottom` | `0 \| 1 \| 2 \|
|
|
1293
|
-
| `paddingEnd` | `0 \| 1 \| 2 \|
|
|
1294
|
-
| `paddingStart` | `0 \| 1 \| 2 \|
|
|
1295
|
-
| `paddingTop` | `0 \| 1 \| 2 \|
|
|
1296
|
-
| `paddingX` | `0 \| 1 \| 2 \|
|
|
1297
|
-
| `paddingY` | `0 \| 1 \| 2 \|
|
|
1998
|
+
| `padding` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
1999
|
+
| `paddingBottom` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
2000
|
+
| `paddingEnd` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
2001
|
+
| `paddingStart` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
2002
|
+
| `paddingTop` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
2003
|
+
| `paddingX` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
2004
|
+
| `paddingY` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
1298
2005
|
| `pin` | `top \| bottom \| left \| right \| all` | No | `-` | Direction in which to absolutely pin the box. |
|
|
1299
|
-
| `
|
|
2006
|
+
| `points` | `boolean \| ((defaults: PointBaseProps) => boolean \| Partial<PointProps> \| null) \| undefined` | No | `-` | Controls whether and how to render points at each data point in the series. - true: Show all points with default styling - false or undefined: Hide all points - Function: Called for every entry in the data array to customize individual points |
|
|
2007
|
+
| `position` | `static \| relative \| fixed \| absolute \| sticky` | No | `-` | - |
|
|
1300
2008
|
| `ref` | `((instance: View \| null) => void) \| RefObject<View> \| null` | No | `-` | - |
|
|
1301
|
-
| `renderPoints` | `((params: RenderPointsParams) => boolean \| PointConfig \| null) \| undefined` | No | `-` | Callback function to determine how to render points at each data point in the series. Called for every entry in the data array. |
|
|
1302
2009
|
| `right` | `string \| number` | No | `-` | - |
|
|
1303
|
-
| `rowGap` | `0 \| 1 \| 2 \|
|
|
2010
|
+
| `rowGap` | `0 \| 1 \| 2 \| 0.25 \| 0.5 \| 0.75 \| 1.5 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10` | No | `-` | - |
|
|
1304
2011
|
| `series` | `LineSeries[]` | No | `-` | Configuration objects that define how to visualize the data. Each series supports Line component props for individual customization. |
|
|
1305
|
-
| `showArea` | `boolean` | No | `-` |
|
|
2012
|
+
| `showArea` | `boolean` | No | `-` | Whether to show area fill under the line. |
|
|
1306
2013
|
| `showXAxis` | `boolean` | No | `-` | Whether to show the X axis. |
|
|
1307
2014
|
| `showYAxis` | `boolean` | No | `-` | Whether to show the Y axis. |
|
|
1308
|
-
| `
|
|
1309
|
-
| `
|
|
2015
|
+
| `strokeOpacity` | `number` | No | `1` | Opacity of the line |
|
|
2016
|
+
| `strokeWidth` | `number` | No | `2` | Width of the line |
|
|
2017
|
+
| `style` | `((false \| RegisteredStyle<ViewStyle> \| WithAnimatedObject<ViewStyle> \| Value \| AnimatedInterpolation<string \| number> \| WithAnimatedArray<ViewStyle \| Falsy \| RegisteredStyle<ViewStyle> \| RecursiveArray<ViewStyle \| Falsy \| RegisteredStyle<ViewStyle>> \| readonly (ViewStyle \| Falsy \| RegisteredStyle<ViewStyle>)[]>) & ((false \| RegisteredStyle<ViewStyle> \| WithAnimatedObject<ViewStyle> \| Value \| AnimatedInterpolation<string \| number> \| WithAnimatedArray<ViewStyle \| Falsy \| RegisteredStyle<ViewStyle> \| RecursiveArray<ViewStyle \| Falsy \| RegisteredStyle<ViewStyle>> \| readonly (ViewStyle \| Falsy \| RegisteredStyle<ViewStyle>)[]>) & (false \| ViewStyle \| RegisteredStyle<ViewStyle> \| RecursiveArray<ViewStyle \| Falsy \| RegisteredStyle<ViewStyle>>))) \| null` | No | `-` | Custom styles for the root element. |
|
|
2018
|
+
| `styles` | `{ root?: StyleProp<ViewStyle>; chart?: StyleProp<ViewStyle>; }` | No | `-` | Custom styles for the component. |
|
|
1310
2019
|
| `testID` | `string` | No | `-` | Used to locate this element in unit and end-to-end tests. Used to locate this view in end-to-end tests. |
|
|
1311
|
-
| `textAlign` | `
|
|
2020
|
+
| `textAlign` | `left \| right \| auto \| center \| justify` | No | `-` | - |
|
|
1312
2021
|
| `textDecorationLine` | `none \| underline \| line-through \| underline line-through` | No | `-` | - |
|
|
1313
|
-
| `textDecorationStyle` | `solid \|
|
|
2022
|
+
| `textDecorationStyle` | `solid \| double \| dotted \| dashed` | No | `-` | - |
|
|
1314
2023
|
| `textTransform` | `none \| capitalize \| uppercase \| lowercase` | No | `-` | - |
|
|
1315
2024
|
| `top` | `string \| number` | No | `-` | - |
|
|
1316
|
-
| `transform` | `string \| (({
|
|
1317
|
-
| `
|
|
1318
|
-
| `
|
|
1319
|
-
| `
|
|
1320
|
-
| `
|
|
1321
|
-
| `
|
|
2025
|
+
| `transform` | `string \| (({ perspective: AnimatableNumericValue; } & { rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotate: AnimatableStringValue; } & { perspective?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateX: AnimatableStringValue; } & { perspective?: undefined; rotate?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateY: AnimatableStringValue; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateZ: AnimatableStringValue; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ scale: AnimatableNumericValue; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ scaleX: AnimatableNumericValue; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ scaleY: AnimatableNumericValue; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ translateX: AnimatableNumericValue \| ${number}%; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ translateY: AnimatableNumericValue \| ${number}%; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ skewX: AnimatableStringValue; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ skewY: AnimatableStringValue; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; matrix?: undefined; }) \| ({ matrix: AnimatableNumericValue[]; } & { perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; skewX?: undefined; skewY?: undefined; }))[]` | No | `-` | - |
|
|
2026
|
+
| `transition` | `{ type: timing; } & TimingConfig \| { type: spring; } & { stiffness?: number \| undefined; overshootClamping?: boolean \| undefined; restDisplacementThreshold?: number \| undefined; restSpeedThreshold?: number \| undefined; velocity?: number \| undefined; reduceMotion?: ReduceMotion \| undefined; } & { mass?: number \| undefined; damping?: number \| undefined; duration?: undefined; dampingRatio?: undefined; clamp?: undefined; } \| { type: spring; } & { stiffness?: number \| undefined; overshootClamping?: boolean \| undefined; restDisplacementThreshold?: number \| undefined; restSpeedThreshold?: number \| undefined; velocity?: number \| undefined; reduceMotion?: ReduceMotion \| undefined; } & { mass?: undefined; damping?: undefined; duration?: number \| undefined; dampingRatio?: number \| undefined; clamp?: { min?: number \| undefined; max?: number \| undefined; } \| undefined; }` | No | `-` | Transition configuration for line animations. |
|
|
2027
|
+
| `type` | `solid \| dotted` | No | `'solid'` | The type of line to render. |
|
|
2028
|
+
| `userSelect` | `none \| auto \| text \| contain \| all` | No | `-` | - |
|
|
2029
|
+
| `width` | `string \| number` | No | `-` | - |
|
|
2030
|
+
| `xAxis` | `(Partial<AxisConfigProps> & AxisBaseProps & { GridLineComponent?: LineComponent; LineComponent?: LineComponent \| undefined; TickMarkLineComponent?: LineComponent \| undefined; tickLabelFormatter?: ((value: number) => ChartTextChildren) \| undefined; TickLabelComponent?: AxisTickLabelComponent \| undefined; } & { position?: top \| bottom \| undefined; height?: number \| undefined; }) \| undefined` | No | `-` | Configuration for x-axis. Accepts axis config and axis props. To show the axis, set showXAxis to true. |
|
|
2031
|
+
| `yAxis` | `(Partial<AxisConfigProps> & AxisBaseProps & { GridLineComponent?: LineComponent; LineComponent?: LineComponent \| undefined; TickMarkLineComponent?: LineComponent \| undefined; tickLabelFormatter?: ((value: number) => ChartTextChildren) \| undefined; TickLabelComponent?: AxisTickLabelComponent \| undefined; } & { axisId?: string \| undefined; position?: left \| right \| undefined; width?: number \| undefined; }) \| undefined` | No | `-` | Configuration for y-axis. Accepts axis config and axis props. To show the axis, set showYAxis to true. |
|
|
1322
2032
|
| `zIndex` | `number` | No | `-` | - |
|
|
1323
2033
|
|
|
1324
2034
|
|