@coinbase/cds-mcp-server 8.17.1 → 8.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/mcp-docs/mobile/components/Accordion.txt +189 -0
  3. package/mcp-docs/mobile/components/AccordionItem.txt +30 -0
  4. package/mcp-docs/mobile/components/Alert.txt +156 -0
  5. package/mcp-docs/mobile/components/AreaChart.txt +266 -0
  6. package/mcp-docs/mobile/components/Avatar.txt +196 -0
  7. package/mcp-docs/mobile/components/AvatarButton.txt +226 -0
  8. package/mcp-docs/mobile/components/Banner.txt +222 -0
  9. package/mcp-docs/mobile/components/BarChart.txt +816 -0
  10. package/mcp-docs/mobile/components/Box.txt +174 -0
  11. package/mcp-docs/mobile/components/BrowserBar.txt +147 -0
  12. package/mcp-docs/mobile/components/Button.txt +199 -0
  13. package/mcp-docs/mobile/components/ButtonGroup.txt +80 -0
  14. package/mcp-docs/mobile/components/Carousel.txt +1084 -0
  15. package/mcp-docs/mobile/components/CartesianChart.txt +826 -0
  16. package/mcp-docs/mobile/components/CellMedia.txt +71 -0
  17. package/mcp-docs/mobile/components/Checkbox.txt +246 -0
  18. package/mcp-docs/mobile/components/CheckboxCell.txt +202 -0
  19. package/mcp-docs/mobile/components/CheckboxGroup.txt +285 -0
  20. package/mcp-docs/mobile/components/Chip.txt +195 -0
  21. package/mcp-docs/mobile/components/Coachmark.txt +158 -0
  22. package/mcp-docs/mobile/components/Collapsible.txt +105 -0
  23. package/mcp-docs/mobile/components/ContainedAssetCard.txt +135 -0
  24. package/mcp-docs/mobile/components/ContentCard.txt +366 -0
  25. package/mcp-docs/mobile/components/ContentCardBody.txt +136 -0
  26. package/mcp-docs/mobile/components/ContentCardFooter.txt +128 -0
  27. package/mcp-docs/mobile/components/ContentCardHeader.txt +146 -0
  28. package/mcp-docs/mobile/components/ContentCell.txt +227 -0
  29. package/mcp-docs/mobile/components/ControlGroup.txt +444 -0
  30. package/mcp-docs/mobile/components/DatePicker.txt +497 -0
  31. package/mcp-docs/mobile/components/Divider.txt +139 -0
  32. package/mcp-docs/mobile/components/DotCount.txt +146 -0
  33. package/mcp-docs/mobile/components/DotStatusColor.txt +59 -0
  34. package/mcp-docs/mobile/components/DotSymbol.txt +135 -0
  35. package/mcp-docs/mobile/components/Fallback.txt +158 -0
  36. package/mcp-docs/mobile/components/FloatingAssetCard.txt +156 -0
  37. package/mcp-docs/mobile/components/HStack.txt +235 -0
  38. package/mcp-docs/mobile/components/HeroSquare.txt +48 -0
  39. package/mcp-docs/mobile/components/Icon.txt +52 -0
  40. package/mcp-docs/mobile/components/IconButton.txt +269 -0
  41. package/mcp-docs/mobile/components/InputChip.txt +188 -0
  42. package/mcp-docs/mobile/components/Interactable.txt +187 -0
  43. package/mcp-docs/mobile/components/LineChart.txt +1325 -0
  44. package/mcp-docs/mobile/components/Link.txt +292 -0
  45. package/mcp-docs/mobile/components/ListCell.txt +391 -0
  46. package/mcp-docs/mobile/components/LogoMark.txt +85 -0
  47. package/mcp-docs/mobile/components/LogoWordMark.txt +94 -0
  48. package/mcp-docs/mobile/components/Lottie.txt +139 -0
  49. package/mcp-docs/mobile/components/LottieStatusAnimation.txt +47 -0
  50. package/mcp-docs/mobile/components/Modal.txt +84 -0
  51. package/mcp-docs/mobile/components/ModalBody.txt +34 -0
  52. package/mcp-docs/mobile/components/ModalFooter.txt +25 -0
  53. package/mcp-docs/mobile/components/ModalHeader.txt +28 -0
  54. package/mcp-docs/mobile/components/MultiContentModule.txt +380 -0
  55. package/mcp-docs/mobile/components/NavigationTitle.txt +132 -0
  56. package/mcp-docs/mobile/components/NavigationTitleSelect.txt +142 -0
  57. package/mcp-docs/mobile/components/NudgeCard.txt +90 -0
  58. package/mcp-docs/mobile/components/Numpad.txt +341 -0
  59. package/mcp-docs/mobile/components/Overlay.txt +152 -0
  60. package/mcp-docs/mobile/components/PageFooter.txt +161 -0
  61. package/mcp-docs/mobile/components/PageHeader.txt +186 -0
  62. package/mcp-docs/mobile/components/PeriodSelector.txt +408 -0
  63. package/mcp-docs/mobile/components/Pictogram.txt +48 -0
  64. package/mcp-docs/mobile/components/Point.txt +205 -0
  65. package/mcp-docs/mobile/components/PortalProvider.txt +79 -0
  66. package/mcp-docs/mobile/components/Pressable.txt +211 -0
  67. package/mcp-docs/mobile/components/ProgressBar.txt +130 -0
  68. package/mcp-docs/mobile/components/ProgressBarWithFixedLabels.txt +161 -0
  69. package/mcp-docs/mobile/components/ProgressBarWithFloatLabel.txt +138 -0
  70. package/mcp-docs/mobile/components/ProgressCircle.txt +237 -0
  71. package/mcp-docs/mobile/components/Radio.txt +242 -0
  72. package/mcp-docs/mobile/components/RadioCell.txt +202 -0
  73. package/mcp-docs/mobile/components/RadioGroup.txt +282 -0
  74. package/mcp-docs/mobile/components/ReferenceLine.txt +153 -0
  75. package/mcp-docs/mobile/components/RemoteImage.txt +106 -0
  76. package/mcp-docs/mobile/components/RemoteImageGroup.txt +61 -0
  77. package/mcp-docs/mobile/components/RollingNumber.txt +789 -0
  78. package/mcp-docs/mobile/components/Scrubber.txt +204 -0
  79. package/mcp-docs/mobile/components/SearchInput.txt +192 -0
  80. package/mcp-docs/mobile/components/SectionHeader.txt +205 -0
  81. package/mcp-docs/mobile/components/SegmentedTabs.txt +316 -0
  82. package/mcp-docs/mobile/components/Select.txt +212 -0
  83. package/mcp-docs/mobile/components/SelectChip.txt +324 -0
  84. package/mcp-docs/mobile/components/SelectOption.txt +85 -0
  85. package/mcp-docs/mobile/components/SlideButton.txt +331 -0
  86. package/mcp-docs/mobile/components/Spacer.txt +84 -0
  87. package/mcp-docs/mobile/components/Sparkline.txt +123 -0
  88. package/mcp-docs/mobile/components/SparklineGradient.txt +107 -0
  89. package/mcp-docs/mobile/components/SparklineInteractive.txt +157 -0
  90. package/mcp-docs/mobile/components/SparklineInteractiveHeader.txt +73 -0
  91. package/mcp-docs/mobile/components/Spinner.txt +49 -0
  92. package/mcp-docs/mobile/components/SpotIcon.txt +48 -0
  93. package/mcp-docs/mobile/components/SpotRectangle.txt +48 -0
  94. package/mcp-docs/mobile/components/SpotSquare.txt +48 -0
  95. package/mcp-docs/mobile/components/Stepper.txt +528 -0
  96. package/mcp-docs/mobile/components/SubBrandLogoMark.txt +126 -0
  97. package/mcp-docs/mobile/components/SubBrandLogoWordMark.txt +126 -0
  98. package/mcp-docs/mobile/components/Switch.txt +98 -0
  99. package/mcp-docs/mobile/components/TabIndicator.txt +49 -0
  100. package/mcp-docs/mobile/components/TabLabel.txt +154 -0
  101. package/mcp-docs/mobile/components/TabNavigation.txt +147 -0
  102. package/mcp-docs/mobile/components/TabbedChips.txt +143 -0
  103. package/mcp-docs/mobile/components/Tabs.txt +191 -0
  104. package/mcp-docs/mobile/components/Tag.txt +301 -0
  105. package/mcp-docs/mobile/components/Text.txt +212 -0
  106. package/mcp-docs/mobile/components/TextInput.txt +718 -0
  107. package/mcp-docs/mobile/components/ThemeProvider.txt +133 -0
  108. package/mcp-docs/mobile/components/Toast.txt +197 -0
  109. package/mcp-docs/mobile/components/Tooltip.txt +60 -0
  110. package/mcp-docs/mobile/components/TopNavBar.txt +162 -0
  111. package/mcp-docs/mobile/components/Tour.txt +159 -0
  112. package/mcp-docs/mobile/components/Tray.txt +253 -0
  113. package/mcp-docs/mobile/components/UpsellCard.txt +322 -0
  114. package/mcp-docs/mobile/components/VStack.txt +223 -0
  115. package/mcp-docs/mobile/components/XAxis.txt +622 -0
  116. package/mcp-docs/mobile/components/YAxis.txt +568 -0
  117. package/mcp-docs/mobile/getting-started/introduction.txt +99 -0
  118. package/mcp-docs/mobile/getting-started/mcp-server.txt +94 -0
  119. package/mcp-docs/mobile/getting-started/playground.txt +25 -0
  120. package/mcp-docs/mobile/hooks/useDimensions.txt +47 -0
  121. package/mcp-docs/mobile/hooks/useOverlayContentContext.txt +215 -0
  122. package/mcp-docs/mobile/hooks/useTheme.txt +110 -0
  123. package/mcp-docs/mobile/routes.txt +132 -0
  124. package/mcp-docs/web/components/Accordion.txt +190 -0
  125. package/mcp-docs/web/components/AccordionItem.txt +32 -0
  126. package/mcp-docs/web/components/Alert.txt +165 -0
  127. package/mcp-docs/web/components/AreaChart.txt +511 -0
  128. package/mcp-docs/web/components/Avatar.txt +212 -0
  129. package/mcp-docs/web/components/AvatarButton.txt +241 -0
  130. package/mcp-docs/web/components/Banner.txt +227 -0
  131. package/mcp-docs/web/components/BarChart.txt +1268 -0
  132. package/mcp-docs/web/components/Box.txt +176 -0
  133. package/mcp-docs/web/components/Button.txt +213 -0
  134. package/mcp-docs/web/components/ButtonGroup.txt +80 -0
  135. package/mcp-docs/web/components/Calendar.txt +182 -0
  136. package/mcp-docs/web/components/Carousel.txt +1576 -0
  137. package/mcp-docs/web/components/CartesianChart.txt +1045 -0
  138. package/mcp-docs/web/components/CellMedia.txt +57 -0
  139. package/mcp-docs/web/components/Checkbox.txt +189 -0
  140. package/mcp-docs/web/components/CheckboxCell.txt +203 -0
  141. package/mcp-docs/web/components/CheckboxGroup.txt +220 -0
  142. package/mcp-docs/web/components/Chip.txt +197 -0
  143. package/mcp-docs/web/components/Coachmark.txt +189 -0
  144. package/mcp-docs/web/components/Collapsible.txt +120 -0
  145. package/mcp-docs/web/components/ContainedAssetCard.txt +233 -0
  146. package/mcp-docs/web/components/ContentCard.txt +368 -0
  147. package/mcp-docs/web/components/ContentCardBody.txt +138 -0
  148. package/mcp-docs/web/components/ContentCardFooter.txt +130 -0
  149. package/mcp-docs/web/components/ContentCardHeader.txt +148 -0
  150. package/mcp-docs/web/components/ContentCell.txt +220 -0
  151. package/mcp-docs/web/components/ControlGroup.txt +437 -0
  152. package/mcp-docs/web/components/DatePicker.txt +506 -0
  153. package/mcp-docs/web/components/Divider.txt +144 -0
  154. package/mcp-docs/web/components/DotCount.txt +150 -0
  155. package/mcp-docs/web/components/DotStatusColor.txt +59 -0
  156. package/mcp-docs/web/components/DotSymbol.txt +138 -0
  157. package/mcp-docs/web/components/Dropdown.txt +120 -0
  158. package/mcp-docs/web/components/Fallback.txt +164 -0
  159. package/mcp-docs/web/components/FloatingAssetCard.txt +251 -0
  160. package/mcp-docs/web/components/FullscreenAlert.txt +70 -0
  161. package/mcp-docs/web/components/FullscreenModal.txt +146 -0
  162. package/mcp-docs/web/components/FullscreenModalLayout.txt +188 -0
  163. package/mcp-docs/web/components/Grid.txt +237 -0
  164. package/mcp-docs/web/components/GridColumn.txt +210 -0
  165. package/mcp-docs/web/components/HStack.txt +237 -0
  166. package/mcp-docs/web/components/HeroSquare.txt +49 -0
  167. package/mcp-docs/web/components/Icon.txt +146 -0
  168. package/mcp-docs/web/components/IconButton.txt +391 -0
  169. package/mcp-docs/web/components/InputChip.txt +188 -0
  170. package/mcp-docs/web/components/Interactable.txt +194 -0
  171. package/mcp-docs/web/components/LineChart.txt +1577 -0
  172. package/mcp-docs/web/components/Link.txt +244 -0
  173. package/mcp-docs/web/components/ListCell.txt +397 -0
  174. package/mcp-docs/web/components/LogoMark.txt +85 -0
  175. package/mcp-docs/web/components/LogoWordMark.txt +94 -0
  176. package/mcp-docs/web/components/Lottie.txt +158 -0
  177. package/mcp-docs/web/components/LottieStatusAnimation.txt +58 -0
  178. package/mcp-docs/web/components/MediaQueryProvider.txt +109 -0
  179. package/mcp-docs/web/components/Modal.txt +193 -0
  180. package/mcp-docs/web/components/ModalBody.txt +118 -0
  181. package/mcp-docs/web/components/ModalFooter.txt +120 -0
  182. package/mcp-docs/web/components/ModalHeader.txt +124 -0
  183. package/mcp-docs/web/components/MultiContentModule.txt +382 -0
  184. package/mcp-docs/web/components/NavigationBar.txt +103 -0
  185. package/mcp-docs/web/components/NavigationTitle.txt +26 -0
  186. package/mcp-docs/web/components/NavigationTitleSelect.txt +46 -0
  187. package/mcp-docs/web/components/NudgeCard.txt +182 -0
  188. package/mcp-docs/web/components/Overlay.txt +172 -0
  189. package/mcp-docs/web/components/PageFooter.txt +185 -0
  190. package/mcp-docs/web/components/PageHeader.txt +244 -0
  191. package/mcp-docs/web/components/Pagination.txt +500 -0
  192. package/mcp-docs/web/components/PeriodSelector.txt +704 -0
  193. package/mcp-docs/web/components/Pictogram.txt +49 -0
  194. package/mcp-docs/web/components/Point.txt +461 -0
  195. package/mcp-docs/web/components/PortalProvider.txt +77 -0
  196. package/mcp-docs/web/components/Pressable.txt +194 -0
  197. package/mcp-docs/web/components/ProgressBar.txt +164 -0
  198. package/mcp-docs/web/components/ProgressBarWithFixedLabels.txt +213 -0
  199. package/mcp-docs/web/components/ProgressBarWithFloatLabel.txt +182 -0
  200. package/mcp-docs/web/components/ProgressCircle.txt +444 -0
  201. package/mcp-docs/web/components/Radio.txt +220 -0
  202. package/mcp-docs/web/components/RadioCell.txt +216 -0
  203. package/mcp-docs/web/components/RadioGroup.txt +289 -0
  204. package/mcp-docs/web/components/ReferenceLine.txt +452 -0
  205. package/mcp-docs/web/components/RemoteImage.txt +166 -0
  206. package/mcp-docs/web/components/RemoteImageGroup.txt +87 -0
  207. package/mcp-docs/web/components/RollingNumber.txt +1022 -0
  208. package/mcp-docs/web/components/Scrubber.txt +232 -0
  209. package/mcp-docs/web/components/SearchInput.txt +118 -0
  210. package/mcp-docs/web/components/SectionHeader.txt +218 -0
  211. package/mcp-docs/web/components/SegmentedTabs.txt +325 -0
  212. package/mcp-docs/web/components/Select.txt +225 -0
  213. package/mcp-docs/web/components/SelectChip.txt +315 -0
  214. package/mcp-docs/web/components/SelectOption.txt +166 -0
  215. package/mcp-docs/web/components/Sidebar.txt +350 -0
  216. package/mcp-docs/web/components/SidebarItem.txt +132 -0
  217. package/mcp-docs/web/components/SidebarMoreMenu.txt +31 -0
  218. package/mcp-docs/web/components/Spacer.txt +174 -0
  219. package/mcp-docs/web/components/Sparkline.txt +123 -0
  220. package/mcp-docs/web/components/SparklineGradient.txt +107 -0
  221. package/mcp-docs/web/components/SparklineInteractive.txt +154 -0
  222. package/mcp-docs/web/components/SparklineInteractiveHeader.txt +77 -0
  223. package/mcp-docs/web/components/Spinner.txt +129 -0
  224. package/mcp-docs/web/components/SpotIcon.txt +49 -0
  225. package/mcp-docs/web/components/SpotRectangle.txt +49 -0
  226. package/mcp-docs/web/components/SpotSquare.txt +49 -0
  227. package/mcp-docs/web/components/Stepper.txt +683 -0
  228. package/mcp-docs/web/components/SubBrandLogoMark.txt +126 -0
  229. package/mcp-docs/web/components/SubBrandLogoWordMark.txt +126 -0
  230. package/mcp-docs/web/components/Switch.txt +86 -0
  231. package/mcp-docs/web/components/TabIndicator.txt +49 -0
  232. package/mcp-docs/web/components/TabLabel.txt +159 -0
  233. package/mcp-docs/web/components/TabNavigation.txt +160 -0
  234. package/mcp-docs/web/components/TabbedChips.txt +156 -0
  235. package/mcp-docs/web/components/Table.txt +368 -0
  236. package/mcp-docs/web/components/TableBody.txt +84 -0
  237. package/mcp-docs/web/components/TableCaption.txt +103 -0
  238. package/mcp-docs/web/components/TableCell.txt +166 -0
  239. package/mcp-docs/web/components/TableCellFallback.txt +98 -0
  240. package/mcp-docs/web/components/TableFooter.txt +84 -0
  241. package/mcp-docs/web/components/TableHeader.txt +101 -0
  242. package/mcp-docs/web/components/TableRow.txt +141 -0
  243. package/mcp-docs/web/components/Tabs.txt +213 -0
  244. package/mcp-docs/web/components/Tag.txt +305 -0
  245. package/mcp-docs/web/components/Text.txt +233 -0
  246. package/mcp-docs/web/components/TextInput.txt +653 -0
  247. package/mcp-docs/web/components/ThemeProvider.txt +200 -0
  248. package/mcp-docs/web/components/TileButton.txt +159 -0
  249. package/mcp-docs/web/components/Toast.txt +204 -0
  250. package/mcp-docs/web/components/Tooltip.txt +90 -0
  251. package/mcp-docs/web/components/Tour.txt +180 -0
  252. package/mcp-docs/web/components/Tray.txt +289 -0
  253. package/mcp-docs/web/components/UpsellCard.txt +320 -0
  254. package/mcp-docs/web/components/VStack.txt +225 -0
  255. package/mcp-docs/web/components/XAxis.txt +620 -0
  256. package/mcp-docs/web/components/YAxis.txt +549 -0
  257. package/mcp-docs/web/getting-started/introduction.txt +99 -0
  258. package/mcp-docs/web/getting-started/mcp-server.txt +94 -0
  259. package/mcp-docs/web/getting-started/playground.txt +25 -0
  260. package/mcp-docs/web/hooks/useBreakpoints.txt +33 -0
  261. package/mcp-docs/web/hooks/useDimensions.txt +55 -0
  262. package/mcp-docs/web/hooks/useHasMounted.txt +55 -0
  263. package/mcp-docs/web/hooks/useIsoEffect.txt +42 -0
  264. package/mcp-docs/web/hooks/useMediaQuery.txt +94 -0
  265. package/mcp-docs/web/hooks/useOverlayContentContext.txt +217 -0
  266. package/mcp-docs/web/hooks/useScrollBlocker.txt +63 -0
  267. package/mcp-docs/web/hooks/useTheme.txt +105 -0
  268. package/mcp-docs/web/routes.txt +155 -0
  269. package/package.json +1 -1
