react-native-livechart 1.0.0 → 2.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 (84) hide show
  1. package/README.md +15 -3
  2. package/dist/components/CrosshairLine.d.ts +6 -1
  3. package/dist/components/CrosshairLine.d.ts.map +1 -1
  4. package/dist/components/CrosshairOverlay.d.ts +7 -1
  5. package/dist/components/CrosshairOverlay.d.ts.map +1 -1
  6. package/dist/components/DotOverlay.d.ts +12 -5
  7. package/dist/components/DotOverlay.d.ts.map +1 -1
  8. package/dist/components/LiveChart.d.ts.map +1 -1
  9. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  10. package/dist/components/LoadingOverlay.d.ts +6 -2
  11. package/dist/components/LoadingOverlay.d.ts.map +1 -1
  12. package/dist/components/MarkerOverlay.d.ts +8 -4
  13. package/dist/components/MarkerOverlay.d.ts.map +1 -1
  14. package/dist/components/MultiSeriesDots.d.ts +8 -2
  15. package/dist/components/MultiSeriesDots.d.ts.map +1 -1
  16. package/dist/components/YAxisOverlay.d.ts +4 -2
  17. package/dist/components/YAxisOverlay.d.ts.map +1 -1
  18. package/dist/constants.d.ts +16 -13
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/core/liveChartEngineTick.d.ts +2 -0
  21. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  22. package/dist/core/liveChartSeriesEngineTick.d.ts +2 -0
  23. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
  24. package/dist/core/resolveConfig.d.ts +34 -4
  25. package/dist/core/resolveConfig.d.ts.map +1 -1
  26. package/dist/core/useLiveChartEngine.d.ts +3 -0
  27. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  28. package/dist/core/useLiveChartSeriesEngine.d.ts +3 -0
  29. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  30. package/dist/draw/candle.d.ts +2 -2
  31. package/dist/draw/candle.d.ts.map +1 -1
  32. package/dist/draw/grid.d.ts +2 -1
  33. package/dist/draw/grid.d.ts.map +1 -1
  34. package/dist/draw/line.d.ts +8 -8
  35. package/dist/draw/line.d.ts.map +1 -1
  36. package/dist/draw/markerAtlas.d.ts +44 -0
  37. package/dist/draw/markerAtlas.d.ts.map +1 -0
  38. package/dist/hooks/crosshairShared.d.ts +12 -4
  39. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  40. package/dist/hooks/resolveChartLayout.d.ts +3 -1
  41. package/dist/hooks/resolveChartLayout.d.ts.map +1 -1
  42. package/dist/hooks/useBadge.d.ts +2 -2
  43. package/dist/hooks/useBadge.d.ts.map +1 -1
  44. package/dist/hooks/useCandlePaths.d.ts +2 -2
  45. package/dist/hooks/useCandlePaths.d.ts.map +1 -1
  46. package/dist/hooks/useCrosshair.d.ts +3 -1
  47. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  48. package/dist/hooks/useCrosshairSeries.d.ts +3 -1
  49. package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
  50. package/dist/hooks/useYAxis.d.ts +2 -1
  51. package/dist/hooks/useYAxis.d.ts.map +1 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/types.d.ts +136 -5
  55. package/dist/types.d.ts.map +1 -1
  56. package/package.json +1 -1
  57. package/src/components/CrosshairLine.tsx +30 -10
  58. package/src/components/CrosshairOverlay.tsx +31 -10
  59. package/src/components/DotOverlay.tsx +29 -14
  60. package/src/components/LiveChart.tsx +67 -15
  61. package/src/components/LiveChartSeries.tsx +76 -17
  62. package/src/components/LoadingOverlay.tsx +20 -11
  63. package/src/components/MarkerOverlay.tsx +119 -164
  64. package/src/components/MultiSeriesDots.tsx +30 -2
  65. package/src/components/YAxisOverlay.tsx +8 -4
  66. package/src/constants.ts +54 -14
  67. package/src/core/liveChartEngineTick.ts +7 -2
  68. package/src/core/liveChartSeriesEngineTick.ts +8 -2
  69. package/src/core/resolveConfig.ts +137 -38
  70. package/src/core/useLiveChartEngine.ts +6 -0
  71. package/src/core/useLiveChartSeriesEngine.ts +6 -0
  72. package/src/draw/candle.ts +11 -6
  73. package/src/draw/grid.ts +5 -5
  74. package/src/draw/line.ts +106 -25
  75. package/src/draw/markerAtlas.ts +304 -0
  76. package/src/hooks/crosshairShared.ts +24 -3
  77. package/src/hooks/resolveChartLayout.ts +11 -2
  78. package/src/hooks/useBadge.ts +38 -24
  79. package/src/hooks/useCandlePaths.ts +4 -2
  80. package/src/hooks/useCrosshair.ts +15 -2
  81. package/src/hooks/useCrosshairSeries.ts +8 -2
  82. package/src/hooks/useYAxis.ts +4 -1
  83. package/src/index.ts +7 -0
  84. package/src/types.ts +147 -5
