react-native-livechart 4.21.0 → 4.21.1
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/dist/components/AxisLabelOverlay.d.ts +5 -1
- package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +4 -2
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/CustomMarkerOverlay.d.ts.map +1 -1
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
- package/dist/components/ExtremaConnectorOverlay.d.ts +5 -1
- package/dist/components/ExtremaConnectorOverlay.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/MarkerOverlay.d.ts +4 -2
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/core/chartDataPresence.d.ts +8 -0
- package/dist/core/chartDataPresence.d.ts.map +1 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/markerAtlas.d.ts +3 -2
- package/dist/draw/markerAtlas.d.ts.map +1 -1
- package/dist/hooks/useChartReveal.d.ts +1 -1
- package/dist/hooks/useChartReveal.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useDegen.d.ts.map +1 -1
- package/dist/hooks/useLiveChartHasData.d.ts +2 -2
- package/dist/hooks/useLiveChartHasData.d.ts.map +1 -1
- package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +1 -1
- package/src/components/AxisLabelOverlay.tsx +162 -81
- package/src/components/CrosshairOverlay.tsx +394 -147
- package/src/components/CustomMarkerOverlay.tsx +9 -6
- package/src/components/DegenParticlesOverlay.tsx +34 -23
- package/src/components/ExtremaConnectorOverlay.tsx +43 -3
- package/src/components/LiveChart.tsx +1692 -1025
- package/src/components/LiveChartSeries.tsx +445 -296
- package/src/components/MarkerOverlay.tsx +215 -156
- package/src/components/ReferenceLineOverlay.tsx +77 -50
- package/src/core/chartDataPresence.ts +24 -0
- package/src/core/useLiveChartEngine.ts +6 -3
- package/src/core/useLiveChartSeriesEngine.ts +2 -0
- package/src/draw/markerAtlas.ts +8 -3
- package/src/hooks/useChartReveal.ts +1 -1
- package/src/hooks/useCrosshair.ts +69 -46
- package/src/hooks/useDegen.ts +82 -53
- package/src/hooks/useLiveChartHasData.ts +8 -4
- package/src/hooks/useReverseMorphEngineInputs.ts +11 -20
- package/src/types.ts +3 -3
package/src/draw/markerAtlas.ts
CHANGED
|
@@ -17,6 +17,11 @@ import type {
|
|
|
17
17
|
MarkerKind,
|
|
18
18
|
} from "../types";
|
|
19
19
|
|
|
20
|
+
export type MarkerAtlasPalette = Pick<
|
|
21
|
+
LiveChartPalette,
|
|
22
|
+
"bgRgb" | "line" | "refLine" | "dotUp" | "refLabel"
|
|
23
|
+
>;
|
|
24
|
+
|
|
20
25
|
/** Default icon box (px) when `marker.size` is unset. */
|
|
21
26
|
export const DEFAULT_ICON_SIZE = 16;
|
|
22
27
|
/** Circular badge padding around the icon glyph + background ring width. */
|
|
@@ -78,7 +83,7 @@ export function groupCountTextWidth(
|
|
|
78
83
|
* per-frame overlay worklet can resolve a collapsed cluster's badge color. */
|
|
79
84
|
export function defaultMarkerColor(
|
|
80
85
|
kind: MarkerKind,
|
|
81
|
-
palette:
|
|
86
|
+
palette: MarkerAtlasPalette,
|
|
82
87
|
): string {
|
|
83
88
|
"worklet";
|
|
84
89
|
switch (kind) {
|
|
@@ -188,7 +193,7 @@ interface CellSpec {
|
|
|
188
193
|
* Geometry + draw routine for one marker appearance. Mirrors the per-glyph
|
|
189
194
|
* rendering the old `MarkerGlyph` did inline, so the atlas is pixel-equivalent.
|
|
190
195
|
*/
|
|
191
|
-
function cellSpec(m: Marker, palette:
|
|
196
|
+
function cellSpec(m: Marker, palette: MarkerAtlasPalette, font: SkFont): CellSpec {
|
|
192
197
|
const color = m.color ?? defaultMarkerColor(m.kind, palette);
|
|
193
198
|
const m2 = CELL_MARGIN * 2;
|
|
194
199
|
|
|
@@ -382,7 +387,7 @@ function groupBgCellSpec(
|
|
|
382
387
|
*/
|
|
383
388
|
export function buildMarkerAtlas(
|
|
384
389
|
markers: Marker[],
|
|
385
|
-
palette:
|
|
390
|
+
palette: MarkerAtlasPalette,
|
|
386
391
|
font: SkFont,
|
|
387
392
|
scale = 1,
|
|
388
393
|
/** Also bake count-badge cells (digits + per-color backgrounds) for
|
|
@@ -39,7 +39,7 @@ export interface ChartRevealState {
|
|
|
39
39
|
morphT: SharedValue<number>;
|
|
40
40
|
/** True while loading=true */
|
|
41
41
|
isLoading: SharedValue<boolean>;
|
|
42
|
-
/** True when not loading and
|
|
42
|
+
/** True when not loading and no samples are available. */
|
|
43
43
|
isEmpty: SharedValue<boolean>;
|
|
44
44
|
yAxisOpacity: SharedValue<number>;
|
|
45
45
|
fillOpacity: SharedValue<number>;
|
|
@@ -114,6 +114,34 @@ export interface CrosshairChartOpts {
|
|
|
114
114
|
bridgeUnknown?: boolean;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
function resolveCrosshairSettings(
|
|
118
|
+
chartOpts: CrosshairChartOpts | undefined,
|
|
119
|
+
scrubAction: ResolvedScrubActionConfig | null | undefined,
|
|
120
|
+
onScrubAction: ((point: ScrubActionPoint) => void) | undefined,
|
|
121
|
+
clampToPlot: boolean,
|
|
122
|
+
) {
|
|
123
|
+
const hasScrubAction = scrubAction != null;
|
|
124
|
+
return {
|
|
125
|
+
hasScrubAction,
|
|
126
|
+
clampPlainScrubToPlot: clampToPlot && !hasScrubAction,
|
|
127
|
+
hasOnScrubAction: onScrubAction != null,
|
|
128
|
+
dismissOnTapOutside: scrubAction?.dismissOnTapOutside ?? false,
|
|
129
|
+
dismissOnAction: scrubAction?.dismissOnAction ?? false,
|
|
130
|
+
snapIncrement: scrubAction?.snap,
|
|
131
|
+
actionIcon: scrubAction?.icon ?? "+",
|
|
132
|
+
actionShowText: scrubAction?.text ?? true,
|
|
133
|
+
hasTimeBadge: scrubAction?.timeBadge ?? false,
|
|
134
|
+
isCandleMode: chartOpts?.mode === "candle",
|
|
135
|
+
candlesSV: chartOpts?.candles,
|
|
136
|
+
liveCandleSV: chartOpts?.liveCandle,
|
|
137
|
+
candleWidthSecs: chartOpts?.candleWidthSecs ?? 60,
|
|
138
|
+
chartGaps: chartOpts?.gaps ?? [],
|
|
139
|
+
bridgeNoTrades: chartOpts?.bridgeNoTrades ?? false,
|
|
140
|
+
bridgeUnavailable: chartOpts?.bridgeUnavailable ?? false,
|
|
141
|
+
bridgeUnknown: chartOpts?.bridgeUnknown ?? false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
117
145
|
/**
|
|
118
146
|
* Single-series crosshair + scrub. Use `useCrosshairSeries` for `LiveChartSeries`.
|
|
119
147
|
*/
|
|
@@ -211,24 +239,30 @@ export function useCrosshair(
|
|
|
211
239
|
// screen Y is re-derived from it each frame (see `lockY` below) so the level
|
|
212
240
|
// tracks the price as the axis rescales instead of drifting under a fixed pixel.
|
|
213
241
|
const lockPriceValue = useSharedValue<number | null>(null);
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
242
|
+
const {
|
|
243
|
+
hasScrubAction,
|
|
244
|
+
clampPlainScrubToPlot,
|
|
245
|
+
hasOnScrubAction,
|
|
246
|
+
dismissOnTapOutside,
|
|
247
|
+
dismissOnAction,
|
|
248
|
+
snapIncrement,
|
|
249
|
+
actionIcon,
|
|
250
|
+
actionShowText,
|
|
251
|
+
hasTimeBadge,
|
|
252
|
+
isCandleMode,
|
|
253
|
+
candlesSV,
|
|
254
|
+
liveCandleSV,
|
|
255
|
+
candleWidthSecs,
|
|
256
|
+
chartGaps,
|
|
257
|
+
bridgeNoTrades,
|
|
258
|
+
bridgeUnavailable,
|
|
259
|
+
bridgeUnknown,
|
|
260
|
+
} = resolveCrosshairSettings(
|
|
261
|
+
chartOpts,
|
|
262
|
+
scrubAction,
|
|
263
|
+
onScrubAction,
|
|
264
|
+
clampToPlot,
|
|
265
|
+
);
|
|
232
266
|
|
|
233
267
|
// Monospace advance width, measured once per render (not per scrub frame) so
|
|
234
268
|
// the tooltip layout worklet can size text by character count instead of a
|
|
@@ -407,9 +441,7 @@ export function useCrosshair(
|
|
|
407
441
|
const canvasWidth = engine.canvasWidth.get();
|
|
408
442
|
const canvasHeight = engine.canvasHeight.get();
|
|
409
443
|
const price =
|
|
410
|
-
active && rawPrice !== null
|
|
411
|
-
? snapPrice(rawPrice, snapIncrement)
|
|
412
|
-
: null;
|
|
444
|
+
active && rawPrice !== null ? snapPrice(rawPrice, snapIncrement) : null;
|
|
413
445
|
let y = -1;
|
|
414
446
|
if (
|
|
415
447
|
rawPrice !== null &&
|
|
@@ -586,15 +618,7 @@ export function useCrosshair(
|
|
|
586
618
|
string | null,
|
|
587
619
|
string | null,
|
|
588
620
|
];
|
|
589
|
-
scheduleOnRN(
|
|
590
|
-
handleScrub,
|
|
591
|
-
row[2],
|
|
592
|
-
row[3],
|
|
593
|
-
row[0],
|
|
594
|
-
row[1],
|
|
595
|
-
row[4],
|
|
596
|
-
row[5],
|
|
597
|
-
);
|
|
621
|
+
scheduleOnRN(handleScrub, row[2], row[3], row[0], row[1], row[4], row[5]);
|
|
598
622
|
},
|
|
599
623
|
);
|
|
600
624
|
|
|
@@ -667,13 +691,7 @@ export function useCrosshair(
|
|
|
667
691
|
.onTouchesUp(
|
|
668
692
|
/* istanbul ignore next */ (e, manager) => {
|
|
669
693
|
"worklet";
|
|
670
|
-
delayedPanTouchUp(
|
|
671
|
-
longPressMs,
|
|
672
|
-
e,
|
|
673
|
-
manager,
|
|
674
|
-
fingerDown,
|
|
675
|
-
panActivated,
|
|
676
|
-
);
|
|
694
|
+
delayedPanTouchUp(longPressMs, e, manager, fingerDown, panActivated);
|
|
677
695
|
},
|
|
678
696
|
)
|
|
679
697
|
.onTouchesCancelled(
|
|
@@ -706,7 +724,12 @@ export function useCrosshair(
|
|
|
706
724
|
// stays false and the live crosshair hides itself.
|
|
707
725
|
if (hasScrubAction && lockActive.get()) {
|
|
708
726
|
lockX.set(
|
|
709
|
-
clampPlotX(
|
|
727
|
+
clampPlotX(
|
|
728
|
+
e.x,
|
|
729
|
+
padding.left,
|
|
730
|
+
engine.canvasWidth.get(),
|
|
731
|
+
padding.right,
|
|
732
|
+
),
|
|
710
733
|
);
|
|
711
734
|
// Freeze the chosen price (value at the pointer Y); the line's Y is
|
|
712
735
|
// re-derived from it so the level stays put in PRICE as the axis rescales.
|
|
@@ -747,7 +770,12 @@ export function useCrosshair(
|
|
|
747
770
|
if (!enabled) return;
|
|
748
771
|
if (hasScrubAction && lockActive.get()) {
|
|
749
772
|
lockX.set(
|
|
750
|
-
clampPlotX(
|
|
773
|
+
clampPlotX(
|
|
774
|
+
e.x,
|
|
775
|
+
padding.left,
|
|
776
|
+
engine.canvasWidth.get(),
|
|
777
|
+
padding.right,
|
|
778
|
+
),
|
|
751
779
|
);
|
|
752
780
|
// Freeze the chosen price (value at the pointer Y); the line's Y is
|
|
753
781
|
// re-derived from it so the level stays put in PRICE as the axis rescales.
|
|
@@ -895,13 +923,8 @@ export function useCrosshair(
|
|
|
895
923
|
: undefined;
|
|
896
924
|
// Keep the bottom axis scroll-only. Do not apply horizontal plot bounds here:
|
|
897
925
|
// the action badge is intentionally tappable in the right gutter.
|
|
898
|
-
const tapHitSlop = resolveScrubHitSlop(
|
|
899
|
-
|
|
900
|
-
false,
|
|
901
|
-
scrubBottomExclude,
|
|
902
|
-
);
|
|
903
|
-
if (tapGesture && tapHitSlop)
|
|
904
|
-
tapGesture = tapGesture.hitSlop(tapHitSlop);
|
|
926
|
+
const tapHitSlop = resolveScrubHitSlop(padding, false, scrubBottomExclude);
|
|
927
|
+
if (tapGesture && tapHitSlop) tapGesture = tapGesture.hitSlop(tapHitSlop);
|
|
905
928
|
|
|
906
929
|
return {
|
|
907
930
|
scrubX,
|
package/src/hooks/useDegen.ts
CHANGED
|
@@ -13,6 +13,32 @@ import type { SingleEngineState } from "../core/useLiveChartEngine";
|
|
|
13
13
|
import { computeShake, spawnBurst, tickParticles } from "../math/degenTick";
|
|
14
14
|
import type { DegenShakePayload, Momentum } from "../types";
|
|
15
15
|
|
|
16
|
+
function resolveDegenSettings(
|
|
17
|
+
config: ResolvedDegenConfig | null,
|
|
18
|
+
hasShakeListener: boolean,
|
|
19
|
+
) {
|
|
20
|
+
return {
|
|
21
|
+
off: config === null,
|
|
22
|
+
scale: config?.scale ?? 1,
|
|
23
|
+
down: config?.downMomentum ?? false,
|
|
24
|
+
shake: config?.shake ?? true,
|
|
25
|
+
shakeIntensity: config?.shakeIntensity ?? 1,
|
|
26
|
+
shakeDurationSec: config?.shakeDurationSec ?? 0.45,
|
|
27
|
+
slotCount: config?.particleSlotCount ?? 60,
|
|
28
|
+
burstParticleCount: config?.burstParticleCount ?? 20,
|
|
29
|
+
particleBurstDurationSec: config?.particleBurstDurationSec ?? 1,
|
|
30
|
+
drag: config?.drag ?? 0.95,
|
|
31
|
+
sizeMin: config?.particleSizeMin ?? 1,
|
|
32
|
+
sizeMax: config?.particleSizeMax ?? 2.2,
|
|
33
|
+
spreadAngle: config?.spreadAngle ?? Math.PI * 1.2,
|
|
34
|
+
jitterX: config?.positionJitterX ?? 24,
|
|
35
|
+
jitterY: config?.positionJitterY ?? 8,
|
|
36
|
+
speedMin: config?.speedMin ?? 60,
|
|
37
|
+
speedMax: config?.speedMax ?? 160,
|
|
38
|
+
hasShakeListener,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
export function useDegen(
|
|
17
43
|
engine: SingleEngineState,
|
|
18
44
|
dotX: SharedValue<number>,
|
|
@@ -65,23 +91,26 @@ export function useDegen(
|
|
|
65
91
|
onShakeRef.current?.({ direction });
|
|
66
92
|
};
|
|
67
93
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
const {
|
|
95
|
+
off,
|
|
96
|
+
scale,
|
|
97
|
+
down,
|
|
98
|
+
shake,
|
|
99
|
+
shakeIntensity,
|
|
100
|
+
shakeDurationSec,
|
|
101
|
+
slotCount,
|
|
102
|
+
burstParticleCount,
|
|
103
|
+
particleBurstDurationSec,
|
|
104
|
+
drag,
|
|
105
|
+
sizeMin,
|
|
106
|
+
sizeMax,
|
|
107
|
+
spreadAngle,
|
|
108
|
+
jitterX,
|
|
109
|
+
jitterY,
|
|
110
|
+
speedMin,
|
|
111
|
+
speedMax,
|
|
112
|
+
hasShakeListener,
|
|
113
|
+
} = resolveDegenSettings(cfg, onShake != null);
|
|
85
114
|
|
|
86
115
|
useEffect(() => {
|
|
87
116
|
// Mirror the resolved config into SharedValues for the UI-thread worklet.
|
|
@@ -90,43 +119,43 @@ export function useDegen(
|
|
|
90
119
|
// is off, enabledSV is 0 and the frame worklet early-returns (zeroing the
|
|
91
120
|
// particle buffer and shake itself), so the remaining values are inert; the
|
|
92
121
|
// resolved* defaults already stand in for the null-config case.
|
|
93
|
-
enabledSV.set(
|
|
94
|
-
shakeEnabledSV.set(!
|
|
95
|
-
hasOnShakeListenerSV.set(!
|
|
96
|
-
scaleSV.set(
|
|
97
|
-
downSV.set(
|
|
98
|
-
shakeIntensitySV.set(
|
|
99
|
-
shakeDurationSecSV.set(
|
|
100
|
-
slotCountSV.set(
|
|
101
|
-
burstParticleCountSV.set(
|
|
102
|
-
particleBurstDurationSecSV.set(
|
|
103
|
-
dragSV.set(
|
|
104
|
-
sizeMinSV.set(
|
|
105
|
-
sizeMaxSV.set(
|
|
106
|
-
spreadAngleSV.set(
|
|
107
|
-
jitterXSV.set(
|
|
108
|
-
jitterYSV.set(
|
|
109
|
-
speedMinSV.set(
|
|
110
|
-
speedMaxSV.set(
|
|
122
|
+
enabledSV.set(off ? 0 : 1);
|
|
123
|
+
shakeEnabledSV.set(!off && shake ? 1 : 0);
|
|
124
|
+
hasOnShakeListenerSV.set(!off && hasShakeListener ? 1 : 0);
|
|
125
|
+
scaleSV.set(scale);
|
|
126
|
+
downSV.set(down ? 1 : 0);
|
|
127
|
+
shakeIntensitySV.set(shakeIntensity);
|
|
128
|
+
shakeDurationSecSV.set(shakeDurationSec);
|
|
129
|
+
slotCountSV.set(slotCount);
|
|
130
|
+
burstParticleCountSV.set(burstParticleCount);
|
|
131
|
+
particleBurstDurationSecSV.set(particleBurstDurationSec);
|
|
132
|
+
dragSV.set(drag);
|
|
133
|
+
sizeMinSV.set(sizeMin);
|
|
134
|
+
sizeMaxSV.set(sizeMax);
|
|
135
|
+
spreadAngleSV.set(spreadAngle);
|
|
136
|
+
jitterXSV.set(jitterX);
|
|
137
|
+
jitterYSV.set(jitterY);
|
|
138
|
+
speedMinSV.set(speedMin);
|
|
139
|
+
speedMaxSV.set(speedMax);
|
|
111
140
|
}, [
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
off,
|
|
142
|
+
scale,
|
|
143
|
+
down,
|
|
144
|
+
shake,
|
|
145
|
+
shakeIntensity,
|
|
146
|
+
shakeDurationSec,
|
|
147
|
+
slotCount,
|
|
148
|
+
burstParticleCount,
|
|
149
|
+
particleBurstDurationSec,
|
|
150
|
+
drag,
|
|
151
|
+
sizeMin,
|
|
152
|
+
sizeMax,
|
|
153
|
+
spreadAngle,
|
|
154
|
+
jitterX,
|
|
155
|
+
jitterY,
|
|
156
|
+
speedMin,
|
|
157
|
+
speedMax,
|
|
158
|
+
hasShakeListener,
|
|
130
159
|
// SharedValue refs are stable (useSharedValue), so listing them satisfies
|
|
131
160
|
// exhaustive-deps without ever re-running the effect.
|
|
132
161
|
enabledSV,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
2
|
+
import {
|
|
3
|
+
hasCandleChartData,
|
|
4
|
+
hasLineChartData,
|
|
5
|
+
} from "../core/chartDataPresence";
|
|
2
6
|
import type { CandlePoint, LiveChartPoint } from "../types";
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
|
-
* Data presence for {@link LiveChart}: line mode needs ≥
|
|
6
|
-
* candle mode needs ≥
|
|
9
|
+
* Data presence for {@link LiveChart}: line mode needs ≥1 point;
|
|
10
|
+
* candle mode needs ≥1 committed candle (`liveCandle` alone does not count).
|
|
7
11
|
*
|
|
8
12
|
* {@link useChartReveal} seeds its first-paint state off `hasData` directly (in a
|
|
9
13
|
* layout effect), so no JS-thread snapshot is read during render here.
|
|
@@ -22,9 +26,9 @@ export function useLiveChartHasData({
|
|
|
22
26
|
const hasData = useDerivedValue(() => {
|
|
23
27
|
"worklet";
|
|
24
28
|
if (isCandle) {
|
|
25
|
-
return (candles?.value
|
|
29
|
+
return hasCandleChartData(candles?.value);
|
|
26
30
|
}
|
|
27
|
-
return data.value
|
|
31
|
+
return hasLineChartData(data.value);
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
return { hasData };
|
|
@@ -3,6 +3,11 @@ import {
|
|
|
3
3
|
useSharedValue,
|
|
4
4
|
type SharedValue,
|
|
5
5
|
} from "react-native-reanimated";
|
|
6
|
+
import {
|
|
7
|
+
hasCandleChartData,
|
|
8
|
+
hasLineChartData,
|
|
9
|
+
hasMultiSeriesChartData,
|
|
10
|
+
} from "../core/chartDataPresence";
|
|
6
11
|
import type { CandlePoint, LiveChartPoint, SeriesConfig } from "../types";
|
|
7
12
|
|
|
8
13
|
const STASH_MORPH_EPS = 0.01;
|
|
@@ -42,20 +47,19 @@ export function useSingleChartReverseMorphInputs({
|
|
|
42
47
|
has: hasData.get(),
|
|
43
48
|
m: morphT.get(),
|
|
44
49
|
d: data.get(),
|
|
45
|
-
cLen: candles?.get().length ?? 0,
|
|
46
50
|
c: candles?.get(),
|
|
47
51
|
lc: liveCandle?.get() ?? null,
|
|
48
52
|
}),
|
|
49
53
|
(curr) => {
|
|
50
54
|
"worklet";
|
|
51
55
|
if (!curr.candle) {
|
|
52
|
-
if (curr.has && curr.d
|
|
56
|
+
if (curr.has && hasLineChartData(curr.d)) {
|
|
53
57
|
lineStash.set(curr.d.slice());
|
|
54
58
|
lineEngineData.set(curr.d);
|
|
55
59
|
} else if (
|
|
56
60
|
!curr.has &&
|
|
57
61
|
curr.m > STASH_MORPH_EPS &&
|
|
58
|
-
lineStash.get()
|
|
62
|
+
hasLineChartData(lineStash.get())
|
|
59
63
|
) {
|
|
60
64
|
lineEngineData.set(lineStash.get());
|
|
61
65
|
} else {
|
|
@@ -65,14 +69,14 @@ export function useSingleChartReverseMorphInputs({
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
const cArr = curr.c;
|
|
68
|
-
if (curr.has &&
|
|
72
|
+
if (curr.has && hasCandleChartData(cArr) && cArr) {
|
|
69
73
|
candleStash.set(cArr.slice());
|
|
70
74
|
candlesEngine.set(cArr);
|
|
71
75
|
liveEngine.set(curr.lc);
|
|
72
76
|
} else if (
|
|
73
77
|
!curr.has &&
|
|
74
78
|
curr.m > STASH_MORPH_EPS &&
|
|
75
|
-
candleStash.get()
|
|
79
|
+
hasCandleChartData(candleStash.get())
|
|
76
80
|
) {
|
|
77
81
|
candlesEngine.set(candleStash.get());
|
|
78
82
|
liveEngine.set(null);
|
|
@@ -118,27 +122,14 @@ export function useMultiSeriesReverseMorphInputs({
|
|
|
118
122
|
(curr) => {
|
|
119
123
|
"worklet";
|
|
120
124
|
const live = curr.live;
|
|
121
|
-
|
|
122
|
-
let anyReady = false;
|
|
123
|
-
for (let i = 0; i < live.length; i++) {
|
|
124
|
-
if (live[i].data.length >= 2) {
|
|
125
|
-
anyReady = true;
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
125
|
+
const anyReady = hasMultiSeriesChartData(live);
|
|
129
126
|
|
|
130
127
|
if (curr.has && anyReady) {
|
|
131
128
|
seriesStash.set(snapshotSeriesWorklet(live));
|
|
132
129
|
effectiveSeries.set(live);
|
|
133
130
|
} else {
|
|
134
|
-
let stashReady = false;
|
|
135
131
|
const stash = seriesStash.get();
|
|
136
|
-
|
|
137
|
-
if (stash[i].data.length >= 2) {
|
|
138
|
-
stashReady = true;
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
132
|
+
const stashReady = hasMultiSeriesChartData(stash);
|
|
142
133
|
|
|
143
134
|
if (!curr.has && curr.m > STASH_MORPH_EPS && stashReady) {
|
|
144
135
|
effectiveSeries.set(seriesStash.get());
|
package/src/types.ts
CHANGED
|
@@ -2261,7 +2261,7 @@ export interface LiveChartCoreProps {
|
|
|
2261
2261
|
transitions?: boolean | TransitionConfig;
|
|
2262
2262
|
/**
|
|
2263
2263
|
* Breathing-line loading shell. When this becomes `false`, the chart reveals
|
|
2264
|
-
* only if there is data (≥
|
|
2264
|
+
* only if there is data (≥1 line point or ≥1 committed candle).
|
|
2265
2265
|
*
|
|
2266
2266
|
* `true` shows the shell with the defaults; pass a {@link LoadingConfig} to
|
|
2267
2267
|
* restyle it — `color` / `strokeWidth` for the squiggle + skeleton, `amplitude`
|
|
@@ -2330,8 +2330,8 @@ export interface LiveChartCoreProps {
|
|
|
2330
2330
|
*/
|
|
2331
2331
|
yRangeScale?: SharedValue<number>;
|
|
2332
2332
|
/**
|
|
2333
|
-
* Label in the empty state when `loading` is false and there are
|
|
2334
|
-
*
|
|
2333
|
+
* Label in the empty state when `loading` is false and there are no line
|
|
2334
|
+
* points or committed candles. Default `"No data"`.
|
|
2335
2335
|
*/
|
|
2336
2336
|
emptyText?: string;
|
|
2337
2337
|
/** Custom formatter for value labels (axes, badge, tooltips). Default `v => v.toFixed(2)`. */
|