react-native-livechart 3.0.0 → 3.2.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 (66) hide show
  1. package/README.md +9 -3
  2. package/dist/components/AxisLabelOverlay.d.ts +29 -0
  3. package/dist/components/AxisLabelOverlay.d.ts.map +1 -0
  4. package/dist/components/CrosshairLine.d.ts +12 -1
  5. package/dist/components/CrosshairLine.d.ts.map +1 -1
  6. package/dist/components/CrosshairOverlay.d.ts +15 -1
  7. package/dist/components/CrosshairOverlay.d.ts.map +1 -1
  8. package/dist/components/DegenParticlesOverlay.d.ts +17 -0
  9. package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
  10. package/dist/components/LiveChart.d.ts.map +1 -1
  11. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  12. package/dist/components/SelectionDot.d.ts +32 -0
  13. package/dist/components/SelectionDot.d.ts.map +1 -0
  14. package/dist/core/resolveConfig.d.ts +46 -1
  15. package/dist/core/resolveConfig.d.ts.map +1 -1
  16. package/dist/core/useLiveChartEngine.d.ts +6 -0
  17. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  18. package/dist/draw/particleAtlas.d.ts +39 -0
  19. package/dist/draw/particleAtlas.d.ts.map +1 -0
  20. package/dist/hooks/crosshairShared.d.ts +10 -0
  21. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  22. package/dist/hooks/useCandlePaths.d.ts +3 -1
  23. package/dist/hooks/useCandlePaths.d.ts.map +1 -1
  24. package/dist/hooks/useCanvasLayout.d.ts +3 -2
  25. package/dist/hooks/useCanvasLayout.d.ts.map +1 -1
  26. package/dist/hooks/useChartColors.d.ts +5 -0
  27. package/dist/hooks/useChartColors.d.ts.map +1 -1
  28. package/dist/hooks/useChartReveal.d.ts +3 -1
  29. package/dist/hooks/useChartReveal.d.ts.map +1 -1
  30. package/dist/hooks/useCrosshair.d.ts +1 -1
  31. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  32. package/dist/hooks/useCrosshairSeries.d.ts +1 -1
  33. package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
  34. package/dist/hooks/useDegen.d.ts +3 -1
  35. package/dist/hooks/useDegen.d.ts.map +1 -1
  36. package/dist/hooks/useMarkers.d.ts +3 -1
  37. package/dist/hooks/useMarkers.d.ts.map +1 -1
  38. package/dist/hooks/useTradeStream.d.ts +3 -1
  39. package/dist/hooks/useTradeStream.d.ts.map +1 -1
  40. package/dist/index.d.ts +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/types.d.ts +102 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/src/components/AxisLabelOverlay.tsx +133 -0
  46. package/src/components/CrosshairLine.tsx +27 -0
  47. package/src/components/CrosshairOverlay.tsx +50 -18
  48. package/src/components/DegenParticlesOverlay.tsx +93 -109
  49. package/src/components/LiveChart.tsx +79 -13
  50. package/src/components/LiveChartSeries.tsx +40 -0
  51. package/src/components/SelectionDot.tsx +92 -0
  52. package/src/core/resolveConfig.ts +98 -0
  53. package/src/core/useLiveChartEngine.ts +78 -27
  54. package/src/draw/particleAtlas.ts +95 -0
  55. package/src/hooks/crosshairShared.ts +26 -0
  56. package/src/hooks/useCandlePaths.ts +3 -1
  57. package/src/hooks/useCanvasLayout.ts +5 -3
  58. package/src/hooks/useChartColors.ts +19 -0
  59. package/src/hooks/useChartReveal.ts +7 -4
  60. package/src/hooks/useCrosshair.ts +46 -7
  61. package/src/hooks/useCrosshairSeries.ts +47 -8
  62. package/src/hooks/useDegen.ts +6 -0
  63. package/src/hooks/useMarkers.ts +3 -0
  64. package/src/hooks/useTradeStream.ts +3 -0
  65. package/src/index.ts +4 -0
  66. package/src/types.ts +106 -0
