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,207 @@
1
+ /**
2
+ * UI-thread chart engine: time window, Y-range smoothing, and tick cadence pursue
3
+ * the same “live chart” feel as liveline’s `requestAnimationFrame` loop; mechanism
4
+ * here is Reanimated frame callbacks and SharedValues, not DOM canvas.
5
+ *
6
+ * @see https://github.com/benjitaylor/liveline
7
+ */
8
+ import { useState } from "react";
9
+ import {
10
+ useDerivedValue,
11
+ useFrameCallback,
12
+ useSharedValue,
13
+ type SharedValue,
14
+ } from "react-native-reanimated";
15
+ import { MS_PER_FRAME_60FPS } from "../constants";
16
+ import type { CandlePoint, LiveChartPoint, SeriesConfig } from "../types";
17
+ import { tickLiveChartEngineFrame } from "./liveChartEngineTick";
18
+
19
+ export interface EngineConfig {
20
+ data: SharedValue<LiveChartPoint[]>;
21
+ value: SharedValue<number>;
22
+ timeWindow: number;
23
+ smoothing: number;
24
+ exaggerate?: boolean;
25
+ referenceValue?: number;
26
+ referenceValues?: number[];
27
+ nonNegative?: boolean;
28
+ maxValue?: number;
29
+ nowOverride?: number;
30
+ windowBuffer?: number;
31
+ paused?: boolean;
32
+ mode?: "line" | "candle";
33
+ candles?: SharedValue<CandlePoint[]>;
34
+ liveCandle?: SharedValue<CandlePoint | null>;
35
+ }
36
+
37
+ /** Canvas, time window, and Y-range — shared by single- and multi-series engines. */
38
+ export interface ChartEngineLayout {
39
+ displayMin: SharedValue<number>;
40
+ displayMax: SharedValue<number>;
41
+ displayWindow: SharedValue<number>;
42
+ canvasWidth: SharedValue<number>;
43
+ canvasHeight: SharedValue<number>;
44
+ timestamp: SharedValue<number>;
45
+ }
46
+
47
+ export interface SingleEngineState extends ChartEngineLayout {
48
+ data: SharedValue<LiveChartPoint[]>;
49
+ value: SharedValue<number>;
50
+ displayValue: SharedValue<number>;
51
+ }
52
+
53
+ export interface MultiEngineState extends ChartEngineLayout {
54
+ data: SharedValue<LiveChartPoint[]>;
55
+ value: SharedValue<number>;
56
+ displayValue: SharedValue<number>;
57
+ series: SharedValue<SeriesConfig[]>;
58
+ displaySeriesValues: SharedValue<number[]>;
59
+ seriesOpacities: SharedValue<number[]>;
60
+ }
61
+
62
+ export type EngineState = SingleEngineState | MultiEngineState;
63
+
64
+ /** Canvas, range, and animated live value (badge / value line). */
65
+ export type ChartEngineWithLiveValue = ChartEngineLayout & {
66
+ displayValue: SharedValue<number>;
67
+ };
68
+
69
+ export interface EngineFrameRefs {
70
+ data: SharedValue<LiveChartPoint[]>;
71
+ value: SharedValue<number>;
72
+ displayValue: SharedValue<number>;
73
+ displayMin: SharedValue<number>;
74
+ displayMax: SharedValue<number>;
75
+ displayWindow: SharedValue<number>;
76
+ canvasWidth: SharedValue<number>;
77
+ canvasHeight: SharedValue<number>;
78
+ timestamp: SharedValue<number>;
79
+ timeWindow: SharedValue<number>;
80
+ smoothing: SharedValue<number>;
81
+ exaggerateSV: SharedValue<boolean>;
82
+ referenceValue: SharedValue<number | undefined>;
83
+ referenceValues?: SharedValue<number[] | undefined>;
84
+ nonNegativeSV?: SharedValue<boolean>;
85
+ maxValueSV?: SharedValue<number | undefined>;
86
+ nowOverrideSV?: SharedValue<number | undefined>;
87
+ windowBufferSV?: SharedValue<number>;
88
+ pausedSV: SharedValue<boolean>;
89
+ modeSV: SharedValue<"line" | "candle">;
90
+ candles?: SharedValue<CandlePoint[]>;
91
+ liveCandle?: SharedValue<CandlePoint | null>;
92
+ }
93
+
94
+ /**
95
+ * Shared between the `useFrameCallback` worklet and unit tests.
96
+ * Mutates shared values from a snapshot tick (`tickLiveChartEngineFrame`).
97
+ */
98
+ export function applyLiveChartEngineFrame(
99
+ frameInfo: { timeSincePreviousFrame?: number | null },
100
+ sv: EngineFrameRefs,
101
+ ): void {
102
+ "worklet";
103
+ const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
104
+ const state = {
105
+ displayValue: sv.displayValue.value,
106
+ displayMin: sv.displayMin.value,
107
+ displayMax: sv.displayMax.value,
108
+ displayWindow: sv.displayWindow.value,
109
+ timestamp: sv.timestamp.value,
110
+ };
111
+ tickLiveChartEngineFrame(state, {
112
+ dt,
113
+ canvasWidth: sv.canvasWidth.value,
114
+ canvasHeight: sv.canvasHeight.value,
115
+ timeWindow: sv.timeWindow.value,
116
+ smoothing: sv.smoothing.value,
117
+ exaggerate: sv.exaggerateSV.value,
118
+ referenceValue: sv.referenceValue.value,
119
+ referenceValues: sv.referenceValues?.value,
120
+ nonNegative: sv.nonNegativeSV?.value ?? false,
121
+ maxValue: sv.maxValueSV?.value,
122
+ nowOverride: sv.nowOverrideSV?.value,
123
+ windowBuffer: sv.windowBufferSV?.value ?? 0,
124
+ targetValue: sv.value.value,
125
+ points: sv.data.value,
126
+ nowSeconds: Date.now() / 1000,
127
+ paused: sv.pausedSV.value,
128
+ mode: sv.modeSV.value,
129
+ candles: sv.candles?.value,
130
+ liveCandle: sv.liveCandle?.value,
131
+ });
132
+ sv.displayValue.value = state.displayValue;
133
+ sv.displayMin.value = state.displayMin;
134
+ sv.displayMax.value = state.displayMax;
135
+ sv.displayWindow.value = state.displayWindow;
136
+ sv.timestamp.value = state.timestamp;
137
+ }
138
+
139
+ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
140
+ // Low-frequency config → UI thread via useDerivedValue
141
+ const timeWindow = useDerivedValue(() => config.timeWindow);
142
+ const smoothing = useDerivedValue(() => config.smoothing);
143
+ const exaggerateSV = useDerivedValue(() => config.exaggerate ?? false);
144
+ const referenceValue = useDerivedValue(() => config.referenceValue);
145
+ const referenceValues = useDerivedValue(() => config.referenceValues);
146
+ const nonNegativeSV = useDerivedValue(() => config.nonNegative ?? false);
147
+ const maxValueSV = useDerivedValue(() => config.maxValue);
148
+ const nowOverrideSV = useDerivedValue(() => config.nowOverride);
149
+ const windowBufferSV = useDerivedValue(() => config.windowBuffer ?? 0);
150
+ const pausedSV = useDerivedValue(() => config.paused ?? false);
151
+ const modeSV = useDerivedValue(() => config.mode ?? "line");
152
+
153
+ // Animation state (mutated on UI thread each frame)
154
+ const displayValue = useSharedValue(0);
155
+ const displayMin = useSharedValue(0);
156
+ const displayMax = useSharedValue(1);
157
+ const displayWindow = useSharedValue(config.timeWindow);
158
+ const canvasWidth = useSharedValue(0);
159
+ const canvasHeight = useSharedValue(0);
160
+ // Seed once; overwritten by the frame callback on the first tick.
161
+ const [initialTimestamp] = useState(() => Date.now() / 1000);
162
+ const timestamp = useSharedValue(initialTimestamp);
163
+
164
+ // High-frequency data reads directly from the caller's shared values —
165
+ // no useDerivedValue bridging, no closure serialization per tick.
166
+ const { data, value, candles, liveCandle } = config;
167
+
168
+ useFrameCallback((frameInfo) => {
169
+ "worklet";
170
+ applyLiveChartEngineFrame(frameInfo, {
171
+ data,
172
+ value,
173
+ displayValue,
174
+ displayMin,
175
+ displayMax,
176
+ displayWindow,
177
+ timestamp,
178
+ canvasWidth,
179
+ canvasHeight,
180
+ timeWindow,
181
+ smoothing,
182
+ exaggerateSV,
183
+ referenceValue,
184
+ referenceValues,
185
+ nonNegativeSV,
186
+ maxValueSV,
187
+ nowOverrideSV,
188
+ windowBufferSV,
189
+ pausedSV,
190
+ modeSV,
191
+ candles,
192
+ liveCandle,
193
+ });
194
+ });
195
+
196
+ return {
197
+ data,
198
+ value,
199
+ displayValue,
200
+ displayMin,
201
+ displayMax,
202
+ displayWindow,
203
+ canvasWidth,
204
+ canvasHeight,
205
+ timestamp,
206
+ };
207
+ }
@@ -0,0 +1,215 @@
1
+ import { useRef, useState } from "react";
2
+ import {
3
+ useDerivedValue,
4
+ useFrameCallback,
5
+ useSharedValue,
6
+ type SharedValue,
7
+ } from "react-native-reanimated";
8
+ import { MS_PER_FRAME_60FPS } from "../constants";
9
+ import type { LiveChartPoint, SeriesConfig } from "../types";
10
+ import { tickLiveChartSeriesEngineFrame } from "./liveChartSeriesEngineTick";
11
+ import type { MultiEngineState } from "./useLiveChartEngine";
12
+
13
+ export interface MultiSeriesEngineConfig {
14
+ series: SharedValue<SeriesConfig[]>;
15
+ timeWindow: number;
16
+ smoothing: number;
17
+ exaggerate?: boolean;
18
+ referenceValue?: number;
19
+ referenceValues?: number[];
20
+ nonNegative?: boolean;
21
+ maxValue?: number;
22
+ nowOverride?: number;
23
+ windowBuffer?: number;
24
+ paused?: boolean;
25
+ }
26
+
27
+ export interface MultiEngineFrameRefs {
28
+ series: SharedValue<SeriesConfig[]>;
29
+ displaySeriesValues: SharedValue<number[]>;
30
+ seriesOpacities: SharedValue<number[]>;
31
+ displayMin: SharedValue<number>;
32
+ displayMax: SharedValue<number>;
33
+ displayWindow: SharedValue<number>;
34
+ timestamp: SharedValue<number>;
35
+ canvasWidth: SharedValue<number>;
36
+ canvasHeight: SharedValue<number>;
37
+ timeWindow: SharedValue<number>;
38
+ smoothing: SharedValue<number>;
39
+ exaggerateSV: SharedValue<boolean>;
40
+ referenceValue: SharedValue<number | undefined>;
41
+ referenceValues?: SharedValue<number[] | undefined>;
42
+ nonNegativeSV?: SharedValue<boolean>;
43
+ maxValueSV?: SharedValue<number | undefined>;
44
+ nowOverrideSV?: SharedValue<number | undefined>;
45
+ windowBufferSV?: SharedValue<number>;
46
+ pausedSV: SharedValue<boolean>;
47
+ }
48
+
49
+ /**
50
+ * Reusable per-frame scratch for {@link applyLiveChartSeriesEngineFrame}. The two
51
+ * output arrays ping-pong so the assigned reference still changes each frame
52
+ * (Reanimated propagates on reference change) without allocating a fresh array
53
+ * per frame. Create one per engine (the React Compiler keeps it stable).
54
+ */
55
+ export interface MultiSeriesEngineScratch {
56
+ dvA: number[];
57
+ dvB: number[];
58
+ opA: number[];
59
+ opB: number[];
60
+ tick: boolean;
61
+ }
62
+
63
+ /**
64
+ * One frame of the multi-series chart engine.
65
+ * Mirrors `applyLiveChartEngineFrame` but iterates over each series
66
+ * to lerp per-series display values and opacities. Extracted as a
67
+ * pure function so it can be called from both `useFrameCallback` and tests.
68
+ *
69
+ * Pass `scratch` to reuse the output arrays across frames instead of allocating
70
+ * two via `.slice()` each frame; omit it (tests) to fall back to allocation.
71
+ */
72
+ export function applyLiveChartSeriesEngineFrame(
73
+ frameInfo: { timeSincePreviousFrame?: number | null },
74
+ sv: MultiEngineFrameRefs,
75
+ scratch?: MultiSeriesEngineScratch,
76
+ ): void {
77
+ "worklet";
78
+ const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
79
+ const seriesSnap = sv.series.value;
80
+ const curDv = sv.displaySeriesValues.value;
81
+ const curOp = sv.seriesOpacities.value;
82
+ let displayValues: number[];
83
+ let opacities: number[];
84
+ if (scratch) {
85
+ scratch.tick = !scratch.tick;
86
+ displayValues = scratch.tick ? scratch.dvA : scratch.dvB;
87
+ opacities = scratch.tick ? scratch.opA : scratch.opB;
88
+ displayValues.length = curDv.length;
89
+ for (let i = 0; i < curDv.length; i++) displayValues[i] = curDv[i];
90
+ opacities.length = curOp.length;
91
+ for (let i = 0; i < curOp.length; i++) opacities[i] = curOp[i];
92
+ } else {
93
+ displayValues = curDv.slice();
94
+ opacities = curOp.slice();
95
+ }
96
+ const state = {
97
+ displayMin: sv.displayMin.value,
98
+ displayMax: sv.displayMax.value,
99
+ displayWindow: sv.displayWindow.value,
100
+ timestamp: sv.timestamp.value,
101
+ displayValues,
102
+ opacities,
103
+ };
104
+ tickLiveChartSeriesEngineFrame(state, {
105
+ dt,
106
+ canvasWidth: sv.canvasWidth.value,
107
+ canvasHeight: sv.canvasHeight.value,
108
+ timeWindow: sv.timeWindow.value,
109
+ smoothing: sv.smoothing.value,
110
+ exaggerate: sv.exaggerateSV.value,
111
+ referenceValue: sv.referenceValue.value,
112
+ referenceValues: sv.referenceValues?.value,
113
+ nonNegative: sv.nonNegativeSV?.value ?? false,
114
+ maxValue: sv.maxValueSV?.value,
115
+ nowOverride: sv.nowOverrideSV?.value,
116
+ windowBuffer: sv.windowBufferSV?.value ?? 0,
117
+ series: seriesSnap,
118
+ nowSeconds: Date.now() / 1000,
119
+ paused: sv.pausedSV.value,
120
+ });
121
+ sv.displayMin.value = state.displayMin;
122
+ sv.displayMax.value = state.displayMax;
123
+ sv.displayWindow.value = state.displayWindow;
124
+ sv.timestamp.value = state.timestamp;
125
+ sv.displaySeriesValues.value = state.displayValues;
126
+ sv.seriesOpacities.value = state.opacities;
127
+ }
128
+
129
+ /**
130
+ * UI-thread engine for multi-series charts. Dummies `data` / `value` / `displayValue`
131
+ * mirror single-series fields for hooks that still read them.
132
+ */
133
+ export function useLiveChartSeriesEngine(
134
+ config: MultiSeriesEngineConfig,
135
+ ): MultiEngineState {
136
+ const timeWindow = useDerivedValue(() => config.timeWindow);
137
+ const smoothing = useDerivedValue(() => config.smoothing);
138
+ const exaggerateSV = useDerivedValue(() => config.exaggerate ?? false);
139
+ const referenceValue = useDerivedValue(() => config.referenceValue);
140
+ const referenceValues = useDerivedValue(() => config.referenceValues);
141
+ const nonNegativeSV = useDerivedValue(() => config.nonNegative ?? false);
142
+ const maxValueSV = useDerivedValue(() => config.maxValue);
143
+ const nowOverrideSV = useDerivedValue(() => config.nowOverride);
144
+ const windowBufferSV = useDerivedValue(() => config.windowBuffer ?? 0);
145
+ const pausedSV = useDerivedValue(() => config.paused ?? false);
146
+
147
+ const displayMin = useSharedValue(0);
148
+ const displayMax = useSharedValue(1);
149
+ const displayWindow = useSharedValue(config.timeWindow);
150
+ const canvasWidth = useSharedValue(0);
151
+ const canvasHeight = useSharedValue(0);
152
+ // Seed once; overwritten by the frame callback on the first tick.
153
+ const [initialTimestamp] = useState(() => Date.now() / 1000);
154
+ const timestamp = useSharedValue(initialTimestamp);
155
+
156
+ const displaySeriesValues = useSharedValue<number[]>([]);
157
+ const seriesOpacities = useSharedValue<number[]>([]);
158
+
159
+ const data = useSharedValue<LiveChartPoint[]>([]);
160
+ const value = useSharedValue(0);
161
+ const displayValue = useSharedValue(0);
162
+
163
+ const { series } = config;
164
+
165
+ // Reused per-frame output buffers (ping-ponged) — see applyLiveChartSeriesEngineFrame.
166
+ const scratchRef = useRef<MultiSeriesEngineScratch | null>(null);
167
+ if (scratchRef.current === null) {
168
+ scratchRef.current = { dvA: [], dvB: [], opA: [], opB: [], tick: false };
169
+ }
170
+
171
+ useFrameCallback((frameInfo) => {
172
+ "worklet";
173
+ const scratch = scratchRef.current!;
174
+ applyLiveChartSeriesEngineFrame(
175
+ frameInfo,
176
+ {
177
+ series,
178
+ displaySeriesValues,
179
+ seriesOpacities,
180
+ displayMin,
181
+ displayMax,
182
+ displayWindow,
183
+ timestamp,
184
+ canvasWidth,
185
+ canvasHeight,
186
+ timeWindow,
187
+ smoothing,
188
+ exaggerateSV,
189
+ referenceValue,
190
+ referenceValues,
191
+ nonNegativeSV,
192
+ maxValueSV,
193
+ nowOverrideSV,
194
+ windowBufferSV,
195
+ pausedSV,
196
+ },
197
+ scratch,
198
+ );
199
+ });
200
+
201
+ return {
202
+ data,
203
+ value,
204
+ displayValue,
205
+ displayMin,
206
+ displayMax,
207
+ displayWindow,
208
+ canvasWidth,
209
+ canvasHeight,
210
+ timestamp,
211
+ series,
212
+ displaySeriesValues,
213
+ seriesOpacities,
214
+ };
215
+ }
@@ -0,0 +1,210 @@
1
+ import type { CandlePoint } from "../types";
2
+ import type { ChartPadding } from "./line";
3
+
4
+ export interface CandleRect {
5
+ x: number;
6
+ y: number;
7
+ w: number;
8
+ h: number;
9
+ up: boolean;
10
+ }
11
+
12
+ export interface CandleWick {
13
+ x: number;
14
+ y1: number;
15
+ y2: number;
16
+ up: boolean;
17
+ }
18
+
19
+ export interface CandleGeometry {
20
+ bodies: CandleRect[];
21
+ wicks: CandleWick[];
22
+ }
23
+
24
+ const MIN_BODY_PX = 1;
25
+ const MAX_BODY_PX = 40;
26
+
27
+ function candleTimeToX(
28
+ t: number,
29
+ padLeft: number,
30
+ winStart: number,
31
+ windowSecs: number,
32
+ chartW: number,
33
+ ): number {
34
+ "worklet";
35
+ return padLeft + ((t - winStart) / windowSecs) * chartW;
36
+ }
37
+
38
+ function candleValueToY(
39
+ v: number,
40
+ padTop: number,
41
+ displayMax: number,
42
+ valRange: number,
43
+ chartH: number,
44
+ ): number {
45
+ "worklet";
46
+ return padTop + ((displayMax - v) / valRange) * chartH;
47
+ }
48
+
49
+ /** One candle → bodies/wicks; top-level worklet (no nested closures). */
50
+ function appendCandleShapes(
51
+ c: CandlePoint,
52
+ winStart: number,
53
+ winEnd: number,
54
+ windowSecs: number,
55
+ chartW: number,
56
+ chartH: number,
57
+ chartLeft: number,
58
+ chartRight: number,
59
+ padTop: number,
60
+ padLeft: number,
61
+ displayMax: number,
62
+ valRange: number,
63
+ candleWidthSecs: number,
64
+ bodyW: number,
65
+ bodies: CandleRect[],
66
+ wicks: CandleWick[],
67
+ ): void {
68
+ "worklet";
69
+ if (c.time + candleWidthSecs < winStart || c.time > winEnd) return;
70
+
71
+ const up = c.close >= c.open;
72
+ const xCenter = candleTimeToX(
73
+ c.time + candleWidthSecs / 2,
74
+ padLeft,
75
+ winStart,
76
+ windowSecs,
77
+ chartW,
78
+ );
79
+
80
+ if (xCenter < chartLeft - bodyW / 2 || xCenter > chartRight + bodyW / 2)
81
+ return;
82
+
83
+ const bodyTop = candleValueToY(
84
+ up ? c.close : c.open,
85
+ padTop,
86
+ displayMax,
87
+ valRange,
88
+ chartH,
89
+ );
90
+ const bodyBot = candleValueToY(
91
+ up ? c.open : c.close,
92
+ padTop,
93
+ displayMax,
94
+ valRange,
95
+ chartH,
96
+ );
97
+ const bodyH = Math.max(MIN_BODY_PX, bodyBot - bodyTop);
98
+
99
+ let bx = xCenter - bodyW / 2;
100
+ let bw = bodyW;
101
+ if (bx < chartLeft) {
102
+ bw -= chartLeft - bx;
103
+ bx = chartLeft;
104
+ }
105
+ /* istanbul ignore next -- right-edge clip rare in tests */
106
+ if (bx + bw > chartRight) {
107
+ bw = chartRight - bx;
108
+ }
109
+ /* istanbul ignore next -- clipped to zero width */
110
+ if (bw <= 0) return;
111
+
112
+ bodies.push({ x: bx, y: bodyTop, w: bw, h: bodyH, up });
113
+
114
+ const wickX = Math.max(chartLeft, Math.min(xCenter, chartRight));
115
+ const wickTop = candleValueToY(c.high, padTop, displayMax, valRange, chartH);
116
+ const wickBot = candleValueToY(c.low, padTop, displayMax, valRange, chartH);
117
+ wicks.push({ x: wickX, y1: wickTop, y2: wickBot, up });
118
+ }
119
+
120
+ /**
121
+ * Build screen-space candle bodies and wicks for a visible window.
122
+ * Pure worklet — no Skia imports, consumed by useCandlePaths.
123
+ */
124
+ export function buildCandleGeometry(
125
+ candles: CandlePoint[],
126
+ liveCandle: CandlePoint | null,
127
+ padding: ChartPadding,
128
+ canvasW: number,
129
+ canvasH: number,
130
+ winStart: number,
131
+ windowSecs: number,
132
+ displayMin: number,
133
+ displayMax: number,
134
+ candleWidthSecs: number,
135
+ ): CandleGeometry {
136
+ "worklet";
137
+ const chartW = canvasW - padding.left - padding.right;
138
+ const chartH = canvasH - padding.top - padding.bottom;
139
+ const valRange = displayMax - displayMin;
140
+
141
+ if (valRange === 0 || chartW <= 0 || chartH <= 0)
142
+ return { bodies: [], wicks: [] };
143
+
144
+ const slotPx = (candleWidthSecs / windowSecs) * chartW;
145
+ const bodyW = Math.max(1, Math.min(slotPx * 0.8, slotPx - 2, MAX_BODY_PX));
146
+
147
+ const bodies: CandleRect[] = [];
148
+ const wicks: CandleWick[] = [];
149
+
150
+ const winEnd = winStart + windowSecs;
151
+ const chartLeft = padding.left;
152
+ const chartRight = canvasW - padding.right;
153
+ const padTop = padding.top;
154
+ const padLeft = padding.left;
155
+
156
+ // Binary search for first candle overlapping the window
157
+ let lo = 0;
158
+ let hi = candles.length;
159
+ while (lo < hi) {
160
+ const mid = (lo + hi) >> 1;
161
+ if (candles[mid].time + candleWidthSecs < winStart) lo = mid + 1;
162
+ else hi = mid;
163
+ }
164
+
165
+ for (let i = lo; i < candles.length; i++) {
166
+ /* istanbul ignore next -- past visible window */
167
+ if (candles[i].time > winEnd) break;
168
+ appendCandleShapes(
169
+ candles[i],
170
+ winStart,
171
+ winEnd,
172
+ windowSecs,
173
+ chartW,
174
+ chartH,
175
+ chartLeft,
176
+ chartRight,
177
+ padTop,
178
+ padLeft,
179
+ displayMax,
180
+ valRange,
181
+ candleWidthSecs,
182
+ bodyW,
183
+ bodies,
184
+ wicks,
185
+ );
186
+ }
187
+
188
+ if (liveCandle) {
189
+ appendCandleShapes(
190
+ liveCandle,
191
+ winStart,
192
+ winEnd,
193
+ windowSecs,
194
+ chartW,
195
+ chartH,
196
+ chartLeft,
197
+ chartRight,
198
+ padTop,
199
+ padLeft,
200
+ displayMax,
201
+ valRange,
202
+ candleWidthSecs,
203
+ bodyW,
204
+ bodies,
205
+ wicks,
206
+ );
207
+ }
208
+
209
+ return { bodies, wicks };
210
+ }