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.
- package/README.md +15 -3
- package/dist/components/CrosshairLine.d.ts +6 -1
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +7 -1
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/DotOverlay.d.ts +12 -5
- package/dist/components/DotOverlay.d.ts.map +1 -1
- package/dist/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/components/LoadingOverlay.d.ts +6 -2
- package/dist/components/LoadingOverlay.d.ts.map +1 -1
- package/dist/components/MarkerOverlay.d.ts +8 -4
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/components/MultiSeriesDots.d.ts +8 -2
- package/dist/components/MultiSeriesDots.d.ts.map +1 -1
- package/dist/components/YAxisOverlay.d.ts +4 -2
- package/dist/components/YAxisOverlay.d.ts.map +1 -1
- package/dist/constants.d.ts +16 -13
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/liveChartEngineTick.d.ts +2 -0
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/liveChartSeriesEngineTick.d.ts +2 -0
- package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +34 -4
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +3 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts +3 -0
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/candle.d.ts +2 -2
- package/dist/draw/candle.d.ts.map +1 -1
- package/dist/draw/grid.d.ts +2 -1
- package/dist/draw/grid.d.ts.map +1 -1
- package/dist/draw/line.d.ts +8 -8
- package/dist/draw/line.d.ts.map +1 -1
- package/dist/draw/markerAtlas.d.ts +44 -0
- package/dist/draw/markerAtlas.d.ts.map +1 -0
- package/dist/hooks/crosshairShared.d.ts +12 -4
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/resolveChartLayout.d.ts +3 -1
- package/dist/hooks/resolveChartLayout.d.ts.map +1 -1
- package/dist/hooks/useBadge.d.ts +2 -2
- package/dist/hooks/useBadge.d.ts.map +1 -1
- package/dist/hooks/useCandlePaths.d.ts +2 -2
- package/dist/hooks/useCandlePaths.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts +3 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useCrosshairSeries.d.ts +3 -1
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
- package/dist/hooks/useYAxis.d.ts +2 -1
- package/dist/hooks/useYAxis.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +136 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/CrosshairLine.tsx +30 -10
- package/src/components/CrosshairOverlay.tsx +31 -10
- package/src/components/DotOverlay.tsx +29 -14
- package/src/components/LiveChart.tsx +67 -15
- package/src/components/LiveChartSeries.tsx +76 -17
- package/src/components/LoadingOverlay.tsx +20 -11
- package/src/components/MarkerOverlay.tsx +119 -164
- package/src/components/MultiSeriesDots.tsx +30 -2
- package/src/components/YAxisOverlay.tsx +8 -4
- package/src/constants.ts +54 -14
- package/src/core/liveChartEngineTick.ts +7 -2
- package/src/core/liveChartSeriesEngineTick.ts +8 -2
- package/src/core/resolveConfig.ts +137 -38
- package/src/core/useLiveChartEngine.ts +6 -0
- package/src/core/useLiveChartSeriesEngine.ts +6 -0
- package/src/draw/candle.ts +11 -6
- package/src/draw/grid.ts +5 -5
- package/src/draw/line.ts +106 -25
- package/src/draw/markerAtlas.ts +304 -0
- package/src/hooks/crosshairShared.ts +24 -3
- package/src/hooks/resolveChartLayout.ts +11 -2
- package/src/hooks/useBadge.ts +38 -24
- package/src/hooks/useCandlePaths.ts +4 -2
- package/src/hooks/useCrosshair.ts +15 -2
- package/src/hooks/useCrosshairSeries.ts +8 -2
- package/src/hooks/useYAxis.ts +4 -1
- package/src/index.ts +7 -0
- package/src/types.ts +147 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CandlePoint, LiveChartPoint } from "../types";
|
|
2
2
|
|
|
3
|
-
import {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
// ───
|
|
502
|
+
// ─── Dot (shared) ─────────────────────────────────────────────────────────────
|
|
481
503
|
|
|
482
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
525
|
+
ring: ResolvedDotRingConfig | null;
|
|
526
|
+
show: boolean;
|
|
527
|
+
color: string | undefined;
|
|
487
528
|
}
|
|
488
529
|
|
|
489
|
-
const
|
|
530
|
+
const DOT_DEFAULTS: ResolvedDotConfig = {
|
|
490
531
|
radius: 3.5,
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
568
|
+
const cfg = typeof prop === "object" && prop !== null ? prop : undefined;
|
|
500
569
|
return {
|
|
501
|
-
|
|
502
|
-
pulse: resolvePulse(
|
|
503
|
-
valueLine: resolveValueLine(
|
|
504
|
-
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,
|
package/src/draw/candle.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
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(
|
|
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(
|
|
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 ?
|
|
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) *
|
|
155
|
+
labelAlphas[key] = (targets[key] ?? 0) * grid.fadeInSpeed;
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|