react-native-livechart 4.11.0 → 4.13.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.
Files changed (82) hide show
  1. package/dist/components/CrosshairLine.d.ts +12 -2
  2. package/dist/components/CrosshairLine.d.ts.map +1 -1
  3. package/dist/components/CrosshairOverlay.d.ts +12 -2
  4. package/dist/components/CrosshairOverlay.d.ts.map +1 -1
  5. package/dist/components/CustomReferenceLineOverlay.d.ts +15 -10
  6. package/dist/components/CustomReferenceLineOverlay.d.ts.map +1 -1
  7. package/dist/components/CustomTooltipOverlay.d.ts +5 -2
  8. package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
  9. package/dist/components/DotOverlay.d.ts +3 -5
  10. package/dist/components/DotOverlay.d.ts.map +1 -1
  11. package/dist/components/LiveChart.d.ts.map +1 -1
  12. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  13. package/dist/components/PerSeriesTooltipOverlay.d.ts +21 -0
  14. package/dist/components/PerSeriesTooltipOverlay.d.ts.map +1 -0
  15. package/dist/components/ReferenceLineOverlay.d.ts +27 -6
  16. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
  17. package/dist/components/YAxisOverlay.d.ts +8 -2
  18. package/dist/components/YAxisOverlay.d.ts.map +1 -1
  19. package/dist/core/liveIndicatorVisibility.d.ts +9 -0
  20. package/dist/core/liveIndicatorVisibility.d.ts.map +1 -0
  21. package/dist/core/resolveConfig.d.ts +45 -0
  22. package/dist/core/resolveConfig.d.ts.map +1 -1
  23. package/dist/draw/grid.d.ts +13 -0
  24. package/dist/draw/grid.d.ts.map +1 -1
  25. package/dist/hooks/crosshairSeries.d.ts +10 -2
  26. package/dist/hooks/crosshairSeries.d.ts.map +1 -1
  27. package/dist/hooks/crosshairShared.d.ts +55 -5
  28. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  29. package/dist/hooks/delayedPanGuard.d.ts +66 -8
  30. package/dist/hooks/delayedPanGuard.d.ts.map +1 -1
  31. package/dist/hooks/useChartPaths.d.ts +5 -9
  32. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  33. package/dist/hooks/useCrosshair.d.ts +13 -1
  34. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  35. package/dist/hooks/useCrosshairSeries.d.ts +27 -3
  36. package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
  37. package/dist/hooks/useCrosshairVisibleOpacity.d.ts +8 -0
  38. package/dist/hooks/useCrosshairVisibleOpacity.d.ts.map +1 -0
  39. package/dist/hooks/useMomentum.d.ts +1 -1
  40. package/dist/hooks/useMomentum.d.ts.map +1 -1
  41. package/dist/hooks/usePanScroll.d.ts +15 -1
  42. package/dist/hooks/usePanScroll.d.ts.map +1 -1
  43. package/dist/hooks/useReferenceLine.d.ts +10 -1
  44. package/dist/hooks/useReferenceLine.d.ts.map +1 -1
  45. package/dist/index.d.ts +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/math/lerp.d.ts.map +1 -1
  48. package/dist/math/momentum.d.ts +1 -1
  49. package/dist/math/momentum.d.ts.map +1 -1
  50. package/dist/math/referenceLines.d.ts +7 -0
  51. package/dist/math/referenceLines.d.ts.map +1 -1
  52. package/dist/types.d.ts +207 -18
  53. package/dist/types.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/src/components/CrosshairLine.tsx +39 -8
  56. package/src/components/CrosshairOverlay.tsx +49 -15
  57. package/src/components/CustomReferenceLineOverlay.tsx +42 -13
  58. package/src/components/CustomTooltipOverlay.tsx +15 -3
  59. package/src/components/DotOverlay.tsx +24 -8
  60. package/src/components/LiveChart.tsx +182 -39
  61. package/src/components/LiveChartSeries.tsx +195 -16
  62. package/src/components/PerSeriesTooltipOverlay.tsx +290 -0
  63. package/src/components/ReferenceLineOverlay.tsx +288 -141
  64. package/src/components/YAxisOverlay.tsx +39 -10
  65. package/src/core/liveIndicatorVisibility.ts +25 -0
  66. package/src/core/resolveConfig.ts +115 -0
  67. package/src/draw/grid.ts +33 -0
  68. package/src/hooks/crosshairSeries.ts +262 -33
  69. package/src/hooks/crosshairShared.ts +117 -6
  70. package/src/hooks/delayedPanGuard.ts +111 -6
  71. package/src/hooks/useChartPaths.ts +25 -15
  72. package/src/hooks/useCrosshair.ts +103 -32
  73. package/src/hooks/useCrosshairSeries.ts +126 -11
  74. package/src/hooks/useCrosshairVisibleOpacity.ts +57 -0
  75. package/src/hooks/useMomentum.ts +8 -1
  76. package/src/hooks/usePanScroll.ts +46 -5
  77. package/src/hooks/useReferenceLine.ts +26 -1
  78. package/src/index.ts +1 -0
  79. package/src/math/lerp.ts +7 -0
  80. package/src/math/momentum.ts +22 -5
  81. package/src/math/referenceLines.ts +38 -0
  82. package/src/types.ts +215 -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,8 +52,10 @@ 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
