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
@@ -1,6 +1,6 @@
1
1
  import type { CandlePoint, LiveChartPoint } from "../types";
2
2
 
3
- import { ADAPTIVE_SPEED_BOOST } from "../constants";
3
+ import { MOTION_METRICS_DEFAULTS } from "../constants";
4
4
  import { lerp } from "../math/lerp";
5
5
 
6
6
  export interface EngineTickMutable {
@@ -18,6 +18,8 @@ export interface EngineTickInput {
18
18
  timeWindow: number;
19
19
  smoothing: number;
20
20
  exaggerate: boolean;
21
+ /** Extra catch-up speed added to `smoothing` when the live value lags. Default `0.12`. */
22
+ adaptiveSpeedBoost?: number;
21
23
  referenceValue: number | undefined;
22
24
  /** Additional reference values (lines + bands) folded into the Y range. */
23
25
  referenceValues?: number[];
@@ -68,7 +70,10 @@ export function tickLiveChartEngineFrame(
68
70
  const range = state.displayMax - state.displayMin;
69
71
  const gapRatio =
70
72
  range > 0 ? Math.min(Math.abs(target - state.displayValue) / range, 1) : 0;
71
- const adaptiveSpeed = speed + (1 - gapRatio) * ADAPTIVE_SPEED_BOOST;
73
+ const adaptiveSpeed =
74
+ speed +
75
+ (1 - gapRatio) *
76
+ (input.adaptiveSpeedBoost ?? MOTION_METRICS_DEFAULTS.adaptiveSpeedBoost);
72
77
 
73
78
  state.displayValue = lerp(
74
79
  state.displayValue,
@@ -1,4 +1,4 @@
1
- import { ADAPTIVE_SPEED_BOOST } from "../constants";
1
+ import { MOTION_METRICS_DEFAULTS } from "../constants";
2
2
  import { lerp } from "../math/lerp";
3
3
  import type { SeriesConfig } from "../types";
4
4
 
@@ -18,6 +18,8 @@ export interface MultiEngineTickInput {
18
18
  timeWindow: number;
19
19
  smoothing: number;
20
20
  exaggerate: boolean;
21
+ /** Extra catch-up speed added to `smoothing` when a series tip lags. Default `0.12`. */
22
+ adaptiveSpeedBoost?: number;
21
23
  referenceValue: number | undefined;
22
24
  /** Additional reference values (lines + bands) folded into the Y range. */
23
25
  referenceValues?: number[];
@@ -80,7 +82,11 @@ export function tickLiveChartSeriesEngineFrame(
80
82
  const cur = state.displayValues[i];
81
83
  const gapRatio =
82
84
  range > 0 ? Math.min(Math.abs(target - cur) / range, 1) : 0;
83
- const adaptiveSpeed = speed + (1 - gapRatio) * ADAPTIVE_SPEED_BOOST;
85
+ const adaptiveSpeed =
86
+ speed +
87
+ (1 - gapRatio) *
88
+ (input.adaptiveSpeedBoost ??
89
+ MOTION_METRICS_DEFAULTS.adaptiveSpeedBoost);
84
90
  state.displayValues[i] = lerp(cur, target, adaptiveSpeed, input.dt);
85
91
 
86
92
  const targetOp = series[i].visible !== false ? 1 : 0;
@@ -9,6 +9,10 @@ import type {
9
9
  LeftEdgeFadeConfig,
10
10
  LegendConfig,
11
11
  LegendStyle,
12
+ LiveChartMetrics,
13
+ LiveChartMetricsOverride,
14
+ DotConfig,
15
+ DotRingConfig,
12
16
  MultiSeriesDotConfig,
13
17
  PulseConfig,
14
18
  ReferenceLine,
@@ -20,7 +24,14 @@ import type {
20
24
  } from "../types";
21
25
 
22
26
  import type { SharedValue } from "react-native-reanimated";
23
- import { FADE_EDGE_WIDTH } from "../constants";
27
+ import {
28
+ BADGE_METRICS_DEFAULTS,
29
+ CANDLE_METRICS_DEFAULTS,
30
+ EMPTY_STATE_METRICS_DEFAULTS,
31
+ FADE_EDGE_WIDTH,
32
+ GRID_METRICS_DEFAULTS,
33
+ MOTION_METRICS_DEFAULTS,
34
+ } from "../constants";
24
35
 
25
36
  // ─── Resolved types (all fields required, no optionals) ──────────────────────
26
37
 
@@ -60,6 +71,8 @@ export interface ResolvedScrubConfig {
60
71
  tooltipColor: string | undefined;
61
72
  /** undefined → palette.tooltipBorder */
62
73
  tooltipBorderColor: string | undefined;
74
+ /** Press-and-hold delay (ms) before scrubbing activates. 0 = immediate. */
75
+ panGestureDelay: number;
63
76
  }
64
77
 
65
78
  export interface ResolvedGradientConfig {
@@ -135,6 +148,28 @@ export interface ResolvedLeftEdgeFadeConfig {
135
148
 
136
149
  // ─── Resolver functions ───────────────────────────────────────────────────────
137
150
 
151
+ /**
152
+ * Uniform `boolean | Config` feature-flag resolver, shared by every toggle below.
153
+ * - `false` → `null` (explicitly disabled)
154
+ * - `undefined` → `defaultOn ? defaults : null` (the feature's default state)
155
+ * - `true` → `defaults`
156
+ * - object → defaults shallow-merged with the caller's overrides
157
+ *
158
+ * `defaultOn` makes each toggle's default explicit at the resolver. Toggles whose
159
+ * default-on is owned by the component prop (e.g. `badge = true`) pass
160
+ * `defaultOn: false` here, so a bare resolver call without that default stays off.
161
+ */
162
+ function resolveToggle<C extends object, R extends object>(
163
+ prop: boolean | C | undefined,
164
+ defaults: R,
165
+ defaultOn: boolean,
166
+ ): R | null {
167
+ if (prop === false) return null;
168
+ if (prop == null) return defaultOn ? defaults : null;
169
+ if (prop === true) return defaults;
170
+ return { ...defaults, ...prop } as R;
171
+ }
172
+
138
173
  const VALUE_LINE_DEFAULTS: ResolvedValueLineConfig = {
139
174
  strokeWidth: 1,
140
175
  intervals: [4, 4],
@@ -148,9 +183,7 @@ const VALUE_LINE_DEFAULTS: ResolvedValueLineConfig = {
148
183
  export function resolveValueLine(
149
184
  prop: boolean | ValueLineConfig | undefined,
150
185
  ): ResolvedValueLineConfig | null {
151
- if (!prop) return null;
152
- if (prop === true) return VALUE_LINE_DEFAULTS;
153
- return { ...VALUE_LINE_DEFAULTS, ...prop };
186
+ return resolveToggle(prop, VALUE_LINE_DEFAULTS, false);
154
187
  }
155
188
 
156
189
  const BADGE_DEFAULTS: ResolvedBadgeConfig = {
@@ -167,9 +200,7 @@ const BADGE_DEFAULTS: ResolvedBadgeConfig = {
167
200
  export function resolveBadge(
168
201
  prop: boolean | BadgeConfig | undefined,
169
202
  ): ResolvedBadgeConfig | null {
170
- if (!prop) return null;
171
- if (prop === true) return BADGE_DEFAULTS;
172
- return { ...BADGE_DEFAULTS, ...prop };
203
+ return resolveToggle(prop, BADGE_DEFAULTS, false);
173
204
  }
174
205
 
175
206
  const Y_AXIS_DEFAULTS: ResolvedYAxisConfig = {
@@ -183,9 +214,7 @@ const Y_AXIS_DEFAULTS: ResolvedYAxisConfig = {
183
214
  export function resolveYAxis(
184
215
  prop: boolean | YAxisConfig | undefined,
185
216
  ): ResolvedYAxisConfig | null {
186
- if (!prop) return null;
187
- if (prop === true) return Y_AXIS_DEFAULTS;
188
- return { ...Y_AXIS_DEFAULTS, ...prop };
217
+ return resolveToggle(prop, Y_AXIS_DEFAULTS, false);
189
218
  }
190
219
 
191
220
  const X_AXIS_DEFAULTS: ResolvedXAxisConfig = {
@@ -194,14 +223,12 @@ const X_AXIS_DEFAULTS: ResolvedXAxisConfig = {
194
223
 
195
224
  /**
196
225
  * Resolves `xAxis` prop to a fully-typed config or null (disabled).
197
- * Defaults to enabled (`true`) so `undefined` also returns the defaults.
226
+ * Defaults to enabled (`true`) so a bare `undefined` also returns the defaults.
198
227
  */
199
228
  export function resolveXAxis(
200
229
  prop: boolean | XAxisConfig | undefined,
201
230
  ): ResolvedXAxisConfig | null {
202
- if (prop === false) return null;
203
- if (prop === undefined || prop === true) return X_AXIS_DEFAULTS;
204
- return { ...X_AXIS_DEFAULTS, ...prop };
231
+ return resolveToggle(prop, X_AXIS_DEFAULTS, true);
205
232
  }
206
233
 
207
234
  const SCRUB_DEFAULTS: ResolvedScrubConfig = {
@@ -212,6 +239,7 @@ const SCRUB_DEFAULTS: ResolvedScrubConfig = {
212
239
  tooltipBackground: undefined,
213
240
  tooltipColor: undefined,
214
241
  tooltipBorderColor: undefined,
242
+ panGestureDelay: 0,
215
243
  };
216
244
 
217
245
  /**
@@ -221,9 +249,7 @@ const SCRUB_DEFAULTS: ResolvedScrubConfig = {
221
249
  export function resolveScrub(
222
250
  prop: boolean | ScrubConfig | undefined,
223
251
  ): ResolvedScrubConfig | null {
224
- if (!prop) return null;
225
- if (prop === true) return SCRUB_DEFAULTS;
226
- return { ...SCRUB_DEFAULTS, ...prop };
252
+ return resolveToggle(prop, SCRUB_DEFAULTS, false);
227
253
  }
228
254
 
229
255
  const GRADIENT_DEFAULTS: ResolvedGradientConfig = {
@@ -238,9 +264,7 @@ const GRADIENT_DEFAULTS: ResolvedGradientConfig = {
238
264
  export function resolveGradient(
239
265
  prop: boolean | GradientConfig | undefined,
240
266
  ): ResolvedGradientConfig | null {
241
- if (!prop) return null;
242
- if (prop === true) return GRADIENT_DEFAULTS;
243
- return { ...GRADIENT_DEFAULTS, ...prop };
267
+ return resolveToggle(prop, GRADIENT_DEFAULTS, false);
244
268
  }
245
269
 
246
270
  /** Fallback when no theme background is passed (e.g. unit tests). */
@@ -290,9 +314,7 @@ const PULSE_DEFAULTS: ResolvedPulseConfig = {
290
314
  export function resolvePulse(
291
315
  prop: boolean | PulseConfig | undefined,
292
316
  ): ResolvedPulseConfig | null {
293
- if (!prop) return null;
294
- if (prop === true) return PULSE_DEFAULTS;
295
- return { ...PULSE_DEFAULTS, ...prop };
317
+ return resolveToggle(prop, PULSE_DEFAULTS, false);
296
318
  }
297
319
 
298
320
  const REFERENCE_LINE_VISUAL_DEFAULTS = {
@@ -477,31 +499,78 @@ export function resolveTradeStream(
477
499
  };
478
500
  }
479
501
 
480
- // ─── Multi-series dot ─────────────────────────────────────────────────────────
502
+ // ─── Dot (shared) ─────────────────────────────────────────────────────────────
481
503
 
482
- export interface ResolvedMultiSeriesDotConfig {
504
+ /** Resolved halo ring. `color: undefined` means "use the theme `badgeOuterBg`". */
505
+ export interface ResolvedDotRingConfig {
506
+ color: string | undefined;
507
+ width: number;
508
+ }
509
+
510
+ const RING_DEFAULTS: ResolvedDotRingConfig = {
511
+ color: undefined,
512
+ width: 2.5,
513
+ };
514
+
515
+ /** `undefined`/`true` → haloed defaults; `false` → null (flat circle). */
516
+ export function resolveDotRing(
517
+ prop: boolean | DotRingConfig | undefined,
518
+ ): ResolvedDotRingConfig | null {
519
+ return resolveToggle(prop, RING_DEFAULTS, true);
520
+ }
521
+
522
+ /** Shared, fully-resolved dot styling (single- and multi-series). */
523
+ export interface ResolvedDotConfig {
483
524
  radius: number;
484
- pulse: ResolvedPulseConfig | null;
485
- valueLine: ResolvedValueLineConfig | null;
486
- valueLabel: boolean;
525
+ ring: ResolvedDotRingConfig | null;
526
+ show: boolean;
527
+ color: string | undefined;
487
528
  }
488
529
 
489
- const MULTI_DOT_DEFAULTS: ResolvedMultiSeriesDotConfig = {
530
+ const DOT_DEFAULTS: ResolvedDotConfig = {
490
531
  radius: 3.5,
491
- pulse: PULSE_DEFAULTS,
492
- valueLine: null,
493
- valueLabel: true,
532
+ ring: RING_DEFAULTS,
533
+ show: true,
534
+ color: undefined,
494
535
  };
495
536
 
537
+ /**
538
+ * Resolves the `dot` prop. Always returns a config (the live dot's geometry is
539
+ * read unconditionally); visibility rides on `show`.
540
+ * - `false` → defaults with `show: false` (the canonical "hide the dot")
541
+ * - `undefined`/`true` → shown defaults
542
+ * - object → merged with defaults (honoring an explicit `show`)
543
+ */
544
+ export function resolveDot(
545
+ prop: boolean | DotConfig | undefined,
546
+ ): ResolvedDotConfig {
547
+ if (prop === false) return { ...DOT_DEFAULTS, show: false };
548
+ if (prop == null || prop === true) return DOT_DEFAULTS;
549
+ return {
550
+ radius: prop.radius ?? DOT_DEFAULTS.radius,
551
+ ring: resolveDotRing(prop.ring),
552
+ show: prop.show ?? DOT_DEFAULTS.show,
553
+ color: prop.color,
554
+ };
555
+ }
556
+
557
+ // ─── Multi-series dot ─────────────────────────────────────────────────────────
558
+
559
+ export interface ResolvedMultiSeriesDotConfig extends ResolvedDotConfig {
560
+ pulse: ResolvedPulseConfig | null;
561
+ valueLine: ResolvedValueLineConfig | null;
562
+ valueLabel: boolean;
563
+ }
564
+
496
565
  export function resolveMultiSeriesDot(
497
- prop: MultiSeriesDotConfig | undefined,
566
+ prop: boolean | MultiSeriesDotConfig | undefined,
498
567
  ): ResolvedMultiSeriesDotConfig {
499
- if (!prop) return MULTI_DOT_DEFAULTS;
568
+ const cfg = typeof prop === "object" && prop !== null ? prop : undefined;
500
569
  return {
501
- radius: prop.radius ?? MULTI_DOT_DEFAULTS.radius,
502
- pulse: resolvePulse(prop.pulse ?? true),
503
- valueLine: resolveValueLine(prop.valueLine),
504
- valueLabel: prop.valueLabel ?? MULTI_DOT_DEFAULTS.valueLabel,
570
+ ...resolveDot(prop),
571
+ pulse: resolvePulse(cfg?.pulse ?? true),
572
+ valueLine: resolveValueLine(cfg?.valueLine),
573
+ valueLabel: cfg?.valueLabel ?? true,
505
574
  };
506
575
  }
507
576
 
@@ -536,3 +605,33 @@ export function resolveLegend(
536
605
  style: prop.style,
537
606
  };
538
607
  }
608
+
609
+ // ─── Metrics (sizing & motion tokens) ─────────────────────────────────────────
610
+
611
+ /** Canonical resolved metrics — every namespace/field present. */
612
+ const METRICS_DEFAULTS: LiveChartMetrics = {
613
+ badge: BADGE_METRICS_DEFAULTS,
614
+ candle: CANDLE_METRICS_DEFAULTS,
615
+ grid: GRID_METRICS_DEFAULTS,
616
+ motion: MOTION_METRICS_DEFAULTS,
617
+ emptyState: EMPTY_STATE_METRICS_DEFAULTS,
618
+ };
619
+
620
+ /**
621
+ * Resolve the `metrics` prop into a fully-typed config. Per-namespace shallow
622
+ * merge over the defaults — only the keys the caller sets are replaced, the same
623
+ * model as `applyPaletteOverride`. Returns the shared defaults object when no
624
+ * override is supplied (treated read-only by consumers).
625
+ */
626
+ export function resolveMetrics(
627
+ prop: LiveChartMetricsOverride | undefined,
628
+ ): LiveChartMetrics {
629
+ if (!prop) return METRICS_DEFAULTS;
630
+ return {
631
+ badge: { ...METRICS_DEFAULTS.badge, ...prop.badge },
632
+ candle: { ...METRICS_DEFAULTS.candle, ...prop.candle },
633
+ grid: { ...METRICS_DEFAULTS.grid, ...prop.grid },
634
+ motion: { ...METRICS_DEFAULTS.motion, ...prop.motion },
635
+ emptyState: { ...METRICS_DEFAULTS.emptyState, ...prop.emptyState },
636
+ };
637
+ }
@@ -21,6 +21,8 @@ export interface EngineConfig {
21
21
  value: SharedValue<number>;
22
22
  timeWindow: number;
23
23
  smoothing: number;
24
+ /** Extra catch-up speed added to `smoothing` when the live value lags. */
25
+ adaptiveSpeedBoost?: number;
24
26
  exaggerate?: boolean;
25
27
  referenceValue?: number;
26
28
  referenceValues?: number[];
@@ -78,6 +80,7 @@ export interface EngineFrameRefs {
78
80
  timestamp: SharedValue<number>;
79
81
  timeWindow: SharedValue<number>;
80
82
  smoothing: SharedValue<number>;
83
+ adaptiveSpeedBoostSV?: SharedValue<number | undefined>;
81
84
  exaggerateSV: SharedValue<boolean>;
82
85
  referenceValue: SharedValue<number | undefined>;
83
86
  referenceValues?: SharedValue<number[] | undefined>;
@@ -114,6 +117,7 @@ export function applyLiveChartEngineFrame(
114
117
  canvasHeight: sv.canvasHeight.value,
115
118
  timeWindow: sv.timeWindow.value,
116
119
  smoothing: sv.smoothing.value,
120
+ adaptiveSpeedBoost: sv.adaptiveSpeedBoostSV?.value,
117
121
  exaggerate: sv.exaggerateSV.value,
118
122
  referenceValue: sv.referenceValue.value,
119
123
  referenceValues: sv.referenceValues?.value,
@@ -140,6 +144,7 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
140
144
  // Low-frequency config → UI thread via useDerivedValue
141
145
  const timeWindow = useDerivedValue(() => config.timeWindow);
142
146
  const smoothing = useDerivedValue(() => config.smoothing);
147
+ const adaptiveSpeedBoostSV = useDerivedValue(() => config.adaptiveSpeedBoost);
143
148
  const exaggerateSV = useDerivedValue(() => config.exaggerate ?? false);
144
149
  const referenceValue = useDerivedValue(() => config.referenceValue);
145
150
  const referenceValues = useDerivedValue(() => config.referenceValues);
@@ -179,6 +184,7 @@ export function useLiveChartEngine(config: EngineConfig): SingleEngineState {
179
184
  canvasHeight,
180
185
  timeWindow,
181
186
  smoothing,
187
+ adaptiveSpeedBoostSV,
182
188
  exaggerateSV,
183
189
  referenceValue,
184
190
  referenceValues,
@@ -14,6 +14,8 @@ export interface MultiSeriesEngineConfig {
14
14
  series: SharedValue<SeriesConfig[]>;
15
15
  timeWindow: number;
16
16
  smoothing: number;
17
+ /** Extra catch-up speed added to `smoothing` when a series tip lags. */
18
+ adaptiveSpeedBoost?: number;
17
19
  exaggerate?: boolean;
18
20
  referenceValue?: number;
19
21
  referenceValues?: number[];
@@ -36,6 +38,7 @@ export interface MultiEngineFrameRefs {
36
38
  canvasHeight: SharedValue<number>;
37
39
  timeWindow: SharedValue<number>;
38
40
  smoothing: SharedValue<number>;
41
+ adaptiveSpeedBoostSV?: SharedValue<number | undefined>;
39
42
  exaggerateSV: SharedValue<boolean>;
40
43
  referenceValue: SharedValue<number | undefined>;
41
44
  referenceValues?: SharedValue<number[] | undefined>;
@@ -107,6 +110,7 @@ export function applyLiveChartSeriesEngineFrame(
107
110
  canvasHeight: sv.canvasHeight.value,
108
111
  timeWindow: sv.timeWindow.value,
109
112
  smoothing: sv.smoothing.value,
113
+ adaptiveSpeedBoost: sv.adaptiveSpeedBoostSV?.value,
110
114
  exaggerate: sv.exaggerateSV.value,
111
115
  referenceValue: sv.referenceValue.value,
112
116
  referenceValues: sv.referenceValues?.value,
@@ -135,6 +139,7 @@ export function useLiveChartSeriesEngine(
135
139
  ): MultiEngineState {
136
140
  const timeWindow = useDerivedValue(() => config.timeWindow);
137
141
  const smoothing = useDerivedValue(() => config.smoothing);
142
+ const adaptiveSpeedBoostSV = useDerivedValue(() => config.adaptiveSpeedBoost);
138
143
  const exaggerateSV = useDerivedValue(() => config.exaggerate ?? false);
139
144
  const referenceValue = useDerivedValue(() => config.referenceValue);
140
145
  const referenceValues = useDerivedValue(() => config.referenceValues);
@@ -185,6 +190,7 @@ export function useLiveChartSeriesEngine(
185
190
  canvasHeight,
186
191
  timeWindow,
187
192
  smoothing,
193
+ adaptiveSpeedBoostSV,
188
194
  exaggerateSV,
189
195
  referenceValue,
190
196
  referenceValues,
@@ -1,4 +1,5 @@
1
- import type { CandlePoint } from "../types";
1
+ import { CANDLE_METRICS_DEFAULTS } from "../constants";
2
+ import type { CandleMetrics, CandlePoint } from "../types";
2
3
  import type { ChartPadding } from "./line";
3
4
 
4
5
  export interface CandleRect {
@@ -21,9 +22,6 @@ export interface CandleGeometry {
21
22
  wicks: CandleWick[];
22
23
  }
23
24
 
24
- const MIN_BODY_PX = 1;
25
- const MAX_BODY_PX = 40;
26
-
27
25
  function candleTimeToX(
28
26
  t: number,
29
27
  padLeft: number,
@@ -62,6 +60,7 @@ function appendCandleShapes(
62
60
  valRange: number,
63
61
  candleWidthSecs: number,
64
62
  bodyW: number,
63
+ minBodyPx: number,
65
64
  bodies: CandleRect[],
66
65
  wicks: CandleWick[],
67
66
  ): void {
@@ -94,7 +93,7 @@ function appendCandleShapes(
94
93
  valRange,
95
94
  chartH,
96
95
  );
97
- const bodyH = Math.max(MIN_BODY_PX, bodyBot - bodyTop);
96
+ const bodyH = Math.max(minBodyPx, bodyBot - bodyTop);
98
97
 
99
98
  let bx = xCenter - bodyW / 2;
100
99
  let bw = bodyW;
@@ -132,6 +131,7 @@ export function buildCandleGeometry(
132
131
  displayMin: number,
133
132
  displayMax: number,
134
133
  candleWidthSecs: number,
134
+ metrics: CandleMetrics = CANDLE_METRICS_DEFAULTS,
135
135
  ): CandleGeometry {
136
136
  "worklet";
137
137
  const chartW = canvasW - padding.left - padding.right;
@@ -142,7 +142,10 @@ export function buildCandleGeometry(
142
142
  return { bodies: [], wicks: [] };
143
143
 
144
144
  const slotPx = (candleWidthSecs / windowSecs) * chartW;
145
- const bodyW = Math.max(1, Math.min(slotPx * 0.8, slotPx - 2, MAX_BODY_PX));
145
+ const bodyW = Math.max(
146
+ 1,
147
+ Math.min(slotPx * metrics.bodyWidthRatio, slotPx - 2, metrics.maxBodyPx),
148
+ );
146
149
 
147
150
  const bodies: CandleRect[] = [];
148
151
  const wicks: CandleWick[] = [];
@@ -180,6 +183,7 @@ export function buildCandleGeometry(
180
183
  valRange,
181
184
  candleWidthSecs,
182
185
  bodyW,
186
+ metrics.minBodyPx,
183
187
  bodies,
184
188
  wicks,
185
189
  );
@@ -201,6 +205,7 @@ export function buildCandleGeometry(
201
205
  valRange,
202
206
  candleWidthSecs,
203
207
  bodyW,
208
+ metrics.minBodyPx,
204
209
  bodies,
205
210
  wicks,
206
211
  );
package/src/draw/grid.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import { GRID_METRICS_DEFAULTS } from "../constants";
1
2
  import { lerp } from "../math/lerp";
3
+ import type { GridMetrics } from "../types";
2
4
 
3
5
  export interface YAxisEntry {
4
6
  y: number;
@@ -64,9 +66,6 @@ function divisible(val: number, interval: number): boolean {
64
66
  return Math.abs(ratio - Math.round(ratio)) < 0.01;
65
67
  }
66
68
 
67
- const FADE_IN = 0.18;
68
- const FADE_OUT = 0.12;
69
-
70
69
  /** Map data value → canvas Y (nested arrows inside worklets are not UI-thread-safe). */
71
70
  function gridValueToY(
72
71
  val: number,
@@ -94,6 +93,7 @@ export function computeGridEntries(
94
93
  formatValue: (v: number) => string,
95
94
  dt: number,
96
95
  minGap = 36,
96
+ grid: GridMetrics = GRID_METRICS_DEFAULTS,
97
97
  ): { entries: YAxisEntry[]; interval: number } {
98
98
  "worklet";
99
99
  const chartH = canvasHeight - padTop - padBottom;
@@ -135,7 +135,7 @@ export function computeGridEntries(
135
135
  const key = Number(keys[i]);
136
136
  const alpha = labelAlphas[key];
137
137
  const target = targets[key] ?? 0;
138
- const speed = target >= alpha ? FADE_IN : FADE_OUT;
138
+ const speed = target >= alpha ? grid.fadeInSpeed : grid.fadeOutSpeed;
139
139
  let next = lerp(alpha, target, speed, dt);
140
140
  if (Math.abs(next - target) < 0.02) next = target;
141
141
  if (next < 0.01 && target === 0) {
@@ -152,7 +152,7 @@ export function computeGridEntries(
152
152
  /* istanbul ignore else -- key already tracked after phase 2 when targets repeat across frames */
153
153
  if (labelAlphas[key] === undefined) {
154
154
  /* istanbul ignore next -- targets keys always map to a number; ?? keeps types safe */
155
- labelAlphas[key] = (targets[key] ?? 0) * FADE_IN;
155
+ labelAlphas[key] = (targets[key] ?? 0) * grid.fadeInSpeed;
156
156
  }
157
157
  }
158
158