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,165 @@
1
+ import { Skia, type SkPath } from "@shopify/react-native-skia";
2
+ import { useRef } from "react";
3
+ import {
4
+ useDerivedValue,
5
+ useFrameCallback,
6
+ useSharedValue,
7
+ type SharedValue,
8
+ } from "react-native-reanimated";
9
+ import { MS_PER_FRAME_60FPS } from "../constants";
10
+ import type { SingleEngineState } from "../core/useLiveChartEngine";
11
+ import { buildCandleGeometry } from "../draw/candle";
12
+ import type { ChartPadding } from "../draw/line";
13
+ import { lerp } from "../math/lerp";
14
+ import type { CandlePoint } from "../types";
15
+
16
+ const CANDLE_WIDTH_LERP_SPEED = 0.08;
17
+
18
+ /**
19
+ * Persistent candle `SkPath`s — two per geometry slot (up/down bodies + up/down wicks).
20
+ * Each derived value ping-pongs between its pair and mutates the picked path in place
21
+ * so we don't allocate a new JSI-backed `SkPath` per frame. See {@link useChartPaths}
22
+ * for the rationale (this was the primary driver of the iOS memory climb under live
23
+ * ticking).
24
+ */
25
+ export function useCandlePaths(
26
+ engine: SingleEngineState,
27
+ padding: ChartPadding,
28
+ candles: SharedValue<CandlePoint[]> | undefined,
29
+ liveCandle: SharedValue<CandlePoint | null> | undefined,
30
+ candleWidthSecs: number,
31
+ active: boolean,
32
+ ) {
33
+ const targetCandleWidth = useDerivedValue(() => candleWidthSecs);
34
+ const displayCandleWidth = useSharedValue(candleWidthSecs);
35
+
36
+ const cacheRef = useRef<{
37
+ upBodiesA: SkPath;
38
+ upBodiesB: SkPath;
39
+ downBodiesA: SkPath;
40
+ downBodiesB: SkPath;
41
+ upWicksA: SkPath;
42
+ upWicksB: SkPath;
43
+ downWicksA: SkPath;
44
+ downWicksB: SkPath;
45
+ ubTick: boolean;
46
+ dbTick: boolean;
47
+ uwTick: boolean;
48
+ dwTick: boolean;
49
+ } | null>(null);
50
+ if (cacheRef.current === null) {
51
+ cacheRef.current = {
52
+ upBodiesA: Skia.Path.Make(),
53
+ upBodiesB: Skia.Path.Make(),
54
+ downBodiesA: Skia.Path.Make(),
55
+ downBodiesB: Skia.Path.Make(),
56
+ upWicksA: Skia.Path.Make(),
57
+ upWicksB: Skia.Path.Make(),
58
+ downWicksA: Skia.Path.Make(),
59
+ downWicksB: Skia.Path.Make(),
60
+ ubTick: false,
61
+ dbTick: false,
62
+ uwTick: false,
63
+ dwTick: false,
64
+ };
65
+ }
66
+
67
+ useFrameCallback((frameInfo) => {
68
+ "worklet";
69
+ if (!active) return;
70
+ const dt = frameInfo.timeSincePreviousFrame ?? MS_PER_FRAME_60FPS;
71
+ displayCandleWidth.set(
72
+ lerp(
73
+ displayCandleWidth.get(),
74
+ targetCandleWidth.get(),
75
+ CANDLE_WIDTH_LERP_SPEED,
76
+ dt,
77
+ ),
78
+ );
79
+ });
80
+
81
+ /* istanbul ignore next -- worklet */
82
+ const geometry = useDerivedValue(() => {
83
+ if (!active || !candles) return { bodies: [], wicks: [] };
84
+ return buildCandleGeometry(
85
+ candles.value,
86
+ liveCandle?.value ?? null,
87
+ padding,
88
+ engine.canvasWidth.value,
89
+ engine.canvasHeight.value,
90
+ engine.timestamp.value - engine.displayWindow.value,
91
+ engine.displayWindow.value,
92
+ engine.displayMin.value,
93
+ engine.displayMax.value,
94
+ displayCandleWidth.get(),
95
+ );
96
+ });
97
+
98
+ /* istanbul ignore next -- worklet */
99
+ const upBodiesPath = useDerivedValue(() => {
100
+ const cache = cacheRef.current!;
101
+ cache.ubTick = !cache.ubTick;
102
+ const path = cache.ubTick ? cache.upBodiesA : cache.upBodiesB;
103
+ path.reset();
104
+ const { bodies } = geometry.value;
105
+ for (let i = 0; i < bodies.length; i++) {
106
+ if (bodies[i].up) {
107
+ path.addRect(
108
+ Skia.XYWHRect(bodies[i].x, bodies[i].y, bodies[i].w, bodies[i].h),
109
+ );
110
+ }
111
+ }
112
+ return path;
113
+ });
114
+
115
+ /* istanbul ignore next -- worklet */
116
+ const downBodiesPath = useDerivedValue(() => {
117
+ const cache = cacheRef.current!;
118
+ cache.dbTick = !cache.dbTick;
119
+ const path = cache.dbTick ? cache.downBodiesA : cache.downBodiesB;
120
+ path.reset();
121
+ const { bodies } = geometry.value;
122
+ for (let i = 0; i < bodies.length; i++) {
123
+ if (!bodies[i].up) {
124
+ path.addRect(
125
+ Skia.XYWHRect(bodies[i].x, bodies[i].y, bodies[i].w, bodies[i].h),
126
+ );
127
+ }
128
+ }
129
+ return path;
130
+ });
131
+
132
+ /* istanbul ignore next -- worklet */
133
+ const upWicksPath = useDerivedValue(() => {
134
+ const cache = cacheRef.current!;
135
+ cache.uwTick = !cache.uwTick;
136
+ const path = cache.uwTick ? cache.upWicksA : cache.upWicksB;
137
+ path.reset();
138
+ const { wicks } = geometry.value;
139
+ for (let i = 0; i < wicks.length; i++) {
140
+ if (wicks[i].up) {
141
+ path.moveTo(wicks[i].x, wicks[i].y1);
142
+ path.lineTo(wicks[i].x, wicks[i].y2);
143
+ }
144
+ }
145
+ return path;
146
+ });
147
+
148
+ /* istanbul ignore next -- worklet */
149
+ const downWicksPath = useDerivedValue(() => {
150
+ const cache = cacheRef.current!;
151
+ cache.dwTick = !cache.dwTick;
152
+ const path = cache.dwTick ? cache.downWicksA : cache.downWicksB;
153
+ path.reset();
154
+ const { wicks } = geometry.value;
155
+ for (let i = 0; i < wicks.length; i++) {
156
+ if (!wicks[i].up) {
157
+ path.moveTo(wicks[i].x, wicks[i].y1);
158
+ path.lineTo(wicks[i].x, wicks[i].y2);
159
+ }
160
+ }
161
+ return path;
162
+ });
163
+
164
+ return { upBodiesPath, downBodiesPath, upWicksPath, downWicksPath } as const;
165
+ }
@@ -0,0 +1,22 @@
1
+ import { useState } from "react";
2
+
3
+ import type { LayoutChangeEvent } from "react-native";
4
+ import type { ChartEngineLayout } from "../core/useLiveChartEngine";
5
+
6
+ /**
7
+ * Track canvas dimensions via `onLayout` and sync them into engine shared values.
8
+ * Returns `{ layoutHeight, onLayout }` — pass `onLayout` to the container View
9
+ * and use `layoutHeight` for conditional rendering (e.g. minimum height guards).
10
+ */
11
+ export function useCanvasLayout(engine: ChartEngineLayout) {
12
+ const [layoutHeight, setLayoutHeight] = useState(0);
13
+
14
+ const onLayout = (e: LayoutChangeEvent) => {
15
+ const { width, height } = e.nativeEvent.layout;
16
+ engine.canvasWidth.set(width);
17
+ engine.canvasHeight.set(height);
18
+ setLayoutHeight(height);
19
+ };
20
+
21
+ return { layoutHeight, onLayout } as const;
22
+ }
@@ -0,0 +1,48 @@
1
+ import type { ResolvedGradientConfig } from "../core/resolveConfig";
2
+ import type { ChartPadding } from "../draw/line";
3
+ import { parseColorRgb } from "../theme";
4
+ import type { LiveChartPalette } from "../types";
5
+
6
+ export interface ChartColors {
7
+ /** Background color derived from the theme palette. */
8
+ backgroundColor: string;
9
+ /** Y coordinate of the gradient bottom edge — accounts for bottom inset. */
10
+ gradientEnd: number;
11
+ /** Top gradient stop — custom opacity or palette default. */
12
+ gradientTopColor: string;
13
+ /** Bottom gradient stop — custom opacity or palette default. */
14
+ gradientBottomColor: string;
15
+ }
16
+
17
+ /**
18
+ * Derives background and gradient colors from the resolved palette and
19
+ * optional GradientConfig opacities. The React Compiler memoizes the result
20
+ * so strings are only recomputed when inputs change.
21
+ */
22
+ export function useChartColors(
23
+ palette: LiveChartPalette,
24
+ gradientCfg: ResolvedGradientConfig | null,
25
+ accentColor: string,
26
+ layoutHeight: number,
27
+ padding: ChartPadding,
28
+ ): ChartColors {
29
+ const backgroundColor = `rgb(${palette.bgRgb[0]}, ${palette.bgRgb[1]}, ${palette.bgRgb[2]})`;
30
+ const gradientEnd = Math.max(1, layoutHeight - padding.bottom);
31
+
32
+ const [r, g, b] = parseColorRgb(accentColor);
33
+ const gradientTopColor =
34
+ gradientCfg?.topOpacity !== undefined
35
+ ? `rgba(${r}, ${g}, ${b}, ${gradientCfg.topOpacity})`
36
+ : palette.fillTop;
37
+ const gradientBottomColor =
38
+ gradientCfg?.bottomOpacity !== undefined
39
+ ? `rgba(${r}, ${g}, ${b}, ${gradientCfg.bottomOpacity})`
40
+ : palette.fillBottom;
41
+
42
+ return {
43
+ backgroundColor,
44
+ gradientEnd,
45
+ gradientTopColor,
46
+ gradientBottomColor,
47
+ };
48
+ }
@@ -0,0 +1,121 @@
1
+ import { Skia, type SkPath } from "@shopify/react-native-skia";
2
+ import { useRef } from "react";
3
+ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
4
+ import type { SingleEngineState } from "../core/useLiveChartEngine";
5
+ import { buildLinePoints, type ChartPadding } from "../draw/line";
6
+ import { drawSpline, makeSplineScratch } from "../math/spline";
7
+ import { blendPtsY, squigglifyPts } from "../math/squiggly";
8
+
9
+ /**
10
+ * Persistent `linePath` / `fillPath` `SkPath`s that are **mutated in place** each frame.
11
+ *
12
+ * Why not `useDerivedValue(() => Skia.Path.Make())`? Each call would allocate a brand-new
13
+ * JSI-backed `SkPath`, and on iOS the native GC lags the JS GC under a steady firehose —
14
+ * resident memory climbs steadily for as long as the chart is mounted. Here we allocate
15
+ * two paths per curve once and ping-pong the reference each frame so Reanimated's
16
+ * value-equality early-return still fires its subscribers (i.e. the Skia reanimated
17
+ * container re-records and repaints).
18
+ */
19
+ export function useChartPaths(
20
+ engine: SingleEngineState,
21
+ padding: ChartPadding,
22
+ morphT?: SharedValue<number>,
23
+ ) {
24
+ // Two persistent paths per curve; ping-pong so the returned reference changes
25
+ // every frame, which keeps Reanimated's setter from short-circuiting the notify.
26
+ const cacheRef = useRef<{
27
+ lineA: SkPath;
28
+ lineB: SkPath;
29
+ fillA: SkPath;
30
+ fillB: SkPath;
31
+ tick: boolean;
32
+ ptsA: number[];
33
+ ptsB: number[];
34
+ ptsTick: boolean;
35
+ scratch: ReturnType<typeof makeSplineScratch>;
36
+ } | null>(null);
37
+ if (cacheRef.current === null) {
38
+ cacheRef.current = {
39
+ lineA: Skia.Path.Make(),
40
+ lineB: Skia.Path.Make(),
41
+ fillA: Skia.Path.Make(),
42
+ fillB: Skia.Path.Make(),
43
+ tick: false,
44
+ // Ping-pong point buffers: reused across frames but the returned reference
45
+ // still alternates, so Reanimated keeps notifying linePath/fillPath.
46
+ ptsA: [] as number[],
47
+ ptsB: [] as number[],
48
+ ptsTick: false,
49
+ scratch: makeSplineScratch(),
50
+ };
51
+ }
52
+
53
+ const flatPts = useDerivedValue(() => {
54
+ const cache = cacheRef.current!;
55
+ cache.ptsTick = !cache.ptsTick;
56
+ const buf = cache.ptsTick ? cache.ptsA : cache.ptsB;
57
+ const realPts = buildLinePoints(
58
+ engine.data.get(),
59
+ engine.displayValue.get(),
60
+ engine.timestamp.get(),
61
+ engine.displayWindow.get(),
62
+ engine.displayMin.get(),
63
+ engine.displayMax.get(),
64
+ engine.canvasWidth.get(),
65
+ engine.canvasHeight.get(),
66
+ padding,
67
+ buf,
68
+ );
69
+
70
+ // Skip blending when fully revealed or no morphT provided
71
+ const t = morphT?.get() ?? 1;
72
+ if (t >= 1 || realPts.length === 0) return realPts;
73
+
74
+ // Compute squiggly Y values at the same X positions as the real line
75
+ const centerY =
76
+ (engine.canvasHeight.get() - padding.bottom + padding.top) / 2;
77
+ const squigglyPts = squigglifyPts(realPts, engine.timestamp.get(), centerY);
78
+
79
+ // Blend center-out: centre of chart reveals first, edges last
80
+ return blendPtsY(
81
+ squigglyPts,
82
+ realPts,
83
+ t,
84
+ padding,
85
+ engine.canvasWidth.get(),
86
+ );
87
+ });
88
+
89
+ const linePath = useDerivedValue(() => {
90
+ const cache = cacheRef.current!;
91
+ const pts = flatPts.get();
92
+ cache.tick = !cache.tick;
93
+ const path = cache.tick ? cache.lineA : cache.lineB;
94
+ path.reset();
95
+ const n = pts.length >> 1;
96
+ if (n < 2) return path;
97
+ path.moveTo(pts[0], pts[1]);
98
+ drawSpline(path, pts, cache.scratch);
99
+ return path;
100
+ });
101
+
102
+ const fillPath = useDerivedValue(() => {
103
+ const cache = cacheRef.current!;
104
+ const pts = flatPts.get();
105
+ // Use the same tick flag flipped by linePath — flipping again here would
106
+ // keep both paths in sync, so just read the current parity.
107
+ const path = cache.tick ? cache.fillA : cache.fillB;
108
+ path.reset();
109
+ const n = pts.length >> 1;
110
+ if (n < 2) return path;
111
+ path.moveTo(pts[0], pts[1]);
112
+ drawSpline(path, pts, cache.scratch);
113
+ const bottom = engine.canvasHeight.get() - padding.bottom;
114
+ path.lineTo(pts[(n - 1) * 2], bottom);
115
+ path.lineTo(pts[0], bottom);
116
+ path.close();
117
+ return path;
118
+ });
119
+
120
+ return { linePath, fillPath } as const;
121
+ }
@@ -0,0 +1,123 @@
1
+ import { useLayoutEffect } from "react";
2
+ import {
3
+ Easing,
4
+ useAnimatedReaction,
5
+ useDerivedValue,
6
+ useSharedValue,
7
+ withTiming,
8
+ type SharedValue,
9
+ } from "react-native-reanimated";
10
+ import { smoothstep } from "../math/squiggly";
11
+
12
+ /** Duration for chart reveal (loading/empty → live) and collapse (live → empty). */
13
+ export const CHART_REVEAL_DURATION_MS = 600;
14
+
15
+ /**
16
+ * Per-overlay reveal delay (fraction of morphT at which the overlay starts
17
+ * fading in). Earlier delays = earlier appearance.
18
+ */
19
+ const DELAY = {
20
+ fill: 0.05,
21
+ yAxis: 0.15,
22
+ line: 0.0,
23
+ dot: 0.4,
24
+ badge: 0.55,
25
+ } as const;
26
+
27
+ /**
28
+ * Maps a [0,1] morphT to a [0,1] opacity, starting at `delay` and reaching
29
+ * 1 at morphT=1. Uses smoothstep for a polished ease-in/out.
30
+ */
31
+ export function revealRamp(morphT: number, delay: number): number {
32
+ "worklet";
33
+ if (delay >= 1) return 0;
34
+ return smoothstep((morphT - delay) / (1 - delay));
35
+ }
36
+
37
+ export interface ChartRevealState {
38
+ /** 0 = loading/empty shell, 1 = fully live chart (matches web chartReveal). */
39
+ morphT: SharedValue<number>;
40
+ /** True while loading=true */
41
+ isLoading: SharedValue<boolean>;
42
+ /** True when not loading and fewer than two samples. */
43
+ isEmpty: SharedValue<boolean>;
44
+ yAxisOpacity: SharedValue<number>;
45
+ fillOpacity: SharedValue<number>;
46
+ lineOpacity: SharedValue<number>;
47
+ dotOpacity: SharedValue<number>;
48
+ badgeOpacity: SharedValue<number>;
49
+ }
50
+
51
+ /**
52
+ * Drives loading / empty / live visibility.
53
+ *
54
+ * Chart is fully revealed only when `!loading && hasData`. `morphT` animates
55
+ * between 0 and 1 when that condition changes. `isEmpty` is derived as
56
+ * `!loading && !hasData` for the empty overlay label.
57
+ */
58
+ export function useChartReveal(
59
+ loading: boolean,
60
+ hasData: SharedValue<boolean>,
61
+ ): ChartRevealState {
62
+ // Best-guess initial so the common case — a live chart that already has data
63
+ // and isn't loading — paints fully revealed with no flash. The reaction below
64
+ // snaps it to the exact state on its first run.
65
+ const morphT = useSharedValue(loading ? 0 : 1);
66
+ const isLoading = useSharedValue(loading);
67
+
68
+ useLayoutEffect(() => {
69
+ isLoading.set(loading);
70
+ }, [loading, isLoading]);
71
+
72
+ // The reaction reads presence/loading on the UI thread (a worklet, never during
73
+ // render — so no Reanimated "reading `value` during render" warning), seeds the
74
+ // exact reveal state on its first run, then animates on later changes.
75
+ useAnimatedReaction(
76
+ () => !isLoading.get() && hasData.get(),
77
+ (chartVisible, prev) => {
78
+ "worklet";
79
+ if (prev === null || prev === undefined) {
80
+ // First run: snap to the correct initial reveal state (no animation).
81
+ morphT.set(chartVisible ? 1 : 0);
82
+ return;
83
+ }
84
+ if (prev !== chartVisible) {
85
+ morphT.set(
86
+ withTiming(chartVisible ? 1 : 0, {
87
+ duration: CHART_REVEAL_DURATION_MS,
88
+ easing: Easing.out(Easing.cubic),
89
+ }),
90
+ );
91
+ }
92
+ },
93
+ [hasData],
94
+ );
95
+
96
+ const isEmpty = useDerivedValue(() => !isLoading.get() && !hasData.get());
97
+
98
+ const yAxisOpacity = useDerivedValue(() =>
99
+ revealRamp(morphT.get(), DELAY.yAxis),
100
+ );
101
+ const fillOpacity = useDerivedValue(() =>
102
+ revealRamp(morphT.get(), DELAY.fill),
103
+ );
104
+ const lineOpacity = useDerivedValue(() =>
105
+ revealRamp(morphT.get(), DELAY.line),
106
+ );
107
+ const dotOpacity = useDerivedValue(() => revealRamp(morphT.get(), DELAY.dot));
108
+ const badgeOpacity = useDerivedValue(() =>
109
+ revealRamp(morphT.get(), DELAY.badge),
110
+ );
111
+
112
+ return {
113
+ morphT,
114
+ isLoading,
115
+ // useDerivedValue is SharedValue-compatible for `.value` reads in Skia.
116
+ isEmpty: isEmpty as unknown as SharedValue<boolean>,
117
+ yAxisOpacity,
118
+ fillOpacity,
119
+ lineOpacity,
120
+ dotOpacity,
121
+ badgeOpacity,
122
+ };
123
+ }
@@ -0,0 +1,34 @@
1
+ import {
2
+ matchFont,
3
+ Skia,
4
+ useFont,
5
+ type SkFont,
6
+ } from "@shopify/react-native-skia";
7
+
8
+ import { resolveFontConfig } from "../core/resolveConfig";
9
+ import type { FontConfig } from "../types";
10
+
11
+ /**
12
+ * Resolves Skia text for charts: optional bundled `typeface` via `useFont`, otherwise
13
+ * `matchFont` with optional custom `fontManager`.
14
+ */
15
+ export function useChartSkiaFont(
16
+ fontProp: FontConfig | undefined,
17
+ defaultFamily: string,
18
+ defaultSize: number,
19
+ ): SkFont {
20
+ const resolved = resolveFontConfig(fontProp, defaultFamily, defaultSize);
21
+ const { fontFamily, fontSize, fontWeight } = resolved;
22
+ const typefaceSource = fontProp?.typeface ?? null;
23
+ const customFont = useFont(typefaceSource, fontSize);
24
+
25
+ const fallbackFont = matchFont(
26
+ { fontFamily, fontSize, fontWeight },
27
+ fontProp?.fontManager ?? Skia.FontMgr.System(),
28
+ );
29
+
30
+ if (typefaceSource != null) {
31
+ return customFont ?? fallbackFont;
32
+ }
33
+ return fallbackFont;
34
+ }