@@ -6,11 +6,8 @@ import {
6
6
  type SharedValue,
7
7
  } from "react-native-reanimated";
8
8
  import {
9
- BADGE_DOT_GAP,
10
- BADGE_MARGIN_RIGHT,
11
- BADGE_PILL_PAD_X,
12
- BADGE_PILL_PAD_Y,
13
- BADGE_TAIL_LEN,
9
+ BADGE_METRICS_DEFAULTS,
10
+ MOTION_METRICS_DEFAULTS,
14
11
  MS_PER_FRAME_60FPS,
15
12
  } from "../constants";
16
13
  import { measureFontTextWidth } from "../lib/measureFontTextWidth";
@@ -22,9 +19,12 @@ import {
22
19
  } from "../draw/line";
23
20
  import { hexToRgb, lerpColor } from "../math/color";
24
21
  import { lerp } from "../math/lerp";
25
- import type { BadgeVariant, LiveChartPalette, Momentum } from "../types";
26
-
27
- const TAIL_SPREAD = 2.5;
22
+ import type {
23
+ BadgeMetrics,
24
+ BadgeVariant,
25
+ LiveChartPalette,
26
+ Momentum,
27
+ } from "../types";
28
28
 
29
29
  export function useBadge(
30
30
  engine: ChartEngineWithLiveValue,
@@ -37,6 +37,8 @@ export function useBadge(
37
37
  momentum?: SharedValue<Momentum>,
38
38
  position: "right" | "left" = "right",
39
39
  background?: string,
40
+ badgeMetrics: BadgeMetrics = BADGE_METRICS_DEFAULTS,
41
+ badgeColorSpeed: number = MOTION_METRICS_DEFAULTS.badgeColorSpeed,
40
42
  ) {
41
43
  const colorR = useSharedValue(0);
42
44
  const colorG = useSharedValue(0);
@@ -93,7 +95,7 @@ export function useBadge(
93
95
  const text = formatValue(engine.displayValue.get());
94
96
  const textW = measureFontTextWidth(font, text);
95
97
 
96
- const pillH = font.getSize() + BADGE_PILL_PAD_Y * 2;
98
+ const pillH = font.getSize() + badgeMetrics.padY * 2;
97
99
  const r = pillH / 2;
98
100
  const badgeY = dotY - pillH / 2;
99
101
  let textX: number;
@@ -101,9 +103,9 @@ export function useBadge(
101
103
  if (position === "left") {
102
104
  // Pill to the left of the live dot; no tail (`showTail` applies to right gutter only).
103
105
  const dotXPos = w - padding.right;
104
- const pillW = 2 * BADGE_PILL_PAD_X + textW;
105
- const bodyRight = dotXPos - BADGE_DOT_GAP;
106
- const bodyLeft = Math.max(BADGE_MARGIN_RIGHT, bodyRight - pillW);
106
+ const pillW = 2 * badgeMetrics.padX + textW;
107
+ const bodyRight = dotXPos - badgeMetrics.dotGap;
108
+ const bodyLeft = Math.max(badgeMetrics.marginEdge, bodyRight - pillW);
107
109
  const pillBodyW = bodyRight - bodyLeft;
108
110
  textX = (bodyLeft + bodyRight - textW) / 2;
109
111
  path.addRRect({
@@ -113,15 +115,21 @@ export function useBadge(
113
115
  });
114
116
  } else {
115
117
  // Right-gutter badge (default): asymmetric layout with optional tail.
116
- const tl = badgeTailAndCap(font.getSize(), showTail);
117
- const bodyLeft = w - padding.right + BADGE_DOT_GAP + tl;
118
- const bodyRight = w - BADGE_MARGIN_RIGHT;
118
+ const tl = badgeTailAndCap(font.getSize(), showTail, badgeMetrics);
119
+ const bodyLeft = w - padding.right + badgeMetrics.dotGap + tl;
120
+ const bodyRight = w - badgeMetrics.marginEdge;
119
121
  const pillW = bodyRight - bodyLeft;
120
122
  // Text centered in pill body — same formula used by GridOverlay.
121
- textX = pillTextLeftX(w, padding.right, BADGE_DOT_GAP + tl, textW);
123
+ textX = pillTextLeftX(
124
+ w,
125
+ padding.right,
126
+ badgeMetrics.dotGap + tl,
127
+ textW,
128
+ badgeMetrics,
129
+ );
122
130
 
123
131
  if (showTail) {
124
- const badgeX = w - padding.right + BADGE_DOT_GAP;
132
+ const badgeX = w - padding.right + badgeMetrics.dotGap;
125
133
  const cx = tl + pillW - r;
126
134
 
127
135
  path.moveTo(badgeX + tl, badgeY);
@@ -134,17 +142,17 @@ export function useBadge(
134
142
  );
135
143
  path.lineTo(badgeX + tl, badgeY + pillH);
136
144
  path.cubicTo(
137
- badgeX + BADGE_TAIL_LEN + 2,
145
+ badgeX + badgeMetrics.tailLength + 2,
138
146
  badgeY + pillH,
139
147
  badgeX + 3,
140
- badgeY + r + TAIL_SPREAD,
148
+ badgeY + r + badgeMetrics.tailSpread,
141
149
  badgeX,
142
150
  badgeY + r,
143
151
  );
144
152
  path.cubicTo(
145
153
  badgeX + 3,
146
- badgeY + r - TAIL_SPREAD,
147
- badgeX + BADGE_TAIL_LEN + 2,
154
+ badgeY + r - badgeMetrics.tailSpread,
155
+ badgeX + badgeMetrics.tailLength + 2,
148
156
  badgeY,
149
157
  badgeX + tl,
150
158
  badgeY,
@@ -170,9 +178,15 @@ export function useBadge(
170
178
  } else if (momentum) {
171
179
  const m = momentum.get();
172
180
  const targetRgb = m === "up" ? upRgb : m === "down" ? downRgb : accentRgb;
173
- colorR.set(lerp(colorR.get(), targetRgb[0], 0.08, MS_PER_FRAME_60FPS));
174
- colorG.set(lerp(colorG.get(), targetRgb[1], 0.08, MS_PER_FRAME_60FPS));
175
- colorB.set(lerp(colorB.get(), targetRgb[2], 0.08, MS_PER_FRAME_60FPS));
181
+ colorR.set(
182
+ lerp(colorR.get(), targetRgb[0], badgeColorSpeed, MS_PER_FRAME_60FPS),
183
+ );
184
+ colorG.set(
185
+ lerp(colorG.get(), targetRgb[1], badgeColorSpeed, MS_PER_FRAME_60FPS),
186
+ );
187
+ colorB.set(
188
+ lerp(colorB.get(), targetRgb[2], badgeColorSpeed, MS_PER_FRAME_60FPS),
189
+ );
176
190
  bgColor = lerpColor(
177
191
  [colorR.get(), colorG.get(), colorB.get()],
178
192
  [colorR.get(), colorG.get(), colorB.get()],
@@ -6,12 +6,12 @@ import {
6
6
  useSharedValue,
7
7
  type SharedValue,
8
8
  } from "react-native-reanimated";
9
- import { MS_PER_FRAME_60FPS } from "../constants";
9
+ import { CANDLE_METRICS_DEFAULTS, MS_PER_FRAME_60FPS } from "../constants";
10
10
  import type { SingleEngineState } from "../core/useLiveChartEngine";
11
11
  import { buildCandleGeometry } from "../draw/candle";
12
12
  import type { ChartPadding } from "../draw/line";
13
13
  import { lerp } from "../math/lerp";
14
- import type { CandlePoint } from "../types";
14
+ import type { CandleMetrics, CandlePoint } from "../types";
15
15
 
16
16
  const CANDLE_WIDTH_LERP_SPEED = 0.08;
17
17
 
@@ -29,6 +29,7 @@ export function useCandlePaths(
29
29
  liveCandle: SharedValue<CandlePoint | null> | undefined,
30
30
  candleWidthSecs: number,
31
31
  active: boolean,
32
+ candleMetrics: CandleMetrics = CANDLE_METRICS_DEFAULTS,
32
33
  ) {
33
34
  const targetCandleWidth = useDerivedValue(() => candleWidthSecs);
34
35
  const displayCandleWidth = useSharedValue(candleWidthSecs);
@@ -92,6 +93,7 @@ export function useCandlePaths(
92
93
  engine.displayMin.value,
93
94
  engine.displayMax.value,
94
95
  displayCandleWidth.get(),
96
+ candleMetrics,
95
97
  );
96
98
  });
97
99
 
@@ -55,6 +55,8 @@ export function useCrosshair(
55
55
  enabled: boolean,
56
56
  onScrub?: (point: ScrubPoint | null) => void,
57
57
  candleOpts?: CrosshairCandleOpts,
58
+ /** Press-and-hold delay (ms) before scrubbing activates. 0 = immediate. */
59
+ panGestureDelay = 0,
58
60
  ): CrosshairState {
59
61
  const scrubX = useSharedValue(-1);
60
62
  const scrubActive = useSharedValue(false);
@@ -110,6 +112,11 @@ export function useCrosshair(
110
112
  ),
111
113
  );
112
114
 
115
+ // Monospace advance width, measured once per render (not per scrub frame) so
116
+ // the tooltip layout worklet can size text by character count instead of a
117
+ // per-frame Skia measureText.
118
+ const monoCharWidth = font.measureText("0").width;
119
+
113
120
  const tooltipLayout = useDerivedValue(() => {
114
121
  if (isCandleMode) {
115
122
  return computeCandleTooltipLayout(
@@ -122,6 +129,7 @@ export function useCrosshair(
122
129
  formatValue,
123
130
  formatTime,
124
131
  font,
132
+ monoCharWidth,
125
133
  );
126
134
  }
127
135
  return deriveCrosshairTooltipSingle(
@@ -134,6 +142,7 @@ export function useCrosshair(
134
142
  formatValue,
135
143
  formatTime,
136
144
  font,
145
+ monoCharWidth,
137
146
  );
138
147
  });
139
148
 
@@ -207,10 +216,14 @@ export function useCrosshair(
207
216
 
208
217
  let gesture = Gesture.Pan()
209
218
  .minDistance(Platform.OS === "android" ? 10 : 0)
210
- .activateAfterLongPress(0)
219
+ .activateAfterLongPress(panGestureDelay)
211
220
  .maxPointers(1)
212
221
  .shouldCancelWhenOutside(false)
213
- .onBegin(
222
+ // Start scrubbing on ACTIVE (onStart), not on touch-down (onBegin):
223
+ // `activateAfterLongPress` only delays activation, so onBegin still fires
224
+ // immediately — using it would scrub instantly and ignore panGestureDelay,
225
+ // and leave scrubActive stuck for taps that never reach the long-press.
226
+ .onStart(
214
227
  /* istanbul ignore next */ (e) => {
215
228
  "worklet";
216
229
  if (!enabled) return;
@@ -28,6 +28,8 @@ export function useCrosshairSeries(
28
28
  padding: ChartPadding,
29
29
  enabled: boolean,
30
30
  onScrub?: (point: ScrubPointMulti | null) => void,
31
+ /** Press-and-hold delay (ms) before scrubbing activates. 0 = immediate. */
32
+ panGestureDelay = 0,
31
33
  ): CrosshairState {
32
34
  const scrubX = useSharedValue(-1);
33
35
  const scrubActive = useSharedValue(false);
@@ -121,10 +123,14 @@ export function useCrosshairSeries(
121
123
 
122
124
  let gesture = Gesture.Pan()
123
125
  .minDistance(Platform.OS === "android" ? 10 : 0)
124
- .activateAfterLongPress(0)
126
+ .activateAfterLongPress(panGestureDelay)
125
127
  .maxPointers(1)
126
128
  .shouldCancelWhenOutside(false)
127
- .onBegin(
129
+ // Start scrubbing on ACTIVE (onStart), not on touch-down (onBegin):
130
+ // `activateAfterLongPress` only delays activation, so onBegin still fires
131
+ // immediately — using it would scrub instantly and ignore panGestureDelay,
132
+ // and leave scrubActive stuck for taps that never reach the long-press.
133
+ .onStart(
128
134
  /* istanbul ignore next */ (e) => {
129
135
  "worklet";
130
136
  if (!enabled) return;
@@ -1,10 +1,11 @@
1
1
  import { useDerivedValue, useSharedValue } from "react-native-reanimated";
2
2
 
3
3
  import type { SkFont } from "@shopify/react-native-skia";
4
- import { MS_PER_FRAME_60FPS } from "../constants";
4
+ import { GRID_METRICS_DEFAULTS, MS_PER_FRAME_60FPS } from "../constants";
5
5
  import type { ChartEngineLayout } from "../core/useLiveChartEngine";
6
6
  import { computeGridEntries } from "../draw/grid";
7
7
  import type { ChartPadding } from "../draw/line";
8
+ import type { GridMetrics } from "../types";
8
9
 
9
10
  /**
10
11
  * Compute Y-axis grid entries (values + labels) with animated fade-in/out.
@@ -17,6 +18,7 @@ export function useYAxis(
17
18
  formatValue: (v: number) => string,
18
19
  font: SkFont,
19
20
  minGap = 36,
21
+ gridMetrics: GridMetrics = GRID_METRICS_DEFAULTS,
20
22
  ) {
21
23
  const prevInterval = useSharedValue(0);
22
24
  const labelAlphas = useSharedValue<Record<number, number>>({});
@@ -36,6 +38,7 @@ export function useYAxis(
36
38
  formatValue,
37
39
  dt,
38
40
  minGap,
41
+ gridMetrics,
39
42
  );
40
43
 
41
44
  prevInterval.set(result.interval);
package/src/index.ts CHANGED
@@ -29,20 +29,26 @@ export { useTradeStream } from "./hooks/useTradeStream";
29
29
 
30
30
  export type {
31
31
  BadgeConfig,
32
+ BadgeMetrics,
32
33
  BadgeVariant,
34
+ CandleMetrics,
33
35
  CandlePoint,
34
36
  ChartInsets,
35
37
  DegenOptions,
36
38
  DegenShakePayload,
39
+ EmptyStateMetrics,
37
40
  FontConfig,
38
41
  FontWeight,
39
42
  GradientConfig,
43
+ GridMetrics,
40
44
  GridStyleConfig,
41
45
  LeftEdgeFadeConfig,
42
46
  LegendConfig,
43
47
  LegendStyle,
44
48
  LineConfig,
45
49
  LiveChartCoreProps,
50
+ LiveChartMetrics,
51
+ LiveChartMetricsOverride,
46
52
  LiveChartPalette,
47
53
  LiveChartPoint,
48
54
  LiveChartProps,
@@ -52,6 +58,7 @@ export type {
52
58
  MarkerKind,
53
59
  Momentum,
54
60
  MomentumConfig,
61
+ MotionMetrics,
55
62
  MultiSeriesDotConfig,
56
63
  PulseConfig,
57
64
  ReferenceLine,
package/src/types.ts CHANGED
@@ -203,6 +203,14 @@ export interface ScrubConfig {
203
203
  tooltipColor?: string;
204
204
  /** Tooltip pill border color. Omit to use theme `tooltipBorder`. */
205
205
  tooltipBorderColor?: string;
206
+ /**
207
+ * Press-and-hold delay in milliseconds before scrubbing activates — think of
208
+ * it as "press and hold to scrub." During the delay the pan is not captured,
209
+ * so a quick horizontal swipe falls through to a parent gesture (e.g. a
210
+ * navigator's swipe-back-to-previous-route). `0` = scrub immediately on drag.
211
+ * Default `0`.
212
+ */
213
+ panGestureDelay?: number;
206
214
  }
207
215
 
208
216
  /** Left-edge fade — soft erase so the chart blends into the left gutter (web liveline parity). */
@@ -392,10 +400,43 @@ export interface DegenShakePayload {
392
400
  direction: "up" | "down";
393
401
  }
394
402
 
395
- /** Live dot configuration for multi-series charts. */
396
- export interface MultiSeriesDotConfig {
397
- /** Dot radius in pixels. Default `3.5`. */
403
+ /** Contrasting outer ring drawn behind each series dot — the "haloed" look the
404
+ * single-series live dot has, so dots stand out against the lines and one
405
+ * another. */
406
+ export interface DotRingConfig {
407
+ /** Ring color. Default: theme `badgeOuterBg` (a near-background halo). */
408
+ color?: string;
409
+ /** Ring thickness in pixels — how far the halo extends past the dot. Default `2.5`. */
410
+ width?: number;
411
+ }
412
+
413
+ /**
414
+ * Shared live-dot styling, used by both `LiveChart` (`dot`) and
415
+ * `LiveChartSeries` (`dot`, which extends this). A dot is a color-filled circle
416
+ * of `radius` with an optional contrasting outer `ring` (halo).
417
+ */
418
+ export interface DotConfig {
419
+ /** Radius of the (color-filled) dot in pixels. Default `3.5`. */
398
420
  radius?: number;
421
+ /**
422
+ * Contrasting outer ring (halo) behind the dot, so it reads clearly against
423
+ * the line(s). `true` = defaults, `false` = a flat circle, or pass
424
+ * `DotRingConfig`. Default `true`.
425
+ */
426
+ ring?: boolean | DotRingConfig;
427
+ /**
428
+ * Show the dot. `false` hides it (line, badge, and labels still render). Default `true`.
429
+ *
430
+ * @deprecated Pass `dot={false}` to hide the dot — the uniform `boolean | Config`
431
+ * convention. `show` still works and is equivalent to `dot={{ show: false }}`.
432
+ */
433
+ show?: boolean;
434
+ /** Dot fill color. Defaults to the chart line color (per series for multi-series). */
435
+ color?: string;
436
+ }
437
+
438
+ /** Live dot configuration for multi-series charts (extends the shared {@link DotConfig}). */
439
+ export interface MultiSeriesDotConfig extends DotConfig {
399
440
  /** Pulsing ring animation on each series dot. `true` = defaults, or pass `PulseConfig`. Default `true`. */
400
441
  pulse?: boolean | PulseConfig;
401
442
  /** Horizontal dashed line at each series' live value. `true` = defaults, or pass `ValueLineConfig`. Default `false`. */
@@ -513,6 +554,92 @@ export interface CandlePoint {
513
554
  close: number;
514
555
  }
515
556
 
557
+ // ── Metrics (sizing & motion tokens) ─────────────────────────────────────────
558
+
559
+ /**
560
+ * Spatial sizing & motion tokens — the geometry/timing analogue of
561
+ * {@link LiveChartPalette}. Where `palette` controls *color*, `metrics` controls
562
+ * *shape* (badge geometry, candle bounds) and *feel* (fade/lerp speeds). Override
563
+ * via the `metrics` prop; only the namespaces/keys you set are replaced.
564
+ */
565
+ export interface LiveChartMetrics {
566
+ /** Value-badge pill geometry. */
567
+ badge: BadgeMetrics;
568
+ /** Candlestick body/wick geometry. */
569
+ candle: CandleMetrics;
570
+ /** Grid + axis-label fade animation. */
571
+ grid: GridMetrics;
572
+ /** Per-frame lerp speeds for value/color transitions. */
573
+ motion: MotionMetrics;
574
+ /** Empty-state (no-data) layout. */
575
+ emptyState: EmptyStateMetrics;
576
+ }
577
+
578
+ /** Value-badge pill geometry (the "metrics" analogue of badge colors). */
579
+ export interface BadgeMetrics {
580
+ /** Horizontal padding inside the pill, each side of the label. Default `10`. */
581
+ padX: number;
582
+ /** Vertical padding above and below the label. Default `3`. */
583
+ padY: number;
584
+ /** Length of the pointed tail toward the live dot. Default `5`. */
585
+ tailLength: number;
586
+ /** Gap between the pill's outer edge and the canvas edge. Default `4`. */
587
+ marginEdge: number;
588
+ /** Gap between the live dot and the badge tail tip. Default `12`. */
589
+ dotGap: number;
590
+ /** Vertical spread of the tail curve's control points. Default `2.5`. */
591
+ tailSpread: number;
592
+ }
593
+
594
+ /** Candlestick body/wick geometry. */
595
+ export interface CandleMetrics {
596
+ /** Minimum candle body height in pixels (so dojis stay visible). Default `1`. */
597
+ minBodyPx: number;
598
+ /** Maximum candle body width in pixels. Default `40`. */
599
+ maxBodyPx: number;
600
+ /** Body width as a fraction of the per-candle slot width (0–1). Default `0.8`. */
601
+ bodyWidthRatio: number;
602
+ }
603
+
604
+ /** Grid-line and axis-label fade animation speeds. */
605
+ export interface GridMetrics {
606
+ /** Per-frame alpha lerp speed when a grid line / label fades in. Default `0.18`. */
607
+ fadeInSpeed: number;
608
+ /** Per-frame alpha lerp speed when a grid line / label fades out. Default `0.12`. */
609
+ fadeOutSpeed: number;
610
+ }
611
+
612
+ /** Per-frame lerp speeds for value/color transitions. */
613
+ export interface MotionMetrics {
614
+ /** Per-frame lerp speed for the badge background color transition. Default `0.08`. */
615
+ badgeColorSpeed: number;
616
+ /** Extra catch-up speed added to `smoothing` when the live value lags its target. Default `0.12`. */
617
+ adaptiveSpeedBoost: number;
618
+ }
619
+
620
+ /** Empty-state (no-data) layout. */
621
+ export interface EmptyStateMetrics {
622
+ /** Opacity of the empty-state label. Default `0.35`. */
623
+ labelOpacity: number;
624
+ /** Half-padding (px) around the empty text for the squiggle "gap" erase band. Default `20`. */
625
+ gapPad: number;
626
+ /** Horizontal fade width (px) on each side of the empty-text gap. Default `30`. */
627
+ gapFadeWidth: number;
628
+ }
629
+
630
+ /**
631
+ * Caller-supplied `metrics` override — every namespace and field is optional, and
632
+ * only the keys you set replace the resolved default (per-namespace shallow merge,
633
+ * same model as `palette`).
634
+ */
635
+ export interface LiveChartMetricsOverride {
636
+ badge?: Partial<BadgeMetrics>;
637
+ candle?: Partial<CandleMetrics>;
638
+ grid?: Partial<GridMetrics>;
639
+ motion?: Partial<MotionMetrics>;
640
+ emptyState?: Partial<EmptyStateMetrics>;
641
+ }
642
+
516
643
  // ── Component Props ──────────────────────────────────────────────────────────
517
644
 
518
645
  /** Props shared between `LiveChart` and `LiveChartSeries`. */
@@ -586,6 +713,12 @@ export interface LiveChartCoreProps {
586
713
  * `accentColor` + `theme`. Only the keys you set are replaced.
587
714
  */
588
715
  palette?: Partial<LiveChartPalette>;
716
+ /**
717
+ * Override sizing & motion tokens — the geometry/feel analogue of `palette`.
718
+ * Namespaced (`badge`, `candle`, `grid`, `motion`, `emptyState`); only the keys
719
+ * you set are replaced. See {@link LiveChartMetrics}.
720
+ */
721
+ metrics?: LiveChartMetricsOverride;
589
722
  /**
590
723
  * Right-edge buffer as a fraction of `timeWindow`. Pushes the live edge past the
591
724
  * current time so the latest point has breathing room; pass `0` to land the latest
@@ -629,6 +762,11 @@ export interface LiveChartProps extends LiveChartCoreProps {
629
762
  momentum?: boolean | Momentum | MomentumConfig;
630
763
  /** Pulsing ring animation on the live dot. `true` = defaults, or pass `PulseConfig`. Default `true`. */
631
764
  pulse?: boolean | PulseConfig;
765
+ /**
766
+ * Live dot styling. `true`/omitted = shown defaults, `false` = hidden, or pass
767
+ * `DotConfig` (`radius`, `ring` halo, `color`). See {@link DotConfig}. Default `true`.
768
+ */
769
+ dot?: boolean | DotConfig;
632
770
  /** Horizontal dashed line at the current live value. `true` = defaults, or pass `ValueLineConfig`. */
633
771
  valueLine?: boolean | ValueLineConfig;
634
772
  /** Render the live value as a large text overlay in the top-left. Default `false`. */
@@ -673,8 +811,12 @@ export interface LiveChartSeriesProps extends LiveChartCoreProps {
673
811
  series: SharedValue<SeriesConfig[]>;
674
812
  /** Called when a series toggle chip is tapped. */
675
813
  onSeriesToggle?: (id: string, visible: boolean) => void;
676
- /** Live dot configuration (radius, pulse, value line, inline labels). */
677
- dot?: MultiSeriesDotConfig;
814
+ /**
815
+ * Per-series live dot configuration. `true`/omitted = shown defaults, `false` =
816
+ * hidden, or pass `MultiSeriesDotConfig` (radius, pulse, value line, inline
817
+ * labels). Default `true`.
818
+ */
819
+ dot?: boolean | MultiSeriesDotConfig;
678
820
  /** Legend (toggle chips) configuration. `true` = defaults, `false` = hidden, or pass `LegendConfig`. Default `true`. */
679
821
  legend?: boolean | LegendConfig;
680
822
  /**