@@ -16,6 +16,7 @@ import type { CandlePoint, LiveChartPalette, ScrubPoint } from "../types";
16
16
  import {
17
17
  computeCandleTooltipLayout,
18
18
  computeCrosshairOpacity,
19
+ computeScrubDotY,
19
20
  computeScrubTime,
20
21
  deriveCrosshairTooltipSingle,
21
22
  type CrosshairState,
@@ -57,9 +58,14 @@ export function useCrosshair(
57
58
  candleOpts?: CrosshairCandleOpts,
58
59
  /** Press-and-hold delay (ms) before scrubbing activates. 0 = immediate. */
59
60
  panGestureDelay = 0,
61
+ onGestureStart?: () => void,
62
+ onGestureEnd?: () => void,
60
63
  ): CrosshairState {
61
64
  const scrubX = useSharedValue(-1);
62
65
  const scrubActive = useSharedValue(false);
66
+ // Tracks whether the active scrub phase actually began, so a tap that never
67
+ // activates doesn't emit a spurious onGestureEnd.
68
+ const gestureStarted = useSharedValue(false);
63
69
 
64
70
  const scrubTime = useDerivedValue(() =>
65
71
  computeScrubTime(
@@ -112,6 +118,19 @@ export function useCrosshair(
112
118
  ),
113
119
  );
114
120
 
121
+ // Y pixel of the scrub intersection — used by the selection dot. -1 when
122
+ // there's no value to mark.
123
+ const scrubDotY = useDerivedValue(() =>
124
+ computeScrubDotY(
125
+ scrubValue.get(),
126
+ engine.displayMin.get(),
127
+ engine.displayMax.get(),
128
+ engine.canvasHeight.get(),
129
+ padding.top,
130
+ padding.bottom,
131
+ ),
132
+ );
133
+
115
134
  // Monospace advance width, measured once per render (not per scrub frame) so
116
135
  // the tooltip layout worklet can size text by character count instead of a
117
136
  // per-frame Skia measureText.
@@ -165,7 +184,19 @@ export function useCrosshair(
165
184
  onScrub?.(null);
166
185
  }
167
186
 
187
+ /* istanbul ignore next */
188
+ function handleGestureStart() {
189
+ onGestureStart?.();
190
+ }
191
+
192
+ /* istanbul ignore next */
193
+ function handleGestureEnd() {
194
+ onGestureEnd?.();
195
+ }
196
+
168
197
  const hasOnScrub = onScrub != null;
198
+ const hasOnGestureStart = onGestureStart != null;
199
+ const hasOnGestureEnd = onGestureEnd != null;
169
200
 
170
201
  useAnimatedReaction(
171
202
  () => {
@@ -178,13 +209,14 @@ export function useCrosshair(
178
209
  if (val === null || time < 0) return "__pending__";
179
210
  const chartW = engine.canvasWidth.get() - padding.left - padding.right;
180
211
  if (chartW <= 0) return "__pending__";
181
- const h = engine.canvasHeight.get();
182
- const chartH = h - padding.top - padding.bottom;
183
- const valRange = engine.displayMax.get() - engine.displayMin.get();
184
- const dotY =
185
- valRange === 0
186
- ? padding.top + chartH / 2
187
- : padding.top + ((engine.displayMax.get() - val) / valRange) * chartH;
212
+ const dotY = computeScrubDotY(
213
+ val,
214
+ engine.displayMin.get(),
215
+ engine.displayMax.get(),
216
+ engine.canvasHeight.get(),
217
+ padding.top,
218
+ padding.bottom,
219
+ );
188
220
  let candleJson: string | null = null;
189
221
  if (isCandleMode) {
190
222
  const c = scrubCandle.get();
@@ -229,6 +261,8 @@ export function useCrosshair(
229
261
  if (!enabled) return;
230
262
  scrubX.set(e.x);
231
263
  scrubActive.set(true);
264
+ gestureStarted.set(true);
265
+ if (hasOnGestureStart) scheduleOnRN(handleGestureStart);
232
266
  },
233
267
  )
234
268
  .onUpdate(
@@ -243,6 +277,10 @@ export function useCrosshair(
243
277
  "worklet";
244
278
  scrubActive.set(false);
245
279
  if (hasOnScrub) scheduleOnRN(handleScrubEnd);
280
+ if (gestureStarted.get()) {
281
+ gestureStarted.set(false);
282
+ if (hasOnGestureEnd) scheduleOnRN(handleGestureEnd);
283
+ }
246
284
  },
247
285
  );
248
286
 
@@ -258,6 +296,7 @@ export function useCrosshair(
258
296
  scrubValue,
259
297
  crosshairOpacity,
260
298
  tooltipLayout,
299
+ scrubDotY,
261
300
  gesture,
262
301
  };
263
302
  }
@@ -5,6 +5,7 @@ import {
5
5
  useDerivedValue,
6
6
  useSharedValue,
7
7
  } from "react-native-reanimated";
8
+ import { scheduleOnRN } from "react-native-worklets";
8
9
  import type { MultiEngineState } from "../core/useLiveChartEngine";
9
10
  import { type ChartPadding } from "../draw/line";
10
11
  import type { ScrubPointMulti } from "../types";
@@ -14,6 +15,7 @@ import {
14
15
  } from "./crosshairSeries";
15
16
  import {
16
17
  computeCrosshairOpacity,
18
+ computeScrubDotY,
17
19
  computeScrubTime,
18
20
  HIDDEN_TOOLTIP,
19
21
  type CrosshairState,
@@ -30,9 +32,14 @@ export function useCrosshairSeries(
30
32
  onScrub?: (point: ScrubPointMulti | null) => void,
31
33
  /** Press-and-hold delay (ms) before scrubbing activates. 0 = immediate. */
32
34
  panGestureDelay = 0,
35
+ onGestureStart?: () => void,
36
+ onGestureEnd?: () => void,
33
37
  ): CrosshairState {
34
38
  const scrubX = useSharedValue(-1);
35
39
  const scrubActive = useSharedValue(false);
40
+ // Tracks whether the active scrub phase actually began, so a tap that never
41
+ // activates doesn't emit a spurious onGestureEnd.
42
+ const gestureStarted = useSharedValue(false);
36
43
 
37
44
  const scrubTime = useDerivedValue(() =>
38
45
  computeScrubTime(
@@ -62,9 +69,34 @@ export function useCrosshairSeries(
62
69
  ),
63
70
  );
64
71
 
72
+ // Y pixel of the scrub intersection (leading-series value) — used by the
73
+ // selection dot. -1 when there's no value to mark.
74
+ const scrubDotY = useDerivedValue(() =>
75
+ computeScrubDotY(
76
+ scrubValue.get(),
77
+ engine.displayMin.get(),
78
+ engine.displayMax.get(),
79
+ engine.canvasHeight.get(),
80
+ padding.top,
81
+ padding.bottom,
82
+ ),
83
+ );
84
+
65
85
  const tooltipLayout = useSharedValue(HIDDEN_TOOLTIP);
66
86
 
87
+ /* istanbul ignore next */
88
+ function handleGestureStart() {
89
+ onGestureStart?.();
90
+ }
91
+
92
+ /* istanbul ignore next */
93
+ function handleGestureEnd() {
94
+ onGestureEnd?.();
95
+ }
96
+
67
97
  const hasOnScrub = onScrub != null;
98
+ const hasOnGestureStart = onGestureStart != null;
99
+ const hasOnGestureEnd = onGestureEnd != null;
68
100
 
69
101
  useAnimatedReaction(
70
102
  () => {
@@ -77,14 +109,14 @@ export function useCrosshairSeries(
77
109
  if (chartW <= 0) return "__pending__";
78
110
  const r = interpolateSeriesAtTime(engine.series.get(), time);
79
111
  if (r.primary === null) return "__pending__";
80
- const h = engine.canvasHeight.get();
81
- const chartH = h - padding.top - padding.bottom;
82
- const valRange = engine.displayMax.get() - engine.displayMin.get();
83
- const dotY =
84
- valRange === 0
85
- ? padding.top + chartH / 2
86
- : padding.top +
87
- ((engine.displayMax.get() - r.primary) / valRange) * chartH;
112
+ const dotY = computeScrubDotY(
113
+ r.primary,
114
+ engine.displayMin.get(),
115
+ engine.displayMax.get(),
116
+ engine.canvasHeight.get(),
117
+ padding.top,
118
+ padding.bottom,
119
+ );
88
120
  return JSON.stringify({
89
121
  time,
90
122
  x,
@@ -136,6 +168,8 @@ export function useCrosshairSeries(
136
168
  if (!enabled) return;
137
169
  scrubX.set(e.x);
138
170
  scrubActive.set(true);
171
+ gestureStarted.set(true);
172
+ if (hasOnGestureStart) scheduleOnRN(handleGestureStart);
139
173
  },
140
174
  )
141
175
  .onUpdate(
@@ -149,6 +183,10 @@ export function useCrosshairSeries(
149
183
  /* istanbul ignore next */ () => {
150
184
  "worklet";
151
185
  scrubActive.set(false);
186
+ if (gestureStarted.get()) {
187
+ gestureStarted.set(false);
188
+ if (hasOnGestureEnd) scheduleOnRN(handleGestureEnd);
189
+ }
152
190
  },
153
191
  );
154
192
 
@@ -164,6 +202,7 @@ export function useCrosshairSeries(
164
202
  scrubValue,
165
203
  crosshairOpacity,
166
204
  tooltipLayout,
205
+ scrubDotY,
167
206
  gesture,
168
207
  };
169
208
  }
@@ -20,6 +20,8 @@ export function useDegen(
20
20
  momentumSV: SharedValue<Momentum>,
21
21
  cfg: ResolvedDegenConfig | null,
22
22
  onShake?: (payload: DegenShakePayload) => void,
23
+ /** Static charts run no loops: register the frame callback without starting it. */
24
+ isStatic = false,
23
25
  ): {
24
26
  pack: SharedValue<Float64Array<ArrayBuffer>>;
25
27
  packRevision: SharedValue<number>;
@@ -248,6 +250,10 @@ export function useDegen(
248
250
  }
249
251
  prevActiveCount.set(activeCount);
250
252
  },
253
+ // Static charts register the callback but never start it, so no degen frame
254
+ // loop runs. Live charts keep the default (autostart) loop unchanged. The
255
+ // controller also forces `cfg` to null in static, so degen is doubly off.
256
+ !isStatic,
251
257
  );
252
258
 
253
259
  const shakeTransform = useDerivedValue(() => {
@@ -30,6 +30,8 @@ export function useMarkers(
30
30
  onMarkerHover?: (event: MarkerHoverEvent | null) => void,
31
31
  seriesSV?: SharedValue<SeriesConfig[]>,
32
32
  lineData?: SharedValue<LiveChartPoint[]>,
33
+ /** Static charts run no loops: register without starting. Default `true`. */
34
+ autostart = true,
33
35
  ): { projected: SharedValue<ProjectedMarker[]>; tapGesture: ReturnType<typeof Gesture.Tap> } {
34
36
  const projected = useSharedValue<ProjectedMarker[]>([]);
35
37
  const cacheRef = useRef<{
@@ -73,6 +75,7 @@ export function useMarkers(
73
75
  });
74
76
  projected.set(buf);
75
77
  },
78
+ autostart,
76
79
  );
77
80
 
78
81
  const tapGesture = Gesture.Tap().onEnd(
@@ -25,6 +25,8 @@ export function useTradeStream(
25
25
  tradeStream: SharedValue<TradeEvent[]>,
26
26
  padding: ChartPadding,
27
27
  active: boolean,
28
+ /** Static charts run no loops: register without starting. Default `true`. */
29
+ autostart = true,
28
30
  ): SharedValue<TradeMarker[]> {
29
31
  const markers = useSharedValue<TradeMarker[]>([]);
30
32
  const state = useSharedValue<TradeStreamState>(createTradeStreamState());
@@ -58,6 +60,7 @@ export function useTradeStream(
58
60
  const chartH = chartBottom - chartTop;
59
61
  markers.set(projectLabels(s, chartTop, chartH));
60
62
  },
63
+ autostart,
61
64
  );
62
65
 
63
66
  return markers;
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export { useTradeStream } from "./hooks/useTradeStream";
28
28
  // ── Types ────────────────────────────────────────────────────────────────────
29
29
 
30
30
  export type {
31
+ AxisLabelConfig,
31
32
  BadgeConfig,
32
33
  BadgeMetrics,
33
34
  BadgeVariant,
@@ -67,6 +68,9 @@ export type {
67
68
  ScrubPointCore,
68
69
  ScrubPointMulti,
69
70
  ScrubSeriesValue,
71
+ SelectionDotConfig,
72
+ SelectionDotProps,
73
+ SelectionDotRingConfig,
70
74
  SeriesConfig,
71
75
  ThemeMode,
72
76
  TradeEvent,
package/src/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ComponentType, ReactElement } from "react";
1
2
  import type { ViewStyle } from "react-native";
2
3
  import type { SharedValue } from "react-native-reanimated";
3
4
 
@@ -142,6 +143,11 @@ export interface LineConfig {
142
143
  width?: number;
143
144
  /** Line color override. Defaults to palette-derived accent. */
144
145
  color?: string;
146
+ /**
147
+ * Two or more CSS color strings → horizontal gradient along the stroke
148
+ * (left → right). Takes precedence over `color` when set.
149
+ */
150
+ colors?: string[];
145
151
  }
146
152
 
147
153
  /** Area fill gradient beneath the chart line. */
@@ -150,6 +156,11 @@ export interface GradientConfig {
150
156
  topOpacity?: number;
151
157
  /** Opacity at the bottom of the gradient. Default `0`. */
152
158
  bottomOpacity?: number;
159
+ /** Explicit gradient color stops (top → bottom) for the area fill. Overrides
160
+ * topOpacity/bottomOpacity when provided. Must have at least 2 entries. */
161
+ colors?: string[];
162
+ /** Optional stop positions (0..1, ascending) matching `colors` length. */
163
+ positions?: number[];
153
164
  }
154
165
 
155
166
  /** Value badge pill configuration. */
@@ -170,6 +181,24 @@ export interface YAxisConfig {
170
181
  minGap?: number;
171
182
  }
172
183
 
184
+ /**
185
+ * Axis edge label — the value floated at the plot's top or bottom edge
186
+ * (Robinhood-style high/low). Pass `topLabel`/`bottomLabel` as `true` for the
187
+ * batteries-included value label (the chart's current top/bottom Y-axis bound,
188
+ * updated each frame), an object to configure it, or `{ render }` to float a
189
+ * fully custom element instead.
190
+ */
191
+ export interface AxisLabelConfig {
192
+ /** Formatter for the built-in value. Defaults to the chart's `formatValue`. */
193
+ format?: (v: number) => string;
194
+ /** Text color. Defaults to a muted label color (`palette.gridLabel`). */
195
+ color?: string;
196
+ /** Horizontal alignment within the plot width. Default `"right"`. */
197
+ position?: "left" | "right";
198
+ /** Full custom element, floated at the edge. Overrides the built-in value label. */
199
+ render?: () => ReactElement | null;
200
+ }
201
+
173
202
  /** X-axis (time) configuration. */
174
203
  export interface XAxisConfig {
175
204
  /** Minimum pixel gap between time labels. Default `60`. */
@@ -213,6 +242,55 @@ export interface ScrubConfig {
213
242
  panGestureDelay?: number;
214
243
  }
215
244
 
245
+ /**
246
+ * Props passed to a custom {@link SelectionDotConfig.component} — the dot drawn
247
+ * at the scrub intersection while scrubbing. All positional inputs are
248
+ * SharedValues so the dot animates on the UI thread without re-renders.
249
+ */
250
+ export interface SelectionDotProps {
251
+ /** Scrub X in canvas px. */
252
+ x: SharedValue<number>;
253
+ /** Scrub Y in canvas px (the line/value intersection). */
254
+ y: SharedValue<number>;
255
+ /** Whether scrubbing is active. */
256
+ active: SharedValue<boolean>;
257
+ /** Crosshair fade opacity (0..1), already ramped. */
258
+ opacity: SharedValue<number>;
259
+ /** Resolved dot color (accent/series color). */
260
+ color: string;
261
+ /** Suggested dot radius in px. */
262
+ size: number;
263
+ }
264
+
265
+ /** Outer ring drawn around the built-in selection dot (the subtle halo). */
266
+ export interface SelectionDotRingConfig {
267
+ /** Ring color. Defaults to the dot color. */
268
+ color?: string;
269
+ /** Ring thickness in pixels — how far the halo extends past the dot. Default `2`. */
270
+ width?: number;
271
+ }
272
+
273
+ /**
274
+ * Selection-dot styling — the dot drawn at the scrub intersection while
275
+ * scrubbing. Pass `selectionDot={false}` to hide it, an object to configure the
276
+ * built-in dot, or `{ component }` to supply a fully custom Skia dot.
277
+ */
278
+ export interface SelectionDotConfig {
279
+ /** Dot radius in px. Default `4`. */
280
+ size?: number;
281
+ /** Dot color. Defaults to the line / leading-series color. */
282
+ color?: string;
283
+ /**
284
+ * Outer ring around the dot. `true`/config = on, `false` = off. Default on.
285
+ */
286
+ ring?: boolean | SelectionDotRingConfig;
287
+ /**
288
+ * Fully custom Skia dot — receives the scrub position as SharedValues. When
289
+ * set, the `size` / `color` / `ring` knobs are ignored.
290
+ */
291
+ component?: ComponentType<SelectionDotProps>;
292
+ }
293
+
216
294
  /** Left-edge fade — soft erase so the chart blends into the left gutter (web liveline parity). */
217
295
  export interface LeftEdgeFadeConfig {
218
296
  /**
@@ -695,6 +773,20 @@ export interface LiveChartCoreProps {
695
773
  yAxis?: boolean | YAxisConfig;
696
774
  /** X-axis time labels. `true` = defaults, `false` = hidden, or pass `XAxisConfig`. Default `true`. */
697
775
  xAxis?: boolean | XAxisConfig;
776
+ /**
777
+ * Label floated at the top edge of the plot area. `true` = the built-in value
778
+ * label showing the chart's current TOP Y-axis bound (updated each frame),
779
+ * `false`/omitted = none, or pass `AxisLabelConfig` to configure it (or supply
780
+ * a custom `render`). Default off.
781
+ */
782
+ topLabel?: boolean | AxisLabelConfig;
783
+ /**
784
+ * Label floated at the bottom edge of the plot area. `true` = the built-in
785
+ * value label showing the chart's current BOTTOM Y-axis bound (updated each
786
+ * frame), `false`/omitted = none, or pass `AxisLabelConfig` (or a custom
787
+ * `render`). Default off.
788
+ */
789
+ bottomLabel?: boolean | AxisLabelConfig;
698
790
  /** Reference lines / bands drawn into the chart. Supports all three `ReferenceLine` forms. */
699
791
  referenceLines?: ReferenceLine[];
700
792
  /** Per-instance grid-line styling. Pass an object to override color / width / dash / opacity. */
@@ -737,6 +829,16 @@ export interface LiveChartCoreProps {
737
829
  accessibilityRole?: "image" | "none" | "adjustable" | "summary";
738
830
  /** Crosshair scrubbing on hover/drag. `true` = defaults, `false` = disabled, or pass `ScrubConfig`. Default `true`. */
739
831
  scrub?: boolean | ScrubConfig;
832
+ /**
833
+ * Selection dot drawn at the scrub intersection while scrubbing. `true`/omitted
834
+ * = built-in dot, `false` = hidden, or pass `SelectionDotConfig` (`size`,
835
+ * `color`, `ring`, or a custom `component`). Default `true`.
836
+ */
837
+ selectionDot?: boolean | SelectionDotConfig;
838
+ /** Called once when the user starts scrubbing/panning the chart. */
839
+ onGestureStart?: () => void;
840
+ /** Called once when the user stops scrubbing/panning the chart. */
841
+ onGestureEnd?: () => void;
740
842
  /**
741
843
  * Fade out chart content on the left (destination-out style). `true` = defaults, `false` = off,
742
844
  * or pass `LeftEdgeFadeConfig`. Default `true`.
@@ -796,6 +898,10 @@ export interface LiveChartProps extends LiveChartCoreProps {
796
898
  data: SharedValue<LiveChartPoint[]>;
797
899
  /** Latest live value for smooth interpolation between data updates. */
798
900
  value: SharedValue<number>;
901
+ /** Render once with no per-frame animation loop — for many small charts (sparklines)
902
+ * in a list. Pulse/scrub/degen and the entry animation are disabled. Frame the data
903
+ * with `timeWindow` + `nowOverride` (see the historical-data-fill pattern). */
904
+ static?: boolean;
799
905
  /** Called when the user scrubs the crosshair. `null` when scrub ends. */
800
906
  onScrub?: (point: ScrubPoint | null) => void;
801
907
  /**