react-native-livechart 4.10.0 → 4.12.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 +24 -0
- package/dist/components/CrosshairLine.d.ts +5 -1
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +3 -1
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/CustomReferenceLineOverlay.d.ts +19 -12
- package/dist/components/CustomReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
- package/dist/components/DotOverlay.d.ts +3 -5
- package/dist/components/DotOverlay.d.ts.map +1 -1
- package/dist/components/LeftEdgeFade.d.ts +3 -1
- package/dist/components/LeftEdgeFade.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 +3 -1
- package/dist/components/LoadingOverlay.d.ts.map +1 -1
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/components/MultiSeriesDots.d.ts +3 -1
- package/dist/components/MultiSeriesDots.d.ts.map +1 -1
- package/dist/components/MultiSeriesTooltipStack.d.ts.map +1 -1
- package/dist/components/MultiSeriesValueLabels.d.ts +3 -1
- package/dist/components/MultiSeriesValueLabels.d.ts.map +1 -1
- package/dist/components/MultiSeriesValueLines.d.ts +3 -1
- package/dist/components/MultiSeriesValueLines.d.ts.map +1 -1
- package/dist/components/PerSeriesTooltipOverlay.d.ts +19 -0
- package/dist/components/PerSeriesTooltipOverlay.d.ts.map +1 -0
- package/dist/components/ReferenceLineOverlay.d.ts +20 -6
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/ThresholdSplitShader.d.ts.map +1 -1
- package/dist/components/YAxisOverlay.d.ts +3 -1
- package/dist/components/YAxisOverlay.d.ts.map +1 -1
- package/dist/components/thresholdSplitShaderSource.d.ts +13 -0
- package/dist/components/thresholdSplitShaderSource.d.ts.map +1 -0
- package/dist/core/liveIndicatorVisibility.d.ts +9 -0
- package/dist/core/liveIndicatorVisibility.d.ts.map +1 -0
- package/dist/core/resolveConfig.d.ts +29 -0
- 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 +10 -5
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/particleAtlas.d.ts +3 -2
- package/dist/draw/particleAtlas.d.ts.map +1 -1
- package/dist/hooks/crosshairSeries.d.ts +10 -2
- package/dist/hooks/crosshairSeries.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +37 -2
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/delayedPanGuard.d.ts +66 -8
- package/dist/hooks/delayedPanGuard.d.ts.map +1 -1
- package/dist/hooks/useChartPaths.d.ts +5 -9
- package/dist/hooks/useChartPaths.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts +7 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useCrosshairSeries.d.ts +22 -3
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
- package/dist/hooks/useMomentum.d.ts +1 -1
- package/dist/hooks/useMomentum.d.ts.map +1 -1
- package/dist/hooks/useMultiSeriesLinePaths.d.ts +4 -4
- package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -1
- package/dist/hooks/usePanScroll.d.ts +15 -1
- package/dist/hooks/usePanScroll.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/lerp.d.ts.map +1 -1
- package/dist/math/momentum.d.ts +1 -1
- package/dist/math/momentum.d.ts.map +1 -1
- package/dist/math/referenceLines.d.ts +7 -0
- package/dist/math/referenceLines.d.ts.map +1 -1
- package/dist/math/squiggly.d.ts +6 -3
- package/dist/math/squiggly.d.ts.map +1 -1
- package/dist/math/threshold.d.ts +2 -2
- package/dist/types.d.ts +184 -16
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/CrosshairLine.tsx +21 -1
- package/src/components/CrosshairOverlay.tsx +48 -35
- package/src/components/CustomReferenceLineOverlay.tsx +58 -20
- package/src/components/DegenParticlesOverlay.tsx +40 -5
- package/src/components/DotOverlay.tsx +24 -8
- package/src/components/LeftEdgeFade.tsx +12 -6
- package/src/components/LiveChart.tsx +186 -53
- package/src/components/LiveChartSeries.tsx +214 -29
- package/src/components/LoadingOverlay.tsx +26 -12
- package/src/components/MarkerOverlay.tsx +20 -3
- package/src/components/MultiSeriesDots.tsx +4 -2
- package/src/components/MultiSeriesTooltipStack.tsx +4 -2
- package/src/components/MultiSeriesValueLabels.tsx +4 -2
- package/src/components/MultiSeriesValueLines.tsx +4 -2
- package/src/components/PerSeriesTooltipOverlay.tsx +287 -0
- package/src/components/ReferenceLineOverlay.tsx +274 -141
- package/src/components/ThresholdSplitShader.tsx +5 -37
- package/src/components/YAxisOverlay.tsx +5 -1
- package/src/components/thresholdSplitShaderSource.ts +70 -0
- package/src/core/liveIndicatorVisibility.ts +25 -0
- package/src/core/resolveConfig.ts +86 -0
- package/src/core/useLiveChartEngine.ts +110 -42
- package/src/core/useLiveChartSeriesEngine.ts +137 -70
- package/src/draw/particleAtlas.ts +21 -10
- package/src/hooks/crosshairSeries.ts +262 -33
- package/src/hooks/crosshairShared.ts +41 -2
- package/src/hooks/delayedPanGuard.ts +111 -6
- package/src/hooks/useChartPaths.ts +36 -15
- package/src/hooks/useCrosshair.ts +73 -14
- package/src/hooks/useCrosshairSeries.ts +106 -13
- package/src/hooks/useMomentum.ts +8 -1
- package/src/hooks/useMultiSeriesLinePaths.ts +15 -9
- package/src/hooks/usePanScroll.ts +46 -5
- package/src/index.ts +2 -0
- package/src/math/lerp.ts +7 -0
- package/src/math/momentum.ts +22 -5
- package/src/math/referenceLines.ts +38 -0
- package/src/math/squiggly.ts +28 -11
- package/src/math/threshold.ts +2 -2
- package/src/types.ts +197 -17
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useEffect,
|
|
3
|
-
useLayoutEffect,
|
|
4
|
-
useMemo,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
} from "react";
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
2
|
import { StyleSheet, View } from "react-native";
|
|
9
3
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
10
4
|
import Animated, {
|
|
@@ -27,6 +21,7 @@ import {
|
|
|
27
21
|
Group,
|
|
28
22
|
LinearGradient,
|
|
29
23
|
Path,
|
|
24
|
+
Rect,
|
|
30
25
|
vec,
|
|
31
26
|
} from "@shopify/react-native-skia";
|
|
32
27
|
|
|
@@ -63,7 +58,11 @@ import {
|
|
|
63
58
|
resolveYAxis,
|
|
64
59
|
} from "../core/resolveConfig";
|
|
65
60
|
import type { ResolvedThresholdConfig } from "../core/resolveConfig";
|
|
66
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
liveIndicatorScrollOpacity,
|
|
63
|
+
resolveHideLiveOnScrollBack,
|
|
64
|
+
} from "../core/liveIndicatorVisibility";
|
|
65
|
+
import { resolveSegment, type ResolvedSegment } from "../core/resolveSegment";
|
|
67
66
|
import { useLiveChartEngine } from "../core/useLiveChartEngine";
|
|
68
67
|
import { pulseRadialOutset } from "../draw/line";
|
|
69
68
|
import { resolveChartLayout } from "../hooks/resolveChartLayout";
|
|
@@ -83,7 +82,10 @@ import { useMarkers } from "../hooks/useMarkers";
|
|
|
83
82
|
import { useReferenceDrag } from "../hooks/useReferenceDrag";
|
|
84
83
|
import { useReferenceLinePress } from "../hooks/useReferenceLinePress";
|
|
85
84
|
import { useModeBlend } from "../hooks/useModeBlend";
|
|
86
|
-
import {
|
|
85
|
+
import {
|
|
86
|
+
resolveMomentumProp,
|
|
87
|
+
useMomentum,
|
|
88
|
+
} from "../hooks/useMomentum";
|
|
87
89
|
import { AXIS_GRAB_MIN_PX, usePanScroll } from "../hooks/usePanScroll";
|
|
88
90
|
import { usePinchZoom } from "../hooks/usePinchZoom";
|
|
89
91
|
import { useVisibleRange } from "../hooks/useVisibleRange";
|
|
@@ -110,6 +112,7 @@ import {
|
|
|
110
112
|
import {
|
|
111
113
|
collectReferenceValues,
|
|
112
114
|
referenceLineForm,
|
|
115
|
+
referenceLineReactKeys,
|
|
113
116
|
resolveReferenceGroupBadge,
|
|
114
117
|
} from "../math/referenceLines";
|
|
115
118
|
import {
|
|
@@ -253,6 +256,7 @@ function useLiveChartController({
|
|
|
253
256
|
font: fontProp,
|
|
254
257
|
insets,
|
|
255
258
|
style,
|
|
259
|
+
canvasMode = "transparent",
|
|
256
260
|
|
|
257
261
|
// ── Candlestick ─────────────────────────────────────────────────────────
|
|
258
262
|
mode = "line",
|
|
@@ -315,6 +319,7 @@ function useLiveChartController({
|
|
|
315
319
|
renderTooltip,
|
|
316
320
|
renderOverlay,
|
|
317
321
|
renderReferenceLine,
|
|
322
|
+
renderOffAxisReferenceLine,
|
|
318
323
|
referenceLineGrouping,
|
|
319
324
|
leftEdgeFade = true,
|
|
320
325
|
|
|
@@ -391,18 +396,18 @@ function useLiveChartController({
|
|
|
391
396
|
const seededRef = useRef<(number | undefined)[]>([]);
|
|
392
397
|
const refValueSig = allRefLines.map((l) => l.value ?? "_").join(",");
|
|
393
398
|
useEffect(() => {
|
|
394
|
-
const active = dragActive.
|
|
395
|
-
const cur = dragValues.
|
|
399
|
+
const active = dragActive.get();
|
|
400
|
+
const cur = dragValues.get();
|
|
396
401
|
const seeded = seededRef.current;
|
|
397
|
-
dragValues.
|
|
402
|
+
dragValues.set(allRefLines.map((l, i) => {
|
|
398
403
|
const prop = l.value ?? 0;
|
|
399
404
|
if (active[i]) return cur[i] ?? prop; // mid-drag → keep the dragged value
|
|
400
405
|
if (l.value !== seeded[i]) return prop; // prop changed → adopt (controlled)
|
|
401
406
|
return cur[i] ?? prop; // unchanged → keep current (uncontrolled persist)
|
|
402
|
-
});
|
|
407
|
+
}));
|
|
403
408
|
seededRef.current = allRefLines.map((l) => l.value);
|
|
404
|
-
if (dragActive.
|
|
405
|
-
dragActive.
|
|
409
|
+
if (dragActive.get().length !== allRefLines.length) {
|
|
410
|
+
dragActive.set(allRefLines.map((_, i) => active[i] ?? false));
|
|
406
411
|
}
|
|
407
412
|
// allRefLines is rebuilt every render; key off the value signature + length.
|
|
408
413
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -414,6 +419,17 @@ function useLiveChartController({
|
|
|
414
419
|
allRefLines,
|
|
415
420
|
renderReferenceLine,
|
|
416
421
|
);
|
|
422
|
+
// An off-axis renderer replaces only the edge-pinned tag. A full custom tag
|
|
423
|
+
// takes precedence for the same line to avoid mounting two native tags.
|
|
424
|
+
const refLineOffAxisCustom = customReferenceLineFlags(
|
|
425
|
+
allRefLines,
|
|
426
|
+
renderOffAxisReferenceLine,
|
|
427
|
+
"off-axis",
|
|
428
|
+
).map((custom, index) => custom && !refLineCustom[index]);
|
|
429
|
+
const refLineKeys = referenceLineReactKeys(allRefLines);
|
|
430
|
+
// RN custom tags report their measured widths here so the Skia connector can
|
|
431
|
+
// start after the native badge instead of the hidden built-in pill.
|
|
432
|
+
const refLineCustomTagWidths = useSharedValue<number[]>([]);
|
|
417
433
|
|
|
418
434
|
// Live Y values of the *draggable* Form-A lines, folded into the engine's
|
|
419
435
|
// axis-range fit so dragging a line toward / past the visible edge expands the
|
|
@@ -425,13 +441,17 @@ function useLiveChartController({
|
|
|
425
441
|
// contributes its live value; a series contributes its window min/max
|
|
426
442
|
// (respecting `extendToNow`), so an off-range break-even expands the axis and
|
|
427
443
|
// stays on-plot like a reference line would.
|
|
428
|
-
const draggableRefIdx =
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
444
|
+
const draggableRefIdx: number[] = [];
|
|
445
|
+
for (let i = 0; i < allRefLines.length; i++) {
|
|
446
|
+
const line = allRefLines[i];
|
|
447
|
+
if (
|
|
448
|
+
line.draggable &&
|
|
449
|
+
!line.excludeFromRange &&
|
|
450
|
+
referenceLineForm(line) === "line"
|
|
451
|
+
) {
|
|
452
|
+
draggableRefIdx.push(i);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
435
455
|
const thresholdInRange = thresholdCfg?.includeInRange === true;
|
|
436
456
|
// Constant benchmark → its live value rides this channel. A series threshold
|
|
437
457
|
// is folded inside the engine tick instead (`thresholdRangePoints`), which
|
|
@@ -576,6 +596,7 @@ function useLiveChartController({
|
|
|
576
596
|
// react-doctor-disable-next-line react-doctor/no-derived-state-effect -- Reanimated: must read the SharedValue off the render path
|
|
577
597
|
useLayoutEffect(() => {
|
|
578
598
|
// react-doctor-disable-next-line react-hooks-js/set-state-in-effect -- Reanimated: seeding from a SharedValue off render is the warning-free path
|
|
599
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Reanimated: seed from the SharedValue outside render to avoid strict-mode access warnings
|
|
579
600
|
setValueLayoutSample(value.get());
|
|
580
601
|
}, [value]);
|
|
581
602
|
|
|
@@ -725,7 +746,7 @@ function useLiveChartController({
|
|
|
725
746
|
if (
|
|
726
747
|
referenceLineForm(l) !== "line" ||
|
|
727
748
|
l.value === undefined ||
|
|
728
|
-
refLineCustom[i]
|
|
749
|
+
refLineCustom[i] || refLineOffAxisCustom[i]
|
|
729
750
|
) {
|
|
730
751
|
ys.push(-1);
|
|
731
752
|
continue;
|
|
@@ -870,11 +891,8 @@ function useLiveChartController({
|
|
|
870
891
|
// polyline passed as the last arg below).
|
|
871
892
|
thresholdCfg?.fill && !thresholdIsSeries ? thresholdGeom.lineY : undefined,
|
|
872
893
|
lineIsLinear,
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
// live value when `followViewEdge` tracks the scrolled-back window.
|
|
876
|
-
engine.edgeValue,
|
|
877
|
-
badgeCfg?.followViewEdge ?? false,
|
|
894
|
+
// The plotted line independently selects the visible edge while historical;
|
|
895
|
+
// badge/live-indicator options affect overlays only.
|
|
878
896
|
// Match the standalone loading squiggle's wave during the reveal morph.
|
|
879
897
|
loadingCfg?.amplitude,
|
|
880
898
|
loadingCfg?.speed,
|
|
@@ -910,6 +928,15 @@ function useLiveChartController({
|
|
|
910
928
|
);
|
|
911
929
|
|
|
912
930
|
const momentumSV = useMomentum(engine, momentum);
|
|
931
|
+
// A follow-edge badge must derive its color from the same historical point
|
|
932
|
+
// as its value and position. While following live, reuse the normal momentum
|
|
933
|
+
// result; while scrolled back, detect against the data prefix ending at the
|
|
934
|
+
// visible right-edge timestamp so incoming live ticks cannot change the pill.
|
|
935
|
+
const badgeMomentumSV = useDerivedValue(() =>
|
|
936
|
+
badgeCfg?.followViewEdge && engine.viewEnd.value !== null
|
|
937
|
+
? resolveMomentumProp(momentum, engine.data.value, engine.timestamp.value)
|
|
938
|
+
: momentumSV.value,
|
|
939
|
+
);
|
|
913
940
|
|
|
914
941
|
// ── Overlay hooks ─────────────────────────────────────────────────────
|
|
915
942
|
// Scrub/crosshair must see the same stash-backed candles as the engine.
|
|
@@ -1001,6 +1028,14 @@ function useLiveChartController({
|
|
|
1001
1028
|
? (timeScrollHoldMs ?? (scrubCfg?.panGestureDelay || HOLD_TO_SCRUB_MS))
|
|
1002
1029
|
: (scrubCfg?.panGestureDelay ?? 0);
|
|
1003
1030
|
|
|
1031
|
+
// Cross-gesture arbitration for the one-finger touch. `Gesture.Race` below is
|
|
1032
|
+
// NOT arbitration — RNGH's Race adds no relation between its children, so both
|
|
1033
|
+
// pans recognize independently and each can activate while the other already
|
|
1034
|
+
// owns the touch. This latch (written by the scroll pan, read by the scrub's
|
|
1035
|
+
// long-press guard) makes "the scroll already won" a hard fact; `scrubActive`
|
|
1036
|
+
// (written by the crosshair, read by the scroll pan) is the mirror image.
|
|
1037
|
+
const scrollActive = useSharedValue(false);
|
|
1038
|
+
|
|
1004
1039
|
const crosshair = useCrosshair(
|
|
1005
1040
|
engine,
|
|
1006
1041
|
effectivePadding,
|
|
@@ -1030,6 +1065,7 @@ function useLiveChartController({
|
|
|
1030
1065
|
timeScrollEnabled && scrollGestureMode === "axisDrag"
|
|
1031
1066
|
? Math.max(effectivePadding.bottom, AXIS_GRAB_MIN_PX)
|
|
1032
1067
|
: 0,
|
|
1068
|
+
scrollActive,
|
|
1033
1069
|
);
|
|
1034
1070
|
|
|
1035
1071
|
// Capture only the shared value in the worklets below. Referencing
|
|
@@ -1052,6 +1088,10 @@ function useLiveChartController({
|
|
|
1052
1088
|
minTime: scrollMinTime,
|
|
1053
1089
|
enabled: timeScrollEnabled,
|
|
1054
1090
|
mode: scrollGestureMode,
|
|
1091
|
+
scrollActive,
|
|
1092
|
+
// Once a scrub is engaged the chart is locked: scrolling goes inert so the
|
|
1093
|
+
// finger only moves the price indicator across a fixed window.
|
|
1094
|
+
scrubActive: crosshairScrubActive,
|
|
1055
1095
|
// Clear any live crosshair when a scroll drag takes over.
|
|
1056
1096
|
onScrollStart: () => {
|
|
1057
1097
|
"worklet";
|
|
@@ -1176,10 +1216,40 @@ function useLiveChartController({
|
|
|
1176
1216
|
// point instead — otherwise both dots show at once. Applies on static charts
|
|
1177
1217
|
// too, now that they're scrubbable.
|
|
1178
1218
|
const selectionDotDuringScrub = scrubCfg !== null && selectionDotCfg !== null;
|
|
1219
|
+
// While scrolled back, the badge, dot, and value line still point at the live
|
|
1220
|
+
// price even though the window is showing history. Hide that live-priced group
|
|
1221
|
+
// together. `badge.followViewEdge` opts back in because all three then track
|
|
1222
|
+
// the visible edge price; `hideLiveOnScrollBack: false` keeps the legacy group.
|
|
1223
|
+
const hideLiveOnScrollBack = resolveHideLiveOnScrollBack(
|
|
1224
|
+
timeScroll,
|
|
1225
|
+
badgeCfg?.followViewEdge ?? false,
|
|
1226
|
+
);
|
|
1179
1227
|
const liveDotOpacity = useDerivedValue(
|
|
1180
1228
|
() =>
|
|
1181
1229
|
reveal.dotOpacity.value *
|
|
1182
|
-
(selectionDotDuringScrub && crosshairScrubActive.value ? 0 : 1)
|
|
1230
|
+
(selectionDotDuringScrub && crosshairScrubActive.value ? 0 : 1) *
|
|
1231
|
+
liveIndicatorScrollOpacity(
|
|
1232
|
+
hideLiveOnScrollBack,
|
|
1233
|
+
engine.viewEnd.value,
|
|
1234
|
+
),
|
|
1235
|
+
);
|
|
1236
|
+
// Same scrolled-back gating for the value line: it would draw a dashed line
|
|
1237
|
+
// at the live value's Y — a price that isn't in the scrolled-back view.
|
|
1238
|
+
const valueLineOpacity = useDerivedValue(
|
|
1239
|
+
() =>
|
|
1240
|
+
reveal.lineOpacity.value *
|
|
1241
|
+
liveIndicatorScrollOpacity(
|
|
1242
|
+
hideLiveOnScrollBack,
|
|
1243
|
+
engine.viewEnd.value,
|
|
1244
|
+
),
|
|
1245
|
+
);
|
|
1246
|
+
const liveBadgeOpacity = useDerivedValue(
|
|
1247
|
+
() =>
|
|
1248
|
+
reveal.badgeOpacity.value *
|
|
1249
|
+
liveIndicatorScrollOpacity(
|
|
1250
|
+
hideLiveOnScrollBack,
|
|
1251
|
+
engine.viewEnd.value,
|
|
1252
|
+
),
|
|
1183
1253
|
);
|
|
1184
1254
|
|
|
1185
1255
|
// Fade the annotation overlays (markers + reference lines) out while scrubbing
|
|
@@ -1204,6 +1274,7 @@ function useLiveChartController({
|
|
|
1204
1274
|
return {
|
|
1205
1275
|
// passthrough props the render needs
|
|
1206
1276
|
style,
|
|
1277
|
+
canvasMode,
|
|
1207
1278
|
accessibilityLabel,
|
|
1208
1279
|
accessibilityRole,
|
|
1209
1280
|
emptyText,
|
|
@@ -1238,10 +1309,14 @@ function useLiveChartController({
|
|
|
1238
1309
|
leftEdgeFadeCfg,
|
|
1239
1310
|
metricsCfg,
|
|
1240
1311
|
allRefLines,
|
|
1312
|
+
refLineKeys,
|
|
1241
1313
|
refLineCustom,
|
|
1314
|
+
refLineOffAxisCustom,
|
|
1315
|
+
refLineCustomTagWidths,
|
|
1242
1316
|
dragValues,
|
|
1243
1317
|
dragActive,
|
|
1244
1318
|
renderReferenceLine,
|
|
1319
|
+
renderOffAxisReferenceLine,
|
|
1245
1320
|
refGroupingActive: refGroupingRadius != null,
|
|
1246
1321
|
refGroupResult,
|
|
1247
1322
|
groupHidden,
|
|
@@ -1314,9 +1389,12 @@ function useLiveChartController({
|
|
|
1314
1389
|
dotX,
|
|
1315
1390
|
dotY,
|
|
1316
1391
|
liveDotOpacity,
|
|
1392
|
+
valueLineOpacity,
|
|
1393
|
+
liveBadgeOpacity,
|
|
1317
1394
|
overlayScrubFade,
|
|
1318
1395
|
markerGroupOpacity,
|
|
1319
1396
|
momentumSV,
|
|
1397
|
+
badgeMomentumSV,
|
|
1320
1398
|
onDegenShake,
|
|
1321
1399
|
crosshair,
|
|
1322
1400
|
rootGesture,
|
|
@@ -1362,9 +1440,7 @@ function ChartWithDegen({
|
|
|
1362
1440
|
onDegenShake,
|
|
1363
1441
|
isStatic,
|
|
1364
1442
|
);
|
|
1365
|
-
return
|
|
1366
|
-
<ChartView model={model} yAxisEntries={yAxisEntries} degen={state} />
|
|
1367
|
-
);
|
|
1443
|
+
return <ChartView model={model} yAxisEntries={yAxisEntries} degen={state} />;
|
|
1368
1444
|
}
|
|
1369
1445
|
|
|
1370
1446
|
/**
|
|
@@ -1419,6 +1495,7 @@ function ChartYAxisLayer({
|
|
|
1419
1495
|
metricsCfg,
|
|
1420
1496
|
gridStyleCfg,
|
|
1421
1497
|
yAxisFloat,
|
|
1498
|
+
liveBadgeOpacity,
|
|
1422
1499
|
} = model;
|
|
1423
1500
|
return (
|
|
1424
1501
|
<Group opacity={reveal.yAxisOpacity}>
|
|
@@ -1436,6 +1513,7 @@ function ChartYAxisLayer({
|
|
|
1436
1513
|
badgeCenterY={badgeUsesRightGutter ? dotY : undefined}
|
|
1437
1514
|
badgeFontSize={badgeUsesRightGutter ? badgeFont.getSize() : undefined}
|
|
1438
1515
|
badgeOffsetY={badgeCfg?.offsetY ?? 0}
|
|
1516
|
+
badgeOpacity={badgeUsesRightGutter ? liveBadgeOpacity : undefined}
|
|
1439
1517
|
gridStyle={gridStyleCfg}
|
|
1440
1518
|
/>
|
|
1441
1519
|
</Group>
|
|
@@ -1675,8 +1753,10 @@ function ChartStack({
|
|
|
1675
1753
|
fontProp,
|
|
1676
1754
|
badgeCfg,
|
|
1677
1755
|
valueLineCfg,
|
|
1756
|
+
valueLineOpacity,
|
|
1678
1757
|
dotY,
|
|
1679
1758
|
allRefLines,
|
|
1759
|
+
refLineKeys,
|
|
1680
1760
|
dragValues,
|
|
1681
1761
|
resolvedSegments,
|
|
1682
1762
|
hasRecolorSegments,
|
|
@@ -1721,14 +1801,15 @@ function ChartStack({
|
|
|
1721
1801
|
loadingStrokeWidth,
|
|
1722
1802
|
loadingAmplitude,
|
|
1723
1803
|
loadingSpeed,
|
|
1804
|
+
canvasMode,
|
|
1724
1805
|
} = model;
|
|
1725
1806
|
return (
|
|
1726
1807
|
<Group transform={degen?.shakeTransform}>
|
|
1727
1808
|
{/* Segment dividers + labels (behind the line). The scrub-focus emphasis is
|
|
1728
1809
|
painted on the line stroke itself, below — this overlay draws no fill. */}
|
|
1729
|
-
{resolvedSegments.map((seg
|
|
1810
|
+
{resolvedSegments.map((seg) => (
|
|
1730
1811
|
<SegmentDividerOverlay
|
|
1731
|
-
key={
|
|
1812
|
+
key={segmentReactKey(seg)}
|
|
1732
1813
|
engine={engine}
|
|
1733
1814
|
padding={effectivePadding}
|
|
1734
1815
|
segment={seg}
|
|
@@ -1738,7 +1819,7 @@ function ChartStack({
|
|
|
1738
1819
|
|
|
1739
1820
|
{/* Value line + reference line (behind chart line) */}
|
|
1740
1821
|
{valueLineCfg && (
|
|
1741
|
-
<Group opacity={
|
|
1822
|
+
<Group opacity={valueLineOpacity}>
|
|
1742
1823
|
<ValueLineOverlay
|
|
1743
1824
|
dotY={dotY}
|
|
1744
1825
|
engine={engine}
|
|
@@ -1750,14 +1831,12 @@ function ChartStack({
|
|
|
1750
1831
|
</Group>
|
|
1751
1832
|
)}
|
|
1752
1833
|
|
|
1753
|
-
{/*
|
|
1754
|
-
|
|
1755
|
-
content-derived key would collapse to one. Wrapped in a fade group so
|
|
1756
|
-
`scrub.hideOverlaysOnScrub` can ease the lines out while scrubbing. */}
|
|
1834
|
+
{/* Wrapped in a fade group so `scrub.hideOverlaysOnScrub` can ease lines
|
|
1835
|
+
out while scrubbing. Explicit ids keep lines stable when reordered. */}
|
|
1757
1836
|
<Group opacity={overlayScrubFade}>
|
|
1758
1837
|
{allRefLines.map((rl, i) => (
|
|
1759
1838
|
<ReferenceLineOverlay
|
|
1760
|
-
key={i}
|
|
1839
|
+
key={refLineKeys[i]}
|
|
1761
1840
|
engine={engine}
|
|
1762
1841
|
padding={effectivePadding}
|
|
1763
1842
|
line={rl}
|
|
@@ -1861,7 +1940,6 @@ function ChartStack({
|
|
|
1861
1940
|
dotX={dotX}
|
|
1862
1941
|
dotY={dotY}
|
|
1863
1942
|
palette={palette}
|
|
1864
|
-
engine={engine}
|
|
1865
1943
|
pulse={pulseCfg}
|
|
1866
1944
|
radius={dotCfg.radius}
|
|
1867
1945
|
ring={dotCfg.ring}
|
|
@@ -1939,6 +2017,7 @@ function ChartStack({
|
|
|
1939
2017
|
lineStrokeWidth={loadingStrokeWidth}
|
|
1940
2018
|
waveAmplitude={loadingAmplitude}
|
|
1941
2019
|
waveSpeed={loadingSpeed}
|
|
2020
|
+
opaqueCanvas={canvasMode === "opaque"}
|
|
1942
2021
|
/>
|
|
1943
2022
|
</Group>
|
|
1944
2023
|
);
|
|
@@ -2003,6 +2082,7 @@ function ChartScrubLayer({
|
|
|
2003
2082
|
selectionDot,
|
|
2004
2083
|
selectionColor,
|
|
2005
2084
|
renderTooltip,
|
|
2085
|
+
canvasMode,
|
|
2006
2086
|
} = model;
|
|
2007
2087
|
// A custom tooltip is an RN overlay (sibling of <Canvas>), so the built-in
|
|
2008
2088
|
// Skia tooltip is suppressed here while it's active — the line pill in line
|
|
@@ -2047,6 +2127,7 @@ function ChartScrubLayer({
|
|
|
2047
2127
|
tooltipBorderRadius={scrubCfg.tooltipBorderRadius}
|
|
2048
2128
|
tooltipShowValue={scrubCfg.tooltipShowValue}
|
|
2049
2129
|
tooltipShowTime={scrubCfg.tooltipShowTime}
|
|
2130
|
+
opaqueCanvas={canvasMode === "opaque"}
|
|
2050
2131
|
>
|
|
2051
2132
|
{/* Candle charts render a multi-line OHLC tooltip; the line
|
|
2052
2133
|
chart falls back to CrosshairOverlay's default value/time
|
|
@@ -2065,6 +2146,19 @@ function ChartScrubLayer({
|
|
|
2065
2146
|
);
|
|
2066
2147
|
}
|
|
2067
2148
|
|
|
2149
|
+
/** A segment's time range and presentation uniquely identify its divider view. */
|
|
2150
|
+
function segmentReactKey(segment: ResolvedSegment): string {
|
|
2151
|
+
return [
|
|
2152
|
+
"seg",
|
|
2153
|
+
segment.from ?? "start",
|
|
2154
|
+
segment.to ?? "end",
|
|
2155
|
+
segment.divider,
|
|
2156
|
+
segment.dividerColor,
|
|
2157
|
+
segment.label ?? "",
|
|
2158
|
+
segment.labelPosition,
|
|
2159
|
+
].join(":");
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2068
2162
|
/** Live-value text drawn as its own canvas layer, above both the area gradient
|
|
2069
2163
|
* and the left-edge fade, so the large number stays crisp at the left edge
|
|
2070
2164
|
* instead of being washed out by the fade's `dstOut` blend. */
|
|
@@ -2116,14 +2210,14 @@ function ChartBadgeLayer({
|
|
|
2116
2210
|
}) {
|
|
2117
2211
|
const {
|
|
2118
2212
|
badgeFont,
|
|
2119
|
-
reveal,
|
|
2120
2213
|
engine,
|
|
2121
2214
|
effectivePadding,
|
|
2122
2215
|
palette,
|
|
2123
2216
|
formatValue,
|
|
2124
|
-
|
|
2217
|
+
badgeMomentumSV,
|
|
2125
2218
|
metricsCfg,
|
|
2126
2219
|
yAxisFloat,
|
|
2220
|
+
liveBadgeOpacity,
|
|
2127
2221
|
} = model;
|
|
2128
2222
|
const badgeCfg = model.badgeCfg!;
|
|
2129
2223
|
const badgeData = useBadge(
|
|
@@ -2134,7 +2228,7 @@ function ChartBadgeLayer({
|
|
|
2134
2228
|
badgeFont,
|
|
2135
2229
|
badgeCfg.variant,
|
|
2136
2230
|
badgeCfg.tail,
|
|
2137
|
-
|
|
2231
|
+
badgeMomentumSV,
|
|
2138
2232
|
badgeCfg.position,
|
|
2139
2233
|
badgeCfg.background,
|
|
2140
2234
|
metricsCfg.badge,
|
|
@@ -2147,7 +2241,7 @@ function ChartBadgeLayer({
|
|
|
2147
2241
|
);
|
|
2148
2242
|
return (
|
|
2149
2243
|
<Group transform={degen?.shakeTransform}>
|
|
2150
|
-
<Group opacity={
|
|
2244
|
+
<Group opacity={liveBadgeOpacity}>
|
|
2151
2245
|
<BadgeOverlay
|
|
2152
2246
|
badge={badgeData}
|
|
2153
2247
|
font={badgeFont}
|
|
@@ -2174,7 +2268,10 @@ function ChartRefBadgeLayer({
|
|
|
2174
2268
|
}) {
|
|
2175
2269
|
const {
|
|
2176
2270
|
allRefLines,
|
|
2271
|
+
refLineKeys,
|
|
2177
2272
|
refLineCustom,
|
|
2273
|
+
refLineOffAxisCustom,
|
|
2274
|
+
refLineCustomTagWidths,
|
|
2178
2275
|
dragValues,
|
|
2179
2276
|
groupHidden,
|
|
2180
2277
|
refGroupResult,
|
|
@@ -2195,7 +2292,7 @@ function ChartRefBadgeLayer({
|
|
|
2195
2292
|
<Group transform={degen?.shakeTransform} opacity={overlayScrubFade}>
|
|
2196
2293
|
{allRefLines.map((rl, i) => (
|
|
2197
2294
|
<ReferenceLineOverlay
|
|
2198
|
-
key={i}
|
|
2295
|
+
key={refLineKeys[i]}
|
|
2199
2296
|
engine={engine}
|
|
2200
2297
|
padding={effectivePadding}
|
|
2201
2298
|
line={rl}
|
|
@@ -2207,6 +2304,8 @@ function ChartRefBadgeLayer({
|
|
|
2207
2304
|
dragValues={dragValues}
|
|
2208
2305
|
index={i}
|
|
2209
2306
|
suppressTag={refLineCustom[i]}
|
|
2307
|
+
suppressTagWhenOffAxis={refLineOffAxisCustom[i]}
|
|
2308
|
+
customTagWidths={refLineCustomTagWidths}
|
|
2210
2309
|
groupHidden={refGroupingActive ? groupHidden : undefined}
|
|
2211
2310
|
/>
|
|
2212
2311
|
))}
|
|
@@ -2277,8 +2376,11 @@ function ChartCustomAnnotations({ model }: { model: LiveChartModel }) {
|
|
|
2277
2376
|
markerClusterCfg,
|
|
2278
2377
|
renderMarker,
|
|
2279
2378
|
renderReferenceLine,
|
|
2379
|
+
renderOffAxisReferenceLine,
|
|
2280
2380
|
allRefLines,
|
|
2281
2381
|
refLineCustom,
|
|
2382
|
+
refLineOffAxisCustom,
|
|
2383
|
+
refLineCustomTagWidths,
|
|
2282
2384
|
dragValues,
|
|
2283
2385
|
dragActive,
|
|
2284
2386
|
engine,
|
|
@@ -2317,6 +2419,21 @@ function ChartCustomAnnotations({ model }: { model: LiveChartModel }) {
|
|
|
2317
2419
|
formatValue={formatValue}
|
|
2318
2420
|
dragValues={dragValues}
|
|
2319
2421
|
dragActive={dragActive}
|
|
2422
|
+
tagWidths={refLineCustomTagWidths}
|
|
2423
|
+
/>
|
|
2424
|
+
)}
|
|
2425
|
+
{renderOffAxisReferenceLine && allRefLines.length > 0 && (
|
|
2426
|
+
<CustomReferenceLineOverlay
|
|
2427
|
+
lines={allRefLines}
|
|
2428
|
+
renderReferenceLine={renderOffAxisReferenceLine}
|
|
2429
|
+
custom={refLineOffAxisCustom}
|
|
2430
|
+
engine={engine}
|
|
2431
|
+
padding={effectivePadding}
|
|
2432
|
+
formatValue={formatValue}
|
|
2433
|
+
dragValues={dragValues}
|
|
2434
|
+
dragActive={dragActive}
|
|
2435
|
+
tagWidths={refLineCustomTagWidths}
|
|
2436
|
+
offAxisOnly
|
|
2320
2437
|
/>
|
|
2321
2438
|
)}
|
|
2322
2439
|
</Animated.View>
|
|
@@ -2358,12 +2475,14 @@ function ChartView({
|
|
|
2358
2475
|
renderTooltip,
|
|
2359
2476
|
renderOverlay,
|
|
2360
2477
|
renderReferenceLine,
|
|
2478
|
+
renderOffAxisReferenceLine,
|
|
2361
2479
|
allRefLines,
|
|
2362
2480
|
scrubCfg,
|
|
2363
2481
|
crosshair,
|
|
2364
2482
|
extremaTimeOffset,
|
|
2365
2483
|
topConnector,
|
|
2366
2484
|
bottomConnector,
|
|
2485
|
+
canvasMode,
|
|
2367
2486
|
} = model;
|
|
2368
2487
|
|
|
2369
2488
|
return (
|
|
@@ -2375,7 +2494,21 @@ function ChartView({
|
|
|
2375
2494
|
accessibilityLabel={accessibilityLabel}
|
|
2376
2495
|
accessibilityRole={accessibilityRole}
|
|
2377
2496
|
>
|
|
2378
|
-
|
|
2497
|
+
{/* Skia chooses TextureView vs SurfaceView when the native Canvas mounts. */}
|
|
2498
|
+
<Canvas
|
|
2499
|
+
key={canvasMode}
|
|
2500
|
+
style={{ flex: 1 }}
|
|
2501
|
+
opaque={canvasMode === "opaque"}
|
|
2502
|
+
>
|
|
2503
|
+
{canvasMode === "opaque" && (
|
|
2504
|
+
<Rect
|
|
2505
|
+
x={0}
|
|
2506
|
+
y={0}
|
|
2507
|
+
width={engine.canvasWidth}
|
|
2508
|
+
height={engine.canvasHeight}
|
|
2509
|
+
color={backgroundColor}
|
|
2510
|
+
/>
|
|
2511
|
+
)}
|
|
2379
2512
|
{/* Background fills first, then the left-edge fade (a canvas-space sibling
|
|
2380
2513
|
so dstOut blends correctly), then the line stack on top — so the fade
|
|
2381
2514
|
softens only the fills and the line stays crisp at the left edge. */}
|
|
@@ -2392,15 +2525,14 @@ function ChartView({
|
|
|
2392
2525
|
startColor={leftEdgeFadeCfg.startColor}
|
|
2393
2526
|
endColor={leftEdgeFadeCfg.endColor}
|
|
2394
2527
|
engine={engine}
|
|
2528
|
+
opaqueBackgroundRgb={
|
|
2529
|
+
canvasMode === "opaque" ? palette.bgRgb : undefined
|
|
2530
|
+
}
|
|
2395
2531
|
/>
|
|
2396
2532
|
)}
|
|
2397
2533
|
|
|
2398
2534
|
{/* Line stack above the fade so the line stays crisp at the left edge. */}
|
|
2399
|
-
<ChartStack
|
|
2400
|
-
model={model}
|
|
2401
|
-
yAxisEntries={yAxisEntries}
|
|
2402
|
-
degen={degen}
|
|
2403
|
-
/>
|
|
2535
|
+
<ChartStack model={model} yAxisEntries={yAxisEntries} degen={degen} />
|
|
2404
2536
|
|
|
2405
2537
|
{/* "extrema-edge" connector lines (dot → edge readout), above the chart
|
|
2406
2538
|
content so the dashed guide reads over the line / candles. */}
|
|
@@ -2453,7 +2585,8 @@ function ChartView({
|
|
|
2453
2585
|
Skia markers (the wrapper is full-bleed; children keep their own
|
|
2454
2586
|
absolute positions). */}
|
|
2455
2587
|
{((markersActive && renderMarker) ||
|
|
2456
|
-
(renderReferenceLine
|
|
2588
|
+
((renderReferenceLine || renderOffAxisReferenceLine) &&
|
|
2589
|
+
allRefLines.length > 0)) && (
|
|
2457
2590
|
<ChartCustomAnnotations model={model} />
|
|
2458
2591
|
)}
|
|
2459
2592
|
|