react-native-livechart 3.9.0 → 3.11.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 (49) hide show
  1. package/dist/components/AreaDotsOverlay.d.ts +24 -0
  2. package/dist/components/AreaDotsOverlay.d.ts.map +1 -0
  3. package/dist/components/AxisLabelOverlay.d.ts +19 -3
  4. package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
  5. package/dist/components/CrosshairOverlay.d.ts +5 -1
  6. package/dist/components/CrosshairOverlay.d.ts.map +1 -1
  7. package/dist/components/CustomTooltipOverlay.d.ts +8 -2
  8. package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
  9. package/dist/components/ExtremaConnectorOverlay.d.ts +32 -0
  10. package/dist/components/ExtremaConnectorOverlay.d.ts.map +1 -0
  11. package/dist/components/LiveChart.d.ts.map +1 -1
  12. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  13. package/dist/core/liveChartEngineTick.d.ts +11 -0
  14. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  15. package/dist/core/liveChartSeriesEngineTick.d.ts +10 -0
  16. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
  17. package/dist/core/resolveConfig.d.ts +45 -2
  18. package/dist/core/resolveConfig.d.ts.map +1 -1
  19. package/dist/core/useLiveChartEngine.d.ts +18 -2
  20. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  21. package/dist/core/useLiveChartSeriesEngine.d.ts +4 -0
  22. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  23. package/dist/hooks/crosshairShared.d.ts +12 -0
  24. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  25. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/theme.d.ts +7 -0
  29. package/dist/theme.d.ts.map +1 -1
  30. package/dist/types.d.ts +103 -8
  31. package/dist/types.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/AreaDotsOverlay.tsx +62 -0
  34. package/src/components/AxisLabelOverlay.tsx +399 -46
  35. package/src/components/CrosshairOverlay.tsx +15 -5
  36. package/src/components/CustomTooltipOverlay.tsx +30 -5
  37. package/src/components/ExtremaConnectorOverlay.tsx +193 -0
  38. package/src/components/LiveChart.tsx +78 -8
  39. package/src/components/LiveChartSeries.tsx +18 -0
  40. package/src/core/liveChartEngineTick.ts +51 -6
  41. package/src/core/liveChartSeriesEngineTick.ts +34 -0
  42. package/src/core/resolveConfig.ts +100 -2
  43. package/src/core/useLiveChartEngine.ts +42 -2
  44. package/src/core/useLiveChartSeriesEngine.ts +26 -0
  45. package/src/hooks/crosshairShared.ts +20 -1
  46. package/src/hooks/useCrosshair.ts +6 -0
  47. package/src/index.ts +1 -0
  48. package/src/theme.ts +14 -0
  49. package/src/types.ts +105 -8
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ AreaDotsConfig,
2
3
  AxisLabelConfig,
3
4
  BadgeConfig,
4
5
  BadgeVariant,