+ import { useCrosshairVisibleOpacity } from "../hooks/useCrosshairVisibleOpacity";
58
59
  import { useMarkers } from "../hooks/useMarkers";
59
60
  import { useMultiSeriesDegen } from "../hooks/useMultiSeriesDegen";
60
61
  import { useMultiSeriesLinePaths } from "../hooks/useMultiSeriesLinePaths";
@@ -70,7 +71,10 @@ import {
70
71
  } from "../lib/format";
71
72
  import { measureFontTextWidth } from "../lib/measureFontTextWidth";
72
73
  import { MONO_FONT_FAMILY } from "../lib/monoFontFamily";
73
- import { collectReferenceValues } from "../math/referenceLines";
74
+ import {
75
+ collectReferenceValues,
76
+ referenceLineReactKeys,
77
+ } from "../math/referenceLines";
74
78
  import {
75
79
  applyPaletteOverride,
76
80
  leftEdgeFadeColorsFromBgRgb,
@@ -78,6 +82,7 @@ import {
78
82
  } from "../theme";
79
83
  import type { LiveChartSeriesProps, Marker, SeriesConfig } from "../types";
80
84
  import { AxisLabelOverlay } from "./AxisLabelOverlay";
85
+ import { ChartOverlayLayer } from "./ChartOverlayLayer";
81
86
  import {
82
87
  ExtremaConnectorOverlay,
83
88
  labelConnector,
@@ -96,6 +101,7 @@ import { MultiSeriesDots } from "./MultiSeriesDots";
96
101
  import { MultiSeriesStroke } from "./MultiSeriesStroke";
97
102
  import { MultiSeriesValueLabels } from "./MultiSeriesValueLabels";
98
103
  import { MultiSeriesValueLines } from "./MultiSeriesValueLines";
104
+ import { PerSeriesTooltipOverlay } from "./PerSeriesTooltipOverlay";
99
105
  import { ReferenceLineOverlay } from "./ReferenceLineOverlay";
100
106
  import { SeriesToggleChips } from "./SeriesToggleChips";
101
107
  import { XAxisOverlay } from "./XAxisOverlay";
@@ -177,7 +183,9 @@ function useLiveChartSeriesController({
177
183
  markerHitRadius = 16,
178
184
  markerCluster,
179
185
  renderMarker,
186
+ renderOverlay,
180
187
  renderReferenceLine,
188
+ renderOffAxisReferenceLine,
181
189
  leftEdgeFade = true,
182
190
  }: LiveChartSeriesProps) {
183
191
  const emptyMarkers = useSharedValue<Marker[]>([]);
@@ -190,6 +198,10 @@ function useLiveChartSeriesController({
190
198
  const bottomLabelCfg = resolveAxisLabel(bottomLabel);
191
199
  const scrubCfg = resolveScrub(scrub);
192
200
  const scrubEnabled = scrubCfg !== null;
201
+ // Opt-in and subordinate to the existing master `tooltip` switch. Keeping
202
+ // this null by default preserves LiveChartSeries' historical guide-only scrub.
203
+ const seriesTooltipCfg =
204
+ scrubCfg?.tooltip === true ? scrubCfg.seriesTooltip : null;
193
205
 
194
206
  // Time-scroll + pinch-zoom (mirrors LiveChart). LiveChartSeries has no static
195
207
  // mode, so these gate on the props alone. `holdToScrub` requires the scrub
@@ -216,8 +228,8 @@ function useLiveChartSeriesController({
216
228
 
217
229
  // Multi-series defaults the scrub selection dot OFF: it can only track one
218
230
  // line (the leading series), which reads as a bug next to the other series.
219
- // The crosshair line + per-series tooltip stack already mark the scrub point.
220
- // Passing `selectionDot` explicitly (true / config) still opts it in.
231
+ // The crosshair line (and the opt-in tooltip's per-series intersection dots)
232
+ // mark the scrub point. Passing `selectionDot` explicitly still opts it in.
221
233
  const selectionDotCfg = resolveSelectionDot(selectionDot ?? false);
222
234
  const gridStyleCfg = resolveGridStyle(gridStyle);
223
235
  const dotCfg = resolveMultiSeriesDot(dotProp);
@@ -237,6 +249,17 @@ function useLiveChartSeriesController({
237
249
  allRefLines,
238
250
  renderReferenceLine,
239
251
  );
252
+ // A full custom tag owns both states, so it takes precedence over an
253
+ // off-axis-only renderer for the same line.
254
+ const refLineOffAxisCustom = customReferenceLineFlags(
255
+ allRefLines,
256
+ renderOffAxisReferenceLine,
257
+ "off-axis",
258
+ ).map((custom, index) => custom && !refLineCustom[index]);
259
+ const refLineKeys = referenceLineReactKeys(allRefLines);
260
+ // RN custom tags report their measured widths here so the Skia connector can
261
+ // start after the native badge instead of the hidden built-in pill.
262
+ const refLineCustomTagWidths = useSharedValue<number[]>([]);
240
263
 
241
264
  const palette = applyPaletteOverride(
242
265
  resolveTheme(accentColor, theme),
@@ -266,6 +289,7 @@ function useLiveChartSeriesController({
266
289
  // render scope for memoization, which trips Reanimated's strict-mode warning;
267
290
  // it leaves the `.get()` method call inside the effect.
268
291
  // react-doctor-disable-next-line react-hooks-js/set-state-in-effect -- Reanimated: seeding from a SharedValue off render is the warning-free path
292
+ // eslint-disable-next-line react-hooks/set-state-in-effect -- Reanimated: seed from the SharedValue outside render to avoid strict-mode access warnings
269
293
  setSeriesSnapshot(series.get().slice());
270
294
  }, [series]);
271
295
 
@@ -407,6 +431,20 @@ function useLiveChartSeriesController({
407
431
  skiaFont,
408
432
  );
409
433
 
434
+ // Cross-gesture arbitration for the one-finger touch. `Gesture.Race` below is
435
+ // NOT arbitration — RNGH's Race adds no relation between its children, so both
436
+ // pans recognize independently and each can activate while the other already
437
+ // owns the touch. This latch (written by the scroll pan, read by the scrub's
438
+ // long-press guard) makes "the scroll already won" a hard fact; `scrubActive`
439
+ // (written by the crosshair, read by the scroll pan) is the mirror image.
440
+ const scrollActive = useSharedValue(false);
441
+ // `liveEdge` includes the optional right breathing-room buffer. The time pill
442
+ // must clamp against the real "now" anchor so its live bucket never ends in
443
+ // that future buffer.
444
+ const tooltipMaxTime = useDerivedValue(
445
+ () => engine.liveEdge.get() - windowBuffer * timeWindow,
446
+ );
447
+
410
448
  const crosshair = useCrosshairSeries(
411
449
  engine,
412
450
  effectivePadding,
@@ -415,6 +453,18 @@ function useLiveChartSeriesController({
415
453
  scrubHoldMs,
416
454
  onGestureStart,
417
455
  onGestureEnd,
456
+ scrollActive,
457
+ seriesTooltipCfg
458
+ ? {
459
+ config: seriesTooltipCfg,
460
+ formatValue,
461
+ formatTime,
462
+ font: skiaFont,
463
+ colors: lineColors,
464
+ maxTime: tooltipMaxTime,
465
+ }
466
+ : undefined,
467
+ scrubCfg?.clampToPlot ?? false,
418
468
  );
419
469
 
420
470
  // Capture only the shared value in the worklets below. Referencing
@@ -458,6 +508,10 @@ function useLiveChartSeriesController({
458
508
  minTime: scrollMinTime,
459
509
  enabled: timeScrollEnabled,
460
510
  mode: scrollGestureMode,
511
+ scrollActive,
512
+ // Once a scrub is engaged the chart is locked: scrolling goes inert so the
513
+ // finger only moves the price indicator across a fixed window.
514
+ scrubActive: crosshairScrubActive,
461
515
  onScrollStart: () => {
462
516
  "worklet";
463
517
  crosshairScrubActive.set(false);
@@ -535,6 +589,7 @@ function useLiveChartSeriesController({
535
589
  yAxisCfg,
536
590
  xAxisCfg,
537
591
  scrubCfg,
592
+ seriesTooltipCfg,
538
593
  gridStyleCfg,
539
594
  dotCfg,
540
595
  dotOuterRadius,
@@ -542,7 +597,10 @@ function useLiveChartSeriesController({
542
597
  degenCfg,
543
598
  metricsCfg,
544
599
  allRefLines,
600
+ refLineKeys,
545
601
  refLineCustom,
602
+ refLineOffAxisCustom,
603
+ refLineCustomTagWidths,
546
604
  leftEdgeFadeCfg,
547
605
  // theme / layout / fonts
548
606
  palette,
@@ -580,7 +638,9 @@ function useLiveChartSeriesController({
580
638
  markerGroupOpacity,
581
639
  overlayScrubFade,
582
640
  renderMarker,
641
+ renderOverlay,
583
642
  renderReferenceLine,
643
+ renderOffAxisReferenceLine,
584
644
  // selection dot: resolved config + fallback color (the leading series' color)
585
645
  selectionDot: selectionDotCfg,
586
646
  selectionColor: lineColors[0],
@@ -638,6 +698,7 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
638
698
  loadingSpeed,
639
699
  canvasMode,
640
700
  activeSeriesCount,
701
+ refLineKeys,
641
702
  } = model;
642
703
 
643
704
  return (
@@ -653,25 +714,27 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
653
714
  badge={false}
654
715
  seriesLabelInset={seriesLabelInset}
655
716
  gridStyle={gridStyleCfg}
717
+ labelRightMargin={yAxisCfg.labelRightMargin}
718
+ gridEndGap={yAxisCfg.gridEndGap}
656
719
  />
657
720
  </Group>
658
721
  )}
659
722
 
660
- {/* Reference lines are index-addressed throughout the drag/press API, and
661
- duplicate working orders may share all visible content. Positional keys
662
- keep both orders mounted without collapsing them to one React child.
663
- Fade group lets `scrub.hideOverlaysOnScrub` ease the lines out. */}
723
+ {/* Fade group lets `scrub.hideOverlaysOnScrub` ease the lines out. Explicit
724
+ ids keep each reference line mounted when the caller reorders it. */}
664
725
  <Group opacity={overlayScrubFade}>
665
726
  {allRefLines.map((rl, i) => (
666
- /* react-doctor-disable-next-line react-doctor/no-array-index-as-key */
667
727
  <ReferenceLineOverlay
668
- key={i}
728
+ key={refLineKeys[i]}
669
729
  engine={engine}
670
730
  padding={effectivePadding}
671
731
  line={rl}
672
732
  palette={palette}
673
733
  formatValue={formatValue}
674
734
  font={skiaFont}
735
+ yAxisEntries={yAxisEntries}
736
+ labelRightMargin={yAxisCfg?.labelRightMargin}
737
+ gridEndGap={yAxisCfg?.gridEndGap}
675
738
  />
676
739
  ))}
677
740
  </Group>
@@ -823,23 +886,26 @@ function SeriesValueLabelLayer({ model }: { model: LiveChartSeriesModel }) {
823
886
  function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
824
887
  const {
825
888
  allRefLines,
889
+ refLineKeys,
826
890
  refLineCustom,
891
+ refLineOffAxisCustom,
892
+ refLineCustomTagWidths,
827
893
  engine,
828
894
  effectivePadding,
829
895
  palette,
830
896
  formatValue,
831
897
  skiaFont,
898
+ yAxisCfg,
899
+ yAxisEntries,
832
900
  degenShakeTransform,
833
901
  overlayScrubFade,
834
- canvasMode,
835
902
  } = model;
836
903
  if (allRefLines.length === 0) return null;
837
904
  return (
838
905
  <Group transform={degenShakeTransform} opacity={overlayScrubFade}>
839
906
  {allRefLines.map((rl, i) => (
840
- /* react-doctor-disable-next-line react-doctor/no-array-index-as-key */
841
907
  <ReferenceLineOverlay
842
- key={i}
908
+ key={refLineKeys[i]}
843
909
  engine={engine}
844
910
  padding={effectivePadding}
845
911
  line={rl}
@@ -848,12 +914,71 @@ function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
848
914
  font={skiaFont}
849
915
  badgeLayer
850
916
  suppressTag={refLineCustom[i]}
917
+ suppressTagWhenOffAxis={refLineOffAxisCustom[i]}
918
+ customTagWidths={refLineCustomTagWidths}
919
+ yAxisEntries={yAxisEntries}
920
+ labelRightMargin={yAxisCfg?.labelRightMargin}
921
+ gridEndGap={yAxisCfg?.gridEndGap}
851
922
  />
852
923
  ))}
853
924
  </Group>
854
925
  );
855
926
  }
856
927
 
928
+ /** Owns the price/time projection worklets for the multi-series custom overlay. */
929
+ function SeriesCustomConsumerOverlay({ model }: { model: LiveChartSeriesModel }) {
930
+ const { engine, effectivePadding, renderOverlay } = model;
931
+ const overlayContext = useChartOverlayContext(engine, effectivePadding);
932
+ return <ChartOverlayLayer render={renderOverlay!} context={overlayContext} />;
933
+ }
934
+
935
+ /** Keeps the extra fade mapper opt-in with the per-series tooltip itself. */
936
+ function SeriesTooltipLayer({
937
+ model,
938
+ config,
939
+ }: {
940
+ model: LiveChartSeriesModel;
941
+ config: NonNullable<LiveChartSeriesModel["seriesTooltipCfg"]>;
942
+ }) {
943
+ const {
944
+ crosshair,
945
+ engine,
946
+ effectivePadding,
947
+ scrubCfg,
948
+ skiaFont,
949
+ palette,
950
+ activeSeriesCount,
951
+ } = model;
952
+ const activeOpacity = useCrosshairVisibleOpacity(
953
+ crosshair.scrubX,
954
+ engine.canvasWidth,
955
+ effectivePadding.right,
956
+ crosshair.scrubActive,
957
+ scrubCfg?.crosshairFade ?? true,
958
+ scrubCfg?.crosshairFadeDistance ?? 4,
959
+ );
960
+ const scrubActive = crosshair.scrubActive;
961
+ const alwaysShow = config.alwaysShow;
962
+ const opacity = useDerivedValue(
963
+ () => (scrubActive.get() ? activeOpacity.get() : alwaysShow ? 1 : 0),
964
+ [scrubActive, activeOpacity, alwaysShow],
965
+ );
966
+
967
+ return (
968
+ <PerSeriesTooltipOverlay
969
+ layout={crosshair.tooltipLayout}
970
+ font={skiaFont}
971
+ palette={palette}
972
+ config={config}
973
+ seriesCount={activeSeriesCount}
974
+ opacity={opacity}
975
+ tooltipBackground={scrubCfg?.tooltipBackground}
976
+ tooltipColor={scrubCfg?.tooltipColor}
977
+ tooltipBorderColor={scrubCfg?.tooltipBorderColor}
978
+ />
979
+ );
980
+ }
981
+
857
982
  export function LiveChartSeries(props: LiveChartSeriesProps) {
858
983
  const model = useLiveChartSeriesController(props);
859
984
  const {
@@ -871,6 +996,7 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
871
996
  effectivePadding,
872
997
  engine,
873
998
  scrubCfg,
999
+ seriesTooltipCfg,
874
1000
  crosshair,
875
1001
  palette,
876
1002
  dotCfg,
@@ -886,9 +1012,13 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
886
1012
  markersSV,
887
1013
  markerClusterCfg,
888
1014
  renderMarker,
1015
+ renderOverlay,
889
1016
  renderReferenceLine,
1017
+ renderOffAxisReferenceLine,
890
1018
  allRefLines,
891
1019
  refLineCustom,
1020
+ refLineOffAxisCustom,
1021
+ refLineCustomTagWidths,
892
1022
  overlayScrubFade,
893
1023
  canvasMode,
894
1024
  } = model;
@@ -988,8 +1118,23 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
988
1118
  selectionColor={selectionColor}
989
1119
  dimOpacity={scrubCfg.dimOpacity}
990
1120
  liveDotExtent={liveDotExtent}
991
- crosshairLineColor={scrubCfg.crosshairLineColor}
992
- crosshairDash={scrubCfg.crosshairDash}
1121
+ crosshairLineColor={
1122
+ seriesTooltipCfg?.guideColor ??
1123
+ scrubCfg.crosshairLineColor
1124
+ }
1125
+ crosshairStrokeWidth={
1126
+ seriesTooltipCfg?.guideWidth ??
1127
+ scrubCfg.crosshairStrokeWidth
1128
+ }
1129
+ crosshairOvershoot={scrubCfg.crosshairOvershoot}
1130
+ crosshairFade={scrubCfg.crosshairFade}
1131
+ crosshairFadeDistance={scrubCfg.crosshairFadeDistance}
1132
+ crosshairLineCap={scrubCfg.crosshairLineCap}
1133
+ crosshairDash={
1134
+ seriesTooltipCfg
1135
+ ? seriesTooltipCfg.guideDashPattern
1136
+ : scrubCfg.crosshairDash
1137
+ }
993
1138
  crosshairDimColor={scrubCfg.crosshairDimColor}
994
1139
  opaqueCanvas={canvasMode === "opaque"}
995
1140
  />
@@ -1017,7 +1162,8 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
1017
1162
  above the left-edge fade and scrub overlay. The box-none fade wrapper
1018
1163
  keeps `scrub.hideOverlaysOnScrub` behavior aligned with Skia. */}
1019
1164
  {((markersActive && renderMarker) ||
1020
- (renderReferenceLine && allRefLines.length > 0)) && (
1165
+ ((renderReferenceLine || renderOffAxisReferenceLine) &&
1166
+ allRefLines.length > 0)) && (
1021
1167
  <Animated.View
1022
1168
  pointerEvents="box-none"
1023
1169
  style={[StyleSheet.absoluteFill, overlayFadeStyle]}
@@ -1040,10 +1186,43 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
1040
1186
  engine={engine}
1041
1187
  padding={effectivePadding}
1042
1188
  formatValue={formatValue}
1189
+ tagWidths={refLineCustomTagWidths}
1190
+ />
1191
+ )}
1192
+ {renderOffAxisReferenceLine && allRefLines.length > 0 && (
1193
+ <CustomReferenceLineOverlay
1194
+ lines={allRefLines}
1195
+ renderReferenceLine={renderOffAxisReferenceLine}
1196
+ custom={refLineOffAxisCustom}
1197
+ engine={engine}
1198
+ padding={effectivePadding}
1199
+ formatValue={formatValue}
1200
+ tagWidths={refLineCustomTagWidths}
1201
+ offAxisOnly
1043
1202
  />
1044
1203
  )}
1045
1204
  </Animated.View>
1046
1205
  )}
1206
+
1207
+ {/* A dedicated transparent Canvas keeps the UI-thread tooltip above
1208
+ custom RN annotations. Rendering it in the primary Canvas would
1209
+ put every post-Canvas `renderReferenceLine` sibling above it and
1210
+ produce mixed stacking through translucent custom tags. */}
1211
+ {seriesTooltipCfg && (
1212
+ <View
1213
+ testID="live-chart-series-tooltip-overlay"
1214
+ pointerEvents="none"
1215
+ style={StyleSheet.absoluteFill}
1216
+ >
1217
+ <Canvas style={StyleSheet.absoluteFill}>
1218
+ <SeriesTooltipLayer model={model} config={seriesTooltipCfg} />
1219
+ </Canvas>
1220
+ </View>
1221
+ )}
1222
+
1223
+ {/* Custom consumer overlay — topmost RN sibling with the live plot
1224
+ rect, including this multi-series chart's resolved axis inset. */}
1225
+ {renderOverlay && <SeriesCustomConsumerOverlay model={model} />}
1047
1226
  </View>
1048
1227
  </GestureDetector>
1049
1228
  {legendCfg.position === "bottom" ? legend : null}