@cdx-ui/components 0.3.4 → 0.3.6

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 (287) hide show
  1. package/README.md +1 -1
  2. package/coverage.svg +1 -1
  3. package/docs/components/bar-chart/content.md +703 -0
  4. package/docs/components/content.md +9 -1
  5. package/docs/components/line-chart/content.md +567 -0
  6. package/docs/components/pie-chart/content.md +493 -0
  7. package/docs/components/progress-segmented/content.md +1 -1
  8. package/docs/components/select/content.md +144 -2
  9. package/docs/components/stepper/content.md +599 -0
  10. package/docs/components/system-alert/content.md +533 -0
  11. package/docs/foundations/expressions/icons/content.md +1 -1
  12. package/docs/foundations/styles/tokens/all-tokens/content.md +2 -1
  13. package/docs/llms.txt +10 -0
  14. package/docs/patterns/content.md +1 -0
  15. package/docs/patterns/dashboard/content.md +423 -19
  16. package/docs/patterns/insights/content.md +590 -0
  17. package/docs/resources/release-notes/0.3.4/content.md +27 -0
  18. package/docs/resources/release-notes/0.3.5/content.md +7 -0
  19. package/docs/resources/release-notes/content.md +2 -0
  20. package/lib/commonjs/components/BarChart/geometry.js +1130 -0
  21. package/lib/commonjs/components/BarChart/geometry.js.map +1 -0
  22. package/lib/commonjs/components/BarChart/index.js +942 -0
  23. package/lib/commonjs/components/BarChart/index.js.map +1 -0
  24. package/lib/commonjs/components/BarChart/styles.js +26 -0
  25. package/lib/commonjs/components/BarChart/styles.js.map +1 -0
  26. package/lib/commonjs/components/Button/index.js +2 -2
  27. package/lib/commonjs/components/Button/index.js.map +1 -1
  28. package/lib/commonjs/components/Chip/styles.js +3 -1
  29. package/lib/commonjs/components/Chip/styles.js.map +1 -1
  30. package/lib/commonjs/components/LineChart/geometry.js +698 -0
  31. package/lib/commonjs/components/LineChart/geometry.js.map +1 -0
  32. package/lib/commonjs/components/LineChart/index.js +747 -0
  33. package/lib/commonjs/components/LineChart/index.js.map +1 -0
  34. package/lib/commonjs/components/LineChart/styles.js +25 -0
  35. package/lib/commonjs/components/LineChart/styles.js.map +1 -0
  36. package/lib/commonjs/components/PieChart/geometry.js +216 -0
  37. package/lib/commonjs/components/PieChart/geometry.js.map +1 -0
  38. package/lib/commonjs/components/PieChart/index.js +476 -0
  39. package/lib/commonjs/components/PieChart/index.js.map +1 -0
  40. package/lib/commonjs/components/PieChart/styles.js +88 -0
  41. package/lib/commonjs/components/PieChart/styles.js.map +1 -0
  42. package/lib/commonjs/components/ProgressCircle/ProgressCircleRing.js +9 -31
  43. package/lib/commonjs/components/ProgressCircle/ProgressCircleRing.js.map +1 -1
  44. package/lib/commonjs/components/ProgressCircle/ProgressRing.js +52 -0
  45. package/lib/commonjs/components/ProgressCircle/ProgressRing.js.map +1 -0
  46. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +8 -7
  47. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
  48. package/lib/commonjs/components/Stepper/index.js +369 -0
  49. package/lib/commonjs/components/Stepper/index.js.map +1 -0
  50. package/lib/commonjs/components/Stepper/styles.js +211 -0
  51. package/lib/commonjs/components/Stepper/styles.js.map +1 -0
  52. package/lib/commonjs/components/Stepper/useStepperLabelFit.js +22 -0
  53. package/lib/commonjs/components/Stepper/useStepperLabelFit.js.map +1 -0
  54. package/lib/commonjs/components/Stepper/useStepperLabelFit.web.js +52 -0
  55. package/lib/commonjs/components/Stepper/useStepperLabelFit.web.js.map +1 -0
  56. package/lib/commonjs/components/Stepper/useStepperRingColors.js +31 -0
  57. package/lib/commonjs/components/Stepper/useStepperRingColors.js.map +1 -0
  58. package/lib/commonjs/components/SystemAlert/animation.js +218 -0
  59. package/lib/commonjs/components/SystemAlert/animation.js.map +1 -0
  60. package/lib/commonjs/components/SystemAlert/index.js +525 -0
  61. package/lib/commonjs/components/SystemAlert/index.js.map +1 -0
  62. package/lib/commonjs/components/SystemAlert/styles.js +178 -0
  63. package/lib/commonjs/components/SystemAlert/styles.js.map +1 -0
  64. package/lib/commonjs/components/SystemAlert/useSystemAlertStatusBarStyle.js +53 -0
  65. package/lib/commonjs/components/SystemAlert/useSystemAlertStatusBarStyle.js.map +1 -0
  66. package/lib/commonjs/components/Tabs/index.js +4 -0
  67. package/lib/commonjs/components/Tabs/index.js.map +1 -1
  68. package/lib/commonjs/components/Tabs/styles.js +1 -1
  69. package/lib/commonjs/components/Tabs/styles.js.map +1 -1
  70. package/lib/commonjs/components/chart/ChartInspector.js +610 -0
  71. package/lib/commonjs/components/chart/ChartInspector.js.map +1 -0
  72. package/lib/commonjs/components/chart/format.js +93 -0
  73. package/lib/commonjs/components/chart/format.js.map +1 -0
  74. package/lib/commonjs/components/chart/index.js +2 -0
  75. package/lib/commonjs/components/chart/index.js.map +1 -0
  76. package/lib/commonjs/components/chart/motion.js +97 -0
  77. package/lib/commonjs/components/chart/motion.js.map +1 -0
  78. package/lib/commonjs/components/chart/palette.js +178 -0
  79. package/lib/commonjs/components/chart/palette.js.map +1 -0
  80. package/lib/commonjs/components/chart/size.js +34 -0
  81. package/lib/commonjs/components/chart/size.js.map +1 -0
  82. package/lib/commonjs/components/chart/ticks.js +59 -0
  83. package/lib/commonjs/components/chart/ticks.js.map +1 -0
  84. package/lib/commonjs/components/chart/types.js +6 -0
  85. package/lib/commonjs/components/chart/types.js.map +1 -0
  86. package/lib/commonjs/components/chart/useChartHostWidth.js +27 -0
  87. package/lib/commonjs/components/chart/useChartHostWidth.js.map +1 -0
  88. package/lib/commonjs/components/index.js +60 -0
  89. package/lib/commonjs/components/index.js.map +1 -1
  90. package/lib/commonjs/figma/SystemAlert.Button.figma.js +28 -0
  91. package/lib/commonjs/figma/SystemAlert.Button.figma.js.map +1 -0
  92. package/lib/commonjs/figma/SystemAlert.Close.figma.js +22 -0
  93. package/lib/commonjs/figma/SystemAlert.Close.figma.js.map +1 -0
  94. package/lib/commonjs/figma/SystemAlert.Description.figma.js +20 -0
  95. package/lib/commonjs/figma/SystemAlert.Description.figma.js.map +1 -0
  96. package/lib/commonjs/figma/SystemAlert.IconButton.figma.js +31 -0
  97. package/lib/commonjs/figma/SystemAlert.IconButton.figma.js.map +1 -0
  98. package/lib/commonjs/figma/SystemAlert.figma.js +48 -0
  99. package/lib/commonjs/figma/SystemAlert.figma.js.map +1 -0
  100. package/lib/module/components/BarChart/geometry.js +1110 -0
  101. package/lib/module/components/BarChart/geometry.js.map +1 -0
  102. package/lib/module/components/BarChart/index.js +936 -0
  103. package/lib/module/components/BarChart/index.js.map +1 -0
  104. package/lib/module/components/BarChart/styles.js +22 -0
  105. package/lib/module/components/BarChart/styles.js.map +1 -0
  106. package/lib/module/components/Button/index.js +2 -2
  107. package/lib/module/components/Button/index.js.map +1 -1
  108. package/lib/module/components/Chip/styles.js +3 -1
  109. package/lib/module/components/Chip/styles.js.map +1 -1
  110. package/lib/module/components/LineChart/geometry.js +685 -0
  111. package/lib/module/components/LineChart/geometry.js.map +1 -0
  112. package/lib/module/components/LineChart/index.js +741 -0
  113. package/lib/module/components/LineChart/index.js.map +1 -0
  114. package/lib/module/components/LineChart/styles.js +21 -0
  115. package/lib/module/components/LineChart/styles.js.map +1 -0
  116. package/lib/module/components/PieChart/geometry.js +209 -0
  117. package/lib/module/components/PieChart/geometry.js.map +1 -0
  118. package/lib/module/components/PieChart/index.js +471 -0
  119. package/lib/module/components/PieChart/index.js.map +1 -0
  120. package/lib/module/components/PieChart/styles.js +84 -0
  121. package/lib/module/components/PieChart/styles.js.map +1 -0
  122. package/lib/module/components/ProgressCircle/ProgressCircleRing.js +10 -32
  123. package/lib/module/components/ProgressCircle/ProgressCircleRing.js.map +1 -1
  124. package/lib/module/components/ProgressCircle/ProgressRing.js +48 -0
  125. package/lib/module/components/ProgressCircle/ProgressRing.js.map +1 -0
  126. package/lib/module/components/ScrollShadow/useScrollShadow.js +8 -7
  127. package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
  128. package/lib/module/components/Stepper/index.js +360 -0
  129. package/lib/module/components/Stepper/index.js.map +1 -0
  130. package/lib/module/components/Stepper/styles.js +206 -0
  131. package/lib/module/components/Stepper/styles.js.map +1 -0
  132. package/lib/module/components/Stepper/useStepperLabelFit.js +18 -0
  133. package/lib/module/components/Stepper/useStepperLabelFit.js.map +1 -0
  134. package/lib/module/components/Stepper/useStepperLabelFit.web.js +48 -0
  135. package/lib/module/components/Stepper/useStepperLabelFit.web.js.map +1 -0
  136. package/lib/module/components/Stepper/useStepperRingColors.js +28 -0
  137. package/lib/module/components/Stepper/useStepperRingColors.js.map +1 -0
  138. package/lib/module/components/SystemAlert/animation.js +213 -0
  139. package/lib/module/components/SystemAlert/animation.js.map +1 -0
  140. package/lib/module/components/SystemAlert/index.js +515 -0
  141. package/lib/module/components/SystemAlert/index.js.map +1 -0
  142. package/lib/module/components/SystemAlert/styles.js +174 -0
  143. package/lib/module/components/SystemAlert/styles.js.map +1 -0
  144. package/lib/module/components/SystemAlert/useSystemAlertStatusBarStyle.js +49 -0
  145. package/lib/module/components/SystemAlert/useSystemAlertStatusBarStyle.js.map +1 -0
  146. package/lib/module/components/Tabs/index.js +4 -0
  147. package/lib/module/components/Tabs/index.js.map +1 -1
  148. package/lib/module/components/Tabs/styles.js +1 -1
  149. package/lib/module/components/Tabs/styles.js.map +1 -1
  150. package/lib/module/components/chart/ChartInspector.js +600 -0
  151. package/lib/module/components/chart/ChartInspector.js.map +1 -0
  152. package/lib/module/components/chart/format.js +84 -0
  153. package/lib/module/components/chart/format.js.map +1 -0
  154. package/lib/module/components/chart/index.js +2 -0
  155. package/lib/module/components/chart/index.js.map +1 -0
  156. package/lib/module/components/chart/motion.js +89 -0
  157. package/lib/module/components/chart/motion.js.map +1 -0
  158. package/lib/module/components/chart/palette.js +168 -0
  159. package/lib/module/components/chart/palette.js.map +1 -0
  160. package/lib/module/components/chart/size.js +29 -0
  161. package/lib/module/components/chart/size.js.map +1 -0
  162. package/lib/module/components/chart/ticks.js +55 -0
  163. package/lib/module/components/chart/ticks.js.map +1 -0
  164. package/lib/module/components/chart/types.js +4 -0
  165. package/lib/module/components/chart/types.js.map +1 -0
  166. package/lib/module/components/chart/useChartHostWidth.js +23 -0
  167. package/lib/module/components/chart/useChartHostWidth.js.map +1 -0
  168. package/lib/module/components/index.js +5 -0
  169. package/lib/module/components/index.js.map +1 -1
  170. package/lib/module/figma/SystemAlert.Button.figma.js +22 -0
  171. package/lib/module/figma/SystemAlert.Button.figma.js.map +1 -0
  172. package/lib/module/figma/SystemAlert.Close.figma.js +18 -0
  173. package/lib/module/figma/SystemAlert.Close.figma.js.map +1 -0
  174. package/lib/module/figma/SystemAlert.Description.figma.js +14 -0
  175. package/lib/module/figma/SystemAlert.Description.figma.js.map +1 -0
  176. package/lib/module/figma/SystemAlert.IconButton.figma.js +25 -0
  177. package/lib/module/figma/SystemAlert.IconButton.figma.js.map +1 -0
  178. package/lib/module/figma/SystemAlert.figma.js +42 -0
  179. package/lib/module/figma/SystemAlert.figma.js.map +1 -0
  180. package/lib/typescript/components/BarChart/geometry.d.ts +236 -0
  181. package/lib/typescript/components/BarChart/geometry.d.ts.map +1 -0
  182. package/lib/typescript/components/BarChart/index.d.ts +193 -0
  183. package/lib/typescript/components/BarChart/index.d.ts.map +1 -0
  184. package/lib/typescript/components/BarChart/styles.d.ts +32 -0
  185. package/lib/typescript/components/BarChart/styles.d.ts.map +1 -0
  186. package/lib/typescript/components/Chip/styles.d.ts.map +1 -1
  187. package/lib/typescript/components/LineChart/geometry.d.ts +248 -0
  188. package/lib/typescript/components/LineChart/geometry.d.ts.map +1 -0
  189. package/lib/typescript/components/LineChart/index.d.ts +105 -0
  190. package/lib/typescript/components/LineChart/index.d.ts.map +1 -0
  191. package/lib/typescript/components/LineChart/styles.d.ts +12 -0
  192. package/lib/typescript/components/LineChart/styles.d.ts.map +1 -0
  193. package/lib/typescript/components/PieChart/geometry.d.ts +43 -0
  194. package/lib/typescript/components/PieChart/geometry.d.ts.map +1 -0
  195. package/lib/typescript/components/PieChart/index.d.ts +147 -0
  196. package/lib/typescript/components/PieChart/index.d.ts.map +1 -0
  197. package/lib/typescript/components/PieChart/styles.d.ts +27 -0
  198. package/lib/typescript/components/PieChart/styles.d.ts.map +1 -0
  199. package/lib/typescript/components/ProgressCircle/ProgressCircleRing.d.ts.map +1 -1
  200. package/lib/typescript/components/ProgressCircle/ProgressRing.d.ts +17 -0
  201. package/lib/typescript/components/ProgressCircle/ProgressRing.d.ts.map +1 -0
  202. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
  203. package/lib/typescript/components/Stepper/index.d.ts +68 -0
  204. package/lib/typescript/components/Stepper/index.d.ts.map +1 -0
  205. package/lib/typescript/components/Stepper/styles.d.ts +46 -0
  206. package/lib/typescript/components/Stepper/styles.d.ts.map +1 -0
  207. package/lib/typescript/components/Stepper/useStepperLabelFit.d.ts +15 -0
  208. package/lib/typescript/components/Stepper/useStepperLabelFit.d.ts.map +1 -0
  209. package/lib/typescript/components/Stepper/useStepperLabelFit.web.d.ts +10 -0
  210. package/lib/typescript/components/Stepper/useStepperLabelFit.web.d.ts.map +1 -0
  211. package/lib/typescript/components/Stepper/useStepperRingColors.d.ts +6 -0
  212. package/lib/typescript/components/Stepper/useStepperRingColors.d.ts.map +1 -0
  213. package/lib/typescript/components/SystemAlert/animation.d.ts +56 -0
  214. package/lib/typescript/components/SystemAlert/animation.d.ts.map +1 -0
  215. package/lib/typescript/components/SystemAlert/index.d.ts +110 -0
  216. package/lib/typescript/components/SystemAlert/index.d.ts.map +1 -0
  217. package/lib/typescript/components/SystemAlert/styles.d.ts +38 -0
  218. package/lib/typescript/components/SystemAlert/styles.d.ts.map +1 -0
  219. package/lib/typescript/components/SystemAlert/useSystemAlertStatusBarStyle.d.ts +14 -0
  220. package/lib/typescript/components/SystemAlert/useSystemAlertStatusBarStyle.d.ts.map +1 -0
  221. package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
  222. package/lib/typescript/components/chart/ChartInspector.d.ts +101 -0
  223. package/lib/typescript/components/chart/ChartInspector.d.ts.map +1 -0
  224. package/lib/typescript/components/chart/format.d.ts +16 -0
  225. package/lib/typescript/components/chart/format.d.ts.map +1 -0
  226. package/lib/typescript/components/chart/index.d.ts +3 -0
  227. package/lib/typescript/components/chart/index.d.ts.map +1 -0
  228. package/lib/typescript/components/chart/motion.d.ts +27 -0
  229. package/lib/typescript/components/chart/motion.d.ts.map +1 -0
  230. package/lib/typescript/components/chart/palette.d.ts +44 -0
  231. package/lib/typescript/components/chart/palette.d.ts.map +1 -0
  232. package/lib/typescript/components/chart/size.d.ts +25 -0
  233. package/lib/typescript/components/chart/size.d.ts.map +1 -0
  234. package/lib/typescript/components/chart/ticks.d.ts +3 -0
  235. package/lib/typescript/components/chart/ticks.d.ts.map +1 -0
  236. package/lib/typescript/components/chart/types.d.ts +163 -0
  237. package/lib/typescript/components/chart/types.d.ts.map +1 -0
  238. package/lib/typescript/components/chart/useChartHostWidth.d.ts +9 -0
  239. package/lib/typescript/components/chart/useChartHostWidth.d.ts.map +1 -0
  240. package/lib/typescript/components/index.d.ts +5 -0
  241. package/lib/typescript/components/index.d.ts.map +1 -1
  242. package/lib/typescript/figma/SystemAlert.Button.figma.d.ts +11 -0
  243. package/lib/typescript/figma/SystemAlert.Button.figma.d.ts.map +1 -0
  244. package/lib/typescript/figma/SystemAlert.Close.figma.d.ts +11 -0
  245. package/lib/typescript/figma/SystemAlert.Close.figma.d.ts.map +1 -0
  246. package/lib/typescript/figma/SystemAlert.Description.figma.d.ts +8 -0
  247. package/lib/typescript/figma/SystemAlert.Description.figma.d.ts.map +1 -0
  248. package/lib/typescript/figma/SystemAlert.IconButton.figma.d.ts +11 -0
  249. package/lib/typescript/figma/SystemAlert.IconButton.figma.d.ts.map +1 -0
  250. package/lib/typescript/figma/SystemAlert.figma.d.ts +8 -0
  251. package/lib/typescript/figma/SystemAlert.figma.d.ts.map +1 -0
  252. package/package.json +5 -5
  253. package/src/components/BarChart/geometry.ts +1469 -0
  254. package/src/components/BarChart/index.tsx +1424 -0
  255. package/src/components/BarChart/styles.ts +31 -0
  256. package/src/components/Button/index.tsx +1 -1
  257. package/src/components/Chip/styles.ts +3 -1
  258. package/src/components/LineChart/geometry.ts +957 -0
  259. package/src/components/LineChart/index.tsx +1109 -0
  260. package/src/components/LineChart/styles.ts +26 -0
  261. package/src/components/PieChart/geometry.ts +295 -0
  262. package/src/components/PieChart/index.tsx +715 -0
  263. package/src/components/PieChart/styles.ts +101 -0
  264. package/src/components/ProgressCircle/ProgressCircleRing.tsx +10 -39
  265. package/src/components/ProgressCircle/ProgressRing.tsx +60 -0
  266. package/src/components/ScrollShadow/useScrollShadow.ts +12 -11
  267. package/src/components/Stepper/index.tsx +452 -0
  268. package/src/components/Stepper/styles.ts +234 -0
  269. package/src/components/Stepper/useStepperLabelFit.ts +21 -0
  270. package/src/components/Stepper/useStepperLabelFit.web.ts +53 -0
  271. package/src/components/Stepper/useStepperRingColors.ts +29 -0
  272. package/src/components/SystemAlert/animation.ts +252 -0
  273. package/src/components/SystemAlert/index.tsx +684 -0
  274. package/src/components/SystemAlert/styles.ts +212 -0
  275. package/src/components/SystemAlert/useSystemAlertStatusBarStyle.ts +57 -0
  276. package/src/components/Tabs/index.tsx +4 -0
  277. package/src/components/Tabs/styles.ts +1 -1
  278. package/src/components/chart/ChartInspector.tsx +726 -0
  279. package/src/components/chart/format.ts +95 -0
  280. package/src/components/chart/index.ts +24 -0
  281. package/src/components/chart/motion.ts +98 -0
  282. package/src/components/chart/palette.tsx +247 -0
  283. package/src/components/chart/size.ts +21 -0
  284. package/src/components/chart/ticks.ts +53 -0
  285. package/src/components/chart/types.ts +183 -0
  286. package/src/components/chart/useChartHostWidth.ts +26 -0
  287. package/src/components/index.ts +5 -0
