react-native-livechart 4.11.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/dist/components/CrosshairLine.d.ts +3 -1
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CustomReferenceLineOverlay.d.ts +15 -10
- package/dist/components/CustomReferenceLineOverlay.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/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.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/YAxisOverlay.d.ts +3 -1
- package/dist/components/YAxisOverlay.d.ts.map +1 -1
- 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/hooks/crosshairSeries.d.ts +10 -2
- package/dist/hooks/crosshairSeries.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +33 -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/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/types.d.ts +160 -18
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/CrosshairLine.tsx +4 -1
- package/src/components/CustomReferenceLineOverlay.tsx +42 -13
- package/src/components/DotOverlay.tsx +24 -8
- package/src/components/LiveChart.tsx +157 -37
- package/src/components/LiveChartSeries.tsx +128 -16
- package/src/components/PerSeriesTooltipOverlay.tsx +287 -0
- package/src/components/ReferenceLineOverlay.tsx +274 -141
- package/src/components/YAxisOverlay.tsx +5 -1
- package/src/core/liveIndicatorVisibility.ts +25 -0
- package/src/core/resolveConfig.ts +86 -0
- package/src/hooks/crosshairSeries.ts +262 -33
- package/src/hooks/crosshairShared.ts +36 -2
- package/src/hooks/delayedPanGuard.ts +111 -6
- package/src/hooks/useChartPaths.ts +25 -15
- package/src/hooks/useCrosshair.ts +57 -4
- package/src/hooks/useCrosshairSeries.ts +95 -7
- package/src/hooks/useMomentum.ts +8 -1
- package/src/hooks/usePanScroll.ts +46 -5
- package/src/index.ts +1 -0
- package/src/math/lerp.ts +7 -0
- package/src/math/momentum.ts +22 -5
- package/src/math/referenceLines.ts +38 -0
- package/src/types.ts +168 -18
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
lineStyleSignatureFromArray,
|
|
29
29
|
resolveMultiSeriesLineColorsSnapshot,
|
|
30
30
|
resolveMultiSeriesLineStylesSnapshot,
|
|
31
|
-
type SeriesLineStyle,
|
|
32
31
|
} from "../core/multiSeriesLayout";
|
|
33
32
|
import {
|
|
34
33
|
resolveAxisLabel,
|
|
@@ -53,6 +52,7 @@ import { pulseRadialOutset } from "../draw/line";
|
|
|
53
52
|
import { resolveChartLayout } from "../hooks/resolveChartLayout";
|
|
54
53
|
import { useCanvasLayout } from "../hooks/useCanvasLayout";
|
|
55
54
|
import { useChartReveal } from "../hooks/useChartReveal";
|
|
55
|
+
import { useChartOverlayContext } from "../hooks/useChartOverlayContext";
|
|
56
56
|
import { useChartSkiaFont } from "../hooks/useChartSkiaFont";
|
|
57
57
|
import { useCrosshairSeries } from "../hooks/useCrosshairSeries";
|
|
58
58
|
import { useMarkers } from "../hooks/useMarkers";
|
|
@@ -70,7 +70,10 @@ import {
|
|
|
70
70
|
} from "../lib/format";
|
|
71
71
|
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
72
72
|
import { MONO_FONT_FAMILY } from "../lib/monoFontFamily";
|
|
73
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
collectReferenceValues,
|
|
75
|
+
referenceLineReactKeys,
|
|
76
|
+
} from "../math/referenceLines";
|
|
74
77
|
import {
|
|
75
78
|
applyPaletteOverride,
|
|
76
79
|
leftEdgeFadeColorsFromBgRgb,
|
|
@@ -78,6 +81,7 @@ import {
|
|
|
78
81
|
} from "../theme";
|
|
79
82
|
import type { LiveChartSeriesProps, Marker, SeriesConfig } from "../types";
|
|
80
83
|
import { AxisLabelOverlay } from "./AxisLabelOverlay";
|
|
84
|
+
import { ChartOverlayLayer } from "./ChartOverlayLayer";
|
|
81
85
|
import {
|
|
82
86
|
ExtremaConnectorOverlay,
|
|
83
87
|
labelConnector,
|
|
@@ -96,6 +100,7 @@ import { MultiSeriesDots } from "./MultiSeriesDots";
|
|
|
96
100
|
import { MultiSeriesStroke } from "./MultiSeriesStroke";
|
|
97
101
|
import { MultiSeriesValueLabels } from "./MultiSeriesValueLabels";
|
|
98
102
|
import { MultiSeriesValueLines } from "./MultiSeriesValueLines";
|
|
103
|
+
import { PerSeriesTooltipOverlay } from "./PerSeriesTooltipOverlay";
|
|
99
104
|
import { ReferenceLineOverlay } from "./ReferenceLineOverlay";
|
|
100
105
|
import { SeriesToggleChips } from "./SeriesToggleChips";
|
|
101
106
|
import { XAxisOverlay } from "./XAxisOverlay";
|
|
@@ -177,7 +182,9 @@ function useLiveChartSeriesController({
|
|
|
177
182
|
markerHitRadius = 16,
|
|
178
183
|
markerCluster,
|
|
179
184
|
renderMarker,
|
|
185
|
+
renderOverlay,
|
|
180
186
|
renderReferenceLine,
|
|
187
|
+
renderOffAxisReferenceLine,
|
|
181
188
|
leftEdgeFade = true,
|
|
182
189
|
}: LiveChartSeriesProps) {
|
|
183
190
|
const emptyMarkers = useSharedValue<Marker[]>([]);
|
|
@@ -190,6 +197,10 @@ function useLiveChartSeriesController({
|
|
|
190
197
|
const bottomLabelCfg = resolveAxisLabel(bottomLabel);
|
|
191
198
|
const scrubCfg = resolveScrub(scrub);
|
|
192
199
|
const scrubEnabled = scrubCfg !== null;
|
|
200
|
+
// Opt-in and subordinate to the existing master `tooltip` switch. Keeping
|
|
201
|
+
// this null by default preserves LiveChartSeries' historical guide-only scrub.
|
|
202
|
+
const seriesTooltipCfg =
|
|
203
|
+
scrubCfg?.tooltip === true ? scrubCfg.seriesTooltip : null;
|
|
193
204
|
|
|
194
205
|
// Time-scroll + pinch-zoom (mirrors LiveChart). LiveChartSeries has no static
|
|
195
206
|
// mode, so these gate on the props alone. `holdToScrub` requires the scrub
|
|
@@ -216,8 +227,8 @@ function useLiveChartSeriesController({
|
|
|
216
227
|
|
|
217
228
|
// Multi-series defaults the scrub selection dot OFF: it can only track one
|
|
218
229
|
// line (the leading series), which reads as a bug next to the other series.
|
|
219
|
-
// The crosshair line
|
|
220
|
-
// Passing `selectionDot` explicitly
|
|
230
|
+
// The crosshair line (and the opt-in tooltip's per-series intersection dots)
|
|
231
|
+
// mark the scrub point. Passing `selectionDot` explicitly still opts it in.
|
|
221
232
|
const selectionDotCfg = resolveSelectionDot(selectionDot ?? false);
|
|
222
233
|
const gridStyleCfg = resolveGridStyle(gridStyle);
|
|
223
234
|
const dotCfg = resolveMultiSeriesDot(dotProp);
|
|
@@ -237,6 +248,17 @@ function useLiveChartSeriesController({
|
|
|
237
248
|
allRefLines,
|
|
238
249
|
renderReferenceLine,
|
|
239
250
|
);
|
|
251
|
+
// A full custom tag owns both states, so it takes precedence over an
|
|
252
|
+
// off-axis-only renderer for the same line.
|
|
253
|
+
const refLineOffAxisCustom = customReferenceLineFlags(
|
|
254
|
+
allRefLines,
|
|
255
|
+
renderOffAxisReferenceLine,
|
|
256
|
+
"off-axis",
|
|
257
|
+
).map((custom, index) => custom && !refLineCustom[index]);
|
|
258
|
+
const refLineKeys = referenceLineReactKeys(allRefLines);
|
|
259
|
+
// RN custom tags report their measured widths here so the Skia connector can
|
|
260
|
+
// start after the native badge instead of the hidden built-in pill.
|
|
261
|
+
const refLineCustomTagWidths = useSharedValue<number[]>([]);
|
|
240
262
|
|
|
241
263
|
const palette = applyPaletteOverride(
|
|
242
264
|
resolveTheme(accentColor, theme),
|
|
@@ -266,6 +288,7 @@ function useLiveChartSeriesController({
|
|
|
266
288
|
// render scope for memoization, which trips Reanimated's strict-mode warning;
|
|
267
289
|
// it leaves the `.get()` method call inside the effect.
|
|
268
290
|
// react-doctor-disable-next-line react-hooks-js/set-state-in-effect -- Reanimated: seeding from a SharedValue off render is the warning-free path
|
|
291
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- Reanimated: seed from the SharedValue outside render to avoid strict-mode access warnings
|
|
269
292
|
setSeriesSnapshot(series.get().slice());
|
|
270
293
|
}, [series]);
|
|
271
294
|
|
|
@@ -407,6 +430,20 @@ function useLiveChartSeriesController({
|
|
|
407
430
|
skiaFont,
|
|
408
431
|
);
|
|
409
432
|
|
|
433
|
+
// Cross-gesture arbitration for the one-finger touch. `Gesture.Race` below is
|
|
434
|
+
// NOT arbitration — RNGH's Race adds no relation between its children, so both
|
|
435
|
+
// pans recognize independently and each can activate while the other already
|
|
436
|
+
// owns the touch. This latch (written by the scroll pan, read by the scrub's
|
|
437
|
+
// long-press guard) makes "the scroll already won" a hard fact; `scrubActive`
|
|
438
|
+
// (written by the crosshair, read by the scroll pan) is the mirror image.
|
|
439
|
+
const scrollActive = useSharedValue(false);
|
|
440
|
+
// `liveEdge` includes the optional right breathing-room buffer. The time pill
|
|
441
|
+
// must clamp against the real "now" anchor so its live bucket never ends in
|
|
442
|
+
// that future buffer.
|
|
443
|
+
const tooltipMaxTime = useDerivedValue(
|
|
444
|
+
() => engine.liveEdge.get() - windowBuffer * timeWindow,
|
|
445
|
+
);
|
|
446
|
+
|
|
410
447
|
const crosshair = useCrosshairSeries(
|
|
411
448
|
engine,
|
|
412
449
|
effectivePadding,
|
|
@@ -415,6 +452,17 @@ function useLiveChartSeriesController({
|
|
|
415
452
|
scrubHoldMs,
|
|
416
453
|
onGestureStart,
|
|
417
454
|
onGestureEnd,
|
|
455
|
+
scrollActive,
|
|
456
|
+
seriesTooltipCfg
|
|
457
|
+
? {
|
|
458
|
+
config: seriesTooltipCfg,
|
|
459
|
+
formatValue,
|
|
460
|
+
formatTime,
|
|
461
|
+
font: skiaFont,
|
|
462
|
+
colors: lineColors,
|
|
463
|
+
maxTime: tooltipMaxTime,
|
|
464
|
+
}
|
|
465
|
+
: undefined,
|
|
418
466
|
);
|
|
419
467
|
|
|
420
468
|
// Capture only the shared value in the worklets below. Referencing
|
|
@@ -458,6 +506,10 @@ function useLiveChartSeriesController({
|
|
|
458
506
|
minTime: scrollMinTime,
|
|
459
507
|
enabled: timeScrollEnabled,
|
|
460
508
|
mode: scrollGestureMode,
|
|
509
|
+
scrollActive,
|
|
510
|
+
// Once a scrub is engaged the chart is locked: scrolling goes inert so the
|
|
511
|
+
// finger only moves the price indicator across a fixed window.
|
|
512
|
+
scrubActive: crosshairScrubActive,
|
|
461
513
|
onScrollStart: () => {
|
|
462
514
|
"worklet";
|
|
463
515
|
crosshairScrubActive.set(false);
|
|
@@ -535,6 +587,7 @@ function useLiveChartSeriesController({
|
|
|
535
587
|
yAxisCfg,
|
|
536
588
|
xAxisCfg,
|
|
537
589
|
scrubCfg,
|
|
590
|
+
seriesTooltipCfg,
|
|
538
591
|
gridStyleCfg,
|
|
539
592
|
dotCfg,
|
|
540
593
|
dotOuterRadius,
|
|
@@ -542,7 +595,10 @@ function useLiveChartSeriesController({
|
|
|
542
595
|
degenCfg,
|
|
543
596
|
metricsCfg,
|
|
544
597
|
allRefLines,
|
|
598
|
+
refLineKeys,
|
|
545
599
|
refLineCustom,
|
|
600
|
+
refLineOffAxisCustom,
|
|
601
|
+
refLineCustomTagWidths,
|
|
546
602
|
leftEdgeFadeCfg,
|
|
547
603
|
// theme / layout / fonts
|
|
548
604
|
palette,
|
|
@@ -580,7 +636,9 @@ function useLiveChartSeriesController({
|
|
|
580
636
|
markerGroupOpacity,
|
|
581
637
|
overlayScrubFade,
|
|
582
638
|
renderMarker,
|
|
639
|
+
renderOverlay,
|
|
583
640
|
renderReferenceLine,
|
|
641
|
+
renderOffAxisReferenceLine,
|
|
584
642
|
// selection dot: resolved config + fallback color (the leading series' color)
|
|
585
643
|
selectionDot: selectionDotCfg,
|
|
586
644
|
selectionColor: lineColors[0],
|
|
@@ -638,6 +696,7 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
638
696
|
loadingSpeed,
|
|
639
697
|
canvasMode,
|
|
640
698
|
activeSeriesCount,
|
|
699
|
+
refLineKeys,
|
|
641
700
|
} = model;
|
|
642
701
|
|
|
643
702
|
return (
|
|
@@ -657,15 +716,12 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
657
716
|
</Group>
|
|
658
717
|
)}
|
|
659
718
|
|
|
660
|
-
{/*
|
|
661
|
-
|
|
662
|
-
keep both orders mounted without collapsing them to one React child.
|
|
663
|
-
Fade group lets `scrub.hideOverlaysOnScrub` ease the lines out. */}
|
|
719
|
+
{/* Fade group lets `scrub.hideOverlaysOnScrub` ease the lines out. Explicit
|
|
720
|
+
ids keep each reference line mounted when the caller reorders it. */}
|
|
664
721
|
<Group opacity={overlayScrubFade}>
|
|
665
722
|
{allRefLines.map((rl, i) => (
|
|
666
|
-
/* react-doctor-disable-next-line react-doctor/no-array-index-as-key */
|
|
667
723
|
<ReferenceLineOverlay
|
|
668
|
-
key={i}
|
|
724
|
+
key={refLineKeys[i]}
|
|
669
725
|
engine={engine}
|
|
670
726
|
padding={effectivePadding}
|
|
671
727
|
line={rl}
|
|
@@ -823,7 +879,10 @@ function SeriesValueLabelLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
823
879
|
function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
824
880
|
const {
|
|
825
881
|
allRefLines,
|
|
882
|
+
refLineKeys,
|
|
826
883
|
refLineCustom,
|
|
884
|
+
refLineOffAxisCustom,
|
|
885
|
+
refLineCustomTagWidths,
|
|
827
886
|
engine,
|
|
828
887
|
effectivePadding,
|
|
829
888
|
palette,
|
|
@@ -831,15 +890,13 @@ function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
831
890
|
skiaFont,
|
|
832
891
|
degenShakeTransform,
|
|
833
892
|
overlayScrubFade,
|
|
834
|
-
canvasMode,
|
|
835
893
|
} = model;
|
|
836
894
|
if (allRefLines.length === 0) return null;
|
|
837
895
|
return (
|
|
838
896
|
<Group transform={degenShakeTransform} opacity={overlayScrubFade}>
|
|
839
897
|
{allRefLines.map((rl, i) => (
|
|
840
|
-
/* react-doctor-disable-next-line react-doctor/no-array-index-as-key */
|
|
841
898
|
<ReferenceLineOverlay
|
|
842
|
-
key={i}
|
|
899
|
+
key={refLineKeys[i]}
|
|
843
900
|
engine={engine}
|
|
844
901
|
padding={effectivePadding}
|
|
845
902
|
line={rl}
|
|
@@ -848,12 +905,21 @@ function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
848
905
|
font={skiaFont}
|
|
849
906
|
badgeLayer
|
|
850
907
|
suppressTag={refLineCustom[i]}
|
|
908
|
+
suppressTagWhenOffAxis={refLineOffAxisCustom[i]}
|
|
909
|
+
customTagWidths={refLineCustomTagWidths}
|
|
851
910
|
/>
|
|
852
911
|
))}
|
|
853
912
|
</Group>
|
|
854
913
|
);
|
|
855
914
|
}
|
|
856
915
|
|
|
916
|
+
/** Owns the price/time projection worklets for the multi-series custom overlay. */
|
|
917
|
+
function SeriesCustomConsumerOverlay({ model }: { model: LiveChartSeriesModel }) {
|
|
918
|
+
const { engine, effectivePadding, renderOverlay } = model;
|
|
919
|
+
const overlayContext = useChartOverlayContext(engine, effectivePadding);
|
|
920
|
+
return <ChartOverlayLayer render={renderOverlay!} context={overlayContext} />;
|
|
921
|
+
}
|
|
922
|
+
|
|
857
923
|
export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
858
924
|
const model = useLiveChartSeriesController(props);
|
|
859
925
|
const {
|
|
@@ -871,6 +937,7 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
871
937
|
effectivePadding,
|
|
872
938
|
engine,
|
|
873
939
|
scrubCfg,
|
|
940
|
+
seriesTooltipCfg,
|
|
874
941
|
crosshair,
|
|
875
942
|
palette,
|
|
876
943
|
dotCfg,
|
|
@@ -886,11 +953,17 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
886
953
|
markersSV,
|
|
887
954
|
markerClusterCfg,
|
|
888
955
|
renderMarker,
|
|
956
|
+
renderOverlay,
|
|
889
957
|
renderReferenceLine,
|
|
958
|
+
renderOffAxisReferenceLine,
|
|
890
959
|
allRefLines,
|
|
891
960
|
refLineCustom,
|
|
961
|
+
refLineOffAxisCustom,
|
|
962
|
+
refLineCustomTagWidths,
|
|
892
963
|
overlayScrubFade,
|
|
893
964
|
canvasMode,
|
|
965
|
+
activeSeriesCount,
|
|
966
|
+
skiaFont,
|
|
894
967
|
} = model;
|
|
895
968
|
|
|
896
969
|
// Mirror the Skia overlay fade onto the RN custom-marker sibling so
|
|
@@ -988,8 +1061,16 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
988
1061
|
selectionColor={selectionColor}
|
|
989
1062
|
dimOpacity={scrubCfg.dimOpacity}
|
|
990
1063
|
liveDotExtent={liveDotExtent}
|
|
991
|
-
crosshairLineColor={
|
|
992
|
-
|
|
1064
|
+
crosshairLineColor={
|
|
1065
|
+
seriesTooltipCfg?.guideColor ??
|
|
1066
|
+
scrubCfg.crosshairLineColor
|
|
1067
|
+
}
|
|
1068
|
+
crosshairLineWidth={seriesTooltipCfg?.guideWidth}
|
|
1069
|
+
crosshairDash={
|
|
1070
|
+
seriesTooltipCfg
|
|
1071
|
+
? seriesTooltipCfg.guideDashPattern
|
|
1072
|
+
: scrubCfg.crosshairDash
|
|
1073
|
+
}
|
|
993
1074
|
crosshairDimColor={scrubCfg.crosshairDimColor}
|
|
994
1075
|
opaqueCanvas={canvasMode === "opaque"}
|
|
995
1076
|
/>
|
|
@@ -998,6 +1079,19 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
998
1079
|
{/* Per-series value labels on top of the scrub dim so the dim never
|
|
999
1080
|
clips them (they track each series' live value, not the scrub). */}
|
|
1000
1081
|
<SeriesValueLabelLayer model={model} />
|
|
1082
|
+
|
|
1083
|
+
{seriesTooltipCfg && (
|
|
1084
|
+
<PerSeriesTooltipOverlay
|
|
1085
|
+
layout={crosshair.tooltipLayout}
|
|
1086
|
+
font={skiaFont}
|
|
1087
|
+
palette={palette}
|
|
1088
|
+
config={seriesTooltipCfg}
|
|
1089
|
+
seriesCount={activeSeriesCount}
|
|
1090
|
+
tooltipBackground={scrubCfg?.tooltipBackground}
|
|
1091
|
+
tooltipColor={scrubCfg?.tooltipColor}
|
|
1092
|
+
tooltipBorderColor={scrubCfg?.tooltipBorderColor}
|
|
1093
|
+
/>
|
|
1094
|
+
)}
|
|
1001
1095
|
</Canvas>
|
|
1002
1096
|
|
|
1003
1097
|
{/* RN labels floated over the canvas (sibling of <Canvas>, an RN
|
|
@@ -1017,7 +1111,8 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
1017
1111
|
above the left-edge fade and scrub overlay. The box-none fade wrapper
|
|
1018
1112
|
keeps `scrub.hideOverlaysOnScrub` behavior aligned with Skia. */}
|
|
1019
1113
|
{((markersActive && renderMarker) ||
|
|
1020
|
-
(renderReferenceLine
|
|
1114
|
+
((renderReferenceLine || renderOffAxisReferenceLine) &&
|
|
1115
|
+
allRefLines.length > 0)) && (
|
|
1021
1116
|
<Animated.View
|
|
1022
1117
|
pointerEvents="box-none"
|
|
1023
1118
|
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
@@ -1040,10 +1135,27 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
1040
1135
|
engine={engine}
|
|
1041
1136
|
padding={effectivePadding}
|
|
1042
1137
|
formatValue={formatValue}
|
|
1138
|
+
tagWidths={refLineCustomTagWidths}
|
|
1139
|
+
/>
|
|
1140
|
+
)}
|
|
1141
|
+
{renderOffAxisReferenceLine && allRefLines.length > 0 && (
|
|
1142
|
+
<CustomReferenceLineOverlay
|
|
1143
|
+
lines={allRefLines}
|
|
1144
|
+
renderReferenceLine={renderOffAxisReferenceLine}
|
|
1145
|
+
custom={refLineOffAxisCustom}
|
|
1146
|
+
engine={engine}
|
|
1147
|
+
padding={effectivePadding}
|
|
1148
|
+
formatValue={formatValue}
|
|
1149
|
+
tagWidths={refLineCustomTagWidths}
|
|
1150
|
+
offAxisOnly
|
|
1043
1151
|
/>
|
|
1044
1152
|
)}
|
|
1045
1153
|
</Animated.View>
|
|
1046
1154
|
)}
|
|
1155
|
+
|
|
1156
|
+
{/* Custom consumer overlay — topmost RN sibling with the live plot
|
|
1157
|
+
rect, including this multi-series chart's resolved axis inset. */}
|
|
1158
|
+
{renderOverlay && <SeriesCustomConsumerOverlay model={model} />}
|
|
1047
1159
|
</View>
|
|
1048
1160
|
</GestureDetector>
|
|
1049
1161
|
{legendCfg.position === "bottom" ? legend : null}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Circle,
|
|
3
|
+
Group,
|
|
4
|
+
RoundedRect,
|
|
5
|
+
Text as SkiaText,
|
|
6
|
+
type SkFont,
|
|
7
|
+
} from "@shopify/react-native-skia";
|
|
8
|
+
import {
|
|
9
|
+
useDerivedValue,
|
|
10
|
+
type DerivedValue,
|
|
11
|
+
type SharedValue,
|
|
12
|
+
} from "react-native-reanimated";
|
|
13
|
+
import type { ResolvedPerSeriesTooltipConfig } from "../core/resolveConfig";
|
|
14
|
+
import type { TooltipLayout } from "../hooks/crosshairShared";
|
|
15
|
+
import type { LiveChartPalette } from "../types";
|
|
16
|
+
|
|
17
|
+
type TooltipLayoutValue =
|
|
18
|
+
| SharedValue<TooltipLayout>
|
|
19
|
+
| DerivedValue<TooltipLayout>;
|
|
20
|
+
|
|
21
|
+
function TimePill({
|
|
22
|
+
layout,
|
|
23
|
+
font,
|
|
24
|
+
palette,
|
|
25
|
+
config,
|
|
26
|
+
background,
|
|
27
|
+
color,
|
|
28
|
+
borderColor,
|
|
29
|
+
}: {
|
|
30
|
+
layout: TooltipLayoutValue;
|
|
31
|
+
font: SkFont;
|
|
32
|
+
palette: LiveChartPalette;
|
|
33
|
+
config: ResolvedPerSeriesTooltipConfig;
|
|
34
|
+
background?: string;
|
|
35
|
+
color?: string;
|
|
36
|
+
borderColor?: string;
|
|
37
|
+
}) {
|
|
38
|
+
const opacity = useDerivedValue(() =>
|
|
39
|
+
layout.value.perSeries?.timePill ? 1 : 0,
|
|
40
|
+
);
|
|
41
|
+
const x = useDerivedValue(
|
|
42
|
+
() => layout.value.perSeries?.timePill?.x ?? -400,
|
|
43
|
+
);
|
|
44
|
+
const y = useDerivedValue(
|
|
45
|
+
() => layout.value.perSeries?.timePill?.y ?? 0,
|
|
46
|
+
);
|
|
47
|
+
const w = useDerivedValue(
|
|
48
|
+
() => layout.value.perSeries?.timePill?.w ?? 0,
|
|
49
|
+
);
|
|
50
|
+
const h = useDerivedValue(
|
|
51
|
+
() => layout.value.perSeries?.timePill?.h ?? 0,
|
|
52
|
+
);
|
|
53
|
+
const text = useDerivedValue(
|
|
54
|
+
() => layout.value.perSeries?.timePill?.text ?? "",
|
|
55
|
+
);
|
|
56
|
+
const textX = useDerivedValue(
|
|
57
|
+
() => layout.value.perSeries?.timePill?.textX ?? -400,
|
|
58
|
+
);
|
|
59
|
+
const baselineY = useDerivedValue(
|
|
60
|
+
() => layout.value.perSeries?.timePill?.baselineY ?? 0,
|
|
61
|
+
);
|
|
62
|
+
const clip = useDerivedValue(() => ({
|
|
63
|
+
x: layout.value.perSeries?.timePill?.x ?? -400,
|
|
64
|
+
y: layout.value.perSeries?.timePill?.y ?? 0,
|
|
65
|
+
width: layout.value.perSeries?.timePill?.w ?? 0,
|
|
66
|
+
height: layout.value.perSeries?.timePill?.h ?? 0,
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Group opacity={opacity}>
|
|
71
|
+
<RoundedRect
|
|
72
|
+
x={x}
|
|
73
|
+
y={y}
|
|
74
|
+
width={w}
|
|
75
|
+
height={h}
|
|
76
|
+
r={config.timePillRadius}
|
|
77
|
+
color={background ?? palette.tooltipBg}
|
|
78
|
+
/>
|
|
79
|
+
<RoundedRect
|
|
80
|
+
x={x}
|
|
81
|
+
y={y}
|
|
82
|
+
width={w}
|
|
83
|
+
height={h}
|
|
84
|
+
r={config.timePillRadius}
|
|
85
|
+
color={borderColor ?? palette.tooltipBorder}
|
|
86
|
+
style="stroke"
|
|
87
|
+
strokeWidth={1}
|
|
88
|
+
/>
|
|
89
|
+
<Group clip={clip}>
|
|
90
|
+
<SkiaText
|
|
91
|
+
x={textX}
|
|
92
|
+
y={baselineY}
|
|
93
|
+
text={text}
|
|
94
|
+
font={font}
|
|
95
|
+
color={color ?? palette.tooltipText}
|
|
96
|
+
/>
|
|
97
|
+
</Group>
|
|
98
|
+
</Group>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function SeriesPill({
|
|
103
|
+
index,
|
|
104
|
+
layout,
|
|
105
|
+
font,
|
|
106
|
+
palette,
|
|
107
|
+
config,
|
|
108
|
+
background,
|
|
109
|
+
labelColor,
|
|
110
|
+
valueColor,
|
|
111
|
+
borderColor,
|
|
112
|
+
}: {
|
|
113
|
+
index: number;
|
|
114
|
+
layout: TooltipLayoutValue;
|
|
115
|
+
font: SkFont;
|
|
116
|
+
palette: LiveChartPalette;
|
|
117
|
+
config: ResolvedPerSeriesTooltipConfig;
|
|
118
|
+
background?: string;
|
|
119
|
+
labelColor?: string;
|
|
120
|
+
valueColor?: string;
|
|
121
|
+
borderColor?: string;
|
|
122
|
+
}) {
|
|
123
|
+
const opacity = useDerivedValue(() => {
|
|
124
|
+
const pills = layout.value.perSeries?.pills;
|
|
125
|
+
return pills && index < pills.length ? 1 : 0;
|
|
126
|
+
});
|
|
127
|
+
const x = useDerivedValue(
|
|
128
|
+
() => layout.value.perSeries?.pills[index]?.x ?? -400,
|
|
129
|
+
);
|
|
130
|
+
const y = useDerivedValue(
|
|
131
|
+
() => layout.value.perSeries?.pills[index]?.y ?? 0,
|
|
132
|
+
);
|
|
133
|
+
const w = useDerivedValue(
|
|
134
|
+
() => layout.value.perSeries?.pills[index]?.w ?? 0,
|
|
135
|
+
);
|
|
136
|
+
const h = useDerivedValue(
|
|
137
|
+
() => layout.value.perSeries?.pills[index]?.h ?? 0,
|
|
138
|
+
);
|
|
139
|
+
const anchorX = useDerivedValue(
|
|
140
|
+
() => layout.value.perSeries?.pills[index]?.anchorX ?? -400,
|
|
141
|
+
);
|
|
142
|
+
const anchorY = useDerivedValue(
|
|
143
|
+
() => layout.value.perSeries?.pills[index]?.anchorY ?? -400,
|
|
144
|
+
);
|
|
145
|
+
const dotX = useDerivedValue(
|
|
146
|
+
() => layout.value.perSeries?.pills[index]?.dotX ?? -400,
|
|
147
|
+
);
|
|
148
|
+
const dotY = useDerivedValue(
|
|
149
|
+
() => layout.value.perSeries?.pills[index]?.dotY ?? -400,
|
|
150
|
+
);
|
|
151
|
+
const dotColor = useDerivedValue(
|
|
152
|
+
() => layout.value.perSeries?.pills[index]?.color ?? palette.tooltipText,
|
|
153
|
+
);
|
|
154
|
+
const label = useDerivedValue(
|
|
155
|
+
() => layout.value.perSeries?.pills[index]?.label ?? "",
|
|
156
|
+
);
|
|
157
|
+
const labelX = useDerivedValue(
|
|
158
|
+
() => layout.value.perSeries?.pills[index]?.labelX ?? -400,
|
|
159
|
+
);
|
|
160
|
+
const value = useDerivedValue(
|
|
161
|
+
() => layout.value.perSeries?.pills[index]?.value ?? "",
|
|
162
|
+
);
|
|
163
|
+
const valueX = useDerivedValue(
|
|
164
|
+
() => layout.value.perSeries?.pills[index]?.valueX ?? -400,
|
|
165
|
+
);
|
|
166
|
+
const baselineY = useDerivedValue(
|
|
167
|
+
() => layout.value.perSeries?.pills[index]?.baselineY ?? 0,
|
|
168
|
+
);
|
|
169
|
+
const clip = useDerivedValue(() => ({
|
|
170
|
+
x: layout.value.perSeries?.pills[index]?.x ?? -400,
|
|
171
|
+
y: layout.value.perSeries?.pills[index]?.y ?? 0,
|
|
172
|
+
width: layout.value.perSeries?.pills[index]?.w ?? 0,
|
|
173
|
+
height: layout.value.perSeries?.pills[index]?.h ?? 0,
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<Group opacity={opacity}>
|
|
178
|
+
<Circle
|
|
179
|
+
cx={anchorX}
|
|
180
|
+
cy={anchorY}
|
|
181
|
+
r={config.intersectionDotSize / 2}
|
|
182
|
+
color={dotColor}
|
|
183
|
+
/>
|
|
184
|
+
<RoundedRect
|
|
185
|
+
x={x}
|
|
186
|
+
y={y}
|
|
187
|
+
width={w}
|
|
188
|
+
height={h}
|
|
189
|
+
r={config.seriesPillRadius}
|
|
190
|
+
color={background ?? palette.tooltipBg}
|
|
191
|
+
/>
|
|
192
|
+
<RoundedRect
|
|
193
|
+
x={x}
|
|
194
|
+
y={y}
|
|
195
|
+
width={w}
|
|
196
|
+
height={h}
|
|
197
|
+
r={config.seriesPillRadius}
|
|
198
|
+
color={borderColor ?? palette.tooltipBorder}
|
|
199
|
+
style="stroke"
|
|
200
|
+
strokeWidth={1}
|
|
201
|
+
/>
|
|
202
|
+
<Group clip={clip}>
|
|
203
|
+
<Circle
|
|
204
|
+
cx={dotX}
|
|
205
|
+
cy={dotY}
|
|
206
|
+
r={config.seriesPillDotSize / 2}
|
|
207
|
+
color={dotColor}
|
|
208
|
+
/>
|
|
209
|
+
<SkiaText
|
|
210
|
+
x={labelX}
|
|
211
|
+
y={baselineY}
|
|
212
|
+
text={label}
|
|
213
|
+
font={font}
|
|
214
|
+
color={labelColor ?? palette.tooltipText}
|
|
215
|
+
/>
|
|
216
|
+
<SkiaText
|
|
217
|
+
x={valueX}
|
|
218
|
+
y={baselineY}
|
|
219
|
+
text={value}
|
|
220
|
+
font={font}
|
|
221
|
+
color={valueColor ?? labelColor ?? palette.tooltipText}
|
|
222
|
+
/>
|
|
223
|
+
</Group>
|
|
224
|
+
</Group>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Skia-only per-series tooltip renderer; every position is SharedValue-driven. */
|
|
229
|
+
export function PerSeriesTooltipOverlay({
|
|
230
|
+
layout,
|
|
231
|
+
font,
|
|
232
|
+
palette,
|
|
233
|
+
config,
|
|
234
|
+
seriesCount,
|
|
235
|
+
tooltipBackground,
|
|
236
|
+
tooltipColor,
|
|
237
|
+
tooltipBorderColor,
|
|
238
|
+
}: {
|
|
239
|
+
layout: TooltipLayoutValue;
|
|
240
|
+
font: SkFont;
|
|
241
|
+
palette: LiveChartPalette;
|
|
242
|
+
config: ResolvedPerSeriesTooltipConfig;
|
|
243
|
+
seriesCount: number;
|
|
244
|
+
tooltipBackground?: string;
|
|
245
|
+
tooltipColor?: string;
|
|
246
|
+
tooltipBorderColor?: string;
|
|
247
|
+
}) {
|
|
248
|
+
const timeBackground =
|
|
249
|
+
config.timePillBackground ?? tooltipBackground;
|
|
250
|
+
const timeColor = config.timePillColor ?? tooltipColor;
|
|
251
|
+
const timeBorder =
|
|
252
|
+
config.timePillBorderColor ?? tooltipBorderColor;
|
|
253
|
+
const seriesBackground =
|
|
254
|
+
config.seriesPillBackground ?? tooltipBackground;
|
|
255
|
+
const seriesLabelColor =
|
|
256
|
+
config.seriesPillLabelColor ?? tooltipColor;
|
|
257
|
+
const seriesBorder =
|
|
258
|
+
config.seriesPillBorderColor ?? tooltipBorderColor;
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<Group>
|
|
262
|
+
<TimePill
|
|
263
|
+
layout={layout}
|
|
264
|
+
font={font}
|
|
265
|
+
palette={palette}
|
|
266
|
+
config={config}
|
|
267
|
+
background={timeBackground}
|
|
268
|
+
color={timeColor}
|
|
269
|
+
borderColor={timeBorder}
|
|
270
|
+
/>
|
|
271
|
+
{Array.from({ length: seriesCount }, (_, index) => (
|
|
272
|
+
<SeriesPill
|
|
273
|
+
key={index}
|
|
274
|
+
index={index}
|
|
275
|
+
layout={layout}
|
|
276
|
+
font={font}
|
|
277
|
+
palette={palette}
|
|
278
|
+
config={config}
|
|
279
|
+
background={seriesBackground}
|
|
280
|
+
labelColor={seriesLabelColor}
|
|
281
|
+
valueColor={config.seriesPillValueColor}
|
|
282
|
+
borderColor={seriesBorder}
|
|
283
|
+
/>
|
|
284
|
+
))}
|
|
285
|
+
</Group>
|
|
286
|
+
);
|
|
287
|
+
}
|