@@ -0,0 +1,1325 @@
1
+ # LineChart
2
+
3
+ A flexible line chart component for displaying data trends over time. Supports multiple series, custom curves, areas, scrubbing, and interactive data exploration.
4
+
5
+ ## Import
6
+
7
+ ```jsx
8
+ import { LineChart } from '@coinbase/cds-mobile-visualization'
9
+ ```
10
+
11
+ ## Props
12
+
13
+ | Prop | Type | Required | Default | Description |
14
+ | --- | --- | --- | --- | --- |
15
+ | `AreaComponent` | `AreaComponent` | No | `-` | Custom component to render line area fill. |
16
+ | `LineComponent` | `LineComponent` | No | `-` | Component to render the line. Takes precedence over the type prop if provided. |
17
+ | `alignContent` | `flex-start \| flex-end \| center \| stretch \| space-between \| space-around \| space-evenly` | No | `-` | - |
18
+ | `alignItems` | `flex-start \| flex-end \| center \| stretch \| baseline` | No | `-` | - |
19
+ | `alignSelf` | `auto \| FlexAlignType` | No | `-` | - |
20
+ | `allowOverflowGestures` | `boolean` | No | `-` | Allows continuous gestures on the chart to continue outside the bounds of the chart element. |
21
+ | `animate` | `boolean` | No | `true` | Whether to animate the chart. |
22
+ | `animated` | `boolean` | No | `-` | - |
23
+ | `areaType` | `solid \| dotted \| gradient` | No | `'gradient'` | The type of area fill to add to the line. |
24
+ | `aspectRatio` | `string \| number` | No | `-` | - |
25
+ | `background` | `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 | `-` | - |
26
+ | `borderBottomLeftRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
27
+ | `borderBottomRightRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
28
+ | `borderBottomWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
29
+ | `borderColor` | `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 | `-` | - |
30
+ | `borderEndWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
31
+ | `borderRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
32
+ | `borderStartWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
33
+ | `borderTopLeftRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
34
+ | `borderTopRightRadius` | `0 \| 100 \| 200 \| 300 \| 400 \| 500 \| 600 \| 700 \| 800 \| 900 \| 1000` | No | `-` | - |
35
+ | `borderTopWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
36
+ | `borderWidth` | `0 \| 100 \| 200 \| 300 \| 400 \| 500` | No | `-` | - |
37
+ | `bordered` | `boolean` | No | `-` | Add a border around all sides of the box. |
38
+ | `borderedBottom` | `boolean` | No | `-` | Add a border to the bottom side of the box. |
39
+ | `borderedEnd` | `boolean` | No | `-` | Add a border to the trailing side of the box. |
40
+ | `borderedHorizontal` | `boolean` | No | `-` | Add a border to the leading and trailing sides of the box. |
41
+ | `borderedStart` | `boolean` | No | `-` | Add a border to the leading side of the box. |
42
+ | `borderedTop` | `boolean` | No | `-` | Add a border to the top side of the box. |
43
+ | `borderedVertical` | `boolean` | No | `-` | Add a border to the top and bottom sides of the box. |
44
+ | `bottom` | `string \| number` | No | `-` | - |
45
+ | `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 | `-` | - |
46
+ | `columnGap` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
47
+ | `curve` | `bump \| catmullRom \| linear \| linearClosed \| monotone \| natural \| step \| stepBefore \| stepAfter` | No | `'linear'` | The curve interpolation method to use for the line. |
48
+ | `dangerouslySetBackground` | `string` | No | `-` | - |
49
+ | `display` | `flex \| none` | No | `-` | - |
50
+ | `elevation` | `0 \| 1 \| 2` | No | `-` | Determines box shadow styles. Parent should have overflow set to visible to ensure styles are not clipped. |
51
+ | `enableScrubbing` | `boolean` | No | `-` | Enables scrubbing interactions. When true, allows scrubbing and makes scrubber components interactive. |
52
+ | `flexBasis` | `string \| number` | No | `-` | - |
53
+ | `flexDirection` | `row \| column \| row-reverse \| column-reverse` | No | `-` | - |
54
+ | `flexGrow` | `number` | No | `-` | - |
55
+ | `flexShrink` | `number` | No | `-` | - |
56
+ | `flexWrap` | `wrap \| nowrap \| wrap-reverse` | No | `-` | - |
57
+ | `font` | `inherit \| FontFamily` | No | `-` | - |
58
+ | `fontFamily` | `inherit \| FontFamily` | No | `-` | - |
59
+ | `fontSize` | `inherit \| FontSize` | No | `-` | - |
60
+ | `fontWeight` | `inherit \| FontWeight` | No | `-` | - |
61
+ | `gap` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
62
+ | `height` | `string \| number` | No | `-` | Chart height. If not provided, will use the containers measured height. |
63
+ | `inset` | `number \| Partial<ChartInset>` | No | `-` | Inset around the entire chart (outside the axes). |
64
+ | `justifyContent` | `flex-start \| flex-end \| center \| space-between \| space-around \| space-evenly` | No | `-` | - |
65
+ | `key` | `Key \| null` | No | `-` | - |
66
+ | `left` | `string \| number` | No | `-` | - |
67
+ | `lineHeight` | `inherit \| LineHeight` | No | `-` | - |
68
+ | `margin` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
69
+ | `marginBottom` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
70
+ | `marginEnd` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
71
+ | `marginStart` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
72
+ | `marginTop` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
73
+ | `marginX` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
74
+ | `marginY` | `0 \| -1 \| -2 \| -3 \| -4 \| -5 \| -6 \| -7 \| -8 \| -9 \| -10 \| -0.25 \| -0.5 \| -0.75 \| -1.5` | No | `-` | - |
75
+ | `maxHeight` | `string \| number` | No | `-` | - |
76
+ | `maxWidth` | `string \| number` | No | `-` | - |
77
+ | `minHeight` | `string \| number` | No | `-` | - |
78
+ | `minWidth` | `string \| number` | No | `-` | - |
79
+ | `onPointerCancel` | `((event: PointerEvent) => void)` | No | `-` | - |
80
+ | `onPointerCancelCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
81
+ | `onPointerDown` | `((event: PointerEvent) => void)` | No | `-` | - |
82
+ | `onPointerDownCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
83
+ | `onPointerEnter` | `((event: PointerEvent) => void)` | No | `-` | - |
84
+ | `onPointerEnterCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
85
+ | `onPointerLeave` | `((event: PointerEvent) => void)` | No | `-` | - |
86
+ | `onPointerLeaveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
87
+ | `onPointerMove` | `((event: PointerEvent) => void)` | No | `-` | - |
88
+ | `onPointerMoveCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
89
+ | `onPointerUp` | `((event: PointerEvent) => void)` | No | `-` | - |
90
+ | `onPointerUpCapture` | `((event: PointerEvent) => void)` | No | `-` | - |
91
+ | `onScrubberPositionChange` | `((index: number) => void) \| undefined` | No | `-` | Callback fired when the scrubber position changes. Receives the dataIndex of the scrubber or undefined when not scrubbing. |
92
+ | `opacity` | `number \| AnimatedNode` | No | `-` | - |
93
+ | `overflow` | `visible \| hidden \| scroll` | No | `-` | - |
94
+ | `padding` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
95
+ | `paddingBottom` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
96
+ | `paddingEnd` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
97
+ | `paddingStart` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
98
+ | `paddingTop` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
99
+ | `paddingX` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
100
+ | `paddingY` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
101
+ | `pin` | `top \| bottom \| left \| right \| all` | No | `-` | Direction in which to absolutely pin the box. |
102
+ | `position` | `absolute \| relative \| static \| fixed \| sticky` | No | `-` | - |
103
+ | `ref` | `((instance: View \| null) => void) \| RefObject<View> \| null` | No | `-` | - |
104
+ | `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. |
105
+ | `right` | `string \| number` | No | `-` | - |
106
+ | `rowGap` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9 \| 10 \| 0.25 \| 0.5 \| 0.75 \| 1.5` | No | `-` | - |
107
+ | `series` | `LineSeries[]` | No | `-` | Configuration objects that define how to visualize the data. Each series supports Line component props for individual customization. |
108
+ | `showArea` | `boolean` | No | `-` | Show area fill under the line. |
109
+ | `showXAxis` | `boolean` | No | `-` | Whether to show the X axis. |
110
+ | `showYAxis` | `boolean` | No | `-` | Whether to show the Y axis. |
111
+ | `strokeWidth` | `number` | No | `-` | - |
112
+ | `style` | `false \| RegisteredStyle<ViewStyle> \| Value \| AnimatedInterpolation<string \| number> \| WithAnimatedObject<ViewStyle> \| WithAnimatedArray<Falsy \| ViewStyle \| RegisteredStyle<ViewStyle> \| RecursiveArray<Falsy \| ViewStyle \| RegisteredStyle<ViewStyle>> \| readonly (Falsy \| ViewStyle \| RegisteredStyle<ViewStyle>)[]> \| null` | No | `-` | - |
113
+ | `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. |
114
+ | `textAlign` | `auto \| left \| right \| center \| justify` | No | `-` | - |
115
+ | `textDecorationLine` | `none \| underline \| line-through \| underline line-through` | No | `-` | - |
116
+ | `textDecorationStyle` | `solid \| dotted \| dashed \| double` | No | `-` | - |
117
+ | `textTransform` | `none \| capitalize \| uppercase \| lowercase` | No | `-` | - |
118
+ | `top` | `string \| number` | No | `-` | - |
119
+ | `transform` | `string \| (({ scaleX: AnimatableNumericValue; } & { scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ scaleY: AnimatableNumericValue; } & { scaleX?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ translateX: AnimatableNumericValue \| ${number}%; } & { scaleX?: undefined; scaleY?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ translateY: AnimatableNumericValue \| ${number}%; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ perspective: AnimatableNumericValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotate: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateX: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateY: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ rotateZ: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ scale: AnimatableNumericValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; skewX?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ skewX: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewY?: undefined; matrix?: undefined; }) \| ({ skewY: AnimatableStringValue; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; matrix?: undefined; }) \| ({ matrix: AnimatableNumericValue[]; } & { scaleX?: undefined; scaleY?: undefined; translateX?: undefined; translateY?: undefined; perspective?: undefined; rotate?: undefined; rotateX?: undefined; rotateY?: undefined; rotateZ?: undefined; scale?: undefined; skewX?: undefined; skewY?: undefined; }))[]` | No | `-` | - |
120
+ | `type` | `solid \| dotted \| gradient` | No | `'solid'` | The type of line to render. |
121
+ | `userSelect` | `auto \| none \| text \| contain \| all` | No | `-` | - |
122
+ | `width` | `string \| number` | No | `-` | Chart width. If not provided, will use the containers measured width. |
123
+ | `xAxis` | `(Partial<AxisConfigProps> & AxisBaseProps & { className?: string; classNames?: { root?: string \| undefined; tickLabel?: string \| undefined; gridLine?: string \| undefined; line?: string \| undefined; tickMark?: string \| undefined; } \| undefined; style?: CSSProperties \| undefined; styles?: { root?: CSSProperties \| undefined; tickLabel?: CSSProperties \| undefined; gridLine?: CSSProperties \| undefined; line?: CSSProperties \| undefined; tickMark?: CSSProperties \| undefined; } \| undefined; } & { position?: top \| bottom \| undefined; height?: number \| undefined; }) \| undefined` | No | `-` | - |
124
+ | `yAxis` | `(Partial<AxisConfigProps> & AxisBaseProps & { className?: string; classNames?: { root?: string \| undefined; tickLabel?: string \| undefined; gridLine?: string \| undefined; line?: string \| undefined; tickMark?: string \| undefined; } \| undefined; style?: CSSProperties \| undefined; styles?: { root?: CSSProperties \| undefined; tickLabel?: CSSProperties \| undefined; gridLine?: CSSProperties \| undefined; line?: CSSProperties \| undefined; tickMark?: CSSProperties \| undefined; } \| undefined; } & { axisId?: string \| undefined; position?: left \| right \| undefined; width?: number \| undefined; }) \| undefined` | No | `-` | - |
125
+ | `zIndex` | `number` | No | `-` | - |
126
+
127
+
128
+ ## Examples
129
+
130
+ ### Basic Example
131
+
132
+ ```jsx
133
+ function BasicExample() {
134
+ const [scrubIndex, setScrubIndex] = useState(undefined);
135
+ const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
136
+
137
+ const accessibilityLabel = useMemo(() => {
138
+ if (scrubIndex === undefined) return undefined;
139
+ return `Value: ${data[scrubIndex]} at index ${scrubIndex}`;
140
+ }, [scrubIndex, data]);
141
+
142
+ return (
143
+ <LineChart
144
+ enableScrubbing
145
+ onScrubberPositionChange={setScrubIndex}
146
+ height={150}
147
+ series={[
148
+ {
149
+ id: 'prices',
150
+ data: data,
151
+ },
152
+ ]}
153
+ curve="monotone"
154
+ showYAxis
155
+ showArea
156
+ yAxis={{
157
+ showGrid: true,
158
+ }}
159
+ accessibilityLabel={accessibilityLabel}
160
+ >
161
+ <Scrubber />
162
+ </LineChart>
163
+ );
164
+ }
165
+ ```
166
+
167
+ ### Simple
168
+
169
+ ```jsx
170
+ <LineChart
171
+ height={150}
172
+ series={[
173
+ {
174
+ id: 'prices',
175
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
176
+ },
177
+ ]}
178
+ curve="monotone"
179
+ />
180
+ ```
181
+
182
+ ### Compact
183
+
184
+ You can specify the dimensions of the chart to make it more compact.
185
+
186
+ ```jsx
187
+ function CompactLineChart() {
188
+ const theme = useTheme();
189
+ const dimensions = { width: 62, height: 18 };
190
+
191
+ const sparklineData = prices
192
+ .map((price) => parseFloat(price))
193
+ .filter((price, index) => index % 10 === 0);
194
+ const positiveFloor = Math.min(...sparklineData) - 10;
195
+
196
+ const negativeData = sparklineData.map((price) => -1 * price).reverse();
197
+ const negativeCeiling = Math.max(...negativeData) + 10;
198
+
199
+ const formatPrice = useCallback((price: number) => {
200
+ return `$${price.toLocaleString('en-US', {
201
+ minimumFractionDigits: 2,
202
+ maximumFractionDigits: 2,
203
+ })}`;
204
+ }, []);
205
+
206
+ const CompactChart = memo(({ data, showArea, color, referenceY }) => (
207
+ <Box style={{ padding: 1 }}>
208
+ <LineChart
209
+ {...dimensions}
210
+ enableScrubbing={false}
211
+ overflow="visible"
212
+ inset={0}
213
+ showArea={showArea}
214
+ series={[
215
+ {
216
+ id: 'btc',
217
+ data,
218
+ color,
219
+ },
220
+ ]}
221
+ >
222
+ <ReferenceLine dataY={referenceY} />
223
+ </LineChart>
224
+ </Box>
225
+ ));
226
+
227
+ const ChartCell = memo(({ data, showArea, color, referenceY, subdetail, variant }) => {
228
+ return (
229
+ <ListCell
230
+ detail={formatPrice(parseFloat(prices[0]))}
231
+ intermediary={
232
+ <CompactChart data={data} showArea={showArea} color={color} referenceY={referenceY} />
233
+ }
234
+ media={<CellMedia source={assets.btc.imageUrl} title="BTC" type="image" />}
235
+ onClick={() => console.log('clicked')}
236
+ subdetail={subdetail}
237
+ title={isPhone ? undefined : assets.btc.name}
238
+ variant={variant}
239
+ style={{ padding: 0 }}
240
+ />
241
+ );
242
+ });
243
+
244
+ return (
245
+ <VStack gap={2}>
246
+ <ChartCell
247
+ data={sparklineData}
248
+ color={assets.btc.color}
249
+ referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
250
+ subdetail="-4.55%"
251
+ variant="negative"
252
+ />
253
+ <ChartCell
254
+ data={sparklineData}
255
+ showArea
256
+ color={assets.btc.color}
257
+ referenceY={parseFloat(prices[Math.floor(prices.length / 4)])}
258
+ subdetail="-4.55%"
259
+ variant="negative"
260
+ />
261
+ <ChartCell
262
+ data={sparklineData}
263
+ showArea
264
+ color={theme.color.fgPositive}
265
+ referenceY={positiveFloor}
266
+ subdetail="+0.25%"
267
+ variant="positive"
268
+ />
269
+ <ChartCell
270
+ data={negativeData}
271
+ showArea
272
+ color={theme.color.fgNegative}
273
+ referenceY={negativeCeiling}
274
+ subdetail="-4.55%"
275
+ variant="negative"
276
+ />
277
+ </VStack>
278
+ );
279
+ };
280
+ ```
281
+
282
+ ### Gain/Loss
283
+
284
+ You can use the y-axis scale and a [linearGradient](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/linearGradient) to create a gain/loss chart.
285
+
286
+ ```jsx
287
+ function GainLossChart() {
288
+ const theme = useTheme();
289
+ const gradientId = useId();
290
+
291
+ const data = [-40, -28, -21, -5, 48, -5, -28, 2, -29, -46, 16, -30, -29, 8];
292
+
293
+ const priceFormatter = useCallback(
294
+ (value) =>
295
+ new Intl.NumberFormat('en-US', {
296
+ style: 'currency',
297
+ currency: 'USD',
298
+ maximumFractionDigits: 0,
299
+ }).format(value),
300
+ [],
301
+ );
302
+
303
+ const ChartDefs = ({ threshold = 0 }) => {
304
+ const { getYScale } = useCartesianChartContext();
305
+ // get the default y-axis scale
306
+ const yScale = getYScale();
307
+
308
+ if (yScale) {
309
+ const domain = yScale.domain();
310
+ const range = yScale.range();
311
+
312
+ const baselinePercentage = ((threshold - domain[0]) / (domain[1] - domain[0])) * 100;
313
+
314
+ const negativeColor = `rgb(${theme.color.gray15})`;
315
+ const positiveColor = theme.color.fgPositive;
316
+
317
+ return (
318
+ <Defs>
319
+ <LinearGradient
320
+ gradientUnits="userSpaceOnUse"
321
+ id={`${gradientId}-solid`}
322
+ x1="0%"
323
+ x2="0%"
324
+ y1={range[0]}
325
+ y2={range[1]}
326
+ >
327
+ <Stop offset="0%" stopColor={negativeColor} />
328
+ <Stop offset={`${baselinePercentage}%`} stopColor={negativeColor} />
329
+ <Stop offset={`${baselinePercentage}%`} stopColor={positiveColor} />
330
+ <Stop offset="100%" stopColor={positiveColor} />
331
+ </LinearGradient>
332
+ <LinearGradient
333
+ gradientUnits="userSpaceOnUse"
334
+ id={`${gradientId}-gradient`}
335
+ x1="0%"
336
+ x2="0%"
337
+ y1={range[0]}
338
+ y2={range[1]}
339
+ >
340
+ <Stop offset="0%" stopColor={negativeColor} stopOpacity={0.3} />
341
+ <Stop offset={`${baselinePercentage}%`} stopColor={negativeColor} stopOpacity={0} />
342
+ <Stop offset={`${baselinePercentage}%`} stopColor={positiveColor} stopOpacity={0} />
343
+ <Stop offset="100%" stopColor={positiveColor} stopOpacity={0.3} />
344
+ </LinearGradient>
345
+ </Defs>
346
+ );
347
+ }
348
+
349
+ return null;
350
+ };
351
+
352
+ const solidColor = `url(#${gradientId}-solid)`;
353
+
354
+ return (
355
+ <CartesianChart
356
+ enableScrubbing
357
+ height={150}
358
+ series={[
359
+ {
360
+ id: 'prices',
361
+ data: data,
362
+ color: solidColor,
363
+ },
364
+ ]}
365
+ padding={{ top: 1.5, bottom: 1.5, left: 2, right: 0 }}
366
+ >
367
+ <ChartDefs />
368
+ <YAxis requestedTickCount={2} showGrid tickLabelFormatter={priceFormatter} />
369
+ <Area seriesId="prices" curve="monotone" fill={`url(#${gradientId}-gradient)`} />
370
+ <Line strokeWidth={3} curve="monotone" seriesId="prices" stroke={solidColor} />
371
+ <Scrubber hideOverlay />
372
+ </CartesianChart>
373
+ );
374
+ }
375
+ ```
376
+
377
+ ### Multiple Series
378
+
379
+ You can add multiple series to a line chart.
380
+
381
+ ```jsx
382
+ function MultipleSeriesChart() {
383
+ const theme = useTheme();
384
+ const [scrubIndex, setScrubIndex] = useState(undefined);
385
+
386
+ const prices = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
387
+ const volume = [4, 8, 11, 15, 16, 14, 16, 10, 12, 14, 16, 14, 16, 10];
388
+
389
+ return (
390
+ <LineChart
391
+ enableScrubbing
392
+ height={150}
393
+ series={[
394
+ {
395
+ id: 'prices',
396
+ data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58],
397
+ label: 'Prices',
398
+ color: theme.color.accentBoldBlue,
399
+ },
400
+ {
401
+ id: 'volume',
402
+ data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14, 16, 14, 16, 10],
403
+ label: 'Volume',
404
+ color: theme.color.accentBoldGreen,
405
+ },
406
+ ]}
407
+ showYAxis
408
+ yAxis={{
409
+ domain: {
410
+ min: 0,
411
+ },
412
+ showGrid: true,
413
+ }}
414
+ curve="monotone"
415
+ >
416
+ <Scrubber />
417
+ </LineChart>
418
+ );
419
+ }
420
+ ```
421
+
422
+ ### Points
423
+
424
+ You can use the `renderPoints` prop to dynamically show points on a line.
425
+
426
+ ```jsx
427
+ function PointsChart() {
428
+ const theme = useTheme();
429
+ const keyMarketShiftIndices = [4, 6, 7, 9, 10];
430
+ const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
431
+
432
+ return (
433
+ <CartesianChart
434
+ height={150}
435
+ series={[
436
+ {
437
+ id: 'prices',
438
+ data: data,
439
+ },
440
+ ]}
441
+ >
442
+ <Area seriesId="prices" curve="monotone" fill={`rgb(${theme.color.blue5})`} />
443
+ <Line
444
+ seriesId="prices"
445
+ renderPoints={({ dataX, dataY, ...props }) =>
446
+ keyMarketShiftIndices.includes(dataX)
447
+ ? {
448
+ ...props,
449
+ strokeWidth: 2,
450
+ stroke: theme.color.bg,
451
+ radius: 5,
452
+ onClick: () =>
453
+ alert(
454
+ `You have clicked a key market shift at position ${dataX + 1} with value ${dataY}!`,
455
+ ),
456
+ accessibilityLabel: `Key market shift point at position ${dataX + 1}, value ${dataY}. Click to view details.`,
457
+ }
458
+ : false
459
+ }
460
+ curve="monotone"
461
+ />
462
+ </CartesianChart>
463
+ );
464
+ }
465
+ ```
466
+
467
+ ### Empty State
468
+
469
+ This example shows how to use an empty state for a line chart.
470
+
471
+ ```jsx
472
+ function EmptyStateChart() {
473
+ const theme = useTheme();
474
+ return (
475
+ <LineChart
476
+ series={[
477
+ {
478
+ id: 'line',
479
+ color: `rgb(${theme.color.gray50})`,
480
+ data: [1, 1],
481
+ showArea: true,
482
+ },
483
+ ]}
484
+ yAxis={{ domain: { min: -1, max: 3 } }}
485
+ height={150}
486
+ />
487
+ );
488
+ }
489
+ ```
490
+
491
+ ### Line Styles
492
+
493
+ ```jsx
494
+ <LineChart
495
+ height={150}
496
+ series={[
497
+ {
498
+ id: 'top',
499
+ data: [15, 28, 32, 44, 46, 36, 40, 45, 48, 38],
500
+ },
501
+ {
502
+ id: 'upperMiddle',
503
+ data: [12, 23, 21, 29, 34, 28, 31, 38, 42, 35],
504
+ color: '#ef4444',
505
+ type: 'dotted',
506
+ },
507
+ {
508
+ id: 'lowerMiddle',
509
+ data: [8, 15, 14, 25, 20, 18, 22, 28, 24, 30],
510
+ color: '#f59e0b',
511
+ curve: 'natural',
512
+ LineComponent: (props) => (
513
+ <GradientLine {...props} endColor="#F7931A" startColor="#E3D74D" strokeWidth={4} />
514
+ ),
515
+ },
516
+ {
517
+ id: 'bottom',
518
+ data: [4, 8, 11, 15, 16, 14, 16, 10, 12, 14],
519
+ color: '#800080',
520
+ curve: 'step',
521
+ AreaComponent: DottedArea,
522
+ showArea: true,
523
+ },
524
+ ]}
525
+ />
526
+ ```
527
+
528
+ ### Live Data
529
+
530
+ ```jsx
531
+ function LiveAssetPrice() {
532
+ const scrubberRef = useRef(null);
533
+ const [scrubIndex, setScrubIndex] = useState(undefined);
534
+
535
+ const initialData = useMemo(() => {
536
+ return sparklineInteractiveData.hour.map((d) => d.value);
537
+ }, []);
538
+
539
+ const [priceData, setPriceData] = useState(initialData);
540
+
541
+ const lastDataPointTimeRef = useRef(Date.now());
542
+ const updateCountRef = useRef(0);
543
+
544
+ const intervalSeconds = 3600 / initialData.length;
545
+
546
+ const maxPercentChange = Math.abs(initialData[initialData.length - 1] - initialData[0]) * 0.05;
547
+
548
+ useEffect(() => {
549
+ const priceUpdateInterval = setInterval(
550
+ () => {
551
+ setPriceData((currentData) => {
552
+ const newData = [...currentData];
553
+ const lastPrice = newData[newData.length - 1];
554
+
555
+ const priceChange = (Math.random() - 0.5) * maxPercentChange;
556
+ const newPrice = Math.round((lastPrice + priceChange) * 100) / 100;
557
+
558
+ // Check if we should roll over to a new data point
559
+ const currentTime = Date.now();
560
+ const timeSinceLastPoint = (currentTime - lastDataPointTimeRef.current) / 1000;
561
+
562
+ if (timeSinceLastPoint >= intervalSeconds) {
563
+ // Time for a new data point - remove first, add new at end
564
+ lastDataPointTimeRef.current = currentTime;
565
+ newData.shift(); // Remove oldest data point
566
+ newData.push(newPrice); // Add new data point
567
+ updateCountRef.current = 0;
568
+ } else {
569
+ // Just update the last data point
570
+ newData[newData.length - 1] = newPrice;
571
+ updateCountRef.current++;
572
+ }
573
+
574
+ return newData;
575
+ });
576
+
577
+ // Pulse the scrubber on each update
578
+ scrubberRef.current?.pulse();
579
+ },
580
+ 2000 + Math.random() * 1000,
581
+ );
582
+
583
+ return () => clearInterval(priceUpdateInterval);
584
+ }, [intervalSeconds, maxPercentChange]);
585
+
586
+ const accessibilityLabel = useMemo(() => {
587
+ if (scrubIndex === undefined)
588
+ return `Bitcoin Price: $${priceData[priceData.length - 1].toFixed(2)}`;
589
+ const price = priceData[scrubIndex];
590
+ return `Bitcoin Price: $${price.toFixed(2)} at position ${scrubIndex + 1}`;
591
+ }, [scrubIndex, priceData]);
592
+
593
+ return (
594
+ <LineChart
595
+ enableScrubbing
596
+ onScrubberPositionChange={setScrubIndex}
597
+ showArea
598
+ height={150}
599
+ series={[
600
+ {
601
+ id: 'btc',
602
+ data: priceData,
603
+ color: assets.btc.color,
604
+ },
605
+ ]}
606
+ inset={{ right: 64 }}
607
+ accessibilityLabel={accessibilityLabel}
608
+ >
609
+ <Scrubber ref={scrubberRef} labelProps={{ elevation: 1 }} />
610
+ </LineChart>
611
+ );
612
+ }
613
+ ```
614
+
615
+ ### Data Format
616
+
617
+ You can adjust the y values for a series of data by setting the `data` prop on the xAxis.
618
+
619
+ ```jsx
620
+ function DataFormatChart() {
621
+ const [scrubIndex, setScrubIndex] = useState(undefined);
622
+
623
+ const yData = [2, 5.5, 2, 8.5, 1.5, 5];
624
+ const xData = [1, 2, 3, 5, 8, 10];
625
+
626
+ const accessibilityLabel = useMemo(() => {
627
+ if (scrubIndex === undefined) return undefined;
628
+ return `X: ${xData[scrubIndex]}, Y: ${yData[scrubIndex]} at point ${scrubIndex + 1}`;
629
+ }, [scrubIndex, xData, yData]);
630
+
631
+ return (
632
+ <LineChart
633
+ enableScrubbing
634
+ onScrubberPositionChange={setScrubIndex}
635
+ series={[
636
+ {
637
+ id: 'line',
638
+ data: yData,
639
+ },
640
+ ]}
641
+ height={150}
642
+ showArea
643
+ renderPoints={() => true}
644
+ curve="natural"
645
+ showXAxis
646
+ xAxis={{ data: xData, showLine: true, showTickMarks: true, showGrid: true }}
647
+ showYAxis
648
+ yAxis={{
649
+ domain: { min: 0 },
650
+ position: 'left',
651
+ showLine: true,
652
+ showTickMarks: true,
653
+ showGrid: true,
654
+ }}
655
+ inset={{ top: 16, right: 16, bottom: 0, left: 0 }}
656
+ accessibilityLabel={accessibilityLabel}
657
+ >
658
+ <Scrubber hideOverlay />
659
+ </LineChart>
660
+ );
661
+ }
662
+ ```
663
+
664
+ ### Accessibility
665
+
666
+ You can use `accessibilityLabel` to provide a descriptive label for the chart. This is especially important for charts with scrubbing enabled, where the label should update dynamically to reflect the current data point.
667
+
668
+ ```jsx
669
+ function AccessibleBasicChart() {
670
+ const [scrubIndex, setScrubIndex] = useState(undefined);
671
+ const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
672
+
673
+ const accessibilityLabel = useMemo(() => {
674
+ if (scrubIndex === undefined) {
675
+ return `Current price: ${data[data.length - 1]}`;
676
+ }
677
+ return `Price at position ${scrubIndex + 1}: ${data[scrubIndex]}`;
678
+ }, [scrubIndex, data]);
679
+
680
+ return (
681
+ <LineChart
682
+ enableScrubbing
683
+ onScrubberPositionChange={setScrubIndex}
684
+ height={150}
685
+ series={[
686
+ {
687
+ id: 'prices',
688
+ data: data,
689
+ },
690
+ ]}
691
+ curve="monotone"
692
+ showYAxis
693
+ showArea
694
+ yAxis={{
695
+ showGrid: true,
696
+ }}
697
+ accessibilityLabel={accessibilityLabel}
698
+ >
699
+ <Scrubber />
700
+ </LineChart>
701
+ );
702
+ }
703
+ ```
704
+
705
+ When a chart has a visible header or title, you can use `aria-labelledby` to reference it.
706
+
707
+ ```jsx
708
+ function AccessibleChartWithHeader() {
709
+ const headerId = useId();
710
+ const [scrubIndex, setScrubIndex] = useState(undefined);
711
+ const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58];
712
+
713
+ const accessibilityLabel = useMemo(() => {
714
+ if (scrubIndex === undefined) {
715
+ return `Line chart showing price trend. Current value: ${data[data.length - 1]}`;
716
+ }
717
+ return `Value: ${data[scrubIndex]} at position ${scrubIndex + 1}`;
718
+ }, [scrubIndex, data]);
719
+
720
+ return (
721
+ <VStack gap={2}>
722
+ <Text id={headerId} font="label1">
723
+ {accessibilityLabel}
724
+ </Text>
725
+ <LineChart
726
+ enableScrubbing
727
+ onScrubberPositionChange={setScrubIndex}
728
+ height={150}
729
+ series={[
730
+ {
731
+ id: 'revenue',
732
+ data: data,
733
+ },
734
+ ]}
735
+ curve="monotone"
736
+ showYAxis
737
+ showArea
738
+ yAxis={{
739
+ showGrid: true,
740
+ }}
741
+ aria-labelledby={headerId}
742
+ >
743
+ <Scrubber />
744
+ </LineChart>
745
+ </VStack>
746
+ );
747
+ }
748
+ ```
749
+
750
+ ### Customization
751
+
752
+ #### Asset Price with Dotted Area
753
+
754
+ ```jsx
755
+ function AssetPriceWithDottedArea() {
756
+ const BTCTab: TabComponent = memo(
757
+ forwardRef(
758
+ ({ label, ...props }: SegmentedTabProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
759
+ const { activeTab } = useTabsContext();
760
+ const isActive = activeTab?.id === props.id;
761
+
762
+ return (
763
+ <SegmentedTab
764
+ ref={ref}
765
+ label={
766
+ <TextLabel1
767
+ style={{
768
+ transition: 'color 0.2s ease',
769
+ color: isActive ? assets.btc.color : undefined,
770
+ }}
771
+ >
772
+ {label}
773
+ </TextLabel1>
774
+ }
775
+ {...props}
776
+ />
777
+ );
778
+ },
779
+ ),
780
+ );
781
+
782
+ const BTCActiveIndicator = memo(({ style, ...props }: TabsActiveIndicatorProps) => (
783
+ <PeriodSelectorActiveIndicator
784
+ {...props}
785
+ style={{ ...style, backgroundColor: `${assets.btc.color}1A` }}
786
+ />
787
+ ));
788
+
789
+ const AssetPriceDotted = memo(() => {
790
+ const [scrubIndex, setScrubIndex] = useState<number | undefined>(undefined);
791
+ const currentPrice =
792
+ sparklineInteractiveData.hour[sparklineInteractiveData.hour.length - 1].value;
793
+ const tabs = [
794
+ { id: 'hour', label: '1H' },
795
+ { id: 'day', label: '1D' },
796
+ { id: 'week', label: '1W' },
797
+ { id: 'month', label: '1M' },
798
+ { id: 'year', label: '1Y' },
799
+ { id: 'all', label: 'All' },
800
+ ];
801
+ const [timePeriod, setTimePeriod] = useState<TabValue>(tabs[0]);
802
+
803
+ const sparklineTimePeriodData = useMemo(() => {
804
+ return sparklineInteractiveData[timePeriod.id as keyof typeof sparklineInteractiveData];
805
+ }, [timePeriod]);
806
+
807
+ const sparklineTimePeriodDataValues = useMemo(() => {
808
+ return sparklineTimePeriodData.map((d) => d.value);
809
+ }, [sparklineTimePeriodData]);
810
+
811
+ const sparklineTimePeriodDataTimestamps = useMemo(() => {
812
+ return sparklineTimePeriodData.map((d) => d.date);
813
+ }, [sparklineTimePeriodData]);
814
+
815
+ const onPeriodChange = useCallback(
816
+ (period: TabValue | null) => {
817
+ setTimePeriod(period || tabs[0]);
818
+ },
819
+ [tabs, setTimePeriod],
820
+ );
821
+
822
+ const priceFormatter = useMemo(
823
+ () =>
824
+ new Intl.NumberFormat('en-US', {
825
+ style: 'currency',
826
+ currency: 'USD',
827
+ }),
828
+ [],
829
+ );
830
+
831
+ const scrubberPriceFormatter = useMemo(
832
+ () =>
833
+ new Intl.NumberFormat('en-US', {
834
+ minimumFractionDigits: 2,
835
+ maximumFractionDigits: 2,
836
+ }),
837
+ [],
838
+ );
839
+
840
+ const formatPrice = useCallback((price: number) => {
841
+ return priceFormatter.format(price);
842
+ }, [priceFormatter]);
843
+
844
+ const formatDate = useCallback((date: Date) => {
845
+ const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
846
+
847
+ const monthDay = date.toLocaleDateString('en-US', {
848
+ month: 'short',
849
+ day: 'numeric',
850
+ });
851
+
852
+ const time = date.toLocaleTimeString('en-US', {
853
+ hour: 'numeric',
854
+ minute: '2-digit',
855
+ hour12: true,
856
+ });
857
+
858
+ return `${dayOfWeek}, ${monthDay}, ${time}`;
859
+ }, []);
860
+
861
+ const scrubberLabel = useCallback(
862
+ (index: number) => {
863
+ const price = scrubberPriceFormatter.format(sparklineTimePeriodDataValues[index]);
864
+ const date = formatDate(sparklineTimePeriodDataTimestamps[index]);
865
+ return (
866
+ <>
867
+ <tspan style={{ fontWeight: 'bold' }}>{price} USD</tspan> {date}
868
+ </>
869
+ );
870
+ },
871
+ [scrubberPriceFormatter, sparklineTimePeriodDataValues, sparklineTimePeriodDataTimestamps, formatDate],
872
+ );
873
+
874
+ const accessibilityLabel = useCallback(
875
+ (index: number) => {
876
+ const price = scrubberPriceFormatter.format(sparklineTimePeriodDataValues[index]);
877
+ const date = formatDate(sparklineTimePeriodDataTimestamps[index]);
878
+ return `${price} USD ${date}`;
879
+ },
880
+ [scrubberPriceFormatter, sparklineTimePeriodDataValues, sparklineTimePeriodDataTimestamps, formatDate],
881
+ );
882
+
883
+ return (
884
+ <VStack gap={2}>
885
+ <SectionHeader
886
+ style={{ padding: 0 }}
887
+ title={<Text font="title1">Bitcoin</Text>}
888
+ balance={<Text font="title2">{formatPrice(currentPrice)}</Text>}
889
+ end={
890
+ <VStack justifyContent="center">
891
+ <RemoteImage source={assets.btc.imageUrl} size="xl" shape="circle" />
892
+ </VStack>
893
+ }
894
+ />
895
+ <LineChart
896
+ overflow="visible"
897
+ enableScrubbing
898
+ onScrubberPositionChange={setScrubIndex}
899
+ series={[
900
+ {
901
+ id: 'btc',
902
+ data: sparklineTimePeriodDataValues,
903
+ color: assets.btc.color,
904
+ },
905
+ ]}
906
+ showArea
907
+ areaType="dotted"
908
+ height={150}
909
+ style={{ outlineColor: assets.btc.color }}
910
+ accessibilityLabel={scrubberLabel}
911
+ padding={{ left: 2, right: 2 }}
912
+ >
913
+ <Scrubber label={scrubberLabel} labelProps={{ elevation: 1 }} idlePulse />
914
+ </LineChart>
915
+ <PeriodSelector
916
+ TabComponent={BTCTab}
917
+ TabsActiveIndicatorComponent={BTCActiveIndicator}
918
+ tabs={tabs}
919
+ activeTab={timePeriod}
920
+ onChange={onPeriodChange}
921
+ />
922
+ </VStack>
923
+ )});
924
+
925
+ return <AssetPriceDotted />;
926
+ };
927
+ ```
928
+
929
+ #### Forecast Asset Price
930
+
931
+ ```jsx
932
+ function ForecastAssetPrice() {
933
+ const ForecastAreaComponent = memo(
934
+ (props: AreaComponentProps) => (
935
+ <DottedArea {...props} peakOpacity={0.4} baselineOpacity={0.4} />
936
+ ),
937
+ );
938
+
939
+ const ForecastChart = memo(() => {
940
+ const [scrubIndex, setScrubIndex] = useState<number | undefined>(undefined);
941
+
942
+ const getDataFromSparkline = useCallback((startDate: Date) => {
943
+ const allData = sparklineInteractiveData.all;
944
+ if (!allData || allData.length === 0) return [];
945
+
946
+ const timelineData = allData.filter((point) => point.date >= startDate);
947
+
948
+ return timelineData.map((point) => ({
949
+ date: point.date,
950
+ value: point.value,
951
+ }));
952
+ }, []);
953
+
954
+ const historicalData = useMemo(() => getDataFromSparkline(new Date('2019-01-01')), [getDataFromSparkline]);
955
+
956
+ const annualGrowthRate = 10;
957
+
958
+ const generateForecastData = useCallback(
959
+ (lastDate: Date, lastPrice: number, growthRate: number) => {
960
+ const dailyGrowthRate = Math.pow(1 + growthRate / 100, 1 / 365) - 1;
961
+ const forecastData = [];
962
+ const fiveYearsFromNow = new Date(lastDate);
963
+ fiveYearsFromNow.setFullYear(fiveYearsFromNow.getFullYear() + 5);
964
+
965
+ // Generate daily forecast points for 5 years
966
+ const currentDate = new Date(lastDate);
967
+ let currentPrice = lastPrice;
968
+
969
+ while (currentDate <= fiveYearsFromNow) {
970
+ currentPrice = currentPrice * (1 + dailyGrowthRate * 10);
971
+ forecastData.push({
972
+ date: new Date(currentDate),
973
+ value: Math.round(currentPrice),
974
+ });
975
+ currentDate.setDate(currentDate.getDate() + 10);
976
+ }
977
+
978
+ return forecastData;
979
+ },
980
+ [],
981
+ );
982
+
983
+ const priceFormatter = useMemo(
984
+ () =>
985
+ new Intl.NumberFormat('en-US', {
986
+ minimumFractionDigits: 2,
987
+ maximumFractionDigits: 2,
988
+ }),
989
+ [],
990
+ );
991
+
992
+ const formatDate = useCallback((date: Date) => {
993
+ const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'short' });
994
+
995
+ const monthDay = date.toLocaleDateString('en-US', {
996
+ month: 'short',
997
+ day: 'numeric',
998
+ });
999
+
1000
+ const time = date.toLocaleTimeString('en-US', {
1001
+ hour: 'numeric',
1002
+ minute: '2-digit',
1003
+ hour12: true,
1004
+ });
1005
+
1006
+ return `${dayOfWeek}, ${monthDay}, ${time}`;
1007
+ }, []);
1008
+
1009
+ const forecastData = useMemo(() => {
1010
+ if (historicalData.length === 0) return [];
1011
+ const lastPoint = historicalData[historicalData.length - 1];
1012
+ return generateForecastData(lastPoint.date, lastPoint.value, annualGrowthRate);
1013
+ }, [generateForecastData, historicalData, annualGrowthRate]);
1014
+
1015
+ // Combine all data points with dates converted to timestamps for x-axis
1016
+ const allDataPoints = useMemo(
1017
+ () => [...historicalData, ...forecastData],
1018
+ [historicalData, forecastData],
1019
+ );
1020
+
1021
+ const historicalDataValues = useMemo(
1022
+ () => historicalData.map((d) => d.value),
1023
+ [historicalData],
1024
+ );
1025
+
1026
+ const forecastDataValues = useMemo(
1027
+ () => [...historicalData.map((d) => null), ...forecastData.map((d) => d.value)],
1028
+ [historicalData, forecastData],
1029
+ );
1030
+
1031
+ const xAxisData = useMemo(
1032
+ () => allDataPoints.map((d) => d.date.getTime()),
1033
+ [allDataPoints],
1034
+ );
1035
+
1036
+ const scrubberLabel = useCallback(
1037
+ (index: number) => {
1038
+ const price = priceFormatter.format(allDataPoints[index].value);
1039
+ const date = formatDate(allDataPoints[index].date);
1040
+ return (
1041
+ <>
1042
+ <tspan style={{ fontWeight: 'bold' }}>{price} USD</tspan> {date}
1043
+ </>
1044
+ );
1045
+ },
1046
+ [priceFormatter, allDataPoints, formatDate],
1047
+ );
1048
+
1049
+ const accessibilityLabel = useCallback(
1050
+ (index: number) => {
1051
+ const price = priceFormatter.format(allDataPoints[index].value);
1052
+ const date = formatDate(allDataPoints[index].date);
1053
+ return `${price} USD ${date}`;
1054
+ },
1055
+ [priceFormatter, allDataPoints, formatDate],
1056
+ );
1057
+
1058
+ return (
1059
+ <LineChart
1060
+ overflow="visible"
1061
+ animate={false}
1062
+ enableScrubbing
1063
+ showArea
1064
+ showXAxis
1065
+ AreaComponent={ForecastAreaComponent}
1066
+ height={150}
1067
+ padding={{
1068
+ top: 4,
1069
+ left: 2,
1070
+ right: 2,
1071
+ bottom: 0,
1072
+ }}
1073
+ series={[
1074
+ {
1075
+ id: 'historical',
1076
+ data: historicalDataValues,
1077
+ color: assets.btc.color,
1078
+ },
1079
+ {
1080
+ id: 'forecast',
1081
+ data: forecastDataValues,
1082
+ color: assets.btc.color,
1083
+ type: 'dotted',
1084
+ },
1085
+ ]}
1086
+ xAxis={{
1087
+ data: xAxisData,
1088
+ tickLabelFormatter: (value: number) => {
1089
+ return new Date(value).toLocaleDateString('en-US', {
1090
+ month: 'numeric',
1091
+ year: 'numeric',
1092
+ });
1093
+ },
1094
+ tickInterval: 2,
1095
+ }}
1096
+ accessibilityLabel={accessibilityLabel}
1097
+ style={{ outlineColor: assets.btc.color }}
1098
+ >
1099
+ <Scrubber label={scrubberLabel} labelProps={{ elevation: 1 }} />
1100
+ </LineChart>
1101
+ );
1102
+ });
1103
+
1104
+ return <ForecastChart />;
1105
+ };
1106
+ ```
1107
+
1108
+ #### Availability
1109
+
1110
+ ```jsx
1111
+ function AvailabilityChart() {
1112
+ const theme = useTheme();
1113
+ const [scrubIndex, setScrubIndex] = useState(undefined);
1114
+
1115
+ const availabilityEvents = [
1116
+ {
1117
+ date: new Date('2022-01-01'),
1118
+ availability: 79,
1119
+ },
1120
+ {
1121
+ date: new Date('2022-01-03'),
1122
+ availability: 81,
1123
+ },
1124
+ {
1125
+ date: new Date('2022-01-04'),
1126
+ availability: 82,
1127
+ },
1128
+ {
1129
+ date: new Date('2022-01-06'),
1130
+ availability: 91,
1131
+ },
1132
+ {
1133
+ date: new Date('2022-01-07'),
1134
+ availability: 92,
1135
+ },
1136
+ {
1137
+ date: new Date('2022-01-10'),
1138
+ availability: 86,
1139
+ },
1140
+ ];
1141
+
1142
+ const accessibilityLabel = useMemo(() => {
1143
+ if (scrubIndex === undefined) return undefined;
1144
+ const event = availabilityEvents[scrubIndex];
1145
+ const formattedDate = event.date.toLocaleDateString('en-US', {
1146
+ weekday: 'short',
1147
+ month: 'short',
1148
+ day: 'numeric',
1149
+ year: 'numeric',
1150
+ });
1151
+ const status =
1152
+ event.availability >= 90 ? 'Good' : event.availability >= 85 ? 'Warning' : 'Critical';
1153
+ return `${formattedDate}: Availability ${event.availability}% - Status: ${status}`;
1154
+ }, [scrubIndex, availabilityEvents]);
1155
+
1156
+ const ChartDefs = memo(({ yellowThresholdPercentage = 85, greenThresholdPercentage = 90 }) => {
1157
+ const { drawingArea, height, series, getYScale, getYAxis } = useCartesianChartContext();
1158
+ const yScale = getYScale();
1159
+ const yAxis = getYAxis();
1160
+
1161
+ if (!series || !drawingArea || !yScale) return null;
1162
+
1163
+ const rangeBounds = yAxis?.domain;
1164
+ const rangeMin = rangeBounds?.min ?? 0;
1165
+ const rangeMax = rangeBounds?.max ?? 100;
1166
+
1167
+ // Calculate the Y positions in the chart coordinate system
1168
+ const yellowThresholdY = yScale(yellowThresholdPercentage) ?? 0;
1169
+ const greenThresholdY = yScale(greenThresholdPercentage) ?? 0;
1170
+ const minY = yScale(rangeMax) ?? 0; // Top of chart (max value)
1171
+ const maxY = yScale(rangeMin) ?? drawingArea.height; // Bottom of chart (min value)
1172
+
1173
+ // Calculate percentages based on actual chart positions
1174
+ const yellowThreshold = ((yellowThresholdY - minY) / (maxY - minY)) * 100;
1175
+ const greenThreshold = ((greenThresholdY - minY) / (maxY - minY)) * 100;
1176
+
1177
+ return (
1178
+ <Defs>
1179
+ <LinearGradient
1180
+ gradientUnits="userSpaceOnUse"
1181
+ id="availabilityGradient"
1182
+ x1="0%"
1183
+ x2="0%"
1184
+ y1={minY}
1185
+ y2={maxY}
1186
+ >
1187
+ <stop offset="0%" stopColor={theme.color.fgPositive} />
1188
+ <stop offset={`${greenThreshold}%`} stopColor={theme.color.fgPositive} />
1189
+ <stop offset={`${greenThreshold}%`} stopColor={theme.color.fgWarning} />
1190
+ <stop offset={`${yellowThreshold}%`} stopColor={theme.color.fgWarning} />
1191
+ <stop offset={`${yellowThreshold}%`} stopColor={theme.color.fgNegative} />
1192
+ <stop offset="100%" stopColor={theme.color.fgNegative} />
1193
+ </LinearGradient>
1194
+ </Defs>
1195
+ );
1196
+ });
1197
+
1198
+ return (
1199
+ <CartesianChart
1200
+ enableScrubbing
1201
+ onScrubberPositionChange={setScrubIndex}
1202
+ height={150}
1203
+ series={[
1204
+ {
1205
+ id: 'availability',
1206
+ data: availabilityEvents.map((event) => event.availability),
1207
+ color: 'url(#availabilityGradient)',
1208
+ },
1209
+ ]}
1210
+ xAxis={{
1211
+ data: availabilityEvents.map((event) => event.date.getTime()),
1212
+ }}
1213
+ yAxis={{
1214
+ domain: ({ min, max }) => ({ min: Math.max(min - 2, 0), max: Math.min(max + 2, 100) }),
1215
+ }}
1216
+ padding={{ left: 2, right: 2 }}
1217
+ accessibilityLabel={accessibilityLabel}
1218
+ >
1219
+ <ChartDefs />
1220
+ <XAxis
1221
+ showGrid
1222
+ showLine
1223
+ showTickMarks
1224
+ tickLabelFormatter={(value) => new Date(value).toLocaleDateString()}
1225
+ />
1226
+ <YAxis
1227
+ showGrid
1228
+ showLine
1229
+ showTickMarks
1230
+ position="left"
1231
+ tickLabelFormatter={(value) => `${value}%`}
1232
+ />
1233
+ <Line
1234
+ curve="stepAfter"
1235
+ renderPoints={() => ({
1236
+ fill: theme.color.bg,
1237
+ stroke: 'url(#availabilityGradient)',
1238
+ strokeWidth: 2,
1239
+ })}
1240
+ seriesId="availability"
1241
+ />
1242
+ <Scrubber hideOverlay />
1243
+ </CartesianChart>
1244
+ );
1245
+ }
1246
+ ```
1247
+
1248
+ #### Asset Price Widget
1249
+
1250
+ You can coordinate LineChart with custom styles to create a custom card that shows the latest price and percent change.
1251
+
1252
+ ```jsx
1253
+ function BitcoinChartWithScrubberBeacon() {
1254
+ const theme = useTheme();
1255
+ const prices = [...btcCandles].reverse().map((candle) => parseFloat(candle.close));
1256
+ const latestPrice = prices[prices.length - 1];
1257
+
1258
+ const formatPrice = (price: number) => {
1259
+ return new Intl.NumberFormat('en-US', {
1260
+ style: 'currency',
1261
+ currency: 'USD',
1262
+ }).format(price);
1263
+ };
1264
+
1265
+ const formatPercentChange = (price: number) => {
1266
+ return new Intl.NumberFormat('en-US', {
1267
+ style: 'percent',
1268
+ minimumFractionDigits: 2,
1269
+ maximumFractionDigits: 2,
1270
+ }).format(price);
1271
+ };
1272
+
1273
+ const percentChange = (latestPrice - prices[0]) / prices[0];
1274
+
1275
+ return (
1276
+ <VStack
1277
+ style={{
1278
+ background:
1279
+ 'linear-gradient(0deg, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0.80) 100%), #ED702F',
1280
+ }}
1281
+ borderRadius={300}
1282
+ gap={2}
1283
+ padding={2}
1284
+ paddingBottom={0}
1285
+ overflow="hidden"
1286
+ >
1287
+ <HStack gap={2} alignItems="center">
1288
+ <RemoteImage source={assets.btc.imageUrl} size="xxl" shape="circle" aria-hidden />
1289
+ <VStack gap={0.25} alignItems="flex-end" flexGrow={1}>
1290
+ <Text font="title1" style={{ color: 'white' }}>
1291
+ {formatPrice(latestPrice)}
1292
+ </Text>
1293
+ <Text font="label1" color="fgPositive" accessibilityLabel={`Up ${formatPercentChange(percentChange)}`}>
1294
+ +{formatPercentChange(percentChange)}
1295
+ </Text>
1296
+ </VStack>
1297
+ </HStack>
1298
+ <div
1299
+ style={{
1300
+ marginLeft: `calc(-1 * ${theme.space[2]})`,
1301
+ marginRight: `calc(-1 * ${theme.space[2]})`,
1302
+ }}
1303
+ >
1304
+ <LineChart
1305
+ inset={{ left: 0, right: 18, bottom: 0, top: 0 }}
1306
+ series={[
1307
+ {
1308
+ id: 'btcPrice',
1309
+ data: prices,
1310
+ color: assets.btc.color,
1311
+ },
1312
+ ]}
1313
+ showArea
1314
+ width="100%"
1315
+ height={92}
1316
+ >
1317
+ <Scrubber idlePulse styles={{ beacon: { stroke: 'white' } }} />
1318
+ </LineChart>
1319
+ </div>
1320
+ </VStack>
1321
+ );
1322
+ };
1323
+ ```
1324
+
1325
+