react-native-livechart 1.0.0

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 (243) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +198 -0
  3. package/dist/components/AnimatedLabel.d.ts +21 -0
  4. package/dist/components/AnimatedLabel.d.ts.map +1 -0
  5. package/dist/components/BadgeOverlay.d.ts +16 -0
  6. package/dist/components/BadgeOverlay.d.ts.map +1 -0
  7. package/dist/components/CrosshairLine.d.ts +20 -0
  8. package/dist/components/CrosshairLine.d.ts.map +1 -0
  9. package/dist/components/CrosshairOverlay.d.ts +29 -0
  10. package/dist/components/CrosshairOverlay.d.ts.map +1 -0
  11. package/dist/components/DegenParticlesOverlay.d.ts +16 -0
  12. package/dist/components/DegenParticlesOverlay.d.ts.map +1 -0
  13. package/dist/components/DotOverlay.d.ts +17 -0
  14. package/dist/components/DotOverlay.d.ts.map +1 -0
  15. package/dist/components/LeftEdgeFade.d.ts +9 -0
  16. package/dist/components/LeftEdgeFade.d.ts.map +1 -0
  17. package/dist/components/LiveChart.d.ts +3 -0
  18. package/dist/components/LiveChart.d.ts.map +1 -0
  19. package/dist/components/LiveChartSeries.d.ts +3 -0
  20. package/dist/components/LiveChartSeries.d.ts.map +1 -0
  21. package/dist/components/LiveChartTransition.d.ts +28 -0
  22. package/dist/components/LiveChartTransition.d.ts.map +1 -0
  23. package/dist/components/LoadingOverlay.d.ts +24 -0
  24. package/dist/components/LoadingOverlay.d.ts.map +1 -0
  25. package/dist/components/MarkerOverlay.d.ts +25 -0
  26. package/dist/components/MarkerOverlay.d.ts.map +1 -0
  27. package/dist/components/MultiSeriesDots.d.ts +11 -0
  28. package/dist/components/MultiSeriesDots.d.ts.map +1 -0
  29. package/dist/components/MultiSeriesStroke.d.ts +14 -0
  30. package/dist/components/MultiSeriesStroke.d.ts.map +1 -0
  31. package/dist/components/MultiSeriesTooltipStack.d.ts +10 -0
  32. package/dist/components/MultiSeriesTooltipStack.d.ts.map +1 -0
  33. package/dist/components/MultiSeriesValueLabels.d.ts +11 -0
  34. package/dist/components/MultiSeriesValueLabels.d.ts.map +1 -0
  35. package/dist/components/MultiSeriesValueLines.d.ts +10 -0
  36. package/dist/components/MultiSeriesValueLines.d.ts.map +1 -0
  37. package/dist/components/ReferenceLineOverlay.d.ts +19 -0
  38. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -0
  39. package/dist/components/SeriesToggleChips.d.ts +15 -0
  40. package/dist/components/SeriesToggleChips.d.ts.map +1 -0
  41. package/dist/components/TradeStreamOverlay.d.ts +14 -0
  42. package/dist/components/TradeStreamOverlay.d.ts.map +1 -0
  43. package/dist/components/ValueLineOverlay.d.ts +16 -0
  44. package/dist/components/ValueLineOverlay.d.ts.map +1 -0
  45. package/dist/components/ValueTextOverlay.d.ts +20 -0
  46. package/dist/components/ValueTextOverlay.d.ts.map +1 -0
  47. package/dist/components/XAxisOverlay.d.ts +14 -0
  48. package/dist/components/XAxisOverlay.d.ts.map +1 -0
  49. package/dist/components/YAxisOverlay.d.ts +23 -0
  50. package/dist/components/YAxisOverlay.d.ts.map +1 -0
  51. package/dist/components/seriesMetaSig.d.ts +4 -0
  52. package/dist/components/seriesMetaSig.d.ts.map +1 -0
  53. package/dist/constants.d.ts +38 -0
  54. package/dist/constants.d.ts.map +1 -0
  55. package/dist/core/liveChartEngineTick.d.ts +45 -0
  56. package/dist/core/liveChartEngineTick.d.ts.map +1 -0
  57. package/dist/core/liveChartSeriesEngineTick.d.ts +38 -0
  58. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -0
  59. package/dist/core/multiSeriesLayout.d.ts +22 -0
  60. package/dist/core/multiSeriesLayout.d.ts.map +1 -0
  61. package/dist/core/resolveConfig.d.ts +188 -0
  62. package/dist/core/resolveConfig.d.ts.map +1 -0
  63. package/dist/core/useLiveChartEngine.d.ts +79 -0
  64. package/dist/core/useLiveChartEngine.d.ts.map +1 -0
  65. package/dist/core/useLiveChartSeriesEngine.d.ts +68 -0
  66. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -0
  67. package/dist/draw/candle.d.ts +25 -0
  68. package/dist/draw/candle.d.ts.map +1 -0
  69. package/dist/draw/grid.d.ts +24 -0
  70. package/dist/draw/grid.d.ts.map +1 -0
  71. package/dist/draw/line.d.ts +86 -0
  72. package/dist/draw/line.d.ts.map +1 -0
  73. package/dist/draw/trade.d.ts +38 -0
  74. package/dist/draw/trade.d.ts.map +1 -0
  75. package/dist/hooks/crosshairSeries.d.ts +13 -0
  76. package/dist/hooks/crosshairSeries.d.ts.map +1 -0
  77. package/dist/hooks/crosshairShared.d.ts +72 -0
  78. package/dist/hooks/crosshairShared.d.ts.map +1 -0
  79. package/dist/hooks/index.d.ts +24 -0
  80. package/dist/hooks/index.d.ts.map +1 -0
  81. package/dist/hooks/resolveChartLayout.d.ts +42 -0
  82. package/dist/hooks/resolveChartLayout.d.ts.map +1 -0
  83. package/dist/hooks/useBadge.d.ts +14 -0
  84. package/dist/hooks/useBadge.d.ts.map +1 -0
  85. package/dist/hooks/useCandlePaths.d.ts +19 -0
  86. package/dist/hooks/useCandlePaths.d.ts.map +1 -0
  87. package/dist/hooks/useCanvasLayout.d.ts +12 -0
  88. package/dist/hooks/useCanvasLayout.d.ts.map +1 -0
  89. package/dist/hooks/useChartColors.d.ts +20 -0
  90. package/dist/hooks/useChartColors.d.ts.map +1 -0
  91. package/dist/hooks/useChartPaths.d.ts +19 -0
  92. package/dist/hooks/useChartPaths.d.ts.map +1 -0
  93. package/dist/hooks/useChartReveal.d.ts +30 -0
  94. package/dist/hooks/useChartReveal.d.ts.map +1 -0
  95. package/dist/hooks/useChartSkiaFont.d.ts +8 -0
  96. package/dist/hooks/useChartSkiaFont.d.ts.map +1 -0
  97. package/dist/hooks/useCrosshair.d.ts +20 -0
  98. package/dist/hooks/useCrosshair.d.ts.map +1 -0
  99. package/dist/hooks/useCrosshairSeries.d.ts +10 -0
  100. package/dist/hooks/useCrosshairSeries.d.ts.map +1 -0
  101. package/dist/hooks/useDegen.d.ts +17 -0
  102. package/dist/hooks/useDegen.d.ts.map +1 -0
  103. package/dist/hooks/useLiveChartHasData.d.ts +17 -0
  104. package/dist/hooks/useLiveChartHasData.d.ts.map +1 -0
  105. package/dist/hooks/useLiveDot.d.ts +12 -0
  106. package/dist/hooks/useLiveDot.d.ts.map +1 -0
  107. package/dist/hooks/useMarkers.d.ts +15 -0
  108. package/dist/hooks/useMarkers.d.ts.map +1 -0
  109. package/dist/hooks/useModeBlend.d.ts +18 -0
  110. package/dist/hooks/useModeBlend.d.ts.map +1 -0
  111. package/dist/hooks/useMomentum.d.ts +16 -0
  112. package/dist/hooks/useMomentum.d.ts.map +1 -0
  113. package/dist/hooks/useMultiSeriesDegen.d.ts +25 -0
  114. package/dist/hooks/useMultiSeriesDegen.d.ts.map +1 -0
  115. package/dist/hooks/useMultiSeriesLinePaths.d.ts +15 -0
  116. package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -0
  117. package/dist/hooks/useReferenceLine.d.ts +32 -0
  118. package/dist/hooks/useReferenceLine.d.ts.map +1 -0
  119. package/dist/hooks/useReverseMorphEngineInputs.d.ts +24 -0
  120. package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -0
  121. package/dist/hooks/useTradeStream.d.ts +12 -0
  122. package/dist/hooks/useTradeStream.d.ts.map +1 -0
  123. package/dist/hooks/useXAxis.d.ts +32 -0
  124. package/dist/hooks/useXAxis.d.ts.map +1 -0
  125. package/dist/hooks/useYAxis.d.ts +13 -0
  126. package/dist/hooks/useYAxis.d.ts.map +1 -0
  127. package/dist/index.d.ts +18 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/lib/format.d.ts +5 -0
  130. package/dist/lib/format.d.ts.map +1 -0
  131. package/dist/lib/measureFontTextWidth.d.ts +7 -0
  132. package/dist/lib/measureFontTextWidth.d.ts.map +1 -0
  133. package/dist/lib/monoFontFamily.d.ts +6 -0
  134. package/dist/lib/monoFontFamily.d.ts.map +1 -0
  135. package/dist/math/color.d.ts +5 -0
  136. package/dist/math/color.d.ts.map +1 -0
  137. package/dist/math/degenTick.d.ts +57 -0
  138. package/dist/math/degenTick.d.ts.map +1 -0
  139. package/dist/math/interpolate.d.ts +7 -0
  140. package/dist/math/interpolate.d.ts.map +1 -0
  141. package/dist/math/intervals.d.ts +3 -0
  142. package/dist/math/intervals.d.ts.map +1 -0
  143. package/dist/math/lerp.d.ts +7 -0
  144. package/dist/math/lerp.d.ts.map +1 -0
  145. package/dist/math/markers.d.ts +44 -0
  146. package/dist/math/markers.d.ts.map +1 -0
  147. package/dist/math/momentum.d.ts +8 -0
  148. package/dist/math/momentum.d.ts.map +1 -0
  149. package/dist/math/pickCandle.d.ts +8 -0
  150. package/dist/math/pickCandle.d.ts.map +1 -0
  151. package/dist/math/range.d.ts +10 -0
  152. package/dist/math/range.d.ts.map +1 -0
  153. package/dist/math/referenceLines.d.ts +20 -0
  154. package/dist/math/referenceLines.d.ts.map +1 -0
  155. package/dist/math/spline.d.ts +45 -0
  156. package/dist/math/spline.d.ts.map +1 -0
  157. package/dist/math/squiggly.d.ts +36 -0
  158. package/dist/math/squiggly.d.ts.map +1 -0
  159. package/dist/theme.d.ts +30 -0
  160. package/dist/theme.d.ts.map +1 -0
  161. package/dist/types.d.ts +720 -0
  162. package/dist/types.d.ts.map +1 -0
  163. package/package.json +72 -0
  164. package/src/components/AnimatedLabel.tsx +41 -0
  165. package/src/components/BadgeOverlay.tsx +39 -0
  166. package/src/components/CrosshairLine.tsx +90 -0
  167. package/src/components/CrosshairOverlay.tsx +170 -0
  168. package/src/components/DegenParticlesOverlay.tsx +141 -0
  169. package/src/components/DotOverlay.tsx +69 -0
  170. package/src/components/LeftEdgeFade.tsx +36 -0
  171. package/src/components/LiveChart.tsx +821 -0
  172. package/src/components/LiveChartSeries.tsx +607 -0
  173. package/src/components/LiveChartTransition.tsx +135 -0
  174. package/src/components/LoadingOverlay.tsx +301 -0
  175. package/src/components/MarkerOverlay.tsx +331 -0
  176. package/src/components/MultiSeriesDots.tsx +117 -0
  177. package/src/components/MultiSeriesStroke.tsx +79 -0
  178. package/src/components/MultiSeriesTooltipStack.tsx +78 -0
  179. package/src/components/MultiSeriesValueLabels.tsx +103 -0
  180. package/src/components/MultiSeriesValueLines.tsx +115 -0
  181. package/src/components/ReferenceLineOverlay.tsx +317 -0
  182. package/src/components/SeriesToggleChips.tsx +201 -0
  183. package/src/components/TradeStreamOverlay.tsx +117 -0
  184. package/src/components/ValueLineOverlay.tsx +63 -0
  185. package/src/components/ValueTextOverlay.tsx +43 -0
  186. package/src/components/XAxisOverlay.tsx +109 -0
  187. package/src/components/YAxisOverlay.tsx +135 -0
  188. package/src/components/seriesMetaSig.ts +13 -0
  189. package/src/constants.ts +44 -0
  190. package/src/core/liveChartEngineTick.ts +185 -0
  191. package/src/core/liveChartSeriesEngineTick.ts +163 -0
  192. package/src/core/multiSeriesLayout.ts +80 -0
  193. package/src/core/resolveConfig.ts +538 -0
  194. package/src/core/useLiveChartEngine.ts +207 -0
  195. package/src/core/useLiveChartSeriesEngine.ts +215 -0
  196. package/src/draw/candle.ts +210 -0
  197. package/src/draw/grid.ts +174 -0
  198. package/src/draw/line.ts +257 -0
  199. package/src/draw/trade.ts +202 -0
  200. package/src/hooks/crosshairSeries.ts +78 -0
  201. package/src/hooks/crosshairShared.ts +296 -0
  202. package/src/hooks/index.ts +26 -0
  203. package/src/hooks/resolveChartLayout.ts +144 -0
  204. package/src/hooks/useBadge.ts +192 -0
  205. package/src/hooks/useCandlePaths.ts +165 -0
  206. package/src/hooks/useCanvasLayout.ts +22 -0
  207. package/src/hooks/useChartColors.ts +48 -0
  208. package/src/hooks/useChartPaths.ts +121 -0
  209. package/src/hooks/useChartReveal.ts +123 -0
  210. package/src/hooks/useChartSkiaFont.ts +34 -0
  211. package/src/hooks/useCrosshair.ts +250 -0
  212. package/src/hooks/useCrosshairSeries.ts +163 -0
  213. package/src/hooks/useDegen.ts +262 -0
  214. package/src/hooks/useLiveChartHasData.ts +31 -0
  215. package/src/hooks/useLiveDot.ts +31 -0
  216. package/src/hooks/useMarkers.ts +95 -0
  217. package/src/hooks/useModeBlend.ts +52 -0
  218. package/src/hooks/useMomentum.ts +37 -0
  219. package/src/hooks/useMultiSeriesDegen.ts +260 -0
  220. package/src/hooks/useMultiSeriesLinePaths.ts +76 -0
  221. package/src/hooks/useReferenceLine.ts +212 -0
  222. package/src/hooks/useReverseMorphEngineInputs.ts +154 -0
  223. package/src/hooks/useTradeStream.ts +64 -0
  224. package/src/hooks/useXAxis.ts +200 -0
  225. package/src/hooks/useYAxis.ts +48 -0
  226. package/src/index.ts +69 -0
  227. package/src/lib/format.ts +36 -0
  228. package/src/lib/measureFontTextWidth.ts +10 -0
  229. package/src/lib/monoFontFamily.ts +8 -0
  230. package/src/math/color.ts +31 -0
  231. package/src/math/degenTick.ts +151 -0
  232. package/src/math/interpolate.ts +32 -0
  233. package/src/math/intervals.ts +17 -0
  234. package/src/math/lerp.ts +17 -0
  235. package/src/math/markers.ts +175 -0
  236. package/src/math/momentum.ts +38 -0
  237. package/src/math/pickCandle.ts +42 -0
  238. package/src/math/range.ts +54 -0
  239. package/src/math/referenceLines.ts +56 -0
  240. package/src/math/spline.ts +183 -0
  241. package/src/math/squiggly.ts +108 -0
  242. package/src/theme.ts +231 -0
  243. package/src/types.ts +789 -0