@@ -0,0 +1,1109 @@
1
+ /* SVG `x`/`y` are paint coordinates. RN View `x`/`y` layout props are deprecated and share the name. */
2
+ /* eslint-disable @typescript-eslint/no-deprecated */
3
+ import { forwardRef, useMemo, useRef, useState, type ForwardedRef, type ReactElement } from 'react';
4
+ import { ScrollView, Text, View, type ViewProps } from 'react-native';
5
+ import Animated, { useAnimatedProps } from 'react-native-reanimated';
6
+ import {
7
+ Circle,
8
+ ClipPath,
9
+ Defs,
10
+ G,
11
+ Line,
12
+ LinearGradient,
13
+ Path,
14
+ Rect,
15
+ Stop,
16
+ Svg,
17
+ Text as SvgText,
18
+ } from 'react-native-svg';
19
+ import { dataAttributes, mergeDataAttributes } from '@cdx-ui/primitives';
20
+ import { cn, useControllableState } from '@cdx-ui/utils';
21
+ import type { AnimationRootDisableAll } from '../../animation';
22
+ import {
23
+ ChartInspector,
24
+ ChartTooltipLayer,
25
+ chartValueText,
26
+ type ChartInspectorTarget,
27
+ } from '../chart/ChartInspector';
28
+ import { resolveChartLocaleText, resolveValueFormatter } from '../chart/format';
29
+ import { resolveChartBox } from '../chart/size';
30
+ import type {
31
+ ChartAxis,
32
+ ChartGrid,
33
+ ChartLocaleText,
34
+ ChartReferenceLine,
35
+ ChartSelectedItem,
36
+ ChartSizeProps,
37
+ ChartSummaryCategory,
38
+ ChartTooltipItem,
39
+ ChartTooltipProps,
40
+ ChartValueFormatter,
41
+ } from '../chart/types';
42
+ import { useChartHostWidth } from '../chart/useChartHostWidth';
43
+ import {
44
+ ChartLinearFills,
45
+ chartFillUrl,
46
+ chartSeriesColor,
47
+ resolveChartColor,
48
+ useChartChrome,
49
+ useChartColorPaints,
50
+ useChartGradientId,
51
+ type ChartChrome,
52
+ type ChartColorPaints,
53
+ type ChartPalette,
54
+ } from '../chart/palette';
55
+ import { lerpVector, useChartMorph, useChartMotionEnabled, type ChartMorph } from '../chart/motion';
56
+ import {
57
+ layoutLineChart,
58
+ layoutLineChartMarkers,
59
+ lineChartGutters,
60
+ lineChartMarkerGutters,
61
+ nearestCategoryInPlot,
62
+ lineSeriesPaths,
63
+ resolveLineChartContentSize,
64
+ resolveLineChartData,
65
+ type LineChartCurve,
66
+ type LineChartLayout,
67
+ type LineChartMarker,
68
+ type LineChartMarkerLayout,
69
+ type LineChartSeries,
70
+ type LineSeriesLayout,
71
+ } from './geometry';
72
+ import {
73
+ LINE_ACTIVE_DOT_RADIUS,
74
+ LINE_DOT_RADIUS,
75
+ LINE_STROKE_WIDTH,
76
+ lineChartLegendItemVariants,
77
+ lineChartLegendLabelVariants,
78
+ lineChartLegendVariants,
79
+ lineChartRootVariants,
80
+ } from './styles';
81
+
82
+ const INACTIVE_OPACITY = 0.4;
83
+ const DASH = '6 4';
84
+ /** Within this many px of a plot edge, callouts anchor inward so they are not clipped. */
85
+ const EDGE_LABEL_ROOM = 32;
86
+ /** Paint slack around the plot so dots and strokes on its edges are not cut in half. */
87
+ const CLIP_BLEED = LINE_ACTIVE_DOT_RADIUS + LINE_STROKE_WIDTH;
88
+
89
+ const AnimatedPath = Animated.createAnimatedComponent(Path);
90
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle);
91
+
92
+ export type * from '../chart';
93
+ export type { LineChartCurve, LineChartMarker, LineChartSeries };
94
+
95
+ export interface LineChartProps<TRow extends object = object> extends ViewProps, ChartSizeProps {
96
+ /**
97
+ * One object per point, in x order. Columns are read only through `xAxis[0].dataKey` and
98
+ * `series[].dataKey`, so any key (including `label` or `tick`) is plain data. Non-finite
99
+ * values open a gap in that series.
100
+ */
101
+ dataset: readonly TRow[];
102
+ /** One line per entry. */
103
+ series: readonly LineChartSeries<TRow>[];
104
+ /**
105
+ * Category axis. Only the first entry is read; `dataKey` names the column with the category
106
+ * labels (defaults to the row index).
107
+ */
108
+ xAxis?: readonly ChartAxis<TRow>[];
109
+ /**
110
+ * Value axis. Only the first entry is read. `min` / `max` / `tickNumber` set the domain.
111
+ * `position: 'none'` hides the ticks; other sides draw on the left.
112
+ */
113
+ yAxis?: readonly ChartAxis<TRow>[];
114
+ /**
115
+ * Grid lines behind the plot. Only `horizontal` (at each value tick) is drawn; it stays on
116
+ * unless set to `false`.
117
+ * @default { horizontal: true }
118
+ */
119
+ grid?: ChartGrid;
120
+ /**
121
+ * Default series colors. `auto` uses the FI brand family (`simple-primary`, `-secondary`,
122
+ * `-tertiary`) for up to three series and switches the whole chart to `categorical-1`…`6`
123
+ * past that. `categorical` always uses the categorical slots. A series `color` wins.
124
+ * @default 'auto'
125
+ */
126
+ palette?: ChartPalette;
127
+ /**
128
+ * Hides the series legend under the chart.
129
+ * @default false
130
+ */
131
+ hideLegend?: boolean;
132
+ /**
133
+ * Shows every series at a category on hover, keyboard focus, or tap. On web, Tab / Shift+Tab
134
+ * and arrow keys move between categories, Tab past the last one leaves the chart, Home / End
135
+ * jump to the ends, and Escape hides the tooltip. `'item'` behaves like `'axis'` in v1.
136
+ * `'none'` hides it.
137
+ * @default { trigger: 'axis' }
138
+ */
139
+ tooltip?: ChartTooltipProps;
140
+ /**
141
+ * Controlled selection. `dataIndex` is the category; `seriesId` is the first series with a
142
+ * value there. `null` clears it. Omit to let the chart track selection itself.
143
+ */
144
+ selectedItem?: ChartSelectedItem;
145
+ /**
146
+ * Initial selection when `selectedItem` is omitted. Also makes the chart selectable.
147
+ */
148
+ defaultSelectedItem?: ChartSelectedItem;
149
+ /**
150
+ * Called when a category is pressed, with `null` when the selected category is pressed again.
151
+ * Providing this, `selectedItem`, or `defaultSelectedItem` makes the chart selectable and draws a crosshair.
152
+ */
153
+ onSelectedItemChange?: (item: ChartSelectedItem) => void;
154
+ /**
155
+ * When points need more room than the visible box, scroll instead of squeezing.
156
+ * The value axis stays pinned.
157
+ * @default true
158
+ */
159
+ scrollable?: boolean;
160
+ /**
161
+ * How many points fit in the viewport when `scrollable` is on. Omit to use a
162
+ * readable minimum band (~32px).
163
+ */
164
+ visiblePoints?: number;
165
+ /** Dashed horizontal lines across the plot at a value, with an optional caption. */
166
+ referenceLines?: readonly ChartReferenceLine[];
167
+ /** Point callouts (event name, value, extra legend row), pinned by `dataIndex`. */
168
+ markers?: readonly LineChartMarker[];
169
+ /**
170
+ * BCP 47 locale for default number formatting. Defaults to the `@cdx-ui/formatters` locale.
171
+ */
172
+ locale?: string;
173
+ /** Overrides for generated strings (summary, no-value text, default series name). */
174
+ localeText?: Partial<ChartLocaleText>;
175
+ /**
176
+ * Lines rise from the baseline on mount and tween to new values when `dataset` changes. Pass
177
+ * `'disable-all'` to draw the final state. Reduce Motion and `GlobalAnimationSettingsProvider`
178
+ * `disable-all` also turn it off.
179
+ */
180
+ animation?: AnimationRootDisableAll;
181
+ /** Accessible summary. Defaults to `localeText.lineChartSummary` of the last category. */
182
+ accessibilityLabel?: string;
183
+ /** Additional Uniwind/Tailwind classes, merged after the root's own styles. */
184
+ className?: string;
185
+ }
186
+
187
+ function LineChartInner<TRow extends object>(
188
+ {
189
+ dataset,
190
+ series: seriesProp,
191
+ xAxis: xAxisProp,
192
+ yAxis: yAxisProp,
193
+ size = 'lg',
194
+ width: widthProp,
195
+ height: heightProp,
196
+ grid,
197
+ palette = 'auto',
198
+ hideLegend = false,
199
+ tooltip: tooltipProp,
200
+ scrollable = true,
201
+ visiblePoints,
202
+ selectedItem: selectedItemProp,
203
+ defaultSelectedItem,
204
+ onSelectedItemChange,
205
+ referenceLines,
206
+ markers,
207
+ locale,
208
+ localeText: localeTextProp,
209
+ animation,
210
+ accessibilityLabel,
211
+ className,
212
+ style,
213
+ onLayout,
214
+ ...props
215
+ }: LineChartProps<TRow>,
216
+ ref: ForwardedRef<View>,
217
+ ) {
218
+ const animated = useChartMotionEnabled(animation);
219
+ const gradientId = useChartGradientId('line-fill');
220
+ const chrome = useChartChrome();
221
+ const paints = useChartColorPaints();
222
+ const localeText = useMemo(() => resolveChartLocaleText(localeTextProp), [localeTextProp]);
223
+ const xAxis = xAxisProp?.[0];
224
+ const yAxis = yAxisProp?.[0];
225
+ const showValueTicks = yAxis?.position !== 'none';
226
+ const showCategoryLabels = xAxis?.position !== 'none';
227
+ const resolved = useMemo(
228
+ () => resolveLineChartData({ dataset, series: seriesProp, xAxis }),
229
+ [dataset, seriesProp, xAxis],
230
+ );
231
+ const formatters = useMemo(
232
+ () => seriesProp.map((item) => resolveValueFormatter(item, yAxis, locale)),
233
+ [seriesProp, yAxis, locale],
234
+ );
235
+ const formatTick = useMemo(() => {
236
+ const format = resolveValueFormatter(null, yAxis, locale);
237
+ return (value: number) => format(value, { location: 'tick' });
238
+ }, [yAxis, locale]);
239
+ const box = resolveChartBox(size, widthProp, heightProp);
240
+ const [hostWidth, handleHostLayout] = useChartHostWidth(onLayout);
241
+ const viewportWidth = hostWidth > 0 ? Math.min(hostWidth, box.width) : box.width;
242
+ const viewportHeight = box.height;
243
+ const gutters = useMemo(
244
+ () =>
245
+ lineChartMarkerGutters(
246
+ dataset.length,
247
+ markers,
248
+ lineChartGutters({ showValueTicks, showCategoryLabels }),
249
+ ),
250
+ [dataset.length, markers, showValueTicks, showCategoryLabels],
251
+ );
252
+ const content = useMemo(
253
+ () =>
254
+ resolveLineChartContentSize({
255
+ viewportWidth,
256
+ viewportHeight,
257
+ categoryCount: dataset.length,
258
+ gutters,
259
+ scrollable,
260
+ visiblePoints,
261
+ }),
262
+ [viewportWidth, viewportHeight, dataset.length, gutters, scrollable, visiblePoints],
263
+ );
264
+ const layout = useMemo(
265
+ () =>
266
+ layoutLineChart({
267
+ categories: resolved.categories,
268
+ series: resolved.series,
269
+ width: content.width,
270
+ height: content.height,
271
+ referenceLines,
272
+ yAxis,
273
+ formatTick,
274
+ gutters,
275
+ }),
276
+ [resolved, content.width, content.height, referenceLines, yAxis, formatTick, gutters],
277
+ );
278
+ const markerLayout = useMemo(
279
+ () => layoutLineChartMarkers(markers ?? [], layout.series),
280
+ [markers, layout.series],
281
+ );
282
+ const overflowsX = content.width > viewportWidth + 0.5;
283
+ const morph = useChartMorph(
284
+ layout.series.flatMap((item) => item.points.map((point) => point.y ?? Number.NaN)),
285
+ layout.series.flatMap((item) =>
286
+ item.points.map((point) => (point.y === null ? Number.NaN : layout.baselineY)),
287
+ ),
288
+ animated,
289
+ );
290
+ const fills = seriesProp.map(
291
+ (item, seriesIndex) =>
292
+ resolveChartColor(item.color, paints) ??
293
+ paints[chartSeriesColor(seriesIndex, seriesProp.length, palette, 'line')],
294
+ );
295
+ const marks = seriesProp.map((item) => item.showMark ?? false);
296
+ const summary = accessibilityLabel ?? lineSummary(layout, formatters, localeText);
297
+ const interactive =
298
+ onSelectedItemChange !== undefined ||
299
+ selectedItemProp !== undefined ||
300
+ defaultSelectedItem !== undefined;
301
+ const [selectedItemState, setSelectedItem] = useControllableState<ChartSelectedItem>({
302
+ prop: selectedItemProp,
303
+ defaultProp: defaultSelectedItem ?? null,
304
+ onChange: (next) => {
305
+ onSelectedItemChange?.(next ?? null);
306
+ },
307
+ });
308
+ const selectedIndex = selectedItemState?.dataIndex ?? null;
309
+ const showTooltip = (tooltipProp?.trigger ?? 'axis') !== 'none';
310
+ const inspectable = (interactive || showTooltip) && dataset.length > 0;
311
+ const [activeIndex, setActiveIndex] = useState<number | null>(null);
312
+ const [dismissed, setDismissed] = useState(false);
313
+ const [scrubbing, setScrubbing] = useState(false);
314
+ const scrollX = useRef(0);
315
+ const highlightIndex = activeIndex ?? selectedIndex;
316
+ const tooltipIndex = showTooltip && !dismissed && activeIndex !== null ? activeIndex : null;
317
+
318
+ const activate = (index: number) => {
319
+ setDismissed(false);
320
+ setActiveIndex((prev) => (prev === index ? prev : index));
321
+ };
322
+ const deactivate = (index: number) => {
323
+ setActiveIndex((prev) => (prev === index ? null : prev));
324
+ };
325
+ const selectIndex = (index: number) => {
326
+ if (selectedIndex === index) {
327
+ setSelectedItem(null);
328
+ return;
329
+ }
330
+ const owner =
331
+ layout.series.find((item) => item.points[index]?.value != null) ?? layout.series[0];
332
+ setSelectedItem(owner === undefined ? null : { seriesId: owner.key, dataIndex: index });
333
+ };
334
+
335
+ const markerLegend = markerLayout.filter(
336
+ (item) => item.legend !== undefined && item.legend.length > 0,
337
+ );
338
+ const showLegend = !hideLegend && (seriesProp.length > 0 || markerLegend.length > 0);
339
+
340
+ const plot = (
341
+ <LinePlot
342
+ layout={layout}
343
+ morph={morph}
344
+ fills={fills}
345
+ gradientId={gradientId}
346
+ chrome={chrome}
347
+ marks={marks}
348
+ showGrid={grid?.horizontal !== false}
349
+ highlightIndex={highlightIndex}
350
+ formatters={formatters}
351
+ markers={markerLayout}
352
+ paints={paints}
353
+ />
354
+ );
355
+ const plotWidth = content.width - layout.plot.x;
356
+ const bleed = Math.min(CLIP_BLEED, layout.plot.x);
357
+ const axisWidth = layout.plot.x - bleed;
358
+ const targets = inspectable
359
+ ? lineInspectorTargets({
360
+ layout,
361
+ width: plotWidth,
362
+ height: viewportHeight,
363
+ fills,
364
+ formatters,
365
+ localeText,
366
+ selectedIndex,
367
+ markers: markerLayout,
368
+ })
369
+ : [];
370
+ const tip = tooltipIndex === null ? undefined : targets.at(tooltipIndex);
371
+
372
+ const ticks = showValueTicks ? (
373
+ <>
374
+ {layout.valueTicks.map((tick) => (
375
+ <SvgText
376
+ key={`tick-${String(tick.value)}`}
377
+ x={tick.x}
378
+ y={tick.y}
379
+ fill={chrome.label}
380
+ fontFamily={chrome.fontFamily}
381
+ fontSize={chrome.labelSize}
382
+ textAnchor="end"
383
+ alignmentBaseline="middle"
384
+ >
385
+ {tick.text}
386
+ </SvgText>
387
+ ))}
388
+ </>
389
+ ) : null;
390
+
391
+ const chart = (
392
+ <View
393
+ className="flex-row"
394
+ style={{ width: viewportWidth, height: viewportHeight }}
395
+ {...dataAttributes({ slot: 'line-chart-plot' })}
396
+ >
397
+ <Svg
398
+ width={axisWidth}
399
+ height={viewportHeight}
400
+ viewBox={`0 0 ${String(axisWidth)} ${String(content.height)}`}
401
+ accessible={false}
402
+ accessibilityElementsHidden
403
+ importantForAccessibility="no-hide-descendants"
404
+ >
405
+ {ticks}
406
+ </Svg>
407
+ <ScrollView
408
+ horizontal
409
+ nestedScrollEnabled
410
+ scrollEnabled={overflowsX && !scrubbing}
411
+ showsHorizontalScrollIndicator={overflowsX}
412
+ scrollEventThrottle={16}
413
+ onScroll={(event) => {
414
+ scrollX.current = event.nativeEvent.contentOffset.x;
415
+ if (tooltipIndex !== null) {
416
+ setDismissed(true);
417
+ }
418
+ }}
419
+ className="flex-1"
420
+ style={{ height: viewportHeight }}
421
+ >
422
+ <View style={{ width: plotWidth + bleed, height: viewportHeight }}>
423
+ <Svg
424
+ width={plotWidth + bleed}
425
+ height={viewportHeight}
426
+ viewBox={`0 0 ${String(plotWidth + bleed)} ${String(content.height)}`}
427
+ pointerEvents="none"
428
+ accessible={false}
429
+ accessibilityElementsHidden
430
+ importantForAccessibility="no-hide-descendants"
431
+ >
432
+ <G transform={`translate(${String(-axisWidth)}, 0)`}>{plot}</G>
433
+ </Svg>
434
+ {inspectable ? (
435
+ <View
436
+ className="absolute top-0 bottom-0"
437
+ style={{ left: bleed, width: plotWidth }}
438
+ pointerEvents="box-none"
439
+ >
440
+ <LineInspector
441
+ layout={layout}
442
+ targets={targets}
443
+ summary={summary}
444
+ selectable={interactive}
445
+ selectedIndex={selectedIndex}
446
+ activeIndex={activeIndex}
447
+ holdToScrub={overflowsX}
448
+ onScrubLockChange={setScrubbing}
449
+ onActivate={activate}
450
+ onDeactivate={deactivate}
451
+ onDismiss={() => {
452
+ setDismissed(true);
453
+ }}
454
+ onSelectIndex={selectIndex}
455
+ />
456
+ </View>
457
+ ) : null}
458
+ </View>
459
+ </ScrollView>
460
+ {tip !== undefined ? (
461
+ <ChartTooltipLayer
462
+ content={tip.tooltip}
463
+ noValueText={localeText.noValue}
464
+ slot="line-chart"
465
+ anchorX={layout.plot.x + tip.anchorX - scrollX.current}
466
+ anchorY={tip.anchorY}
467
+ anchorRadius={tip.anchorRadius}
468
+ width={viewportWidth}
469
+ height={viewportHeight}
470
+ />
471
+ ) : null}
472
+ </View>
473
+ );
474
+
475
+ return (
476
+ <View
477
+ ref={ref}
478
+ className={cn(lineChartRootVariants(), className)}
479
+ style={style}
480
+ accessible={!inspectable && !showLegend}
481
+ accessibilityRole={!inspectable && !showLegend ? 'image' : undefined}
482
+ accessibilityLabel={!inspectable && !showLegend ? summary : undefined}
483
+ {...props}
484
+ onLayout={handleHostLayout}
485
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'line-chart' }))}
486
+ >
487
+ {chart}
488
+ {showLegend ? (
489
+ <View
490
+ className={lineChartLegendVariants()}
491
+ {...dataAttributes({ slot: 'line-chart-legend' })}
492
+ >
493
+ {seriesProp.map((item, seriesIndex) => {
494
+ const selectedPoint =
495
+ highlightIndex === null
496
+ ? undefined
497
+ : layout.series[seriesIndex]?.points.at(highlightIndex);
498
+ const isSelected = selectedPoint !== undefined && selectedPoint.value !== null;
499
+ return (
500
+ <View
501
+ key={item.id}
502
+ className={lineChartLegendItemVariants()}
503
+ {...dataAttributes({ slot: 'line-chart-legend-item' })}
504
+ >
505
+ <View
506
+ className="h-0.5 w-3 rounded-full"
507
+ style={{ backgroundColor: fills[seriesIndex] }}
508
+ />
509
+ <Text className={lineChartLegendLabelVariants({ selected: isSelected })}>
510
+ {seriesLegendLabel(item, seriesProp.length, localeText)}
511
+ </Text>
512
+ </View>
513
+ );
514
+ })}
515
+ {markerLegend.map((item) => (
516
+ <View
517
+ key={`legend-${item.key}`}
518
+ className={lineChartLegendItemVariants()}
519
+ {...dataAttributes({ slot: 'line-chart-legend-item' })}
520
+ >
521
+ <View
522
+ className="size-2.5 rounded-full"
523
+ style={{
524
+ backgroundColor:
525
+ resolveChartColor(item.color, paints) ?? fills[item.seriesIndex] ?? fills[0],
526
+ }}
527
+ />
528
+ <Text className={lineChartLegendLabelVariants()}>{item.legend}</Text>
529
+ </View>
530
+ ))}
531
+ </View>
532
+ ) : null}
533
+ </View>
534
+ );
535
+ }
536
+
537
+ export const LineChart = forwardRef(LineChartInner) as (<TRow extends object>(
538
+ props: LineChartProps<TRow> & { ref?: ForwardedRef<View> },
539
+ ) => ReactElement | null) & { displayName?: string };
540
+
541
+ LineChart.displayName = 'LineChart';
542
+
543
+ function lineSummary(
544
+ layout: LineChartLayout,
545
+ formatters: readonly ChartValueFormatter[],
546
+ localeText: ChartLocaleText,
547
+ ): string {
548
+ const lastIndex = layout.categoryLabels.length - 1;
549
+ const last = layout.categoryLabels[lastIndex];
550
+ if (last === undefined) {
551
+ return localeText.lineChartSummary(null);
552
+ }
553
+ const latest: ChartSummaryCategory = {
554
+ label: last.title,
555
+ values: layout.series.flatMap((item, seriesIndex) => {
556
+ const value = item.points[lastIndex]?.value;
557
+ if (value == null) {
558
+ return [];
559
+ }
560
+ const format = formatters[seriesIndex];
561
+ return [
562
+ {
563
+ label: item.label,
564
+ formattedValue:
565
+ format?.(value, { location: 'a11y', seriesId: item.key, dataIndex: lastIndex }) ?? '',
566
+ },
567
+ ];
568
+ }),
569
+ };
570
+ return localeText.lineChartSummary(latest);
571
+ }
572
+
573
+ function showsMark(mark: LineChartSeries<object>['showMark'], index: number): boolean {
574
+ return typeof mark === 'function' ? mark({ index }) : mark === true;
575
+ }
576
+
577
+ function LinePlot({
578
+ layout,
579
+ morph,
580
+ fills,
581
+ gradientId,
582
+ chrome,
583
+ marks,
584
+ showGrid,
585
+ highlightIndex,
586
+ formatters,
587
+ markers,
588
+ paints,
589
+ }: {
590
+ layout: LineChartLayout;
591
+ morph: ChartMorph;
592
+ fills: readonly string[];
593
+ gradientId: string;
594
+ chrome: ChartChrome;
595
+ marks: readonly LineChartSeries<object>['showMark'][];
596
+ showGrid: boolean;
597
+ highlightIndex: number | null;
598
+ formatters: readonly ChartValueFormatter[];
599
+ markers: readonly LineChartMarkerLayout[];
600
+ paints: ChartColorPaints;
601
+ }) {
602
+ const xs = layout.categoryLabels.map((item) => item.x);
603
+ const clipId = `${gradientId}-clip`;
604
+
605
+ return (
606
+ <>
607
+ <ChartLinearFills idPrefix={gradientId} colors={fills} x2="100%" y2="0%" />
608
+ <Defs>
609
+ {fills.map((color, index) => (
610
+ <LinearGradient
611
+ key={`area-${String(index)}`}
612
+ id={`${gradientId}-area-${String(index)}`}
613
+ x1="0%"
614
+ y1="0%"
615
+ x2="0%"
616
+ y2="100%"
617
+ >
618
+ <Stop offset="0%" stopColor={color} stopOpacity={0.32} />
619
+ <Stop offset="100%" stopColor={color} stopOpacity={0} />
620
+ </LinearGradient>
621
+ ))}
622
+ <ClipPath id={clipId}>
623
+ <Rect
624
+ x={layout.plot.x - CLIP_BLEED}
625
+ y={layout.plot.y - CLIP_BLEED}
626
+ width={layout.plot.width + CLIP_BLEED * 2}
627
+ height={layout.plot.height + CLIP_BLEED * 2}
628
+ />
629
+ </ClipPath>
630
+ </Defs>
631
+ {showGrid
632
+ ? layout.gridLines.map((line) => (
633
+ <Line
634
+ key={`grid-${String(line.x1)}-${String(line.y1)}`}
635
+ x1={line.x1}
636
+ y1={line.y1}
637
+ x2={line.x2}
638
+ y2={line.y2}
639
+ stroke={chrome.grid}
640
+ strokeWidth={1}
641
+ />
642
+ ))
643
+ : null}
644
+ {layout.referenceLines.map((line, index) => (
645
+ <G key={`ref-${String(line.y1)}-${String(index)}`}>
646
+ <Line
647
+ x1={line.x1}
648
+ y1={line.y1}
649
+ x2={line.x2}
650
+ y2={line.y2}
651
+ stroke={chrome.label}
652
+ strokeWidth={1}
653
+ strokeDasharray={DASH}
654
+ />
655
+ {line.label !== undefined && line.label.length > 0 ? (
656
+ <SvgText
657
+ x={line.x1 + 4}
658
+ y={line.y1 - 4}
659
+ fill={chrome.label}
660
+ fontFamily={chrome.fontFamily}
661
+ fontSize={chrome.captionSize}
662
+ textAnchor="start"
663
+ >
664
+ {line.label}
665
+ </SvgText>
666
+ ) : null}
667
+ </G>
668
+ ))}
669
+ <G clipPath={`url(#${clipId})`}>
670
+ {layout.series.map((item, seriesIndex) => (
671
+ <AnimatedSeries
672
+ key={item.key}
673
+ item={item}
674
+ seriesIndex={seriesIndex}
675
+ xs={xs}
676
+ baselineY={layout.baselineY}
677
+ morph={morph}
678
+ areaFill={`url(#${gradientId}-area-${String(seriesIndex)})`}
679
+ stroke={chartFillUrl(gradientId, seriesIndex)}
680
+ opacity={highlightIndex !== null ? INACTIVE_OPACITY + 0.35 : 1}
681
+ />
682
+ ))}
683
+ {layout.series.flatMap((item, seriesIndex) =>
684
+ item.points.map((point) =>
685
+ point.y === null || !showsMark(marks[seriesIndex], point.index) ? null : (
686
+ <AnimatedDot
687
+ key={`dot-${item.key}-${String(point.index)}`}
688
+ cx={point.x}
689
+ slot={seriesIndex * xs.length + point.index}
690
+ morph={morph}
691
+ fill={chrome.surface}
692
+ stroke={fills[seriesIndex] ?? chrome.label}
693
+ />
694
+ ),
695
+ ),
696
+ )}
697
+ </G>
698
+ {highlightIndex !== null
699
+ ? (() => {
700
+ const label = layout.categoryLabels.at(highlightIndex);
701
+ if (label === undefined) {
702
+ return null;
703
+ }
704
+ return (
705
+ <Line
706
+ x1={label.x}
707
+ x2={label.x}
708
+ y1={layout.plot.y}
709
+ y2={layout.plot.y + layout.plot.height}
710
+ stroke={chrome.label}
711
+ strokeWidth={1}
712
+ strokeDasharray="3 4"
713
+ />
714
+ );
715
+ })()
716
+ : null}
717
+ {markers.map((marker) =>
718
+ marker.guide ? (
719
+ <Line
720
+ key={`marker-guide-${marker.key}`}
721
+ x1={marker.x}
722
+ x2={marker.x}
723
+ y1={layout.plot.y}
724
+ y2={layout.plot.y + layout.plot.height}
725
+ stroke={chrome.grid}
726
+ strokeWidth={1}
727
+ strokeDasharray="3 4"
728
+ opacity={
729
+ highlightIndex !== null && highlightIndex !== marker.index ? INACTIVE_OPACITY : 1
730
+ }
731
+ />
732
+ ) : null,
733
+ )}
734
+ {highlightIndex !== null
735
+ ? layout.series.flatMap((item, seriesIndex) => {
736
+ const point = item.points.at(highlightIndex);
737
+ if (point?.y == null) {
738
+ return [];
739
+ }
740
+ return [
741
+ <Circle
742
+ key={`active-${item.key}`}
743
+ cx={point.x}
744
+ cy={point.y}
745
+ r={LINE_ACTIVE_DOT_RADIUS}
746
+ fill={chrome.surface}
747
+ stroke={fills[seriesIndex]}
748
+ strokeWidth={LINE_STROKE_WIDTH}
749
+ />,
750
+ ];
751
+ })
752
+ : null}
753
+ {markers.map((marker) => {
754
+ const color =
755
+ resolveChartColor(marker.color, paints) ??
756
+ fills[marker.seriesIndex] ??
757
+ fills.at(0) ??
758
+ chrome.label;
759
+ const nearEnd = marker.x > layout.plot.x + layout.plot.width - EDGE_LABEL_ROOM;
760
+ const nearStart = marker.x < layout.plot.x + EDGE_LABEL_ROOM;
761
+ const captionAnchor = nearEnd ? 'end' : nearStart ? 'start' : 'middle';
762
+ const dimmed = highlightIndex !== null && highlightIndex !== marker.index;
763
+ const markerOpacity = dimmed ? INACTIVE_OPACITY : 1;
764
+ const format = formatters[marker.seriesIndex];
765
+ return (
766
+ <G key={`marker-${marker.key}`}>
767
+ {marker.dot ? (
768
+ <Circle
769
+ cx={marker.x}
770
+ cy={marker.y}
771
+ r={LINE_ACTIVE_DOT_RADIUS}
772
+ fill={color}
773
+ stroke={chrome.surface}
774
+ strokeWidth={LINE_STROKE_WIDTH}
775
+ opacity={markerOpacity}
776
+ />
777
+ ) : null}
778
+ {marker.label !== undefined && marker.label.length > 0 ? (
779
+ <SvgText
780
+ x={marker.x}
781
+ y={marker.y - 16}
782
+ fill={chrome.label}
783
+ fontFamily={chrome.fontFamily}
784
+ fontSize={chrome.captionSize}
785
+ textAnchor={captionAnchor}
786
+ opacity={markerOpacity}
787
+ >
788
+ {marker.label}
789
+ </SvgText>
790
+ ) : null}
791
+ {marker.showValue ? (
792
+ <SvgText
793
+ x={nearEnd ? marker.x - 10 : marker.x + 10}
794
+ y={marker.y + 4}
795
+ fill={chrome.label}
796
+ fontFamily={chrome.fontFamily}
797
+ fontSize={chrome.captionSize}
798
+ textAnchor={nearEnd ? 'end' : 'start'}
799
+ opacity={markerOpacity}
800
+ >
801
+ {format?.(marker.value, {
802
+ location: 'label',
803
+ seriesId: marker.seriesId,
804
+ dataIndex: marker.index,
805
+ }) ?? ''}
806
+ </SvgText>
807
+ ) : null}
808
+ </G>
809
+ );
810
+ })}
811
+ {layout.categoryLabels.map((item, index) =>
812
+ item.tick ? (
813
+ <SvgText
814
+ key={`cat-${item.label}-${String(index)}`}
815
+ x={item.x}
816
+ y={item.y}
817
+ fill={chrome.label}
818
+ fontFamily={chrome.fontFamily}
819
+ fontSize={chrome.labelSize}
820
+ textAnchor={
821
+ layout.categoryLabels.length < 2
822
+ ? 'middle'
823
+ : index === 0
824
+ ? 'start'
825
+ : index === layout.categoryLabels.length - 1
826
+ ? 'end'
827
+ : 'middle'
828
+ }
829
+ alignmentBaseline="middle"
830
+ >
831
+ {item.label}
832
+ </SvgText>
833
+ ) : null,
834
+ )}
835
+ </>
836
+ );
837
+ }
838
+
839
+ function categoryValues(
840
+ layout: LineChartLayout,
841
+ index: number,
842
+ fills: readonly string[],
843
+ formatters: readonly ChartValueFormatter[],
844
+ location: 'tooltip' | 'a11y',
845
+ ): ChartTooltipItem[] {
846
+ return layout.series.flatMap((item, seriesIndex) => {
847
+ const point = item.points.at(index);
848
+ if (point?.value == null) {
849
+ return [];
850
+ }
851
+ const format = formatters[seriesIndex];
852
+ return [
853
+ {
854
+ seriesId: item.key,
855
+ label: item.label,
856
+ color: fills[seriesIndex] ?? '',
857
+ formattedValue:
858
+ format?.(point.value, { location, seriesId: item.key, dataIndex: index }) ?? '',
859
+ },
860
+ ];
861
+ });
862
+ }
863
+
864
+ function seriesLegendLabel(
865
+ item: { readonly id: string; readonly label?: string },
866
+ seriesCount: number,
867
+ localeText: ChartLocaleText,
868
+ ): string {
869
+ if (item.label !== undefined && item.label.length > 0) {
870
+ return item.label;
871
+ }
872
+ return seriesCount === 1 ? localeText.defaultSeriesLabel : item.id;
873
+ }
874
+
875
+ function categoryAnnouncement(
876
+ label: string,
877
+ values: readonly ChartTooltipItem[],
878
+ noValue: string,
879
+ notes: readonly string[] = [],
880
+ ): string {
881
+ const parts = values.map(chartValueText);
882
+ const base = parts.length > 0 ? `${label}, ${parts.join(', ')}` : `${label}, ${noValue}`;
883
+ return notes.length > 0 ? `${base}, ${notes.join(', ')}` : base;
884
+ }
885
+
886
+ function markerNotes(markers: readonly LineChartMarkerLayout[], index: number): string[] {
887
+ return markers
888
+ .filter((marker) => marker.index === index)
889
+ .flatMap((marker) =>
890
+ [marker.legend, marker.label].filter(
891
+ (text): text is string => text !== undefined && text.length > 0,
892
+ ),
893
+ );
894
+ }
895
+
896
+ function lineKeyboardNeighbor(index: number, key: string, count: number): number | null {
897
+ if (key === 'Tab') {
898
+ return index + 1 < count ? index + 1 : null;
899
+ }
900
+ if (key === 'Shift+Tab') {
901
+ return index > 0 ? index - 1 : null;
902
+ }
903
+ return null;
904
+ }
905
+
906
+ /** Targets in scroll-content coordinates, which start at `layout.plot.x`. */
907
+ function lineInspectorTargets({
908
+ layout,
909
+ width,
910
+ height,
911
+ fills,
912
+ formatters,
913
+ localeText,
914
+ selectedIndex,
915
+ markers,
916
+ }: {
917
+ layout: LineChartLayout;
918
+ width: number;
919
+ height: number;
920
+ fills: readonly string[];
921
+ formatters: readonly ChartValueFormatter[];
922
+ localeText: ChartLocaleText;
923
+ selectedIndex: number | null;
924
+ markers: readonly LineChartMarkerLayout[];
925
+ }): ChartInspectorTarget[] {
926
+ const count = layout.categoryLabels.length;
927
+ const band = layout.plot.width / Math.max(count - 1, 1);
928
+ const offset = layout.plot.x;
929
+ return layout.categoryLabels.map((item, index): ChartInspectorTarget => {
930
+ const x = item.x - offset;
931
+ const left = index === 0 ? 0 : x - band / 2;
932
+ const right = index === count - 1 ? width : x + band / 2;
933
+ const pointYs = layout.series.flatMap((series) => {
934
+ const y = series.points.at(index)?.y;
935
+ return y == null ? [] : [y];
936
+ });
937
+ return {
938
+ key: `hit-${String(index)}`,
939
+ left,
940
+ top: 0,
941
+ width: Math.max(right - left, 1),
942
+ height,
943
+ anchorX: x,
944
+ anchorY: pointYs.length > 0 ? Math.min(...pointYs) : layout.plot.y,
945
+ tooltip: {
946
+ title: item.title,
947
+ items: categoryValues(layout, index, fills, formatters, 'tooltip'),
948
+ },
949
+ accessibilityLabel: categoryAnnouncement(
950
+ item.title,
951
+ categoryValues(layout, index, fills, formatters, 'a11y'),
952
+ localeText.noValue,
953
+ markerNotes(markers, index),
954
+ ),
955
+ selected: selectedIndex === index,
956
+ };
957
+ });
958
+ }
959
+
960
+ function LineInspector({
961
+ layout,
962
+ targets,
963
+ summary,
964
+ selectable,
965
+ selectedIndex,
966
+ activeIndex,
967
+ onActivate,
968
+ onDeactivate,
969
+ onDismiss,
970
+ onSelectIndex,
971
+ holdToScrub,
972
+ onScrubLockChange,
973
+ }: {
974
+ layout: LineChartLayout;
975
+ targets: readonly ChartInspectorTarget[];
976
+ onScrubLockChange: (locked: boolean) => void;
977
+ summary: string;
978
+ selectable: boolean;
979
+ selectedIndex: number | null;
980
+ activeIndex: number | null;
981
+ holdToScrub: boolean;
982
+ onActivate: (index: number) => void;
983
+ onDeactivate: (index: number) => void;
984
+ onDismiss: () => void;
985
+ onSelectIndex: (index: number) => void;
986
+ }) {
987
+ const count = layout.categoryLabels.length;
988
+ const offset = layout.plot.x;
989
+
990
+ return (
991
+ <ChartInspector
992
+ targets={targets}
993
+ summary={summary}
994
+ selectable={selectable}
995
+ activeIndex={activeIndex}
996
+ selectedIndex={selectedIndex}
997
+ slot="line-chart"
998
+ holdToScrub={holdToScrub}
999
+ onActivate={onActivate}
1000
+ onDeactivate={onDeactivate}
1001
+ onDismiss={onDismiss}
1002
+ onSelectIndex={onSelectIndex}
1003
+ hitTest={(x, y) => nearestCategoryInPlot(layout.categoryLabels, layout.plot, x, y, offset)}
1004
+ neighborIndex={(index, key) => lineKeyboardNeighbor(index, key, count)}
1005
+ onScrubLockChange={onScrubLockChange}
1006
+ />
1007
+ );
1008
+ }
1009
+
1010
+ function AnimatedSeries({
1011
+ item,
1012
+ seriesIndex,
1013
+ xs,
1014
+ baselineY,
1015
+ morph,
1016
+ areaFill,
1017
+ stroke,
1018
+ opacity,
1019
+ }: {
1020
+ item: LineSeriesLayout;
1021
+ seriesIndex: number;
1022
+ xs: readonly number[];
1023
+ baselineY: number;
1024
+ morph: ChartMorph;
1025
+ areaFill: string;
1026
+ stroke: string;
1027
+ opacity: number;
1028
+ }) {
1029
+ const { from, to, progress } = morph;
1030
+ const start = seriesIndex * xs.length;
1031
+ const end = start + xs.length;
1032
+ const dashAt = item.dashAt;
1033
+ const curve = item.curve;
1034
+ const lineProps = useAnimatedProps(() => {
1035
+ const ys = lerpVector(from.get(), to.get(), progress.get()).slice(start, end);
1036
+ return { d: lineSeriesPaths(xs, ys, curve, baselineY, dashAt).path };
1037
+ }, [from, to, progress, start, end, xs, curve, baselineY, dashAt]);
1038
+ const dashProps = useAnimatedProps(() => {
1039
+ const ys = lerpVector(from.get(), to.get(), progress.get()).slice(start, end);
1040
+ return { d: lineSeriesPaths(xs, ys, curve, baselineY, dashAt).dashedPath };
1041
+ }, [from, to, progress, start, end, xs, curve, baselineY, dashAt]);
1042
+ const areaProps = useAnimatedProps(() => {
1043
+ const ys = lerpVector(from.get(), to.get(), progress.get()).slice(start, end);
1044
+ return { d: lineSeriesPaths(xs, ys, curve, baselineY, dashAt).areaPath };
1045
+ }, [from, to, progress, start, end, xs, curve, baselineY, dashAt]);
1046
+
1047
+ return (
1048
+ <G>
1049
+ {item.area && item.areaPath.length > 0 ? (
1050
+ <AnimatedPath animatedProps={areaProps} fill={areaFill} />
1051
+ ) : null}
1052
+ {item.path.length > 0 ? (
1053
+ <AnimatedPath
1054
+ animatedProps={lineProps}
1055
+ fill="none"
1056
+ stroke={stroke}
1057
+ strokeWidth={LINE_STROKE_WIDTH}
1058
+ strokeLinecap="round"
1059
+ strokeLinejoin="round"
1060
+ strokeDasharray={item.dashed ? DASH : undefined}
1061
+ opacity={opacity}
1062
+ />
1063
+ ) : null}
1064
+ {item.dashedPath.length > 0 ? (
1065
+ <AnimatedPath
1066
+ animatedProps={dashProps}
1067
+ fill="none"
1068
+ stroke={stroke}
1069
+ strokeWidth={LINE_STROKE_WIDTH}
1070
+ strokeLinecap="round"
1071
+ strokeLinejoin="round"
1072
+ strokeDasharray={DASH}
1073
+ opacity={opacity}
1074
+ />
1075
+ ) : null}
1076
+ </G>
1077
+ );
1078
+ }
1079
+
1080
+ function AnimatedDot({
1081
+ cx,
1082
+ slot,
1083
+ morph,
1084
+ fill,
1085
+ stroke,
1086
+ }: {
1087
+ cx: number;
1088
+ slot: number;
1089
+ morph: ChartMorph;
1090
+ fill: string;
1091
+ stroke: string;
1092
+ }) {
1093
+ const { from, to, progress } = morph;
1094
+ const animatedProps = useAnimatedProps(() => {
1095
+ const ys = lerpVector(from.get(), to.get(), progress.get());
1096
+ return { cy: ys[slot] ?? 0 };
1097
+ }, [from, to, progress, slot]);
1098
+
1099
+ return (
1100
+ <AnimatedCircle
1101
+ animatedProps={animatedProps}
1102
+ cx={cx}
1103
+ r={LINE_DOT_RADIUS}
1104
+ fill={fill}
1105
+ stroke={stroke}
1106
+ strokeWidth={LINE_STROKE_WIDTH}
1107
+ />
1108
+ );
1109
+ }