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,117 @@
1
+ import {
2
+ Group,
3
+ Text as SkiaText,
4
+ type SkFont,
5
+ } from "@shopify/react-native-skia";
6
+ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
7
+ import type { ChartPadding } from "../draw/line";
8
+ import type { TradeMarker } from "../draw/trade";
9
+ import { lerpColor } from "../math/color";
10
+ import type { LiveChartPalette } from "../types";
11
+
12
+ const MAX_TRADE_LABELS = 20;
13
+
14
+ // Stable per-slot keys for the fixed-size, positional label pool: slot `i` always
15
+ // renders ring-buffer index `i` and the tape never reorders, so each label keeps a
16
+ // permanent identity. Keying by these ids (instead of the bare array index) makes
17
+ // that explicit and avoids remounting labels as the tape scrolls.
18
+ const TAPE_SLOT_KEYS = Array.from(
19
+ { length: MAX_TRADE_LABELS },
20
+ (_, i) => `trade-label-${i}`,
21
+ );
22
+
23
+ const GREEN: [number, number, number] = [34, 197, 94];
24
+ const RED: [number, number, number] = [239, 68, 68];
25
+
26
+ function TapeLabel({
27
+ index,
28
+ markers,
29
+ bgRgb,
30
+ labelX,
31
+ font,
32
+ groupOpacity,
33
+ }: {
34
+ index: number;
35
+ markers: SharedValue<TradeMarker[]>;
36
+ bgRgb: [number, number, number];
37
+ labelX: number;
38
+ font: SkFont;
39
+ groupOpacity: SharedValue<number>;
40
+ }) {
41
+ /* istanbul ignore next -- worklet */
42
+ const y = useDerivedValue(() => markers.value[index]?.y ?? -200);
43
+ /* istanbul ignore next -- worklet */
44
+ const text = useDerivedValue(() => markers.value[index]?.label ?? " ");
45
+
46
+ /* istanbul ignore next -- worklet */
47
+ const fillColor = useDerivedValue(() => {
48
+ const m = markers.value[index];
49
+ if (!m) return "transparent";
50
+ const base = m.green ? GREEN : RED;
51
+ return lerpColor(base, bgRgb, 1 - m.alpha);
52
+ });
53
+
54
+ /* istanbul ignore next -- worklet */
55
+ const outlineColor = useDerivedValue(() => {
56
+ const m = markers.value[index];
57
+ if (!m) return "transparent";
58
+ return `rgb(${bgRgb[0]},${bgRgb[1]},${bgRgb[2]})`;
59
+ });
60
+
61
+ /* istanbul ignore next -- worklet */
62
+ const opacity = useDerivedValue(() => {
63
+ const m = markers.value[index];
64
+ if (!m) return 0;
65
+ return groupOpacity.value;
66
+ });
67
+
68
+ return (
69
+ <Group opacity={opacity}>
70
+ <SkiaText
71
+ x={labelX}
72
+ y={y}
73
+ text={text}
74
+ font={font}
75
+ color={outlineColor}
76
+ style="stroke"
77
+ strokeWidth={0.5}
78
+ opacity={0.75}
79
+ />
80
+ <SkiaText x={labelX} y={y} text={text} font={font} color={fillColor} />
81
+ </Group>
82
+ );
83
+ }
84
+
85
+ export function TradeStreamOverlay({
86
+ markers,
87
+ palette,
88
+ padding,
89
+ font,
90
+ opacity,
91
+ labelOffsetX = 8,
92
+ }: {
93
+ markers: SharedValue<TradeMarker[]>;
94
+ palette: LiveChartPalette;
95
+ padding: ChartPadding;
96
+ font: SkFont;
97
+ opacity: SharedValue<number>;
98
+ labelOffsetX?: number;
99
+ }) {
100
+ const labelX = padding.left + labelOffsetX;
101
+ // Fixed-size persistent slot pool (MAX_TRADE_LABELS). Each <TapeLabel> renders
102
+ // whatever trade currently occupies slot `i` (read from `markers` by index);
103
+ // the list never reorders or filters, so each slot keys by its stable per-slot
104
+ // id (see TAPE_SLOT_KEYS) rather than the bare array index.
105
+ const slots = TAPE_SLOT_KEYS.map((slotKey, i) => (
106
+ <TapeLabel
107
+ key={slotKey}
108
+ index={i}
109
+ markers={markers}
110
+ bgRgb={palette.bgRgb}
111
+ labelX={labelX}
112
+ font={font}
113
+ groupOpacity={opacity}
114
+ />
115
+ ));
116
+ return <Group>{slots}</Group>;
117
+ }
@@ -0,0 +1,63 @@
1
+ import {
2
+ DashPathEffect,
3
+ Path,
4
+ Skia,
5
+ type SkPath,
6
+ } from "@shopify/react-native-skia";
7
+ import { useRef } from "react";
8
+ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
9
+
10
+ import type { ChartPadding } from "../draw/line";
11
+ import type { ChartEngineLayout } from "../core/useLiveChartEngine";
12
+
13
+ /**
14
+ * A dashed horizontal line that tracks the live display value — sitting at
15
+ * the same Y as the badge and live dot. Rendered behind the chart line.
16
+ */
17
+ export function ValueLineOverlay({
18
+ dotY,
19
+ engine,
20
+ padding,
21
+ strokeWidth,
22
+ intervals,
23
+ color,
24
+ }: {
25
+ dotY: SharedValue<number>;
26
+ engine: ChartEngineLayout;
27
+ padding: ChartPadding;
28
+ strokeWidth: number;
29
+ intervals: [number, number];
30
+ color: string;
31
+ }) {
32
+ // Ping-pong persistent paths — avoid allocating a JSI-backed SkPath per frame.
33
+ const cacheRef = useRef<{
34
+ a: SkPath;
35
+ b: SkPath;
36
+ tick: boolean;
37
+ } | null>(null);
38
+ if (cacheRef.current === null) {
39
+ cacheRef.current = {
40
+ a: Skia.Path.Make(),
41
+ b: Skia.Path.Make(),
42
+ tick: false,
43
+ };
44
+ }
45
+
46
+ const path = useDerivedValue(() => {
47
+ const cache = cacheRef.current!;
48
+ cache.tick = !cache.tick;
49
+ const p = cache.tick ? cache.a : cache.b;
50
+ p.reset();
51
+ const y = dotY.get();
52
+ if (y < 0) return p;
53
+ p.moveTo(padding.left, y);
54
+ p.lineTo(engine.canvasWidth.get() - padding.right, y);
55
+ return p;
56
+ });
57
+
58
+ return (
59
+ <Path path={path} style="stroke" strokeWidth={strokeWidth} color={color}>
60
+ <DashPathEffect intervals={intervals} />
61
+ </Path>
62
+ );
63
+ }
@@ -0,0 +1,43 @@
1
+ import { Text as SkiaText, type SkFont } from "@shopify/react-native-skia";
2
+ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
3
+ import type { ChartEngineWithLiveValue } from "../core/useLiveChartEngine";
4
+ import type { ChartPadding } from "../draw/line";
5
+ import type { LiveChartPalette, Momentum } from "../types";
6
+
7
+ /**
8
+ * Large live-value text rendered in the top-left of the plot. Tracks the
9
+ * engine's smoothed display value on the UI thread; optionally tinted by
10
+ * momentum (green up / red down / accent flat).
11
+ */
12
+ export function ValueTextOverlay({
13
+ engine,
14
+ padding,
15
+ palette,
16
+ font,
17
+ formatValue,
18
+ momentum,
19
+ momentumColor,
20
+ }: {
21
+ engine: ChartEngineWithLiveValue;
22
+ padding: ChartPadding;
23
+ palette: LiveChartPalette;
24
+ font: SkFont;
25
+ formatValue: (v: number) => string;
26
+ momentum?: SharedValue<Momentum>;
27
+ momentumColor: boolean;
28
+ }) {
29
+ const text = useDerivedValue(() => formatValue(engine.displayValue.value));
30
+
31
+ const color = useDerivedValue(() => {
32
+ if (!momentumColor || !momentum) return palette.line;
33
+ const m = momentum.value;
34
+ if (m === "up") return palette.dotUp;
35
+ if (m === "down") return palette.dotDown;
36
+ return palette.dotFlat;
37
+ });
38
+
39
+ const x = padding.left + 6;
40
+ const y = padding.top + font.getSize();
41
+
42
+ return <SkiaText x={x} y={y} text={text} font={font} color={color} />;
43
+ }
@@ -0,0 +1,109 @@
1
+ import {
2
+ Group,
3
+ Path,
4
+ Skia,
5
+ type SkFont,
6
+ type SkPath,
7
+ } from "@shopify/react-native-skia";
8
+ import { useRef } from "react";
9
+ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
10
+ import type { ChartEngineLayout } from "../core/useLiveChartEngine";
11
+ import type { ChartPadding } from "../draw/line";
12
+ import type { XAxisEntry } from "../hooks/useXAxis";
13
+ import { measureFontTextWidth } from "../lib/measureFontTextWidth";
14
+ import type { LiveChartPalette } from "../types";
15
+ import { AnimatedLabel } from "./AnimatedLabel";
16
+
17
+ const MAX_X_LABELS = 10;
18
+ const TICK_HEIGHT = 5;
19
+ const LABEL_OFFSET_Y = 19;
20
+
21
+ export function XAxisOverlay({
22
+ entries,
23
+ engine,
24
+ padding,
25
+ palette,
26
+ font,
27
+ }: {
28
+ entries: SharedValue<XAxisEntry[]>;
29
+ engine: ChartEngineLayout;
30
+ padding: ChartPadding;
31
+ palette: LiveChartPalette;
32
+ font: SkFont;
33
+ }) {
34
+ const axisCacheRef = useRef<{
35
+ a: SkPath;
36
+ b: SkPath;
37
+ tick: boolean;
38
+ } | null>(null);
39
+ if (axisCacheRef.current === null) {
40
+ axisCacheRef.current = {
41
+ a: Skia.Path.Make(),
42
+ b: Skia.Path.Make(),
43
+ tick: false,
44
+ };
45
+ }
46
+
47
+ const axisPath = useDerivedValue(() => {
48
+ "worklet";
49
+ const axisCache = axisCacheRef.current!;
50
+ axisCache.tick = !axisCache.tick;
51
+ const path = axisCache.tick ? axisCache.a : axisCache.b;
52
+ path.reset();
53
+ const w = engine.canvasWidth.get();
54
+ const h = engine.canvasHeight.get();
55
+ const lineY = h - padding.bottom;
56
+
57
+ // Bottom axis line
58
+ path.moveTo(padding.left, lineY);
59
+ path.lineTo(w - padding.right, lineY);
60
+
61
+ // Tick marks
62
+ const items = entries.get();
63
+ for (let i = 0; i < items.length; i++) {
64
+ path.moveTo(items[i].x, lineY);
65
+ path.lineTo(items[i].x, lineY + TICK_HEIGHT);
66
+ }
67
+ return path;
68
+ });
69
+
70
+ // Transform XAxisEntry[] into { x, y, label, alpha } for AnimatedLabel
71
+ const labelEntries = useDerivedValue(() => {
72
+ "worklet";
73
+ const items = entries.get();
74
+ const h = engine.canvasHeight.get();
75
+ const y = h - padding.bottom + LABEL_OFFSET_Y;
76
+ const n = items.length;
77
+ const out: { x: number; y: number; label: string; alpha: number }[] = [];
78
+ for (let i = 0; i < n; i++) {
79
+ const e = items[i];
80
+ out.push({
81
+ x: e.x - measureFontTextWidth(font, e.label) / 2,
82
+ y,
83
+ label: e.label,
84
+ alpha: e.alpha,
85
+ });
86
+ }
87
+ return out;
88
+ });
89
+
90
+ return (
91
+ <Group>
92
+ <Path
93
+ path={axisPath}
94
+ style="stroke"
95
+ strokeWidth={1}
96
+ color={palette.gridLine}
97
+ />
98
+ {Array.from({ length: MAX_X_LABELS }, (_, i) => (
99
+ <AnimatedLabel
100
+ key={i}
101
+ entries={labelEntries}
102
+ index={i}
103
+ font={font}
104
+ color={palette.timeLabel}
105
+ />
106
+ ))}
107
+ </Group>
108
+ );
109
+ }
@@ -0,0 +1,135 @@
1
+ import {
2
+ DashPathEffect,
3
+ Group,
4
+ Path,
5
+ Skia,
6
+ type SkFont,
7
+ type SkPath,
8
+ } from "@shopify/react-native-skia";
9
+ import { useRef } from "react";
10
+ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
11
+ import { BADGE_DOT_GAP } from "../constants";
12
+ import type { ResolvedGridStyleConfig } from "../core/resolveConfig";
13
+ import type { YAxisEntry } from "../draw/grid";
14
+ import {
15
+ badgeTailAndCap,
16
+ gutterCenteredTextLeftX,
17
+ gutterRightAlignedTextLeftX,
18
+ pillTextLeftX,
19
+ type ChartPadding,
20
+ } from "../draw/line";
21
+ import { measureFontTextWidth } from "../lib/measureFontTextWidth";
22
+ import type { LiveChartPalette } from "../types";
23
+ import type { ChartEngineLayout } from "../core/useLiveChartEngine";
24
+ import { AnimatedLabel } from "./AnimatedLabel";
25
+
26
+ const MAX_Y_LABELS = 15;
27
+
28
+ export function YAxisOverlay({
29
+ entries,
30
+ engine,
31
+ padding,
32
+ palette,
33
+ font,
34
+ badge = false,
35
+ badgeTail = true,
36
+ seriesLabelInset = 0,
37
+ gridStyle,
38
+ }: {
39
+ entries: SharedValue<YAxisEntry[]>;
40
+ engine: ChartEngineLayout;
41
+ padding: ChartPadding;
42
+ palette: LiveChartPalette;
43
+ font: SkFont;
44
+ /** When true, use the asymmetric pill centering formula so labels align with badge text. */
45
+ badge?: boolean;
46
+ /** Whether the badge tail spike is shown; affects the left inset used for label alignment. */
47
+ badgeTail?: boolean;
48
+ /** When > 0, series labels occupy the left portion of the gutter; Y-axis labels right-align. */
49
+ seriesLabelInset?: number;
50
+ /** Grid-line styling overrides. Omit for the legacy solid 1px line. */
51
+ gridStyle?: ResolvedGridStyleConfig;
52
+ }) {
53
+ const gridColor = gridStyle?.color ?? palette.gridLine;
54
+ const gridWidth = gridStyle?.strokeWidth ?? 1;
55
+ const gridIntervals = gridStyle?.intervals ?? [];
56
+ const gridOpacity = gridStyle?.opacity ?? 1;
57
+ const gridCacheRef = useRef<{
58
+ a: SkPath;
59
+ b: SkPath;
60
+ tick: boolean;
61
+ } | null>(null);
62
+ if (gridCacheRef.current === null) {
63
+ gridCacheRef.current = {
64
+ a: Skia.Path.Make(),
65
+ b: Skia.Path.Make(),
66
+ tick: false,
67
+ };
68
+ }
69
+
70
+ const gridLinesPath = useDerivedValue(() => {
71
+ const gridCache = gridCacheRef.current!;
72
+ gridCache.tick = !gridCache.tick;
73
+ const path = gridCache.tick ? gridCache.a : gridCache.b;
74
+ path.reset();
75
+ const items = entries.get();
76
+ const w = engine.canvasWidth.get();
77
+ for (let i = 0; i < items.length; i++) {
78
+ path.moveTo(padding.left, items[i].y);
79
+ path.lineTo(w - padding.right, items[i].y);
80
+ }
81
+ return path;
82
+ });
83
+
84
+ const leftInset = BADGE_DOT_GAP + badgeTailAndCap(font.getSize(), badgeTail);
85
+
86
+ const labelEntries = useDerivedValue(() => {
87
+ const items = entries.get();
88
+ const w = engine.canvasWidth.get();
89
+ const fm = font.getMetrics();
90
+ const baselineOffset = (fm.ascent + fm.descent) / 2;
91
+ const result: { x: number; y: number; label: string; alpha: number }[] = [];
92
+ for (let i = 0; i < items.length; i++) {
93
+ const e = items[i];
94
+ const textW = measureFontTextWidth(font, e.label);
95
+ const x = badge
96
+ ? pillTextLeftX(w, padding.right, leftInset, textW)
97
+ : seriesLabelInset > 0
98
+ ? gutterRightAlignedTextLeftX(w, textW)
99
+ : gutterCenteredTextLeftX(w, padding.right, textW);
100
+ result.push({
101
+ x,
102
+ y: e.y - baselineOffset,
103
+ label: e.label,
104
+ alpha: e.alpha,
105
+ });
106
+ }
107
+ return result;
108
+ });
109
+
110
+ return (
111
+ <Group>
112
+ <Group opacity={gridOpacity}>
113
+ <Path
114
+ path={gridLinesPath}
115
+ style="stroke"
116
+ strokeWidth={gridWidth}
117
+ color={gridColor}
118
+ >
119
+ {gridIntervals.length > 0 && (
120
+ <DashPathEffect intervals={gridIntervals} />
121
+ )}
122
+ </Path>
123
+ </Group>
124
+ {Array.from({ length: MAX_Y_LABELS }, (_, i) => (
125
+ <AnimatedLabel
126
+ key={i}
127
+ entries={labelEntries}
128
+ index={i}
129
+ font={font}
130
+ color={palette.gridLabel}
131
+ />
132
+ ))}
133
+ </Group>
134
+ );
135
+ }
@@ -0,0 +1,13 @@
1
+ import type { SeriesConfig } from "../types";
2
+
3
+ /** Exported for tests — mirrors chip labels without subscribing to every data tick. */
4
+ export function seriesMetaSig(s: SeriesConfig[]): string {
5
+ "worklet";
6
+ let out = "";
7
+ for (let i = 0; i < s.length; i++) {
8
+ if (i > 0) out += "\x1e";
9
+ const x = s[i];
10
+ out += `${x.id}\x1f${x.label ?? ""}\x1f${x.color ?? ""}\x1f${x.visible === false ? 0 : 1}`;
11
+ }
12
+ return out;
13
+ }
@@ -0,0 +1,44 @@
1
+ /** Milliseconds per frame at 60 fps — baseline for frame-rate-independent lerp. */
2
+ export const MS_PER_FRAME_60FPS = 16.67;
3
+
4
+ /** Extra speed applied to catch up when the live dot lags behind the target. */
5
+ export const ADAPTIVE_SPEED_BOOST = 0.12;
6
+
7
+ /** Horizontal padding inside the badge pill, on each side of the label. */
8
+ export const BADGE_PILL_PAD_X = 10;
9
+ /** Vertical padding above and below the label inside the badge pill. */
10
+ export const BADGE_PILL_PAD_Y = 3;
11
+ /** Length of the badge tail (the pointed spike toward the dot). */
12
+ export const BADGE_TAIL_LEN = 5;
13
+ /** Gap between the pill's right edge and the canvas right edge. */
14
+ export const BADGE_MARGIN_RIGHT = 4;
15
+ /** Gap between the live dot and the badge tail tip. */
16
+ export const BADGE_DOT_GAP = 12;
17
+
18
+ /** Maximum simultaneous series rendered (paths/dots slots). */
19
+ export const MAX_MULTI_SERIES = 12;
20
+
21
+ /** Default width (px) of the left-edge fade band */
22
+ export const FADE_EDGE_WIDTH = 40;
23
+
24
+ /** Empty-state label opacity. */
25
+ export const EMPTY_STATE_LABEL_ALPHA = 0.35;
26
+ /** Half-padding (px) around empty text for the squiggle “gap” erase band. */
27
+ export const EMPTY_TEXT_GAP_PAD = 20;
28
+ /** Horizontal fade width (px) on each side of the empty-text gap. */
29
+ export const EMPTY_GAP_FADE_WIDTH = 30;
30
+
31
+ /**
32
+ * Floats per degen particle slot: `x, y, vx, vy, t0, active, size, colorIndex`.
33
+ * `colorIndex` selects which entry of the renderer's color list a particle uses
34
+ * (multi-series sets it per series; single-series cycles it per particle).
35
+ * Layout is fixed; not on `DegenOptions` because changing it needs matching renderer changes.
36
+ */
37
+ export const DEGEN_STRIDE = 8;
38
+
39
+ /**
40
+ * Default chart accent color — the full light/dark palette is derived from this
41
+ * when a consumer omits `accentColor`. Single source of truth for the brand
42
+ * default; change it here to re-brand the default everywhere.
43
+ */
44
+ export const DEFAULT_ACCENT_COLOR = "#3323E6";