@@ -0,0 +1,607 @@
1
+ /**
2
+ * Multi-series live chart. Same conceptual role as liveline’s multi-series mode;
3
+ * React Native + Skia implementation (see liveline for the web reference).
4
+ *
5
+ * @see https://github.com/benjitaylor/liveline
6
+ */
7
+ import { Canvas, Group } from "@shopify/react-native-skia";
8
+ import { useLayoutEffect, useState } from "react";
9
+ import { View } from "react-native";
10
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
11
+ import {
12
+ useAnimatedReaction,
13
+ useDerivedValue,
14
+ useSharedValue,
15
+ type SharedValue,
16
+ } from "react-native-reanimated";
17
+ import { scheduleOnRN } from "react-native-worklets";
18
+ import { DEFAULT_ACCENT_COLOR, MAX_MULTI_SERIES } from "../constants";
19
+ import {
20
+ lineColorsSignatureFromArray,
21
+ lineStyleSignatureFromArray,
22
+ resolveMultiSeriesLineColorsSnapshot,
23
+ resolveMultiSeriesLineStylesSnapshot,
24
+ type SeriesLineStyle,
25
+ } from "../core/multiSeriesLayout";
26
+ import {
27
+ resolveDegen,
28
+ resolveGridStyle,
29
+ resolveLeftEdgeFade,
30
+ resolveLegend,
31
+ resolveMultiSeriesDot,
32
+ resolveScrub,
33
+ resolveXAxis,
34
+ resolveYAxis,
35
+ } from "../core/resolveConfig";
36
+ import { useLiveChartSeriesEngine } from "../core/useLiveChartSeriesEngine";
37
+ import { resolveChartLayout } from "../hooks/resolveChartLayout";
38
+ import { useCanvasLayout } from "../hooks/useCanvasLayout";
39
+ import { useChartReveal } from "../hooks/useChartReveal";
40
+ import { useChartSkiaFont } from "../hooks/useChartSkiaFont";
41
+ import { useCrosshairSeries } from "../hooks/useCrosshairSeries";
42
+ import { useMarkers } from "../hooks/useMarkers";
43
+ import { useMultiSeriesDegen } from "../hooks/useMultiSeriesDegen";
44
+ import { useMultiSeriesLinePaths } from "../hooks/useMultiSeriesLinePaths";
45
+ import { useMultiSeriesReverseMorphInputs } from "../hooks/useReverseMorphEngineInputs";
46
+ import { useXAxis } from "../hooks/useXAxis";
47
+ import { useYAxis } from "../hooks/useYAxis";
48
+ import {
49
+ formatTime as defaultFormatTime,
50
+ formatValue as defaultFormatValue,
51
+ } from "../lib/format";
52
+ import { measureFontTextWidth } from "../lib/measureFontTextWidth";
53
+ import { MONO_FONT_FAMILY } from "../lib/monoFontFamily";
54
+ import { collectReferenceValues } from "../math/referenceLines";
55
+ import {
56
+ applyPaletteOverride,
57
+ leftEdgeFadeColorsFromBgRgb,
58
+ resolveTheme,
59
+ } from "../theme";
60
+ import type { LiveChartSeriesProps, Marker, SeriesConfig } from "../types";
61
+ import { CrosshairLine } from "./CrosshairLine";
62
+ import { DegenParticlesOverlay } from "./DegenParticlesOverlay";
63
+ import { LeftEdgeFade } from "./LeftEdgeFade";
64
+ import { MarkerOverlay } from "./MarkerOverlay";
65
+ import { LoadingOverlay } from "./LoadingOverlay";
66
+ import { MultiSeriesDots } from "./MultiSeriesDots";
67
+ import { MultiSeriesStroke } from "./MultiSeriesStroke";
68
+ import { MultiSeriesValueLabels } from "./MultiSeriesValueLabels";
69
+ import { MultiSeriesValueLines } from "./MultiSeriesValueLines";
70
+ import { ReferenceLineOverlay } from "./ReferenceLineOverlay";
71
+ import { SeriesToggleChips } from "./SeriesToggleChips";
72
+ import { XAxisOverlay } from "./XAxisOverlay";
73
+ import { YAxisOverlay } from "./YAxisOverlay";
74
+
75
+ /**
76
+ * Signature of the per-series *config* (colors, stroke styles, labels, count) —
77
+ * everything the snapshot below feeds into layout and rendering. Excludes the
78
+ * per-tick `data`, so the snapshot only refreshes on real config changes.
79
+ */
80
+ function seriesConfigSig(s: SharedValue<SeriesConfig[]>) {
81
+ "worklet";
82
+ const arr = s.value;
83
+ let out =
84
+ lineColorsSignatureFromArray(arr) +
85
+ "\x1d" +
86
+ lineStyleSignatureFromArray(arr);
87
+ for (let i = 0; i < arr.length; i++) {
88
+ out += "\x1d" + (arr[i].label ?? arr[i].id);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ /**
94
+ * Resolves props → configs → theme/layout → engine → per-frame derived values,
95
+ * overlay hooks and the color/style reaction state, returning a single render
96
+ * model so `SeriesChartStack` and `LiveChartSeries` stay small and presentational.
97
+ */
98
+ function useLiveChartSeriesController({
99
+ series,
100
+ theme = "dark",
101
+ accentColor = DEFAULT_ACCENT_COLOR,
102
+ line: lineProp,
103
+ font: fontProp,
104
+ insets,
105
+ style,
106
+ timeWindow = 30,
107
+ paused = false,
108
+ loading = false,
109
+ smoothing = 0.08,
110
+ exaggerate = false,
111
+ nonNegative = false,
112
+ maxValue,
113
+ windowBuffer = 0,
114
+ nowOverride,
115
+ accessibilityLabel,
116
+ accessibilityRole = "image",
117
+ emptyText = "No data",
118
+ formatValue = defaultFormatValue,
119
+ formatTime = defaultFormatTime,
120
+ yAxis = true,
121
+ xAxis = true,
122
+ referenceLines,
123
+ gridStyle,
124
+ palette: paletteOverride,
125
+ scrub = false,
126
+ onScrub,
127
+ onSeriesToggle,
128
+ dot: dotProp,
129
+ legend: legendProp,
130
+ degen,
131
+ onDegenShake,
132
+ markers,
133
+ onMarkerHover,
134
+ markerHitRadius = 16,
135
+ leftEdgeFade = true,
136
+ }: LiveChartSeriesProps) {
137
+ const emptyMarkers = useSharedValue<Marker[]>([]);
138
+ const markersSV = markers ?? emptyMarkers;
139
+ const markersActive = markers != null;
140
+ const yAxisCfg = resolveYAxis(yAxis);
141
+ const xAxisCfg = resolveXAxis(xAxis);
142
+ const scrubCfg = resolveScrub(scrub);
143
+ const scrubEnabled = scrubCfg !== null;
144
+ const gridStyleCfg = resolveGridStyle(gridStyle);
145
+ const dotCfg = resolveMultiSeriesDot(dotProp);
146
+ const legendCfg = resolveLegend(legendProp);
147
+ const degenCfg = resolveDegen(degen);
148
+
149
+ const allRefLines = referenceLines ?? [];
150
+ const refValues = collectReferenceValues(allRefLines);
151
+
152
+ const palette = applyPaletteOverride(
153
+ resolveTheme(accentColor, theme),
154
+ paletteOverride,
155
+ );
156
+
157
+ const leftEdgeFadeCfg = resolveLeftEdgeFade(
158
+ leftEdgeFade,
159
+ leftEdgeFadeColorsFromBgRgb(palette.bgRgb),
160
+ );
161
+
162
+ const skiaFont = useChartSkiaFont(
163
+ fontProp,
164
+ MONO_FONT_FAMILY,
165
+ palette.labelFontSize,
166
+ );
167
+
168
+ // Snapshot of the series config (colors, styles, labels) for layout + line
169
+ // rendering. Seeded off the render path below and refreshed by the reaction
170
+ // further down — reading the `series` SharedValue during render trips
171
+ // Reanimated's strict-mode warning. React flushes layout-effect state before
172
+ // paint, so the seed causes no flash.
173
+ const [seriesSnapshot, setSeriesSnapshot] = useState<SeriesConfig[]>([]);
174
+ // react-doctor-disable-next-line react-doctor/no-derived-state-effect -- Reanimated: series must be read off the render path
175
+ useLayoutEffect(() => {
176
+ // `.get()` (not `.value`): React Compiler hoists the `.value` getter read into
177
+ // render scope for memoization, which trips Reanimated's strict-mode warning;
178
+ // it leaves the `.get()` method call inside the effect.
179
+ // react-doctor-disable-next-line react-hooks-js/set-state-in-effect -- Reanimated: seeding from a SharedValue off render is the warning-free path
180
+ setSeriesSnapshot(series.get().slice());
181
+ }, [series]);
182
+
183
+ const maxSeriesLabelWidth = dotCfg.valueLabel
184
+ ? Math.max(
185
+ 0,
186
+ ...seriesSnapshot.map((s) =>
187
+ measureFontTextWidth(skiaFont, s.label ?? s.id),
188
+ ),
189
+ )
190
+ : 0;
191
+
192
+ const seriesLabelInset = dotCfg.valueLabel
193
+ ? dotCfg.radius + 8 + maxSeriesLabelWidth + 8
194
+ : 0;
195
+
196
+ const representativeValue =
197
+ seriesSnapshot.length > 0
198
+ ? Math.max(...seriesSnapshot.map((s) => s.value))
199
+ : 0;
200
+
201
+ const { strokeWidth, padding: effectivePadding } = resolveChartLayout({
202
+ palette,
203
+ lineWidthOverride: lineProp?.width,
204
+ insetsOverride: insets,
205
+ yAxis: yAxisCfg !== null,
206
+ badge: false,
207
+ xAxis: xAxisCfg !== null,
208
+ font: skiaFont,
209
+ formatValue,
210
+ currentValue: representativeValue,
211
+ pulse: dotCfg.pulse,
212
+ multiSeriesDotRadius: dotCfg.radius,
213
+ multiSeriesValueLabel: dotCfg.valueLabel,
214
+ multiSeriesMaxLabelWidth: maxSeriesLabelWidth,
215
+ });
216
+
217
+ const hasData = useDerivedValue(() => {
218
+ "worklet";
219
+ const arr = series.value;
220
+ for (let i = 0; i < arr.length; i++) {
221
+ if (arr[i].data.length >= 2) return true;
222
+ }
223
+ return false;
224
+ });
225
+
226
+ const reveal = useChartReveal(loading, hasData);
227
+
228
+ const effectiveSeries = useMultiSeriesReverseMorphInputs({
229
+ series,
230
+ hasData,
231
+ morphT: reveal.morphT,
232
+ });
233
+
234
+ const engine = useLiveChartSeriesEngine({
235
+ series: effectiveSeries,
236
+ timeWindow,
237
+ paused,
238
+ smoothing,
239
+ exaggerate,
240
+ referenceValues: refValues,
241
+ nonNegative,
242
+ maxValue,
243
+ windowBuffer,
244
+ nowOverride,
245
+ });
246
+ const { layoutHeight, onLayout } = useCanvasLayout(engine);
247
+ const linePaths = useMultiSeriesLinePaths(engine, effectivePadding);
248
+
249
+ // Per-series colors and stroke styles, derived from the off-render snapshot
250
+ // (React state — so no Reanimated read-during-render). The reaction below
251
+ // refreshes the snapshot when the series config signature changes. Plain
252
+ // derivations — React Compiler memoizes them, so no manual useMemo.
253
+ const lineColors = resolveMultiSeriesLineColorsSnapshot(seriesSnapshot);
254
+ const lineStyles = resolveMultiSeriesLineStylesSnapshot(seriesSnapshot);
255
+
256
+ // Read the `series` prop from closure, not a SharedValue passed through
257
+ // `scheduleOnRN`: the handle serialized across the worklet→JS boundary keeps
258
+ // the native `.value` accessor but loses the `.get()` method (`.get()` on it
259
+ // throws). Reading the prop directly is robust to either accessor.
260
+ const syncSnapshot = () => {
261
+ setSeriesSnapshot(series.get().slice());
262
+ };
263
+
264
+ useAnimatedReaction(
265
+ () => seriesConfigSig(series),
266
+ /* istanbul ignore next -- scheduleOnRN from UI-thread reaction */
267
+ (sig, prev) => {
268
+ if (sig !== prev) scheduleOnRN(syncSnapshot);
269
+ },
270
+ [series, syncSnapshot],
271
+ );
272
+
273
+ const {
274
+ pack: degenPack,
275
+ packRevision: degenPackRevision,
276
+ shakeTransform: degenShakeTransform,
277
+ } = useMultiSeriesDegen(engine, effectivePadding, degenCfg, onDegenShake);
278
+
279
+ const { yAxisEntries } = useYAxis(
280
+ engine,
281
+ effectivePadding,
282
+ formatValue,
283
+ skiaFont,
284
+ yAxisCfg?.minGap ?? 36,
285
+ );
286
+
287
+ const { xAxisEntries } = useXAxis(
288
+ engine,
289
+ effectivePadding,
290
+ formatTime,
291
+ skiaFont,
292
+ );
293
+
294
+ const crosshair = useCrosshairSeries(
295
+ engine,
296
+ effectivePadding,
297
+ scrubEnabled,
298
+ onScrub,
299
+ );
300
+
301
+ // `projected` is used internally by the hit-test gesture; the overlay
302
+ // self-projects, so we only need the gesture here.
303
+ const { tapGesture: markerTapGesture } = useMarkers(
304
+ engine,
305
+ effectivePadding,
306
+ markersSV,
307
+ markersActive,
308
+ markerHitRadius,
309
+ onMarkerHover,
310
+ series,
311
+ );
312
+
313
+ const rootGesture = markersActive
314
+ ? Gesture.Race(crosshair.gesture, markerTapGesture)
315
+ : crosshair.gesture;
316
+
317
+ const backgroundColor = `rgb(${palette.bgRgb[0]}, ${palette.bgRgb[1]}, ${palette.bgRgb[2]})`;
318
+
319
+ return {
320
+ // passthrough props the render needs
321
+ series,
322
+ style,
323
+ accessibilityLabel,
324
+ accessibilityRole,
325
+ emptyText,
326
+ formatValue,
327
+ onSeriesToggle,
328
+ // configs
329
+ yAxisCfg,
330
+ xAxisCfg,
331
+ scrubCfg,
332
+ gridStyleCfg,
333
+ dotCfg,
334
+ legendCfg,
335
+ degenCfg,
336
+ allRefLines,
337
+ leftEdgeFadeCfg,
338
+ // theme / layout / fonts
339
+ palette,
340
+ skiaFont,
341
+ seriesLabelInset,
342
+ strokeWidth,
343
+ effectivePadding,
344
+ backgroundColor,
345
+ // engine + reveal
346
+ engine,
347
+ reveal,
348
+ effectiveSeries,
349
+ layoutHeight,
350
+ onLayout,
351
+ linePaths,
352
+ lineColors,
353
+ lineStyles,
354
+ degenPack,
355
+ degenPackRevision,
356
+ degenShakeTransform,
357
+ yAxisEntries,
358
+ xAxisEntries,
359
+ crosshair,
360
+ rootGesture,
361
+ markersActive,
362
+ markersSV,
363
+ };
364
+ }
365
+
366
+ type LiveChartSeriesModel = ReturnType<typeof useLiveChartSeriesController>;
367
+
368
+ /** The shaken multi-series stack: grid, reference/value lines, per-series strokes,
369
+ * axis, dots, value labels, degen, markers, and the loading/empty art. */
370
+ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
371
+ const {
372
+ degenShakeTransform,
373
+ yAxisCfg,
374
+ reveal,
375
+ yAxisEntries,
376
+ engine,
377
+ effectivePadding,
378
+ palette,
379
+ skiaFont,
380
+ seriesLabelInset,
381
+ gridStyleCfg,
382
+ allRefLines,
383
+ formatValue,
384
+ dotCfg,
385
+ lineColors,
386
+ linePaths,
387
+ effectiveSeries,
388
+ strokeWidth,
389
+ lineStyles,
390
+ xAxisCfg,
391
+ xAxisEntries,
392
+ degenCfg,
393
+ degenPack,
394
+ degenPackRevision,
395
+ markersActive,
396
+ markersSV,
397
+ series,
398
+ emptyText,
399
+ } = model;
400
+
401
+ return (
402
+ <Group transform={degenShakeTransform}>
403
+ {yAxisCfg && (
404
+ <Group opacity={reveal.yAxisOpacity}>
405
+ <YAxisOverlay
406
+ entries={yAxisEntries}
407
+ engine={engine}
408
+ padding={effectivePadding}
409
+ palette={palette}
410
+ font={skiaFont}
411
+ badge={false}
412
+ seriesLabelInset={seriesLabelInset}
413
+ gridStyle={gridStyleCfg}
414
+ />
415
+ </Group>
416
+ )}
417
+
418
+ {allRefLines.map((rl) => (
419
+ <ReferenceLineOverlay
420
+ key={`${rl.value ?? ""}:${rl.valueFrom ?? ""}:${rl.valueTo ?? ""}:${rl.from ?? ""}:${rl.to ?? ""}:${rl.label ?? ""}`}
421
+ engine={engine}
422
+ padding={effectivePadding}
423
+ line={rl}
424
+ palette={palette}
425
+ formatValue={formatValue}
426
+ font={skiaFont}
427
+ />
428
+ ))}
429
+
430
+ {dotCfg.valueLine && (
431
+ <Group opacity={reveal.lineOpacity}>
432
+ <MultiSeriesValueLines
433
+ engine={engine}
434
+ padding={effectivePadding}
435
+ colors={lineColors}
436
+ config={dotCfg.valueLine}
437
+ />
438
+ </Group>
439
+ )}
440
+
441
+ <Group opacity={reveal.lineOpacity}>
442
+ {Array.from({ length: MAX_MULTI_SERIES }, (_, i) => (
443
+ <MultiSeriesStroke
444
+ key={i}
445
+ index={i}
446
+ paths={linePaths}
447
+ opacities={engine.seriesOpacities}
448
+ series={effectiveSeries}
449
+ strokeWidth={strokeWidth}
450
+ lineStyle={lineStyles[i]}
451
+ />
452
+ ))}
453
+ </Group>
454
+
455
+ {xAxisCfg && (
456
+ <XAxisOverlay
457
+ entries={xAxisEntries}
458
+ engine={engine}
459
+ padding={effectivePadding}
460
+ palette={palette}
461
+ font={skiaFont}
462
+ />
463
+ )}
464
+
465
+ <Group opacity={reveal.dotOpacity}>
466
+ <MultiSeriesDots
467
+ engine={engine}
468
+ padding={effectivePadding}
469
+ colors={lineColors}
470
+ radius={dotCfg.radius}
471
+ pulse={dotCfg.pulse}
472
+ />
473
+ </Group>
474
+
475
+ {dotCfg.valueLabel && (
476
+ <Group opacity={reveal.dotOpacity}>
477
+ <MultiSeriesValueLabels
478
+ engine={engine}
479
+ padding={effectivePadding}
480
+ colors={lineColors}
481
+ font={skiaFont}
482
+ dotRadius={dotCfg.radius}
483
+ />
484
+ </Group>
485
+ )}
486
+
487
+ {degenCfg && (
488
+ <Group opacity={reveal.dotOpacity}>
489
+ <DegenParticlesOverlay
490
+ pack={degenPack}
491
+ packRevision={degenPackRevision}
492
+ engine={engine}
493
+ palette={palette}
494
+ particleSlotCount={degenCfg.particleSlotCount}
495
+ particleBurstDurationSec={degenCfg.particleBurstDurationSec}
496
+ particleOpacity={degenCfg.particleOpacity}
497
+ colors={degenCfg.colors ?? lineColors}
498
+ />
499
+ </Group>
500
+ )}
501
+
502
+ {markersActive && (
503
+ <Group opacity={reveal.dotOpacity}>
504
+ <MarkerOverlay
505
+ markers={markersSV}
506
+ engine={engine}
507
+ padding={effectivePadding}
508
+ palette={palette}
509
+ font={skiaFont}
510
+ series={series}
511
+ />
512
+ </Group>
513
+ )}
514
+
515
+ <LoadingOverlay
516
+ engine={engine}
517
+ padding={effectivePadding}
518
+ palette={palette}
519
+ font={skiaFont}
520
+ morphT={reveal.morphT}
521
+ isLoading={reveal.isLoading}
522
+ isEmpty={reveal.isEmpty}
523
+ emptyText={emptyText}
524
+ strokeWidth={strokeWidth}
525
+ badge={false}
526
+ />
527
+ </Group>
528
+ );
529
+ }
530
+
531
+ export function LiveChartSeries(props: LiveChartSeriesProps) {
532
+ const model = useLiveChartSeriesController(props);
533
+ const {
534
+ rootGesture,
535
+ backgroundColor,
536
+ style,
537
+ onLayout,
538
+ accessibilityLabel,
539
+ accessibilityRole,
540
+ layoutHeight,
541
+ legendCfg,
542
+ series,
543
+ onSeriesToggle,
544
+ leftEdgeFadeCfg,
545
+ effectivePadding,
546
+ engine,
547
+ scrubCfg,
548
+ crosshair,
549
+ palette,
550
+ } = model;
551
+
552
+ const legend =
553
+ legendCfg.position === "top" || legendCfg.position === "bottom" ? (
554
+ <SeriesToggleChips
555
+ series={series}
556
+ legend={legendCfg}
557
+ palette={palette}
558
+ onSeriesToggle={onSeriesToggle}
559
+ />
560
+ ) : null;
561
+
562
+ return (
563
+ <View
564
+ style={[{ flex: 1, backgroundColor }, style]}
565
+ accessible={accessibilityLabel != null}
566
+ accessibilityLabel={accessibilityLabel}
567
+ accessibilityRole={accessibilityRole}
568
+ >
569
+ {legendCfg.position === "top" ? legend : null}
570
+ {/* Gesture + layout wrap ONLY the canvas: the legend chips are Pressables
571
+ that must sit outside the scrub gesture to receive taps, and the engine
572
+ canvas height must measure the canvas alone (excluding the legend row),
573
+ else points map into a taller area and the x-axis draws past the edge. */}
574
+ <GestureDetector gesture={rootGesture}>
575
+ <View style={{ flex: 1 }} onLayout={onLayout}>
576
+ <Canvas style={{ flex: 1, minHeight: layoutHeight || 1 }}>
577
+ <SeriesChartStack model={model} />
578
+
579
+ {leftEdgeFadeCfg && (
580
+ <LeftEdgeFade
581
+ paddingLeft={effectivePadding.left}
582
+ fadeWidth={leftEdgeFadeCfg.width}
583
+ startColor={leftEdgeFadeCfg.startColor}
584
+ endColor={leftEdgeFadeCfg.endColor}
585
+ engine={engine}
586
+ />
587
+ )}
588
+
589
+ {scrubCfg && (
590
+ <CrosshairLine
591
+ scrubX={crosshair.scrubX}
592
+ crosshairOpacity={crosshair.crosshairOpacity}
593
+ engine={engine}
594
+ padding={effectivePadding}
595
+ palette={palette}
596
+ dimOpacity={scrubCfg.dimOpacity}
597
+ crosshairLineColor={scrubCfg.crosshairLineColor}
598
+ crosshairDimColor={scrubCfg.crosshairDimColor}
599
+ />
600
+ )}
601
+ </Canvas>
602
+ </View>
603
+ </GestureDetector>
604
+ {legendCfg.position === "bottom" ? legend : null}
605
+ </View>
606
+ );
607
+ }