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
@@ -58,7 +58,11 @@ import {
58
58
  resolveYAxis,
59
59
  } from "../core/resolveConfig";
60
60
  import type { ResolvedThresholdConfig } from "../core/resolveConfig";
61
- import { resolveSegment } from "../core/resolveSegment";
61
+ import {
62
+ liveIndicatorScrollOpacity,
63
+ resolveHideLiveOnScrollBack,
64
+ } from "../core/liveIndicatorVisibility";
65
+ import { resolveSegment, type ResolvedSegment } from "../core/resolveSegment";
62
66
  import { useLiveChartEngine } from "../core/useLiveChartEngine";
63
67
  import { pulseRadialOutset } from "../draw/line";
64
68
  import { resolveChartLayout } from "../hooks/resolveChartLayout";
@@ -78,7 +82,10 @@ import { useMarkers } from "../hooks/useMarkers";
78
82
  import { useReferenceDrag } from "../hooks/useReferenceDrag";
79
83
  import { useReferenceLinePress } from "../hooks/useReferenceLinePress";
80
84
  import { useModeBlend } from "../hooks/useModeBlend";
81
- import { useMomentum } from "../hooks/useMomentum";
85
+ import {
86
+ resolveMomentumProp,
87
+ useMomentum,
88
+ } from "../hooks/useMomentum";
82
89
  import { AXIS_GRAB_MIN_PX, usePanScroll } from "../hooks/usePanScroll";
83
90
  import { usePinchZoom } from "../hooks/usePinchZoom";
84
91
  import { useVisibleRange } from "../hooks/useVisibleRange";