@@ -10,6 +11,7 @@ import type {
10
11
  LeftEdgeFadeConfig,
11
12
  LegendConfig,
12
13
  LegendStyle,
14
+ LineStyleConfig,
13
15
  LiveChartMetrics,
14
16
  LiveChartMetricsOverride,
15
17
  DotConfig,
@@ -61,12 +63,35 @@ export interface ResolvedYAxisConfig {
61
63
  minGap: number;
62
64
  }
63
65
 
66
+ /** Resolved straight-line styling (connector, etc.). `color: undefined` → caller default. */
67
+ export interface ResolvedLineStyleConfig {
68
+ color: string | undefined;
69
+ strokeWidth: number;
70
+ /** undefined → solid (no dash). */
71
+ intervals: [number, number] | undefined;
72
+ }
73
+
64
74
  export interface ResolvedAxisLabelConfig {
65
75
  /** undefined → use the chart's `formatValue` at render time. */
66
76
  format?: (v: number) => string;
67
77
  /** undefined → use the muted default label color at render time. */
68
78
  color?: string;
69
- position: "left" | "right";
79
+ /** `"left"`/`"right"` pin to that edge; `"extrema"`(-`edge`) tracks the data point. */
80
+ position: "left" | "right" | "extrema" | "extrema-edge";
81
+ /** undefined → the built-in default text size (11). */
82
+ fontSize?: number;
83
+ /** undefined → the platform `<Text>` default weight. */
84
+ fontWeight?: FontWeight;
85
+ /** undefined → the platform `<Text>` default family. */
86
+ fontFamily?: string;
87
+ /** Extrema dot color; undefined → use `color`. */
88
+ dotColor?: string;
89
+ /** Extrema dot diameter (px); undefined → the built-in default (7). */
90
+ dotSize?: number;
91
+ /** Extrema — whether to draw the marker dot. */
92
+ dot: boolean;
93
+ /** `"extrema-edge"` connector line (dot → edge label); null → none. */
94
+ connector: ResolvedLineStyleConfig | null;
70
95
  /** When set, the built-in value label is replaced by this custom element. */
71
96
  render?: () => ReactElement | null;
72
97
  }
@@ -134,6 +159,14 @@ export interface ResolvedGradientConfig {
134
159
  positions: number[] | undefined;
135
160
  }
136
161
 
162
+ export interface ResolvedAreaDotsConfig {
163
+ spacing: number;
164
+ size: number;
165
+ /** undefined → derive a faint tint from the line/accent color at render time. */
166
+ color: string | undefined;
167
+ opacity: number;
168
+ }
169
+
137
170
  export interface ResolvedPulseConfig {
138
171
  interval: number;
139
172
  duration: number;
@@ -333,18 +366,65 @@ const AXIS_LABEL_DEFAULTS: ResolvedAxisLabelConfig = {
333
366
  format: undefined,
334
367
  color: undefined,
335
368
  position: "right",
369
+ fontSize: undefined,
370
+ fontWeight: undefined,
371
+ fontFamily: undefined,
372
+ dotColor: undefined,
373
+ dotSize: undefined,
374
+ dot: true,
375
+ // Always overwritten by resolveAxisLabel (per-position default-on); placeholder.
376
+ connector: null,
336
377
  render: undefined,
337
378
  };
338
379
 
380
+ /** Dashed by default — a subtle guide tying the extrema dot to its edge label. */
381
+ const CONNECTOR_DEFAULTS: ResolvedLineStyleConfig = {
382
+ color: undefined,
383
+ strokeWidth: 1,
384
+ intervals: [2, 3],
385
+ };
386
+
387
+ /**
388
+ * Resolves the extrema-label `connector` sub-prop to a line style or null.
389
+ * `defaultOn` (true in `"extrema-edge"` mode) means an unset connector draws the
390
+ * dashed default; `false` → null; an object → merged; a `LineStyleConfig` is
391
+ * normalized (its `intervals` may be omitted for a solid line).
392
+ */
393
+ export function resolveConnector(
394
+ prop: boolean | LineStyleConfig | undefined,
395
+ defaultOn: boolean,
396
+ ): ResolvedLineStyleConfig | null {
397
+ if (prop === false) return null;
398
+ if (prop == null) return defaultOn ? CONNECTOR_DEFAULTS : null;
399
+ if (prop === true) return CONNECTOR_DEFAULTS;
400
+ return {
401
+ color: prop.color,
402
+ strokeWidth: prop.strokeWidth ?? CONNECTOR_DEFAULTS.strokeWidth,
403
+ // An explicit object opts into its own dash (or solid when omitted).
404
+ intervals: prop.intervals,
405
+ };
406
+ }
407
+
339
408
  /**
340
409
  * Resolves a `topLabel` / `bottomLabel` prop to a fully-typed config or null
341
410
  * (no label). Opt-in, so `undefined`/`false` → null; `true` → the built-in
342
411
  * value label with defaults; object → configured built-in (or a custom `render`).
412
+ * The `connector` defaults on (dashed) in `"extrema-edge"` mode, else off.
343
413
  */
344
414
  export function resolveAxisLabel(
345
415
  prop: boolean | AxisLabelConfig | undefined,
346
416
  ): ResolvedAxisLabelConfig | null {
347
- return resolveToggle(prop, AXIS_LABEL_DEFAULTS, false);
417
+ const resolved = resolveToggle(prop, AXIS_LABEL_DEFAULTS, false);
418
+ if (!resolved) return null;
419
+ const connectorProp =
420
+ typeof prop === "object" ? prop.connector : undefined;
421
+ return {
422
+ ...resolved,
423
+ connector: resolveConnector(
424
+ connectorProp,
425
+ resolved.position === "extrema-edge",
426
+ ),
427
+ };
348
428
  }
349
429
 
350
430
  const X_AXIS_DEFAULTS: ResolvedXAxisConfig = {
@@ -433,6 +513,24 @@ export function resolveGradient(
433
513
  return resolveToggle(prop, GRADIENT_DEFAULTS, false);
434
514
  }
435
515
 
516
+ const AREA_DOTS_DEFAULTS: ResolvedAreaDotsConfig = {
517
+ spacing: 12,
518
+ size: 1.6,
519
+ color: undefined,
520
+ opacity: 1,
521
+ };
522
+
523
+ /**
524
+ * Resolves `areaDots` prop to a fully-typed config or null (disabled).
525
+ * `true` → defaults (palette-derived color), object → merged with defaults,
526
+ * falsy/omitted → null. Default OFF (unlike `gradient`).
527
+ */
528
+ export function resolveAreaDots(
529
+ prop: boolean | AreaDotsConfig | undefined,
530
+ ): ResolvedAreaDotsConfig | null {
531
+ return resolveToggle(prop, AREA_DOTS_DEFAULTS, false);
532
+ }
533
+
436
534
  /** Fallback when no theme background is passed (e.g. unit tests). */
437
535
  const LEFT_EDGE_FADE_COLOR_FALLBACK: Pick<
438
536
  ResolvedLeftEdgeFadeConfig,
@@ -63,13 +63,26 @@ export interface ChartEngineLayout {
63
63
  timestamp: SharedValue<number>;
64
64
  }
65
65
 
66
- export interface SingleEngineState extends ChartEngineLayout {
66
+ /**
67
+ * Live extrema (value + time of the lowest / highest data point in the visible
68
+ * window) — the raw data high/low, not the smoothed display bounds. Each field
69
+ * is `NaN` when the window holds no data. Provided by both engines so an
70
+ * `"extrema"`-positioned `topLabel` / `bottomLabel` can be pinned at the point.
71
+ */
72
+ export interface ChartEngineExtrema {
73
+ extremaMinValue: SharedValue<number>;
74
+ extremaMaxValue: SharedValue<number>;
75
+ extremaMinTime: SharedValue<number>;
76
+ extremaMaxTime: SharedValue<number>;
77
+ }
78
+
79
+ export interface SingleEngineState extends ChartEngineLayout, ChartEngineExtrema {
67
80
  data: SharedValue<LiveChartPoint[]>;
68
81
  value: SharedValue<number>;
69
82
  displayValue: SharedValue<number>;
70
83
  }
71
84
 
72
- export interface MultiEngineState extends ChartEngineLayout {
85
+ export interface MultiEngineState extends ChartEngineLayout, ChartEngineExtrema {
73
86
  data: SharedValue<LiveChartPoint[]>;
74
87
  value: SharedValue<number>;
75
88
  displayValue: SharedValue<number>;
@@ -109,6 +122,10 @@ export interface EngineFrameRefs {
109
122
  modeSV: SharedValue<"line" | "candle">;
110
123
  candles?: SharedValue<CandlePoint[]>;
111
124
  liveCandle?: SharedValue<CandlePoint | null>;
125
+ extremaMinValue: SharedValue<number>;
126
+ extremaMaxValue: SharedValue<number>;
127
+ extremaMinTime: SharedValue<number>;
128
+ extremaMaxTime: SharedValue<number>;
112
129
  }
113
130
 
114
131
  /**
@@ -127,6 +144,10 @@ export function applyLiveChartEngineFrame(
127
144
  displayMax: sv.displayMax.value,
128
145
  displayWindow: sv.displayWindow.value,
129
146
  timestamp: sv.timestamp.value,
147
+ extremaMinValue: sv.extremaMinValue.value,
148
+ extremaMaxValue: sv.extremaMaxValue.value,
149
+ extremaMinTime: sv.extremaMinTime.value,
150
+ extremaMaxTime: sv.extremaMaxTime.value,
130
151
  };
131
152
  tickLiveChartEngineFrame(state, {
132
153
  dt,
@@ -155,6 +176,10 @@ export function applyLiveChartEngineFrame(
155
176
  sv.displayMax.value = state.displayMax;
156
177
  sv.displayWindow.value = state.displayWindow;
157
178
  sv.timestamp.value = state.timestamp;
179
+ sv.extremaMinValue.value = state.extremaMinValue;
180
+ sv.extremaMaxValue.value = state.extremaMaxValue;
181
+ sv.extremaMinTime.value = state.extremaMinTime;
182
+ sv.extremaMaxTime.value = state.extremaMaxTime;
158
183
  }
159
184
 
160
185
  export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
@@ -187,6 +212,13 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
187
212
  const [initialTimestamp] = useState(() => Date.now() / 1000);
188
213
  const timestamp = useSharedValue(initialTimestamp);
189
214
 
215
+ // Live data extrema (value + time of the visible high / low). NaN until the
216
+ // first tick finds data — the extrema label stays hidden until then.
217
+ const extremaMinValue = useSharedValue(NaN);
218
+ const extremaMaxValue = useSharedValue(NaN);
219
+ const extremaMinTime = useSharedValue(NaN);
220
+ const extremaMaxTime = useSharedValue(NaN);
221
+
190
222
  // High-frequency data reads directly from the caller's shared values —
191
223
  // no useDerivedValue bridging, no closure serialization per tick.
192
224
  const { data, value, candles, liveCandle } = config;
@@ -221,6 +253,10 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
221
253
  modeSV,
222
254
  candles,
223
255
  liveCandle,
256
+ extremaMinValue,
257
+ extremaMaxValue,
258
+ extremaMinTime,
259
+ extremaMaxTime,
224
260
  };
225
261
 
226
262
  // `autostart=false` registers the frame callback without running it — the live
@@ -271,5 +307,9 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
271
307
  canvasWidth,
272
308
  canvasHeight,
273
309
  timestamp,
310
+ extremaMinValue,
311
+ extremaMaxValue,
312
+ extremaMinTime,
313
+ extremaMaxTime,
274
314
  };
275
315
  }
@@ -47,6 +47,10 @@ export interface MultiEngineFrameRefs {
47
47
  nowOverrideSV?: SharedValue<number | undefined>;
48
48
  windowBufferSV?: SharedValue<number>;
49
49
  pausedSV: SharedValue<boolean>;
50
+ extremaMinValue: SharedValue<number>;
51
+ extremaMaxValue: SharedValue<number>;
52
+ extremaMinTime: SharedValue<number>;
53
+ extremaMaxTime: SharedValue<number>;
50
54
  }
51
55
 
52
56
  /**
@@ -103,6 +107,10 @@ export function applyLiveChartSeriesEngineFrame(
103
107
  timestamp: sv.timestamp.value,
104
108
  displayValues,
105
109
  opacities,
110
+ extremaMinValue: sv.extremaMinValue.value,
111
+ extremaMaxValue: sv.extremaMaxValue.value,
112
+ extremaMinTime: sv.extremaMinTime.value,
113
+ extremaMaxTime: sv.extremaMaxTime.value,
106
114
  };
107
115
  tickLiveChartSeriesEngineFrame(state, {
108
116
  dt,
@@ -128,6 +136,10 @@ export function applyLiveChartSeriesEngineFrame(
128
136
  sv.timestamp.value = state.timestamp;
129
137
  sv.displaySeriesValues.value = state.displayValues;
130
138
  sv.seriesOpacities.value = state.opacities;
139
+ sv.extremaMinValue.value = state.extremaMinValue;
140
+ sv.extremaMaxValue.value = state.extremaMaxValue;
141
+ sv.extremaMinTime.value = state.extremaMinTime;
142
+ sv.extremaMaxTime.value = state.extremaMaxTime;
131
143
  }
132
144
 
133
145
  /**
@@ -161,6 +173,12 @@ export function useLiveChartSeriesEngine(
161
173
  const displaySeriesValues = useSharedValue<number[]>([]);
162
174
  const seriesOpacities = useSharedValue<number[]>([]);
163
175
 
176
+ // Live data extrema (value + time of the visible high / low across series).
177
+ const extremaMinValue = useSharedValue(NaN);
178
+ const extremaMaxValue = useSharedValue(NaN);
179
+ const extremaMinTime = useSharedValue(NaN);
180
+ const extremaMaxTime = useSharedValue(NaN);
181
+
164
182
  const data = useSharedValue<LiveChartPoint[]>([]);
165
183
  const value = useSharedValue(0);
166
184
  const displayValue = useSharedValue(0);
@@ -199,6 +217,10 @@ export function useLiveChartSeriesEngine(
199
217
  nowOverrideSV,
200
218
  windowBufferSV,
201
219
  pausedSV,
220
+ extremaMinValue,
221
+ extremaMaxValue,
222
+ extremaMinTime,
223
+ extremaMaxTime,
202
224
  },
203
225
  scratch,
204
226
  );
@@ -218,5 +240,9 @@ export function useLiveChartSeriesEngine(
218
240
  series,
219
241
  displaySeriesValues,
220
242
  seriesOpacities,
243
+ extremaMinValue,
244
+ extremaMaxValue,
245
+ extremaMinTime,
246
+ extremaMaxTime,
221
247
  };
222
248
  }
@@ -4,6 +4,7 @@ import type { SharedValue } from "react-native-reanimated";
4
4
  import { measureFontTextWidth } from "../lib/measureFontTextWidth";
5
5
  import { type ChartPadding } from "../draw/line";
6
6
  import { interpolateAtTime } from "../math/interpolate";
7
+ import type { CandlePoint } from "../types";
7
8
 
8
9
  const TOOLTIP_PAD_X = 8;
9
10
  const TOOLTIP_PAD_Y = 6;
@@ -57,6 +58,17 @@ export interface CrosshairState {
57
58
  /** Scrub intersection Y in canvas px; -1 when there's no dot to draw
58
59
  * (inactive / no value / degenerate range). See {@link computeScrubDotY}. */
59
60
  scrubDotY: SharedValue<number>;
61
+ /** OHLC candle under the crosshair in candle mode (`null` in line mode or when
62
+ * inactive) — single-series `useCrosshair` only; undefined on the multi-series
63
+ * crosshair. Surfaced for a custom candle `renderTooltip`. */
64
+ scrubCandle?: SharedValue<CandlePoint | null>;
65
+ /** Canvas-Y where the crosshair line should start so it stops at a top-pinned
66
+ * custom tooltip's bottom edge instead of running up through it. Written by
67
+ * {@link CustomTooltipOverlay} (the label's measured bottom) when
68
+ * `tooltipPlacement: "top"` is active, read by {@link CrosshairOverlay}; -1
69
+ * means "no top tooltip" → the line starts at `padding.top` as before.
70
+ * Single-series `useCrosshair` only; undefined on the multi-series crosshair. */
71
+ tooltipLineTop?: SharedValue<number>;
60
72
  gesture: ReturnType<typeof Gesture.Pan>;
61
73
 
62
74
  // ── Scrub-action ("order ticket") lock state — single-series `useCrosshair`
@@ -383,7 +395,7 @@ export function computeCandleTooltipLayout(
383
395
  { text: `C ${formatValue(candle.close)}`, dim: false },
384
396
  { text: formatTime(scrubTime), dim: true },
385
397
  ];
386
- return computeTooltipLayoutMulti(
398
+ const layout = computeTooltipLayoutMulti(
387
399
  scrubActive,
388
400
  scrubX,
389
401
  lines,
@@ -392,6 +404,13 @@ export function computeCandleTooltipLayout(
392
404
  font,
393
405
  monoCharWidth,
394
406
  );
407
+ // Surface the close + time as the single-value strings too. The built-in
408
+ // OHLC stack renders `stackedLines`, but a custom `renderTooltip` reads
409
+ // `valueStr` / `timeStr` off the layout (same as line mode) — so a custom
410
+ // candle tooltip gets a ready-made close + time without its own formatter.
411
+ layout.valueStr = formatValue(candle.close);
412
+ layout.timeStr = formatTime(scrubTime);
413
+ return layout;
395
414
  }
396
415
 
397
416
  /** Single-series scrub value at window time — extracted for tests. */
@@ -143,6 +143,10 @@ export function useCrosshair(
143
143
  // Tracks whether the active scrub phase actually began, so a tap that never
144
144
  // activates doesn't emit a spurious onGestureEnd.
145
145
  const gestureStarted = useSharedValue(false);
146
+ // Where the crosshair line should start (canvas Y) so it stops at a top-pinned
147
+ // custom tooltip instead of running through it. -1 = no top tooltip → the line
148
+ // starts at padding.top. Written by CustomTooltipOverlay, read by CrosshairOverlay.
149
+ const tooltipLineTop = useSharedValue(-1);
146
150
 
147
151
  // Scrub-action lock state. Created unconditionally (hooks must be), but the
148
152
  // lock gestures are only wired by the controller when `scrubAction` is set.
@@ -634,9 +638,11 @@ export function useCrosshair(
634
638
  scrubActive,
635
639
  scrubTime,
636
640
  scrubValue,
641
+ scrubCandle,
637
642
  crosshairOpacity,
638
643
  tooltipLayout,
639
644
  scrubDotY,
645
+ tooltipLineTop,
640
646
  gesture,
641
647
  lockActive,
642
648
  lockX,
package/src/index.ts CHANGED
@@ -28,6 +28,7 @@ export { useTradeStream } from "./hooks/useTradeStream";
28
28
  // ── Types ────────────────────────────────────────────────────────────────────
29
29
 
30
30
  export type {
31
+ AreaDotsConfig,
31
32
  AxisLabelConfig,
32
33
  BadgeConfig,
33
34
  BadgeMetrics,
package/src/theme.ts CHANGED
@@ -98,6 +98,20 @@ function rgba(r: number, g: number, b: number, a: number): string {
98
98
  return `rgba(${r}, ${g}, ${b}, ${a})`;
99
99
  }
100
100
 
101
+ /**
102
+ * Parse a color into `[r, g, b, a]` — RGB channels 0–255, alpha 0–1. Extends
103
+ * {@link parseColorRgb} with the alpha channel of an `rgba(...)` string;
104
+ * `rgb(...)` and `#hex` (which carry no alpha) default to `a = 1`. Used to feed
105
+ * the area-dots shader a vec4 color uniform.
106
+ */
107
+ export function parseColorRgba(
108
+ color: string,
109
+ ): [number, number, number, number] {
110
+ const [r, g, b] = parseColorRgb(color);
111
+ const rgba = color.match(/rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/);
112
+ return [r, g, b, rgba ? +rgba[1] : 1];
113
+ }
114
+
101
115
  /**
102
116
  * Default left-edge fade gradient stops for `dstOut` blending: same RGB as the chart
103
117
  * background (`palette.bgRgb`), opacity 1 → 0. Matches the container `backgroundColor`.
package/src/types.ts CHANGED
@@ -228,6 +228,20 @@ export interface ValueLineConfig {
228
228
  color?: string;
229
229
  }
230
230
 
231
+ /**
232
+ * Shared styling for a simple straight line — color, thickness, and an optional
233
+ * dash. The common shape behind the chart's secondary lines (the `valueLine`,
234
+ * the extrema-label `connector`, etc.), so they configure the same way.
235
+ */
236
+ export interface LineStyleConfig {
237
+ /** Line color. Defaults per usage (e.g. the connector uses the label color). */
238
+ color?: string;
239
+ /** Line thickness in pixels. Default `1`. */
240
+ strokeWidth?: number;
241
+ /** Dash pattern as `[dashLength, gapLength]` in pixels. Omit for a solid line. */
242
+ intervals?: [number, number];
243
+ }
244
+
231
245
  /** Main chart line styling. */
232
246
  export interface LineConfig {
233
247
  /** Stroke width of the main line in pixels. Default `2`. */
@@ -326,6 +340,22 @@ export interface GradientConfig {
326
340
  positions?: number[];
327
341
  }
328
342
 
343
+ /**
344
+ * Dot-lattice fill of the area beneath the line — a screen-fixed grid of dots
345
+ * clipped to the region between the line and the baseline. Composes with
346
+ * `gradient` (both paint), or use it alone with `gradient={false}`.
347
+ */
348
+ export interface AreaDotsConfig {
349
+ /** Lattice pitch (px) between dots, both axes. Default `12`. */
350
+ spacing?: number;
351
+ /** Dot diameter (px). Default `1.6`. */
352
+ size?: number;
353
+ /** Dot color. Omit to derive a faint tint from the line/accent color. */
354
+ color?: string;
355
+ /** Overall opacity (0..1) applied to the whole field. Default `1`. */
356
+ opacity?: number;
357
+ }
358
+
329
359
  /** Value badge pill configuration. */
330
360
  export interface BadgeConfig {
331
361
  /** Visual style of the badge pill. Default `"default"`. */
@@ -356,9 +386,52 @@ export interface AxisLabelConfig {
356
386
  format?: (v: number) => string;
357
387
  /** Text color. Defaults to a muted label color (`palette.gridLabel`). */
358
388
  color?: string;
359
- /** Horizontal alignment within the plot width. Default `"right"`. */
360
- position?: "left" | "right";
361
- /** Full custom element, floated at the edge. Overrides the built-in value label. */
389
+ /**
390
+ * Where the label sits.
391
+ * - `"left"` / `"right"` (default `"right"`) pin to that edge of the plot,
392
+ * horizontally aligned.
393
+ * - `"extrema"` — float at the **actual data point** where the value occurs
394
+ * (`topLabel` tracks the highest point, `bottomLabel` the lowest), anchored
395
+ * over the point with a marker dot, so you can see *when* the high / low
396
+ * happened. The dot and label track the point on the UI thread.
397
+ * - `"extrema-edge"` — like `"extrema"`, but the value label is pinned to the
398
+ * **top / bottom edge**, horizontally aligned with the extremum (not floated
399
+ * over the point). The marker dot still sits on the data point, joined to the
400
+ * edge label by a {@link AxisLabelConfig.connector} line. Keeps the readout on
401
+ * a clean rail while still showing where the extremum is.
402
+ */
403
+ position?: "left" | "right" | "extrema" | "extrema-edge";
404
+ /** Built-in value text size in px. Default `11`. */
405
+ fontSize?: number;
406
+ /** Built-in value text weight. Default the platform `<Text>` default. */
407
+ fontWeight?: FontWeight;
408
+ /** Built-in value text font family (e.g. a loaded monospace face). */
409
+ fontFamily?: string;
410
+ /**
411
+ * Extrema modes only — color of the marker dot at the data point. Defaults to
412
+ * `color`. Lets the dot and the value text differ.
413
+ */
414
+ dotColor?: string;
415
+ /** Extrema modes only — marker dot diameter in px. Default `7`. */
416
+ dotSize?: number;
417
+ /**
418
+ * Extrema modes only — draw the marker dot at the data point. Default `true`.
419
+ * Set `false` for a value label with no dot.
420
+ */
421
+ dot?: boolean;
422
+ /**
423
+ * `"extrema-edge"` only — the line joining the marker dot (on the data point)
424
+ * to the edge value label. `true` = a dashed default, `false` = none, or pass a
425
+ * {@link LineStyleConfig} to style it (`color` defaults to the label `color`).
426
+ * Default on (dashed) in `"extrema-edge"` mode.
427
+ */
428
+ connector?: boolean | LineStyleConfig;
429
+ /**
430
+ * Full custom element, floated at the edge (or, in an extrema mode, centered
431
+ * over the extremum point). Overrides the built-in value label (and the
432
+ * `fontSize` / `fontWeight` / `fontFamily` / `dot*` knobs above — you own the
433
+ * styling).
434
+ */
362
435
  render?: () => ReactElement | null;
363
436
  }
364
437
 
@@ -527,16 +600,30 @@ export interface SelectionDotProps {
527
600
  * for the value/date to update on the UI thread too.
528
601
  */
529
602
  export interface TooltipRenderProps {
530
- /** Interpolated value under the crosshair; `null` when none. */
603
+ /**
604
+ * Value under the crosshair; `null` when none. In line mode this is the
605
+ * interpolated value at the scrub time; in candle mode it's the scrubbed
606
+ * candle's close (use {@link TooltipRenderProps.candle} for full OHLC).
607
+ */
531
608
  value: SharedValue<number | null>;
532
609
  /** Window time (unix seconds) under the crosshair. */
533
610
  time: SharedValue<number>;
534
- /** Value formatted with the chart's `formatValue` (computed UI-side). */
611
+ /**
612
+ * Value formatted with the chart's `formatValue` (computed UI-side). In candle
613
+ * mode this is the formatted close.
614
+ */
535
615
  valueStr: SharedValue<string>;
536
616
  /** Time formatted with the chart's `formatTime` (computed UI-side). */
537
617
  timeStr: SharedValue<string>;
538
618
  /** Whether scrubbing is currently active. */
539
619
  active: SharedValue<boolean>;
620
+ /**
621
+ * In candle mode, the OHLC candle under the crosshair (`null` when none or
622
+ * while inactive). Always `null` in line mode — bind it to render OHLC in a
623
+ * custom candlestick tooltip. Format the individual prices with your own
624
+ * worklet-safe formatter (e.g. the chart's `formatValue`).
625
+ */
626
+ candle: SharedValue<CandlePoint | null>;
540
627
  }
541
628
 
542
629
  /** Outer ring drawn around the built-in selection dot (the subtle halo). */
@@ -1100,9 +1187,13 @@ export interface LiveChartCoreProps {
1100
1187
  * background are plain RN styles) and content — bind the {@link
1101
1188
  * TooltipRenderProps} SharedValues to animated text for the value/date.
1102
1189
  *
1103
- * Return `null`/`undefined` to fall back to the built-in pill. Replaces the
1104
- * default pill entirely while active. Single-series **line mode only**
1105
- * ignored in candle mode (the OHLC stack owns the tooltip there).
1190
+ * Supplying `renderTooltip` replaces the built-in tooltip entirely while
1191
+ * scrubbing (the line pill in line mode, the OHLC stack in candle mode).
1192
+ * Returning `null`/`undefined` from a frame renders nothing for that frame
1193
+ * (e.g. to hide the tooltip in certain states) — it does *not* restore the
1194
+ * built-in pill. Works in **both line and candle mode**: in candle mode the
1195
+ * scrubbed candle is available as {@link TooltipRenderProps.candle} for
1196
+ * rendering your own OHLC readout.
1106
1197
  */
1107
1198
  renderTooltip?: (ctx: TooltipRenderProps) => ReactElement | null | undefined;
1108
1199
  /**
@@ -1157,6 +1248,12 @@ export interface LiveChartCoreProps {
1157
1248
  export interface LiveChartProps extends LiveChartCoreProps {
1158
1249
  /** Area gradient fill under the line. `true` = defaults, or pass `GradientConfig`. Default `true`. */
1159
1250
  gradient?: boolean | GradientConfig;
1251
+ /**
1252
+ * Dot-lattice fill of the area beneath the line (clipped to the under-line
1253
+ * region). `true` = defaults, or pass `AreaDotsConfig`. Composes with
1254
+ * `gradient`. Default `false` (off). Inert in candle mode.
1255
+ */
1256
+ areaDots?: boolean | AreaDotsConfig;
1160
1257
  /** Value badge pill at the chart tip. `true` = defaults, or pass `BadgeConfig`. Default `true`. */
1161
1258
  badge?: boolean | BadgeConfig;
1162
1259
  /**