react-native-livechart 4.20.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/CustomThresholdBadgeOverlay.d.ts +18 -0
- package/dist/components/CustomThresholdBadgeOverlay.d.ts.map +1 -0
- package/dist/components/CustomTooltipOverlay.d.ts +4 -2
- package/dist/components/CustomTooltipOverlay.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 +10 -5
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/ReferenceLineSeriesOverlay.d.ts +19 -0
- package/dist/components/ReferenceLineSeriesOverlay.d.ts.map +1 -0
- package/dist/core/chartDataPresence.d.ts +8 -0
- package/dist/core/chartDataPresence.d.ts.map +1 -0
- package/dist/core/liveChartEngineTick.d.ts +6 -1
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +32 -1
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +9 -1
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/candle.d.ts +3 -1
- package/dist/draw/candle.d.ts.map +1 -1
- package/dist/draw/candleGap.d.ts +14 -0
- package/dist/draw/candleGap.d.ts.map +1 -0
- package/dist/draw/lineGap.d.ts +25 -0
- package/dist/draw/lineGap.d.ts.map +1 -0
- package/dist/draw/markerAtlas.d.ts +3 -2
- package/dist/draw/markerAtlas.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +7 -3
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/useCandleGapPaths.d.ts +12 -0
- package/dist/hooks/useCandleGapPaths.d.ts.map +1 -0
- package/dist/hooks/useChartPaths.d.ts +4 -1
- package/dist/hooks/useChartPaths.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 +9 -5
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useDegen.d.ts.map +1 -1
- package/dist/hooks/useLineGapPaths.d.ts +10 -0
- package/dist/hooks/useLineGapPaths.d.ts.map +1 -0
- package/dist/hooks/useLiveChartHasData.d.ts +2 -2
- package/dist/hooks/useLiveChartHasData.d.ts.map +1 -1
- package/dist/hooks/useReferenceLine.d.ts +4 -3
- package/dist/hooks/useReferenceLine.d.ts.map +1 -1
- package/dist/hooks/useReferenceLineSeries.d.ts +23 -0
- package/dist/hooks/useReferenceLineSeries.d.ts.map +1 -0
- package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -1
- package/dist/hooks/useThreshold.d.ts +13 -13
- package/dist/hooks/useThreshold.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/candleGaps.d.ts +18 -0
- package/dist/math/candleGaps.d.ts.map +1 -0
- package/dist/math/referenceLineSeries.d.ts +12 -0
- package/dist/math/referenceLineSeries.d.ts.map +1 -0
- package/dist/math/referenceLines.d.ts +5 -5
- package/dist/math/referenceLines.d.ts.map +1 -1
- package/dist/math/spline.d.ts +5 -1
- package/dist/math/spline.d.ts.map +1 -1
- package/dist/types.d.ts +191 -17
- package/dist/types.d.ts.map +1 -1
- 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/CustomThresholdBadgeOverlay.tsx +74 -0
- package/src/components/CustomTooltipOverlay.tsx +7 -3
- package/src/components/DegenParticlesOverlay.tsx +34 -23
- package/src/components/ExtremaConnectorOverlay.tsx +43 -3
- package/src/components/LiveChart.tsx +1918 -974
- package/src/components/LiveChartSeries.tsx +445 -296
- package/src/components/MarkerOverlay.tsx +215 -156
- package/src/components/ReferenceLineOverlay.tsx +113 -55
- package/src/components/ReferenceLineSeriesOverlay.tsx +141 -0
- package/src/core/chartDataPresence.ts +24 -0
- package/src/core/liveChartEngineTick.ts +34 -1
- package/src/core/resolveConfig.ts +186 -0
- package/src/core/useLiveChartEngine.ts +36 -1
- package/src/core/useLiveChartSeriesEngine.ts +2 -0
- package/src/draw/candle.ts +1 -1
- package/src/draw/candleGap.ts +132 -0
- package/src/draw/lineGap.ts +180 -0
- package/src/draw/markerAtlas.ts +8 -3
- package/src/hooks/crosshairShared.ts +92 -11
- package/src/hooks/useCandleGapPaths.ts +88 -0
- package/src/hooks/useChartPaths.ts +81 -22
- package/src/hooks/useChartReveal.ts +1 -1
- package/src/hooks/useCrosshair.ts +178 -69
- package/src/hooks/useDegen.ts +82 -53
- package/src/hooks/useLineGapPaths.ts +76 -0
- package/src/hooks/useLiveChartHasData.ts +8 -4
- package/src/hooks/useReferenceLine.ts +6 -5
- package/src/hooks/useReferenceLineSeries.ts +101 -0
- package/src/hooks/useReverseMorphEngineInputs.ts +11 -20
- package/src/hooks/useThreshold.ts +31 -22
- package/src/index.ts +15 -0
- package/src/math/candleGaps.ts +120 -0
- package/src/math/referenceLineSeries.ts +83 -0
- package/src/math/referenceLines.ts +34 -6
- package/src/math/spline.ts +15 -5
- package/src/types.ts +202 -17
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,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useDerivedValue } from "react-native-reanimated";
|
|
2
|
+
import type { ResolvedCandleGapsConfig } from "../core/resolveConfig";
|
|
3
|
+
import type { SingleEngineState } from "../core/useLiveChartEngine";
|
|
4
|
+
import type { ChartPadding } from "../draw/line";
|
|
5
|
+
import { buildLineGapGeometry } from "../draw/lineGap";
|
|
6
|
+
import { usePathBuilder } from "./usePathBuilder";
|
|
7
|
+
|
|
8
|
+
/** Batched flat previous-value bridge paths for each semantic line-gap kind. */
|
|
9
|
+
export function useLineGapPaths(
|
|
10
|
+
engine: SingleEngineState,
|
|
11
|
+
padding: ChartPadding,
|
|
12
|
+
config: ResolvedCandleGapsConfig,
|
|
13
|
+
) {
|
|
14
|
+
const noTradesBuilder = usePathBuilder();
|
|
15
|
+
const unavailableBuilder = usePathBuilder();
|
|
16
|
+
const unknownBuilder = usePathBuilder();
|
|
17
|
+
|
|
18
|
+
/* istanbul ignore next -- worklet */
|
|
19
|
+
const geometry = useDerivedValue(() =>
|
|
20
|
+
buildLineGapGeometry(
|
|
21
|
+
engine.data.get(),
|
|
22
|
+
config.gaps,
|
|
23
|
+
padding,
|
|
24
|
+
engine.canvasWidth.get(),
|
|
25
|
+
engine.canvasHeight.get(),
|
|
26
|
+
engine.timestamp.get() - engine.displayWindow.get(),
|
|
27
|
+
engine.displayWindow.get(),
|
|
28
|
+
engine.displayMin.get(),
|
|
29
|
+
engine.displayMax.get(),
|
|
30
|
+
config.styles["no-trades"].bridge !== null,
|
|
31
|
+
config.styles.unavailable.bridge !== null,
|
|
32
|
+
config.styles.unknown.bridge !== null,
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/* istanbul ignore next -- worklet */
|
|
37
|
+
const noTradesPath = useDerivedValue(() => {
|
|
38
|
+
const b = noTradesBuilder.get();
|
|
39
|
+
const bridges = geometry.get().bridges;
|
|
40
|
+
for (let i = 0; i < bridges.length; i++) {
|
|
41
|
+
const bridge = bridges[i];
|
|
42
|
+
if (bridge.kind !== "no-trades") continue;
|
|
43
|
+
b.moveTo(bridge.x1, bridge.y);
|
|
44
|
+
b.lineTo(bridge.x2, bridge.y);
|
|
45
|
+
}
|
|
46
|
+
return b.detach();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/* istanbul ignore next -- worklet */
|
|
50
|
+
const unavailablePath = useDerivedValue(() => {
|
|
51
|
+
const b = unavailableBuilder.get();
|
|
52
|
+
const bridges = geometry.get().bridges;
|
|
53
|
+
for (let i = 0; i < bridges.length; i++) {
|
|
54
|
+
const bridge = bridges[i];
|
|
55
|
+
if (bridge.kind !== "unavailable") continue;
|
|
56
|
+
b.moveTo(bridge.x1, bridge.y);
|
|
57
|
+
b.lineTo(bridge.x2, bridge.y);
|
|
58
|
+
}
|
|
59
|
+
return b.detach();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/* istanbul ignore next -- worklet */
|
|
63
|
+
const unknownPath = useDerivedValue(() => {
|
|
64
|
+
const b = unknownBuilder.get();
|
|
65
|
+
const bridges = geometry.get().bridges;
|
|
66
|
+
for (let i = 0; i < bridges.length; i++) {
|
|
67
|
+
const bridge = bridges[i];
|
|
68
|
+
if (bridge.kind !== "unknown") continue;
|
|
69
|
+
b.moveTo(bridge.x1, bridge.y);
|
|
70
|
+
b.lineTo(bridge.x2, bridge.y);
|
|
71
|
+
}
|
|
72
|
+
return b.detach();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return { noTradesPath, unavailablePath, unknownPath } as const;
|
|
76
|
+
}
|
|
@@ -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 };
|
|
@@ -279,9 +279,10 @@ export function computeReferenceBadgeRect(
|
|
|
279
279
|
|
|
280
280
|
/**
|
|
281
281
|
* Derives screen-space layout for a single reference line or band. Supports all
|
|
282
|
-
* three `ReferenceLine` forms (horizontal line, horizontal value
|
|
283
|
-
* time band) plus the pill badge for a Form-A value (in-range tag
|
|
284
|
-
* chevron pin).
|
|
282
|
+
* three scalar/band `ReferenceLine` forms (horizontal line, horizontal value
|
|
283
|
+
* band, vertical time band) plus the pill badge for a Form-A value (in-range tag
|
|
284
|
+
* + off-screen chevron pin). The series form is handled by
|
|
285
|
+
* `ReferenceLineSeriesOverlay`.
|
|
285
286
|
*/
|
|
286
287
|
export function useReferenceLine(
|
|
287
288
|
engine: ChartEngineLayout,
|
|
@@ -344,7 +345,7 @@ export function useReferenceLine(
|
|
|
344
345
|
const fm = font.getMetrics();
|
|
345
346
|
const baselineOffset = (fm.ascent + fm.descent) / 2;
|
|
346
347
|
|
|
347
|
-
// ── Form
|
|
348
|
+
// ── Form D — vertical time band (independent of the value range) ─────────
|
|
348
349
|
if (form === "time-band") {
|
|
349
350
|
if (line.from === undefined || line.to === undefined) return INVISIBLE;
|
|
350
351
|
const now = engine.timestamp.value;
|
|
@@ -392,7 +393,7 @@ export function useReferenceLine(
|
|
|
392
393
|
if (valRange <= 0) return INVISIBLE;
|
|
393
394
|
const toY = (v: number) => chartTop + ((dMax - v) / valRange) * chartH;
|
|
394
395
|
|
|
395
|
-
// ── Form
|
|
396
|
+
// ── Form C — horizontal value band ───────────────────────────────────────
|
|
396
397
|
if (form === "value-band") {
|
|
397
398
|
if (line.valueFrom === undefined || line.valueTo === undefined) {
|
|
398
399
|
return INVISIBLE;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
3
|
+
|
|
4
|
+
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
5
|
+
import type { ChartPadding } from "../draw/line";
|
|
6
|
+
import { interpolateAtTime } from "../math/interpolate";
|
|
7
|
+
import { buildReferenceLineSeriesPoints } from "../math/referenceLineSeries";
|
|
8
|
+
import {
|
|
9
|
+
thresholdLineY,
|
|
10
|
+
thresholdSeriesVisible,
|
|
11
|
+
thresholdVisible,
|
|
12
|
+
} from "../math/threshold";
|
|
13
|
+
import type { LiveChartPoint } from "../types";
|
|
14
|
+
|
|
15
|
+
export interface ReferenceLineSeriesGeometry {
|
|
16
|
+
/** Visible series polyline as `[x, y, …]` screen coordinates. */
|
|
17
|
+
screenPts: SharedValue<number[]>;
|
|
18
|
+
/** Whether any series segment crosses the visible plot. */
|
|
19
|
+
visible: SharedValue<boolean>;
|
|
20
|
+
/** Series value at the chart's current right edge. */
|
|
21
|
+
currentValue: SharedValue<number>;
|
|
22
|
+
/** Canvas Y coordinate of {@link currentValue}. */
|
|
23
|
+
currentY: SharedValue<number>;
|
|
24
|
+
/** Whether the current-value label belongs inside the visible plot. */
|
|
25
|
+
currentVisible: SharedValue<boolean>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* UI-thread geometry for a static historical reference-line series. Point
|
|
30
|
+
* buffers ping-pong so Reanimated observes a changed array reference each frame
|
|
31
|
+
* without allocating new intermediate arrays.
|
|
32
|
+
*/
|
|
33
|
+
export function useReferenceLineSeries(
|
|
34
|
+
engine: ChartEngineLayout,
|
|
35
|
+
padding: ChartPadding,
|
|
36
|
+
points: LiveChartPoint[],
|
|
37
|
+
extendToNow: boolean,
|
|
38
|
+
): ReferenceLineSeriesGeometry {
|
|
39
|
+
const cacheRef = useRef<{
|
|
40
|
+
a: number[];
|
|
41
|
+
b: number[];
|
|
42
|
+
tick: boolean;
|
|
43
|
+
} | null>(null);
|
|
44
|
+
if (cacheRef.current === null) {
|
|
45
|
+
cacheRef.current = { a: [], b: [], tick: false };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const screenPts = useDerivedValue(() => {
|
|
49
|
+
const cache = cacheRef.current!;
|
|
50
|
+
cache.tick = !cache.tick;
|
|
51
|
+
return buildReferenceLineSeriesPoints(
|
|
52
|
+
points,
|
|
53
|
+
engine.timestamp.get(),
|
|
54
|
+
engine.displayWindow.get(),
|
|
55
|
+
engine.displayMin.get(),
|
|
56
|
+
engine.displayMax.get(),
|
|
57
|
+
engine.canvasWidth.get(),
|
|
58
|
+
engine.canvasHeight.get(),
|
|
59
|
+
padding,
|
|
60
|
+
extendToNow,
|
|
61
|
+
cache.tick ? cache.a : cache.b,
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const visible = useDerivedValue(() =>
|
|
66
|
+
thresholdSeriesVisible(
|
|
67
|
+
screenPts.get(),
|
|
68
|
+
engine.canvasHeight.get(),
|
|
69
|
+
padding.top,
|
|
70
|
+
padding.bottom,
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const currentValue = useDerivedValue(
|
|
75
|
+
() => interpolateAtTime(points, engine.timestamp.get()) ?? NaN,
|
|
76
|
+
);
|
|
77
|
+
const currentY = useDerivedValue(() =>
|
|
78
|
+
thresholdLineY(
|
|
79
|
+
currentValue.get(),
|
|
80
|
+
engine.displayMin.get(),
|
|
81
|
+
engine.displayMax.get(),
|
|
82
|
+
engine.canvasHeight.get(),
|
|
83
|
+
padding.top,
|
|
84
|
+
padding.bottom,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
const currentVisible = useDerivedValue(() => {
|
|
88
|
+
if (points.length === 0) return false;
|
|
89
|
+
if (!extendToNow && points[points.length - 1].time < engine.timestamp.get()) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return thresholdVisible(
|
|
93
|
+
currentY.get(),
|
|
94
|
+
engine.canvasHeight.get(),
|
|
95
|
+
padding.top,
|
|
96
|
+
padding.bottom,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return { screenPts, visible, currentValue, currentY, currentVisible };
|
|
101
|
+
}
|
|
@@ -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());
|
|
@@ -95,15 +95,15 @@ export interface ThresholdSeriesGeometry {
|
|
|
95
95
|
samples: SharedValue<number[]>;
|
|
96
96
|
/** Whether any of the polyline is on-screen (drives marker-line opacity). */
|
|
97
97
|
visible: SharedValue<boolean>;
|
|
98
|
-
/** Threshold value at
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
98
|
+
/** Threshold value at the configured badge anchor: the visible window's
|
|
99
|
+
* left edge (`"first"`) or its live/right edge (`"last"`). */
|
|
100
|
+
badgeValue: SharedValue<number>;
|
|
101
|
+
/** Pixel-Y of `badgeValue` — anchors the badge. */
|
|
102
|
+
badgeLineY: SharedValue<number>;
|
|
103
|
+
/** Whether the configured badge anchor is on-plot and belongs to a visible
|
|
104
|
+
* part of the threshold. (`visible` can still be true when that one endpoint
|
|
105
|
+
* is off-plot.) */
|
|
106
|
+
badgeVisible: SharedValue<boolean>;
|
|
107
107
|
/** Pixel-X where the threshold ends: the last point's X with `extendToNow`
|
|
108
108
|
* off, else {@link THRESHOLD_NO_CLIP}. The shader paints its plain
|
|
109
109
|
* `restColor` right of it; the marker polyline stops there. */
|
|
@@ -118,9 +118,9 @@ const EMPTY_SAMPLES: number[] = new Array(THRESHOLD_SAMPLE_COUNT).fill(0);
|
|
|
118
118
|
* Per-frame screen geometry for a **time-varying** threshold — a plain
|
|
119
119
|
* `LiveChartPoint[]` `value` or a live `SharedValue<LiveChartPoint[]>` `series`
|
|
120
120
|
* (which wins when both are given): the screen polyline (marker line +
|
|
121
|
-
* fill-band bottom), the shader's pixel-Y `samples[]`, the
|
|
122
|
-
* for the badge, and the `extendToNow` cutoff X. The array buffers
|
|
123
|
-
* (Reanimated only re-notifies subscribers when the returned reference
|
|
121
|
+
* fill-band bottom), the shader's pixel-Y `samples[]`, the configured endpoint
|
|
122
|
+
* value/anchor for the badge, and the `extendToNow` cutoff X. The array buffers
|
|
123
|
+
* ping-pong (Reanimated only re-notifies subscribers when the returned reference
|
|
124
124
|
* changes). When the threshold is a constant `SharedValue<number>` every
|
|
125
125
|
* worklet short-circuits cheaply and {@link useThreshold} drives the render
|
|
126
126
|
* instead.
|
|
@@ -131,6 +131,7 @@ export function useThresholdSeries(
|
|
|
131
131
|
value: ThresholdValue,
|
|
132
132
|
series: SharedValue<LiveChartPoint[]> | null = null,
|
|
133
133
|
extendToNow = true,
|
|
134
|
+
labelAnchor: "first" | "last" = "last",
|
|
134
135
|
): ThresholdSeriesGeometry {
|
|
135
136
|
const cacheRef = useRef<{
|
|
136
137
|
ptsA: number[];
|
|
@@ -238,15 +239,19 @@ export function useThresholdSeries(
|
|
|
238
239
|
);
|
|
239
240
|
});
|
|
240
241
|
|
|
241
|
-
const
|
|
242
|
+
const badgeValue = useDerivedValue(() => {
|
|
242
243
|
const pts = series ? series.get() : Array.isArray(value) ? value : null;
|
|
243
244
|
if (pts === null) return NaN;
|
|
244
|
-
|
|
245
|
+
const time =
|
|
246
|
+
labelAnchor === "first"
|
|
247
|
+
? engine.timestamp.get() - engine.displayWindow.get()
|
|
248
|
+
: engine.timestamp.get();
|
|
249
|
+
return interpolateAtTime(pts, time) ?? NaN;
|
|
245
250
|
});
|
|
246
251
|
|
|
247
|
-
const
|
|
252
|
+
const badgeLineY = useDerivedValue(() =>
|
|
248
253
|
thresholdLineY(
|
|
249
|
-
|
|
254
|
+
badgeValue.get(),
|
|
250
255
|
engine.displayMin.get(),
|
|
251
256
|
engine.displayMax.get(),
|
|
252
257
|
engine.canvasHeight.get(),
|
|
@@ -255,15 +260,19 @@ export function useThresholdSeries(
|
|
|
255
260
|
),
|
|
256
261
|
);
|
|
257
262
|
|
|
258
|
-
const
|
|
259
|
-
if (
|
|
263
|
+
const badgeVisible = useDerivedValue(() => {
|
|
264
|
+
if (labelAnchor === "first") {
|
|
265
|
+
// If the non-extended series ended before this window, there is no left
|
|
266
|
+
// endpoint to label even though interpolation can still clamp a value.
|
|
267
|
+
if (screenPts.get().length < 4) return false;
|
|
268
|
+
} else if (!extendToNow) {
|
|
260
269
|
// The threshold ends at its last point — no badge past it.
|
|
261
270
|
const pts = series ? series.get() : Array.isArray(value) ? value : null;
|
|
262
271
|
if (pts === null || pts.length === 0) return false;
|
|
263
272
|
if (pts[pts.length - 1].time < engine.timestamp.get()) return false;
|
|
264
273
|
}
|
|
265
274
|
return thresholdVisible(
|
|
266
|
-
|
|
275
|
+
badgeLineY.get(),
|
|
267
276
|
engine.canvasHeight.get(),
|
|
268
277
|
padding.top,
|
|
269
278
|
padding.bottom,
|
|
@@ -274,9 +283,9 @@ export function useThresholdSeries(
|
|
|
274
283
|
screenPts,
|
|
275
284
|
samples,
|
|
276
285
|
visible,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
286
|
+
badgeValue,
|
|
287
|
+
badgeLineY,
|
|
288
|
+
badgeVisible,
|
|
280
289
|
clipRightX,
|
|
281
290
|
};
|
|
282
291
|
}
|
package/src/index.ts
CHANGED
|
@@ -39,9 +39,23 @@ export type {
|
|
|
39
39
|
BadgeMetrics,
|
|
40
40
|
BadgeStyleConfig,
|
|
41
41
|
BadgeVariant,
|
|
42
|
+
CandleGap,
|
|
43
|
+
CandleGapBandStyle,
|
|
44
|
+
CandleGapBridgeStyle,
|
|
45
|
+
CandleGapKind,
|
|
46
|
+
CandleGapLabelStyle,
|
|
47
|
+
CandleGapStyle,
|
|
48
|
+
CandleGapsConfig,
|
|
42
49
|
CandleMetrics,
|
|
43
50
|
CandlePoint,
|
|
44
51
|
CanvasMode,
|
|
52
|
+
ChartGap,
|
|
53
|
+
ChartGapBandStyle,
|
|
54
|
+
ChartGapBridgeStyle,
|
|
55
|
+
ChartGapKind,
|
|
56
|
+
ChartGapLabelStyle,
|
|
57
|
+
ChartGapsConfig,
|
|
58
|
+
ChartGapStyle,
|
|
45
59
|
ChartInsets,
|
|
46
60
|
ChartOverlayContext,
|
|
47
61
|
ChartPlotRect,
|
|
@@ -100,6 +114,7 @@ export type {
|
|
|
100
114
|
SeriesConfig,
|
|
101
115
|
ThemeMode,
|
|
102
116
|
ThresholdConfig,
|
|
117
|
+
ThresholdBadgeRenderProps,
|
|
103
118
|
ThresholdLineConfig,
|
|
104
119
|
TooltipRenderProps,
|
|
105
120
|
TradeEvent,
|