@@ -105,6 +112,7 @@ import {
105
112
  import {
106
113
  collectReferenceValues,
107
114
  referenceLineForm,
115
+ referenceLineReactKeys,
108
116
  resolveReferenceGroupBadge,
109
117
  } from "../math/referenceLines";
110
118
  import {
@@ -311,6 +319,7 @@ function useLiveChartController({
311
319
  renderTooltip,
312
320
  renderOverlay,
313
321
  renderReferenceLine,
322
+ renderOffAxisReferenceLine,
314
323
  referenceLineGrouping,
315
324
  leftEdgeFade = true,
316
325
 
@@ -387,18 +396,18 @@ function useLiveChartController({
387
396
  const seededRef = useRef<(number | undefined)[]>([]);
388
397
  const refValueSig = allRefLines.map((l) => l.value ?? "_").join(",");
389
398
  useEffect(() => {
390
- const active = dragActive.value;
391
- const cur = dragValues.value;
399
+ const active = dragActive.get();
400
+ const cur = dragValues.get();
392
401
  const seeded = seededRef.current;
393
- dragValues.value = allRefLines.map((l, i) => {
402
+ dragValues.set(allRefLines.map((l, i) => {
394
403
  const prop = l.value ?? 0;
395
404
  if (active[i]) return cur[i] ?? prop; // mid-drag → keep the dragged value
396
405
  if (l.value !== seeded[i]) return prop; // prop changed → adopt (controlled)
397
406
  return cur[i] ?? prop; // unchanged → keep current (uncontrolled persist)
398
- });
407
+ }));
399
408
  seededRef.current = allRefLines.map((l) => l.value);
400
- if (dragActive.value.length !== allRefLines.length) {
401
- dragActive.value = allRefLines.map((_, i) => active[i] ?? false);
409
+ if (dragActive.get().length !== allRefLines.length) {
410
+ dragActive.set(allRefLines.map((_, i) => active[i] ?? false));
402
411
  }
403
412
  // allRefLines is rebuilt every render; key off the value signature + length.
404
413
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -410,6 +419,17 @@ function useLiveChartController({
410
419
  allRefLines,
411
420
  renderReferenceLine,
412
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[]>([]);
413
433
 
414
434
  // Live Y values of the *draggable* Form-A lines, folded into the engine's
415
435
  // axis-range fit so dragging a line toward / past the visible edge expands the
@@ -421,13 +441,17 @@ function useLiveChartController({
421
441
  // contributes its live value; a series contributes its window min/max
422
442
  // (respecting `extendToNow`), so an off-range break-even expands the axis and
423
443
  // stays on-plot like a reference line would.
424
- const draggableRefIdx = allRefLines
425
- .map((l, i) =>
426
- l.draggable && !l.excludeFromRange && referenceLineForm(l) === "line"
427
- ? i
428
- : -1,
429
- )
430
- .filter((i) => i >= 0);
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
+ }
431
455
  const thresholdInRange = thresholdCfg?.includeInRange === true;
432
456
  // Constant benchmark → its live value rides this channel. A series threshold
433
457
  // is folded inside the engine tick instead (`thresholdRangePoints`), which
@@ -572,6 +596,7 @@ function useLiveChartController({
572
596
  // react-doctor-disable-next-line react-doctor/no-derived-state-effect -- Reanimated: must read the SharedValue off the render path
573
597
  useLayoutEffect(() => {
574
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
575
600
  setValueLayoutSample(value.get());
576
601
  }, [value]);
577
602
 
@@ -721,7 +746,7 @@ function useLiveChartController({
721
746
  if (
722
747
  referenceLineForm(l) !== "line" ||
723
748
  l.value === undefined ||
724
- refLineCustom[i]
749
+ refLineCustom[i] || refLineOffAxisCustom[i]
725
750
  ) {
726
751
  ys.push(-1);
727
752
  continue;
@@ -866,11 +891,8 @@ function useLiveChartController({
866
891
  // polyline passed as the last arg below).
867
892
  thresholdCfg?.fill && !thresholdIsSeries ? thresholdGeom.lineY : undefined,
868
893
  lineIsLinear,
869
- // Tip the line at the view-edge price (not the live value) while scrolled,
870
- // matching the live dot so the right edge doesn't drop to the off-screen
871
- // live value when `followViewEdge` tracks the scrolled-back window.
872
- engine.edgeValue,
873
- badgeCfg?.followViewEdge ?? false,
894
+ // The plotted line independently selects the visible edge while historical;
895
+ // badge/live-indicator options affect overlays only.
874
896
  // Match the standalone loading squiggle's wave during the reveal morph.
875
897
  loadingCfg?.amplitude,
876
898
  loadingCfg?.speed,
@@ -906,6 +928,15 @@ function useLiveChartController({
906
928
  );
907
929
 
908
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
+ );
909
940
 
910
941
  // ── Overlay hooks ─────────────────────────────────────────────────────
911
942
  // Scrub/crosshair must see the same stash-backed candles as the engine.
@@ -997,6 +1028,14 @@ function useLiveChartController({
997
1028
  ? (timeScrollHoldMs ?? (scrubCfg?.panGestureDelay || HOLD_TO_SCRUB_MS))
998
1029
  : (scrubCfg?.panGestureDelay ?? 0);
999
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
+
1000
1039
  const crosshair = useCrosshair(
1001
1040
  engine,
1002
1041
  effectivePadding,
@@ -1026,6 +1065,8 @@ function useLiveChartController({
1026
1065
  timeScrollEnabled && scrollGestureMode === "axisDrag"
1027
1066
  ? Math.max(effectivePadding.bottom, AXIS_GRAB_MIN_PX)
1028
1067
  : 0,
1068
+ scrollActive,
1069
+ scrubCfg?.clampToPlot ?? false,
1029
1070
  );
1030
1071
 
1031
1072
  // Capture only the shared value in the worklets below. Referencing
@@ -1048,6 +1089,10 @@ function useLiveChartController({
1048
1089
  minTime: scrollMinTime,
1049
1090
  enabled: timeScrollEnabled,
1050
1091
  mode: scrollGestureMode,
1092
+ scrollActive,
1093
+ // Once a scrub is engaged the chart is locked: scrolling goes inert so the
1094
+ // finger only moves the price indicator across a fixed window.
1095
+ scrubActive: crosshairScrubActive,
1051
1096
  // Clear any live crosshair when a scroll drag takes over.
1052
1097
  onScrollStart: () => {
1053
1098
  "worklet";
@@ -1172,10 +1217,40 @@ function useLiveChartController({
1172
1217
  // point instead — otherwise both dots show at once. Applies on static charts
1173
1218
  // too, now that they're scrubbable.
1174
1219
  const selectionDotDuringScrub = scrubCfg !== null && selectionDotCfg !== null;
1220
+ // While scrolled back, the badge, dot, and value line still point at the live
1221
+ // price even though the window is showing history. Hide that live-priced group
1222
+ // together. `badge.followViewEdge` opts back in because all three then track
1223
+ // the visible edge price; `hideLiveOnScrollBack: false` keeps the legacy group.
1224
+ const hideLiveOnScrollBack = resolveHideLiveOnScrollBack(
1225
+ timeScroll,
1226
+ badgeCfg?.followViewEdge ?? false,
1227
+ );
1175
1228
  const liveDotOpacity = useDerivedValue(
1176
1229
  () =>
1177
1230
  reveal.dotOpacity.value *
1178
- (selectionDotDuringScrub && crosshairScrubActive.value ? 0 : 1),
1231
+ (selectionDotDuringScrub && crosshairScrubActive.value ? 0 : 1) *
1232
+ liveIndicatorScrollOpacity(
1233
+ hideLiveOnScrollBack,
1234
+ engine.viewEnd.value,
1235
+ ),
1236
+ );
1237
+ // Same scrolled-back gating for the value line: it would draw a dashed line
1238
+ // at the live value's Y — a price that isn't in the scrolled-back view.
1239
+ const valueLineOpacity = useDerivedValue(
1240
+ () =>
1241
+ reveal.lineOpacity.value *
1242
+ liveIndicatorScrollOpacity(
1243
+ hideLiveOnScrollBack,
1244
+ engine.viewEnd.value,
1245
+ ),
1246
+ );
1247
+ const liveBadgeOpacity = useDerivedValue(
1248
+ () =>
1249
+ reveal.badgeOpacity.value *
1250
+ liveIndicatorScrollOpacity(
1251
+ hideLiveOnScrollBack,
1252
+ engine.viewEnd.value,
1253
+ ),
1179
1254
  );
1180
1255
 
1181
1256
  // Fade the annotation overlays (markers + reference lines) out while scrubbing
@@ -1235,10 +1310,14 @@ function useLiveChartController({
1235
1310
  leftEdgeFadeCfg,
1236
1311
  metricsCfg,
1237
1312
  allRefLines,
1313
+ refLineKeys,
1238
1314
  refLineCustom,
1315
+ refLineOffAxisCustom,
1316
+ refLineCustomTagWidths,
1239
1317
  dragValues,
1240
1318
  dragActive,
1241
1319
  renderReferenceLine,
1320
+ renderOffAxisReferenceLine,
1242
1321
  refGroupingActive: refGroupingRadius != null,
1243
1322
  refGroupResult,
1244
1323
  groupHidden,
@@ -1311,9 +1390,12 @@ function useLiveChartController({
1311
1390
  dotX,
1312
1391
  dotY,
1313
1392
  liveDotOpacity,
1393
+ valueLineOpacity,
1394
+ liveBadgeOpacity,
1314
1395
  overlayScrubFade,
1315
1396
  markerGroupOpacity,
1316
1397
  momentumSV,
1398
+ badgeMomentumSV,
1317
1399
  onDegenShake,
1318
1400
  crosshair,
1319
1401
  rootGesture,
@@ -1414,6 +1496,8 @@ function ChartYAxisLayer({
1414
1496
  metricsCfg,
1415
1497
  gridStyleCfg,
1416
1498
  yAxisFloat,
1499
+ yAxisCfg,
1500
+ liveBadgeOpacity,
1417
1501
  } = model;
1418
1502
  return (
1419
1503
  <Group opacity={reveal.yAxisOpacity}>
@@ -1431,7 +1515,10 @@ function ChartYAxisLayer({
1431
1515
  badgeCenterY={badgeUsesRightGutter ? dotY : undefined}
1432
1516
  badgeFontSize={badgeUsesRightGutter ? badgeFont.getSize() : undefined}
1433
1517
  badgeOffsetY={badgeCfg?.offsetY ?? 0}
1518
+ badgeOpacity={badgeUsesRightGutter ? liveBadgeOpacity : undefined}
1434
1519
  gridStyle={gridStyleCfg}
1520
+ labelRightMargin={yAxisCfg?.labelRightMargin}
1521
+ gridEndGap={yAxisCfg?.gridEndGap}
1435
1522
  />
1436
1523
  </Group>
1437
1524
  );
@@ -1670,8 +1757,10 @@ function ChartStack({
1670
1757
  fontProp,
1671
1758
  badgeCfg,
1672
1759
  valueLineCfg,
1760
+ valueLineOpacity,
1673
1761
  dotY,
1674
1762
  allRefLines,
1763
+ refLineKeys,
1675
1764
  dragValues,
1676
1765
  resolvedSegments,
1677
1766
  hasRecolorSegments,
@@ -1722,9 +1811,9 @@ function ChartStack({
1722
1811
  <Group transform={degen?.shakeTransform}>
1723
1812
  {/* Segment dividers + labels (behind the line). The scrub-focus emphasis is
1724
1813
  painted on the line stroke itself, below — this overlay draws no fill. */}
1725
- {resolvedSegments.map((seg, i) => (
1814
+ {resolvedSegments.map((seg) => (
1726
1815
  <SegmentDividerOverlay
1727
- key={`seg-${seg.from ?? "start"}-${seg.to ?? "end"}-${i}`}
1816
+ key={segmentReactKey(seg)}
1728
1817
  engine={engine}
1729
1818
  padding={effectivePadding}
1730
1819
  segment={seg}
@@ -1734,7 +1823,7 @@ function ChartStack({
1734
1823
 
1735
1824
  {/* Value line + reference line (behind chart line) */}
1736
1825
  {valueLineCfg && (
1737
- <Group opacity={reveal.lineOpacity}>
1826
+ <Group opacity={valueLineOpacity}>
1738
1827
  <ValueLineOverlay
1739
1828
  dotY={dotY}
1740
1829
  engine={engine}
@@ -1746,14 +1835,12 @@ function ChartStack({
1746
1835
  </Group>
1747
1836
  )}
1748
1837
 
1749
- {/* Index keys: reference lines are a positional array and two may share
1750
- value + label (e.g. duplicate working orders at the same price), which a
1751
- content-derived key would collapse to one. Wrapped in a fade group so
1752
- `scrub.hideOverlaysOnScrub` can ease the lines out while scrubbing. */}
1838
+ {/* Wrapped in a fade group so `scrub.hideOverlaysOnScrub` can ease lines
1839
+ out while scrubbing. Explicit ids keep lines stable when reordered. */}
1753
1840
  <Group opacity={overlayScrubFade}>
1754
1841
  {allRefLines.map((rl, i) => (
1755
1842
  <ReferenceLineOverlay
1756
- key={i}
1843
+ key={refLineKeys[i]}
1757
1844
  engine={engine}
1758
1845
  padding={effectivePadding}
1759
1846
  line={rl}
@@ -1763,6 +1850,9 @@ function ChartStack({
1763
1850
  fontProp={fontProp}
1764
1851
  dragValues={dragValues}
1765
1852
  index={i}
1853
+ yAxisEntries={yAxisEntries}
1854
+ labelRightMargin={yAxisCfg?.labelRightMargin}
1855
+ gridEndGap={yAxisCfg?.gridEndGap}
1766
1856
  />
1767
1857
  ))}
1768
1858
  </Group>
@@ -1857,7 +1947,6 @@ function ChartStack({
1857
1947
  dotX={dotX}
1858
1948
  dotY={dotY}
1859
1949
  palette={palette}
1860
- engine={engine}
1861
1950
  pulse={pulseCfg}
1862
1951
  radius={dotCfg.radius}
1863
1952
  ring={dotCfg.ring}
@@ -2037,6 +2126,11 @@ function ChartScrubLayer({
2037
2126
  dimOpacity={scrubCfg.dimOpacity}
2038
2127
  liveDotExtent={liveDotExtent}
2039
2128
  crosshairLineColor={scrubCfg.crosshairLineColor}
2129
+ crosshairStrokeWidth={scrubCfg.crosshairStrokeWidth}
2130
+ crosshairOvershoot={scrubCfg.crosshairOvershoot}
2131
+ crosshairFade={scrubCfg.crosshairFade}
2132
+ crosshairFadeDistance={scrubCfg.crosshairFadeDistance}
2133
+ crosshairLineCap={scrubCfg.crosshairLineCap}
2040
2134
  crosshairDash={scrubCfg.crosshairDash}
2041
2135
  crosshairDimColor={scrubCfg.crosshairDimColor}
2042
2136
  tooltipBackground={scrubCfg.tooltipBackground}
@@ -2064,6 +2158,19 @@ function ChartScrubLayer({
2064
2158
  );
2065
2159
  }
2066
2160
 
2161
+ /** A segment's time range and presentation uniquely identify its divider view. */
2162
+ function segmentReactKey(segment: ResolvedSegment): string {
2163
+ return [
2164
+ "seg",
2165
+ segment.from ?? "start",
2166
+ segment.to ?? "end",
2167
+ segment.divider,
2168
+ segment.dividerColor,
2169
+ segment.label ?? "",
2170
+ segment.labelPosition,
2171
+ ].join(":");
2172
+ }
2173
+
2067
2174
  /** Live-value text drawn as its own canvas layer, above both the area gradient
2068
2175
  * and the left-edge fade, so the large number stays crisp at the left edge
2069
2176
  * instead of being washed out by the fade's `dstOut` blend. */
@@ -2115,14 +2222,14 @@ function ChartBadgeLayer({
2115
2222
  }) {
2116
2223
  const {
2117
2224
  badgeFont,
2118
- reveal,
2119
2225
  engine,
2120
2226
  effectivePadding,
2121
2227
  palette,
2122
2228
  formatValue,
2123
- momentumSV,
2229
+ badgeMomentumSV,
2124
2230
  metricsCfg,
2125
2231
  yAxisFloat,
2232
+ liveBadgeOpacity,
2126
2233
  } = model;
2127
2234
  const badgeCfg = model.badgeCfg!;
2128
2235
  const badgeData = useBadge(
@@ -2133,7 +2240,7 @@ function ChartBadgeLayer({
2133
2240
  badgeFont,
2134
2241
  badgeCfg.variant,
2135
2242
  badgeCfg.tail,
2136
- momentumSV,
2243
+ badgeMomentumSV,
2137
2244
  badgeCfg.position,
2138
2245
  badgeCfg.background,
2139
2246
  metricsCfg.badge,
@@ -2146,7 +2253,7 @@ function ChartBadgeLayer({
2146
2253
  );
2147
2254
  return (
2148
2255
  <Group transform={degen?.shakeTransform}>
2149
- <Group opacity={reveal.badgeOpacity}>
2256
+ <Group opacity={liveBadgeOpacity}>
2150
2257
  <BadgeOverlay
2151
2258
  badge={badgeData}
2152
2259
  font={badgeFont}
@@ -2167,13 +2274,18 @@ function ChartBadgeLayer({
2167
2274
  function ChartRefBadgeLayer({
2168
2275
  model,
2169
2276
  degen,
2277
+ yAxisEntries,
2170
2278
  }: {
2171
2279
  model: LiveChartModel;
2172
2280
  degen: DegenState | null;
2281
+ yAxisEntries: YAxisEntries | null;
2173
2282
  }) {
2174
2283
  const {
2175
2284
  allRefLines,
2285
+ refLineKeys,
2176
2286
  refLineCustom,
2287
+ refLineOffAxisCustom,
2288
+ refLineCustomTagWidths,
2177
2289
  dragValues,
2178
2290
  groupHidden,
2179
2291
  refGroupResult,
@@ -2187,6 +2299,7 @@ function ChartRefBadgeLayer({
2187
2299
  formatValue,
2188
2300
  skiaFont,
2189
2301
  fontProp,
2302
+ yAxisCfg,
2190
2303
  overlayScrubFade,
2191
2304
  } = model;
2192
2305
  if (allRefLines.length === 0) return null;
@@ -2194,7 +2307,7 @@ function ChartRefBadgeLayer({
2194
2307
  <Group transform={degen?.shakeTransform} opacity={overlayScrubFade}>
2195
2308
  {allRefLines.map((rl, i) => (
2196
2309
  <ReferenceLineOverlay
2197
- key={i}
2310
+ key={refLineKeys[i]}
2198
2311
  engine={engine}
2199
2312
  padding={effectivePadding}
2200
2313
  line={rl}
@@ -2206,7 +2319,12 @@ function ChartRefBadgeLayer({
2206
2319
  dragValues={dragValues}
2207
2320
  index={i}
2208
2321
  suppressTag={refLineCustom[i]}
2322
+ suppressTagWhenOffAxis={refLineOffAxisCustom[i]}
2323
+ customTagWidths={refLineCustomTagWidths}
2209
2324
  groupHidden={refGroupingActive ? groupHidden : undefined}
2325
+ yAxisEntries={yAxisEntries}
2326
+ labelRightMargin={yAxisCfg?.labelRightMargin}
2327
+ gridEndGap={yAxisCfg?.gridEndGap}
2210
2328
  />
2211
2329
  ))}
2212
2330
  {/* Collapsed count handles for grouped (near-value) lines. */}
@@ -2276,8 +2394,11 @@ function ChartCustomAnnotations({ model }: { model: LiveChartModel }) {
2276
2394
  markerClusterCfg,
2277
2395
  renderMarker,
2278
2396
  renderReferenceLine,
2397
+ renderOffAxisReferenceLine,
2279
2398
  allRefLines,
2280
2399
  refLineCustom,
2400
+ refLineOffAxisCustom,
2401
+ refLineCustomTagWidths,
2281
2402
  dragValues,
2282
2403
  dragActive,
2283
2404
  engine,
@@ -2316,6 +2437,21 @@ function ChartCustomAnnotations({ model }: { model: LiveChartModel }) {
2316
2437
  formatValue={formatValue}
2317
2438
  dragValues={dragValues}
2318
2439
  dragActive={dragActive}
2440
+ tagWidths={refLineCustomTagWidths}
2441
+ />
2442
+ )}
2443
+ {renderOffAxisReferenceLine && allRefLines.length > 0 && (
2444
+ <CustomReferenceLineOverlay
2445
+ lines={allRefLines}
2446
+ renderReferenceLine={renderOffAxisReferenceLine}
2447
+ custom={refLineOffAxisCustom}
2448
+ engine={engine}
2449
+ padding={effectivePadding}
2450
+ formatValue={formatValue}
2451
+ dragValues={dragValues}
2452
+ dragActive={dragActive}
2453
+ tagWidths={refLineCustomTagWidths}
2454
+ offAxisOnly
2319
2455
  />
2320
2456
  )}
2321
2457
  </Animated.View>
@@ -2357,6 +2493,7 @@ function ChartView({
2357
2493
  renderTooltip,
2358
2494
  renderOverlay,
2359
2495
  renderReferenceLine,
2496
+ renderOffAxisReferenceLine,
2360
2497
  allRefLines,
2361
2498
  scrubCfg,
2362
2499
  crosshair,
@@ -2428,7 +2565,11 @@ function ChartView({
2428
2565
  )}
2429
2566
 
2430
2567
  {/* Reference-line badges + labels above the fade so they stay crisp. */}
2431
- <ChartRefBadgeLayer model={model} degen={degen} />
2568
+ <ChartRefBadgeLayer
2569
+ model={model}
2570
+ degen={degen}
2571
+ yAxisEntries={yAxisEntries}
2572
+ />
2432
2573
 
2433
2574
  <ChartValueOverlay model={model} degen={degen} />
2434
2575
 
@@ -2466,7 +2607,8 @@ function ChartView({
2466
2607
  Skia markers (the wrapper is full-bleed; children keep their own
2467
2608
  absolute positions). */}
2468
2609
  {((markersActive && renderMarker) ||
2469
- (renderReferenceLine && allRefLines.length > 0)) && (
2610
+ ((renderReferenceLine || renderOffAxisReferenceLine) &&
2611
+ allRefLines.length > 0)) && (
2470
2612
  <ChartCustomAnnotations model={model} />
2471
2613
  )}
2472
2614
 
@@ -2481,12 +2623,13 @@ function ChartView({
2481
2623
  scrubTime={crosshair.scrubTime}
2482
2624
  scrubActive={crosshair.scrubActive}
2483
2625
  scrubCandle={crosshair.scrubCandle}
2484
- crosshairOpacity={crosshair.crosshairOpacity}
2485
2626
  tooltipLayout={crosshair.tooltipLayout}
2486
2627
  engine={engine}
2487
2628
  padding={effectivePadding}
2488
2629
  placement={scrubCfg.tooltipPlacement}
2489
2630
  margin={scrubCfg.tooltipMargin}
2631
+ crosshairFade={scrubCfg.crosshairFade}
2632
+ crosshairFadeDistance={scrubCfg.crosshairFadeDistance}
2490
2633
  lineTop={crosshair.tooltipLineTop}
2491
2634
  scrubDotY={crosshair.scrubDotY}
2492
2635
  />