react-native-livechart 4.20.0 → 4.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/dist/components/AxisLabelOverlay.d.ts +5 -1
  2. package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
  3. package/dist/components/CrosshairOverlay.d.ts +4 -2
  4. package/dist/components/CrosshairOverlay.d.ts.map +1 -1
  5. package/dist/components/CustomMarkerOverlay.d.ts.map +1 -1
  6. package/dist/components/CustomThresholdBadgeOverlay.d.ts +18 -0
  7. package/dist/components/CustomThresholdBadgeOverlay.d.ts.map +1 -0
  8. package/dist/components/CustomTooltipOverlay.d.ts +4 -2
  9. package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
  10. package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
  11. package/dist/components/ExtremaConnectorOverlay.d.ts +5 -1
  12. package/dist/components/ExtremaConnectorOverlay.d.ts.map +1 -1
  13. package/dist/components/LiveChart.d.ts.map +1 -1
  14. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  15. package/dist/components/MarkerOverlay.d.ts +4 -2
  16. package/dist/components/MarkerOverlay.d.ts.map +1 -1
  17. package/dist/components/ReferenceLineOverlay.d.ts +10 -5
  18. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
  19. package/dist/components/ReferenceLineSeriesOverlay.d.ts +19 -0
  20. package/dist/components/ReferenceLineSeriesOverlay.d.ts.map +1 -0
  21. package/dist/core/chartDataPresence.d.ts +8 -0
  22. package/dist/core/chartDataPresence.d.ts.map +1 -0
  23. package/dist/core/liveChartEngineTick.d.ts +6 -1
  24. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  25. package/dist/core/resolveConfig.d.ts +32 -1
  26. package/dist/core/resolveConfig.d.ts.map +1 -1
  27. package/dist/core/useLiveChartEngine.d.ts +9 -1
  28. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  29. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  30. package/dist/draw/candle.d.ts +3 -1
  31. package/dist/draw/candle.d.ts.map +1 -1
  32. package/dist/draw/candleGap.d.ts +14 -0
  33. package/dist/draw/candleGap.d.ts.map +1 -0
  34. package/dist/draw/lineGap.d.ts +25 -0
  35. package/dist/draw/lineGap.d.ts.map +1 -0
  36. package/dist/draw/markerAtlas.d.ts +3 -2
  37. package/dist/draw/markerAtlas.d.ts.map +1 -1
  38. package/dist/hooks/crosshairShared.d.ts +7 -3
  39. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  40. package/dist/hooks/useCandleGapPaths.d.ts +12 -0
  41. package/dist/hooks/useCandleGapPaths.d.ts.map +1 -0
  42. package/dist/hooks/useChartPaths.d.ts +4 -1
  43. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  44. package/dist/hooks/useChartReveal.d.ts +1 -1
  45. package/dist/hooks/useChartReveal.d.ts.map +1 -1
  46. package/dist/hooks/useCrosshair.d.ts +9 -5
  47. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  48. package/dist/hooks/useDegen.d.ts.map +1 -1
  49. package/dist/hooks/useLineGapPaths.d.ts +10 -0
  50. package/dist/hooks/useLineGapPaths.d.ts.map +1 -0
  51. package/dist/hooks/useLiveChartHasData.d.ts +2 -2
  52. package/dist/hooks/useLiveChartHasData.d.ts.map +1 -1
  53. package/dist/hooks/useReferenceLine.d.ts +4 -3
  54. package/dist/hooks/useReferenceLine.d.ts.map +1 -1
  55. package/dist/hooks/useReferenceLineSeries.d.ts +23 -0
  56. package/dist/hooks/useReferenceLineSeries.d.ts.map +1 -0
  57. package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -1
  58. package/dist/hooks/useThreshold.d.ts +13 -13
  59. package/dist/hooks/useThreshold.d.ts.map +1 -1
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/math/candleGaps.d.ts +18 -0
  63. package/dist/math/candleGaps.d.ts.map +1 -0
  64. package/dist/math/referenceLineSeries.d.ts +12 -0
  65. package/dist/math/referenceLineSeries.d.ts.map +1 -0
  66. package/dist/math/referenceLines.d.ts +5 -5
  67. package/dist/math/referenceLines.d.ts.map +1 -1
  68. package/dist/math/spline.d.ts +5 -1
  69. package/dist/math/spline.d.ts.map +1 -1
  70. package/dist/types.d.ts +191 -17
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +1 -1
  73. package/src/components/AxisLabelOverlay.tsx +162 -81
  74. package/src/components/CrosshairOverlay.tsx +394 -147
  75. package/src/components/CustomMarkerOverlay.tsx +9 -6
  76. package/src/components/CustomThresholdBadgeOverlay.tsx +74 -0
  77. package/src/components/CustomTooltipOverlay.tsx +7 -3
  78. package/src/components/DegenParticlesOverlay.tsx +34 -23
  79. package/src/components/ExtremaConnectorOverlay.tsx +43 -3
  80. package/src/components/LiveChart.tsx +1918 -974
  81. package/src/components/LiveChartSeries.tsx +445 -296
  82. package/src/components/MarkerOverlay.tsx +215 -156
  83. package/src/components/ReferenceLineOverlay.tsx +113 -55
  84. package/src/components/ReferenceLineSeriesOverlay.tsx +141 -0
  85. package/src/core/chartDataPresence.ts +24 -0
  86. package/src/core/liveChartEngineTick.ts +34 -1
  87. package/src/core/resolveConfig.ts +186 -0
  88. package/src/core/useLiveChartEngine.ts +36 -1
  89. package/src/core/useLiveChartSeriesEngine.ts +2 -0
  90. package/src/draw/candle.ts +1 -1
  91. package/src/draw/candleGap.ts +132 -0
  92. package/src/draw/lineGap.ts +180 -0
  93. package/src/draw/markerAtlas.ts +8 -3
  94. package/src/hooks/crosshairShared.ts +92 -11
  95. package/src/hooks/useCandleGapPaths.ts +88 -0
  96. package/src/hooks/useChartPaths.ts +81 -22
  97. package/src/hooks/useChartReveal.ts +1 -1
  98. package/src/hooks/useCrosshair.ts +178 -69
  99. package/src/hooks/useDegen.ts +82 -53
  100. package/src/hooks/useLineGapPaths.ts +76 -0
  101. package/src/hooks/useLiveChartHasData.ts +8 -4
  102. package/src/hooks/useReferenceLine.ts +6 -5
  103. package/src/hooks/useReferenceLineSeries.ts +101 -0
  104. package/src/hooks/useReverseMorphEngineInputs.ts +11 -20
  105. package/src/hooks/useThreshold.ts +31 -22
  106. package/src/index.ts +15 -0
  107. package/src/math/candleGaps.ts +120 -0
  108. package/src/math/referenceLineSeries.ts +83 -0
  109. package/src/math/referenceLines.ts +34 -6
  110. package/src/math/spline.ts +15 -5
  111. package/src/types.ts +202 -17
@@ -0,0 +1,120 @@
1
+ import type { CandleGap, CandleGapKind, CandlePoint } from "../types";
2
+
3
+ /** Built-in, overridable copy for gap bands and scrub tooltips. */
4
+ export function candleGapDefaultLabel(kind: CandleGapKind): string {
5
+ "worklet";
6
+ if (kind === "no-trades") return "No trades";
7
+ if (kind === "unavailable") return "Trading unavailable";
8
+ return "Data unavailable";
9
+ }
10
+
11
+ /** Find the explicit half-open gap containing `time`. Input must be start-sorted. */
12
+ export function pickCandleGapAtTime(
13
+ gaps: CandleGap[],
14
+ time: number,
15
+ ): CandleGap | null {
16
+ "worklet";
17
+ let lo = 0;
18
+ let hi = gaps.length - 1;
19
+ while (lo <= hi) {
20
+ const mid = (lo + hi) >> 1;
21
+ if (gaps[mid].from <= time) lo = mid + 1;
22
+ else hi = mid - 1;
23
+ }
24
+ if (hi < 0) return null;
25
+ const gap = gaps[hi];
26
+ return time >= gap.from && time < gap.to ? gap : null;
27
+ }
28
+
29
+ /** Latest real candle strictly before `time`, preferring a newer live candle. */
30
+ export function previousCandleAtTime(
31
+ candles: CandlePoint[],
32
+ liveCandle: CandlePoint | null,
33
+ time: number,
34
+ ): CandlePoint | null {
35
+ "worklet";
36
+ let lo = 0;
37
+ let hi = candles.length;
38
+ while (lo < hi) {
39
+ const mid = (lo + hi) >> 1;
40
+ if (candles[mid].time < time) lo = mid + 1;
41
+ else hi = mid;
42
+ }
43
+ let result = lo > 0 ? candles[lo - 1] : null;
44
+ if (
45
+ liveCandle &&
46
+ liveCandle.time < time &&
47
+ (!result || liveCandle.time > result.time)
48
+ ) {
49
+ result = liveCandle;
50
+ }
51
+ return result;
52
+ }
53
+
54
+ /** Previous observed close for a bridged gap; `null` when no anchor exists. */
55
+ export function previousCandleCloseAtTime(
56
+ candles: CandlePoint[],
57
+ liveCandle: CandlePoint | null,
58
+ time: number,
59
+ ): number | null {
60
+ "worklet";
61
+ return previousCandleAtTime(candles, liveCandle, time)?.close ?? null;
62
+ }
63
+
64
+ /** Whether a real candle starts in the bucket beginning at `bucketTime`. */
65
+ export function candleAtBucketTime(
66
+ candles: CandlePoint[],
67
+ liveCandle: CandlePoint | null,
68
+ bucketTime: number,
69
+ candleWidthSecs: number,
70
+ ): CandlePoint | null {
71
+ "worklet";
72
+ let lo = 0;
73
+ let hi = candles.length - 1;
74
+ while (lo <= hi) {
75
+ const mid = (lo + hi) >> 1;
76
+ const t = candles[mid].time;
77
+ if (t < bucketTime) lo = mid + 1;
78
+ else if (t > bucketTime) hi = mid - 1;
79
+ else return candles[mid];
80
+ }
81
+ if (
82
+ liveCandle &&
83
+ Math.abs(liveCandle.time - bucketTime) < candleWidthSecs * 1e-6
84
+ ) {
85
+ return liveCandle;
86
+ }
87
+ return null;
88
+ }
89
+
90
+ /**
91
+ * Bucket start under `time`, aligned to the nearest preceding real candle.
92
+ * Returns `null` for a partial edge bucket, a bucket with real OHLC, or a gap
93
+ * without a previous-close anchor.
94
+ */
95
+ export function candleGapBucketStartAtTime(
96
+ gap: CandleGap,
97
+ time: number,
98
+ candles: CandlePoint[],
99
+ liveCandle: CandlePoint | null,
100
+ candleWidthSecs: number,
101
+ ): number | null {
102
+ "worklet";
103
+ if (
104
+ candleWidthSecs <= 0 ||
105
+ time < gap.from ||
106
+ time >= gap.to ||
107
+ gap.kind !== "no-trades"
108
+ ) {
109
+ return null;
110
+ }
111
+ const previous = previousCandleAtTime(candles, liveCandle, time);
112
+ if (!previous) return null;
113
+ const steps = Math.floor((time - previous.time) / candleWidthSecs);
114
+ const bucket = previous.time + steps * candleWidthSecs;
115
+ if (bucket < gap.from || bucket + candleWidthSecs > gap.to) return null;
116
+ if (candleAtBucketTime(candles, liveCandle, bucket, candleWidthSecs)) {
117
+ return null;
118
+ }
119
+ return bucket;
120
+ }
@@ -0,0 +1,83 @@
1
+ import type { ChartPadding } from "../draw/line";
2
+ import type { LiveChartPoint } from "../types";
3
+ import { interpolateAtTime } from "./interpolate";
4
+ import { thresholdLineY } from "./threshold";
5
+
6
+ /**
7
+ * Project a historical reference-line series into screen-space `[x, y, …]`
8
+ * points for the visible time window. Window-edge values are interpolated so
9
+ * the path enters and leaves the plot cleanly. The series clamps to its first
10
+ * value on the left and, by default, to its last value at the live edge.
11
+ *
12
+ * Worklet-safe. When `out` is supplied it is cleared and reused.
13
+ */
14
+ export function buildReferenceLineSeriesPoints(
15
+ points: LiveChartPoint[],
16
+ now: number,
17
+ windowSecs: number,
18
+ displayMin: number,
19
+ displayMax: number,
20
+ canvasWidth: number,
21
+ canvasHeight: number,
22
+ padding: ChartPadding,
23
+ extendToNow: boolean,
24
+ out?: number[],
25
+ ): number[] {
26
+ "worklet";
27
+ const result = out ?? [];
28
+ result.length = 0;
29
+
30
+ const plotLeft = padding.left;
31
+ const plotRight = canvasWidth - padding.right;
32
+ const plotWidth = plotRight - plotLeft;
33
+ const plotHeight = canvasHeight - padding.top - padding.bottom;
34
+ const valueRange = displayMax - displayMin;
35
+ if (
36
+ points.length === 0 ||
37
+ !(windowSecs > 0) ||
38
+ plotWidth <= 0 ||
39
+ plotHeight <= 0 ||
40
+ !(valueRange > 0)
41
+ ) {
42
+ return result;
43
+ }
44
+
45
+ const windowStart = now - windowSecs;
46
+ const lastTime = points[points.length - 1].time;
47
+ const endTime = extendToNow ? now : Math.min(now, lastTime);
48
+ if (endTime < windowStart) return result;
49
+
50
+ const startValue = interpolateAtTime(points, windowStart);
51
+ const endValue = interpolateAtTime(points, endTime);
52
+ if (
53
+ startValue === null ||
54
+ endValue === null ||
55
+ !Number.isFinite(startValue) ||
56
+ !Number.isFinite(endValue)
57
+ ) {
58
+ return result;
59
+ }
60
+
61
+ const toX = (time: number) =>
62
+ plotLeft + ((time - windowStart) / windowSecs) * plotWidth;
63
+ const toY = (value: number) =>
64
+ thresholdLineY(
65
+ value,
66
+ displayMin,
67
+ displayMax,
68
+ canvasHeight,
69
+ padding.top,
70
+ padding.bottom,
71
+ );
72
+
73
+ result.push(plotLeft, toY(startValue));
74
+ for (let i = 0; i < points.length; i++) {
75
+ const point = points[i];
76
+ if (point.time <= windowStart) continue;
77
+ if (point.time >= endTime) break;
78
+ if (!Number.isFinite(point.time) || !Number.isFinite(point.value)) continue;
79
+ result.push(toX(point.time), toY(point.value));
80
+ }
81
+ result.push(toX(endTime), toY(endValue));
82
+ return result;
83
+ }
@@ -4,16 +4,22 @@ import type {
4
4
  ReferenceLineBadgeConfig,
5
5
  } from "../types";
6
6
 
7
- /** Which of the three reference-line forms a `ReferenceLine` resolves to. */
8
- export type ReferenceLineForm = "line" | "value-band" | "time-band" | "none";
7
+ /** Which of the four reference-line forms a `ReferenceLine` resolves to. */
8
+ export type ReferenceLineForm =
9
+ | "line"
10
+ | "series"
11
+ | "value-band"
12
+ | "time-band"
13
+ | "none";
9
14
 
10
15
  /**
11
- * Classify a `ReferenceLine` into one of its three forms, applying the
12
- * documented precedence A (line) > B (value band) > C (time band).
16
+ * Classify a `ReferenceLine` into one of its four forms, applying the
17
+ * documented precedence A (line) > B (series) > C (value band) > D (time band).
13
18
  */
14
19
  export function referenceLineForm(rl: ReferenceLine): ReferenceLineForm {
15
20
  "worklet";
16
21
  if (rl.value !== undefined) return "line";
22
+ if (rl.series !== undefined) return "series";
17
23
  if (rl.valueFrom !== undefined && rl.valueTo !== undefined) {
18
24
  return "value-band";
19
25
  }
@@ -24,7 +30,7 @@ export function referenceLineForm(rl: ReferenceLine): ReferenceLineForm {
24
30
  /**
25
31
  * Gather every Y value a set of reference lines should contribute to the
26
32
  * axis-range computation. Lines flagged `excludeFromRange` are skipped, as are
27
- * time bands (Form C) which constrain time, not value.
33
+ * time bands (Form D) which constrain time, not value.
28
34
  */
29
35
  export function collectReferenceValues(lines: ReferenceLine[]): number[] {
30
36
  const out: number[] = [];
@@ -35,6 +41,22 @@ export function collectReferenceValues(lines: ReferenceLine[]): number[] {
35
41
  case "line":
36
42
  out.push(rl.value as number);
37
43
  break;
44
+ case "series": {
45
+ const points = rl.series;
46
+ if (!points || points.length === 0) break;
47
+ let min = Infinity;
48
+ let max = -Infinity;
49
+ for (let j = 0; j < points.length; j++) {
50
+ const value = points[j].value;
51
+ if (!Number.isFinite(value)) continue;
52
+ if (value < min) min = value;
53
+ if (value > max) max = value;
54
+ }
55
+ // Only the extrema are needed by the engine, avoiding a per-frame scan
56
+ // through the full historical annotation series.
57
+ if (min !== Infinity) out.push(min, max);
58
+ break;
59
+ }
38
60
  case "value-band":
39
61
  out.push(rl.valueFrom as number, rl.valueTo as number);
40
62
  break;
@@ -56,7 +78,13 @@ export function referenceLineReactKeys(
56
78
  const occurrences = new Map<string, number>();
57
79
  const keys: string[] = [];
58
80
  for (const line of lines) {
59
- const base = line.id ?? stableReferenceLineSignature(line);
81
+ // A series' samples are render data, not identity. Omitting their contents
82
+ // keeps a growing historical series from remounting its overlay every render.
83
+ const base =
84
+ line.id ??
85
+ stableReferenceLineSignature(
86
+ line.series === undefined ? line : { ...line, series: "series" },
87
+ );
60
88
  const occurrence = occurrences.get(base) ?? 0;
61
89
  occurrences.set(base, occurrence + 1);
62
90
  keys.push(`${base}:${occurrence}`);
@@ -56,16 +56,26 @@ export function drawSpline(
56
56
  /** Straight polyline (`lineTo` per point) instead of the monotone cubic — an
57
57
  * angular, hard-edged line. The caller has already `moveTo`'d point 0. */
58
58
  linear = false,
59
+ /** First point index to draw (inclusive). The caller must moveTo this point. */
60
+ startPoint = 0,
61
+ /** Last point index to draw (exclusive). Defaults to the full array. */
62
+ endPoint = pts.length >> 1,
59
63
  ) {
60
64
  "worklet";
61
- const n = pts.length >> 1;
65
+ const start = Math.max(0, startPoint);
66
+ const end = Math.min(pts.length >> 1, endPoint);
67
+ const n = end - start;
62
68
  if (n < 2) return;
63
69
  if (linear) {
64
- for (let i = 1; i < n; i++) path.lineTo(pts[i * 2], pts[i * 2 + 1]);
70
+ for (let i = 1; i < n; i++) {
71
+ const point = (start + i) * 2;
72
+ path.lineTo(pts[point], pts[point + 1]);
73
+ }
65
74
  return;
66
75
  }
67
76
  if (n === 2) {
68
- path.lineTo(pts[2], pts[3]);
77
+ const point = (start + 1) * 2;
78
+ path.lineTo(pts[point], pts[point + 1]);
69
79
  return;
70
80
  }
71
81
 
@@ -73,7 +83,7 @@ export function drawSpline(
73
83
  const delta: number[] = scratch ? scratch.delta : new Array(n - 1);
74
84
  const h: number[] = scratch ? scratch.h : new Array(n - 1);
75
85
  for (let i = 0; i < n - 1; i++) {
76
- const i2 = i * 2;
86
+ const i2 = (start + i) * 2;
77
87
  const j2 = i2 + 2;
78
88
  h[i] = pts[j2] - pts[i2];
79
89
  delta[i] = h[i] === 0 ? 0 : (pts[j2 + 1] - pts[i2 + 1]) / h[i];
@@ -110,7 +120,7 @@ export function drawSpline(
110
120
 
111
121
  // 4. Draw bezier curves
112
122
  for (let i = 0; i < n - 1; i++) {
113
- const i2 = i * 2;
123
+ const i2 = (start + i) * 2;
114
124
  const j2 = i2 + 2;
115
125
  const hi = h[i];
116
126
  path.cubicTo(
package/src/types.ts CHANGED
@@ -56,11 +56,12 @@ export type CanvasMode = "transparent" | "opaque";
56
56
  export type BadgeVariant = "default" | "minimal";
57
57
 
58
58
  /**
59
- * A reference line or band drawn into the chart. Three mutually-exclusive forms,
60
- * with precedence A > B > C when fields from more than one are present:
59
+ * A reference line or band drawn into the chart. Four mutually-exclusive forms,
60
+ * with precedence A > B > C > D when fields from more than one are present:
61
61
  * - **Form A** — horizontal line at `value`.
62
- * - **Form B** — horizontal band between `valueFrom` and `valueTo`.
63
- * - **Form C** — vertical time band between `from` and `to` (unix seconds).
62
+ * - **Form B** — time-varying line following `series`.
63
+ * - **Form C** — horizontal band between `valueFrom` and `valueTo`.
64
+ * - **Form D** — vertical time band between `from` and `to` (unix seconds).
64
65
  */
65
66
  export interface ReferenceLine {
66
67
  /**
@@ -70,13 +71,25 @@ export interface ReferenceLine {
70
71
  id?: string;
71
72
  /** Form A — the Y-axis value where the horizontal line is drawn. */
72
73
  value?: number;
73
- /** Form B — horizontal band lower Y bound (paired with `valueTo`). */
74
+ /**
75
+ * Form B — a time-varying reference line. Points use unix-second timestamps
76
+ * and should be sorted oldest to newest. The first value extends to the left
77
+ * edge; the last value extends to the live edge unless {@link extendToNow} is
78
+ * `false`. Supported by line and candle charts.
79
+ */
80
+ series?: LiveChartPoint[];
81
+ /**
82
+ * Form B — extend the series' last value flat to the chart's live edge.
83
+ * Set `false` to stop at the last point. Default `true`.
84
+ */
85
+ extendToNow?: boolean;
86
+ /** Form C — horizontal band lower Y bound (paired with `valueTo`). */
74
87
  valueFrom?: number;
75
- /** Form B — horizontal band upper Y bound (paired with `valueFrom`). */
88
+ /** Form C — horizontal band upper Y bound (paired with `valueFrom`). */
76
89
  valueTo?: number;
77
- /** Form C — vertical time-band start, unix seconds (paired with `to`). */
90
+ /** Form D — vertical time-band start, unix seconds (paired with `to`). */
78
91
  from?: number;
79
- /** Form C — vertical time-band end, unix seconds (paired with `from`). */
92
+ /** Form D — vertical time-band end, unix seconds (paired with `from`). */
80
93
  to?: number;
81
94
  /** Optional right-gutter label (e.g. `"Entry"`). */
82
95
  label?: string;
@@ -86,7 +99,7 @@ export interface ReferenceLine {
86
99
  * (top/bottom for value bands, left/right for time bands); omit for no border.
87
100
  */
88
101
  strokeWidth?: number;
89
- /** Dash pattern as `[dashLength, gapLength]` in pixels (line stroke + band border). */
102
+ /** Dash pattern as `[dashLength, gapLength]` in pixels (line/series stroke + band border). */
90
103
  intervals?: [number, number];
91
104
  /**
92
105
  * Span the **full chart width** — edge to edge through the Y-axis gutter, not
@@ -94,13 +107,20 @@ export interface ReferenceLine {
94
107
  * value on the axis (like a price tag). Only the line/band extends; any
95
108
  * `label`/`badge` stays anchored inside the plot. For a Form-A line with a
96
109
  * `badge`, the full-width line replaces the dashed connector. No effect on a
97
- * vertical time band. Default `false` (stops at the plot edge). Form A / B.
110
+ * vertical time band. Default `false` (stops at the plot edge). Form A / C.
98
111
  */
99
112
  fullWidth?: boolean;
100
113
  /** Line / band color override. Defaults to palette `refLine`. */
101
114
  color?: string;
115
+ /**
116
+ * Band fill color override. Defaults to {@link color}, then palette `refLine`.
117
+ * Form C / D only.
118
+ */
119
+ fillColor?: string;
102
120
  /** Fill opacity for a value / time band (0–1). Default `0.16`. */
103
121
  fillOpacity?: number;
122
+ /** Opacity for the line, series, or band border stroke (0–1). Default `1`. */
123
+ strokeOpacity?: number;
104
124
  /** Label text color. Defaults to `color`, then palette `refLabel`. */
105
125
  labelColor?: string;
106
126
  /**
@@ -109,7 +129,7 @@ export interface ReferenceLine {
109
129
  * (default `"left"`).
110
130
  */
111
131
  labelPosition?: "left" | "center" | "right";
112
- /** Append the formatted `value` to the label (Form A only). Default `false`. */
132
+ /** Append the formatted value to the label (Form A/B). Default `false`. */
113
133
  showValue?: boolean;
114
134
  /**
115
135
  * Exclude this line's value(s) from the Y-axis range computation, so it may sit
@@ -541,6 +561,13 @@ export interface ThresholdLineConfig {
541
561
  * gutter like a legacy reference line (may overlap y-axis labels). Default `"left"`.
542
562
  */
543
563
  labelPosition?: "left" | "right";
564
+ /**
565
+ * Time-varying threshold series only: which visible endpoint supplies the
566
+ * label badge's Y position and optional value. `"first"` samples the
567
+ * threshold at the visible window's left edge; `"last"` uses the live/right
568
+ * edge. Independent of {@link labelPosition}. Default `"last"`.
569
+ */
570
+ labelAnchor?: "first" | "last";
544
571
  /** Line + label color. Defaults to palette `refLine` / `refLabel`. */
545
572
  color?: string;
546
573
  /** Dash pattern `[dashLength, gapLength]` in pixels. Default `[4, 4]`. */
@@ -554,6 +581,25 @@ export interface ThresholdLineConfig {
554
581
  labelColor?: string;
555
582
  }
556
583
 
584
+ /**
585
+ * Context passed to {@link LiveChartProps.renderThresholdBadge}. The chart
586
+ * floats the returned React Native element over the canvas and pins it to the
587
+ * live threshold on the UI thread. Bind the SharedValues to animated content
588
+ * when its displayed value must update without React re-renders.
589
+ */
590
+ export interface ThresholdBadgeRenderProps {
591
+ /** Resolved marker-line config for the badge being rendered. */
592
+ line: ThresholdLineConfig;
593
+ /** Live threshold value in Y-axis units. */
594
+ value: SharedValue<number>;
595
+ /** The threshold value formatted with the chart's `formatValue`. */
596
+ valueStr: SharedValue<string>;
597
+ /** Canvas Y pixel of the threshold (`NaN` when geometry is unavailable). */
598
+ y: SharedValue<number>;
599
+ /** Whether the threshold badge currently belongs inside the visible plot. */
600
+ visible: SharedValue<boolean>;
601
+ }
602
+
557
603
  /** Object form of {@link ThresholdConfig.fill} — band tuning. */
558
604
  export interface ThresholdFillConfig {
559
605
  /** Band fill opacity (0–1), applied to the above/below colors. Multiplies an
@@ -1143,6 +1189,12 @@ export interface TooltipRenderProps {
1143
1189
  * worklet-safe formatter (e.g. the chart's `formatValue`).
1144
1190
  */
1145
1191
  candle: SharedValue<CandlePoint | null>;
1192
+ /**
1193
+ * Explicit line or candle gap under the crosshair (`null` outside a
1194
+ * configured gap). A gap never fabricates {@link candle}; inspect this field
1195
+ * for no-trade, unavailable, or unknown-data treatment.
1196
+ */
1197
+ gap: SharedValue<ChartGap | null>;
1146
1198
  }
1147
1199
 
1148
1200
  /** Inner plot rectangle in canvas pixels (a snapshot field of {@link ChartScale}). */
@@ -1743,6 +1795,8 @@ export interface ScrubPointCore {
1743
1795
  export interface ScrubPoint extends ScrubPointCore {
1744
1796
  /** In candle mode, the OHLC data of the candle under the crosshair. */
1745
1797
  candle?: CandlePoint;
1798
+ /** Explicit chart gap under the crosshair, when a bridged gap supplies the value. */
1799
+ gap?: ChartGap;
1746
1800
  }
1747
1801
 
1748
1802
  /** Scrub callback payload for multi-series charts. */
@@ -1772,6 +1826,110 @@ export interface CandlePoint {
1772
1826
  volume?: number;
1773
1827
  }
1774
1828
 
1829
+ /** Why a chart time range contains no observed market sample. */
1830
+ export type CandleGapKind = "no-trades" | "unavailable" | "unknown";
1831
+
1832
+ /**
1833
+ * Explicit metadata for a chart interval without an observed sample. The range
1834
+ * is half-open (`[from, to)`) and uses Unix seconds, matching line points and
1835
+ * {@link CandlePoint.time}. Gaps do not replace or mutate real data.
1836
+ */
1837
+ export interface CandleGap {
1838
+ /** Inclusive range start as a Unix timestamp in seconds. */
1839
+ from: number;
1840
+ /** Exclusive range end as a Unix timestamp in seconds. Must be greater than {@link from}. */
1841
+ to: number;
1842
+ /** Semantic reason the interval has no observation. */
1843
+ kind: CandleGapKind;
1844
+ /** Optional localized label, e.g. `"Exchange maintenance"`. */
1845
+ label?: string;
1846
+ }
1847
+
1848
+ /** Previous-value bridge styling for a chart gap. */
1849
+ export interface CandleGapBridgeStyle {
1850
+ /** Mark color. Defaults to palette `refLine`. */
1851
+ color?: string;
1852
+ /** Mark opacity. Default `0.7` when enabled (`0.55` for unavailable gaps). */
1853
+ opacity?: number;
1854
+ /** Mark stroke width in pixels. Default `2`. */
1855
+ strokeWidth?: number;
1856
+ /** Mark cap style. Default `"round"`. */
1857
+ strokeCap?: "butt" | "round" | "square";
1858
+ }
1859
+
1860
+ /** Full-height time-band styling for a chart gap. */
1861
+ export interface CandleGapBandStyle {
1862
+ /** Band fill color. Defaults to palette `refLine`. */
1863
+ fillColor?: string;
1864
+ /** Band fill opacity. Default `0.12` when enabled (`0.08` for unknown gaps). */
1865
+ fillOpacity?: number;
1866
+ /** Band-edge color. Defaults to palette `refLine`. */
1867
+ borderColor?: string;
1868
+ /** Band-edge opacity. Default `1`. */
1869
+ borderOpacity?: number;
1870
+ /**
1871
+ * Band-edge width in pixels. `0` hides the border. Default `2` (`1` for
1872
+ * unknown gaps).
1873
+ */
1874
+ borderWidth?: number;
1875
+ /** Band-edge dash pattern. Default `[4, 4]`. */
1876
+ intervals?: [number, number];
1877
+ }
1878
+
1879
+ /** Label styling for a chart-gap time band. Text comes from {@link CandleGap.label}. */
1880
+ export interface CandleGapLabelStyle {
1881
+ /** Label color. Defaults to palette `refLabel`. */
1882
+ color?: string;
1883
+ /** Label position inside the band. Default `"left"`. */
1884
+ position?: "left" | "right";
1885
+ }
1886
+
1887
+ /** Per-kind presentation overrides for {@link CandleGapsConfig}. */
1888
+ export interface CandleGapStyle {
1889
+ /**
1890
+ * Previous-close marks. `false` disables them; an object enables and styles
1891
+ * them. Omission preserves the semantic default for the gap kind.
1892
+ */
1893
+ bridge?: false | CandleGapBridgeStyle;
1894
+ /**
1895
+ * Full-height time band. `false` disables it; an object enables and styles it.
1896
+ * Omission preserves the semantic default for the gap kind.
1897
+ */
1898
+ band?: false | CandleGapBandStyle;
1899
+ /**
1900
+ * Band label. `false` hides it; an object enables and styles it. Omission
1901
+ * preserves the semantic default for the gap kind.
1902
+ */
1903
+ label?: false | CandleGapLabelStyle;
1904
+ }
1905
+
1906
+ /**
1907
+ * Data and optional per-kind styling for explicit chart gaps. Gap metadata is
1908
+ * low-frequency React data (like `referenceLines`); live market data remains in
1909
+ * its SharedValue.
1910
+ */
1911
+ export interface CandleGapsConfig {
1912
+ /** Sorted, non-overlapping gap ranges. Invalid ranges are ignored. */
1913
+ gaps: CandleGap[];
1914
+ /** Presentation overrides keyed by semantic gap kind. */
1915
+ styles?: Partial<Record<CandleGapKind, CandleGapStyle>>;
1916
+ }
1917
+
1918
+ /** Semantic reason for a chart interval with no observed market sample. */
1919
+ export type ChartGapKind = CandleGapKind;
1920
+ /** Shared gap metadata used by both line and candlestick charts. */
1921
+ export type ChartGap = CandleGap;
1922
+ /** Previous-value bridge styling shared by line and candlestick gaps. */
1923
+ export type ChartGapBridgeStyle = CandleGapBridgeStyle;
1924
+ /** Full-height time-band styling shared by line and candlestick gaps. */
1925
+ export type ChartGapBandStyle = CandleGapBandStyle;
1926
+ /** Time-band label styling shared by line and candlestick gaps. */
1927
+ export type ChartGapLabelStyle = CandleGapLabelStyle;
1928
+ /** Per-kind gap presentation shared by line and candlestick charts. */
1929
+ export type ChartGapStyle = CandleGapStyle;
1930
+ /** Gap data and per-kind styles shared by line and candlestick charts. */
1931
+ export type ChartGapsConfig = CandleGapsConfig;
1932
+
1775
1933
  // ── Metrics (sizing & motion tokens) ─────────────────────────────────────────
1776
1934
 
1777
1935
  /**
@@ -2103,7 +2261,7 @@ export interface LiveChartCoreProps {
2103
2261
  transitions?: boolean | TransitionConfig;
2104
2262
  /**
2105
2263
  * Breathing-line loading shell. When this becomes `false`, the chart reveals
2106
- * only if there is data (≥2 line points or ≥2 committed candles).
2264
+ * only if there is data (≥1 line point or ≥1 committed candle).
2107
2265
  *
2108
2266
  * `true` shows the shell with the defaults; pass a {@link LoadingConfig} to
2109
2267
  * restyle it — `color` / `strokeWidth` for the squiggle + skeleton, `amplitude`
@@ -2172,8 +2330,8 @@ export interface LiveChartCoreProps {
2172
2330
  */
2173
2331
  yRangeScale?: SharedValue<number>;
2174
2332
  /**
2175
- * Label in the empty state when `loading` is false and there are fewer than
2176
- * two samples (line points or committed candles). Default `"No data"`.
2333
+ * Label in the empty state when `loading` is false and there are no line
2334
+ * points or committed candles. Default `"No data"`.
2177
2335
  */
2178
2336
  emptyText?: string;
2179
2337
  /** Custom formatter for value labels (axes, badge, tooltips). Default `v => v.toFixed(2)`. */
@@ -2207,7 +2365,7 @@ export interface LiveChartCoreProps {
2207
2365
  * `render`). Default off.
2208
2366
  */
2209
2367
  bottomLabel?: boolean | AxisLabelConfig;
2210
- /** Reference lines / bands drawn into the chart. Supports all three `ReferenceLine` forms. */
2368
+ /** Reference lines / bands drawn into the chart. Supports all four `ReferenceLine` forms. */
2211
2369
  referenceLines?: ReferenceLine[];
2212
2370
  /** Per-instance grid-line styling. Pass an object to override color / width / dash / opacity. */
2213
2371
  gridStyle?: GridStyleConfig;
@@ -2426,10 +2584,21 @@ export interface LiveChartProps extends LiveChartCoreProps {
2426
2584
  segments?: ChartSegment[];
2427
2585
  /**
2428
2586
  * Color the line above vs. below a live threshold value (break-even / average
2429
- * cost, VWAP, previous close, a peg). Always a `SharedValue` so the split tracks
2430
- * live on the UI thread. See {@link ThresholdConfig}.
2587
+ * cost, VWAP, previous close, a peg). Supports a live scalar or a static/live
2588
+ * time-varying series. See {@link ThresholdConfig}.
2431
2589
  */
2432
2590
  threshold?: ThresholdConfig;
2591
+ /**
2592
+ * Render the threshold line's badge as a custom **React Native** element
2593
+ * instead of the built-in Skia pill. The chart measures and pins the element
2594
+ * to the threshold's live Y position and configured `labelPosition` on the UI
2595
+ * thread (see {@link ThresholdBadgeRenderProps}); the dashed marker line stays
2596
+ * built in. Requires `threshold.line`. Return `null`/`undefined` to keep the
2597
+ * built-in badge. Single-series line mode only.
2598
+ */
2599
+ renderThresholdBadge?: (
2600
+ ctx: ThresholdBadgeRenderProps,
2601
+ ) => ReactElement | null | undefined;
2433
2602
  /** Render the live value as a large text overlay in the top-left. Default `false`. */
2434
2603
  showValue?: boolean;
2435
2604
  /** Tint the `showValue` text by momentum (green up / red down). Default `false`. */
@@ -2444,6 +2613,22 @@ export interface LiveChartProps extends LiveChartCoreProps {
2444
2613
  candleWidth?: number;
2445
2614
  /** In-progress candle updated each tick. Must be a SharedValue for UI-thread reads. */
2446
2615
  liveCandle?: SharedValue<CandlePoint | null>;
2616
+ /**
2617
+ * Explicit candle-chart gaps. Pass a sorted array for semantic defaults, or a
2618
+ * {@link CandleGapsConfig} to override per-kind styling. Missing timestamps
2619
+ * are never inferred: `"no-trades"` draws neutral previous-close marks,
2620
+ * `"unavailable"` adds a labeled time band, and `"unknown"` leaves price
2621
+ * unfilled. Candle mode only. Default off.
2622
+ */
2623
+ candleGaps?: CandleGap[] | CandleGapsConfig;
2624
+ /**
2625
+ * Explicit line-chart gaps. Pass a sorted array for semantic defaults, or a
2626
+ * {@link ChartGapsConfig} to override per-kind styling. The normal line and
2627
+ * every area fill stop at the interval boundaries; bridge-enabled kinds draw
2628
+ * a flat previous-value segment. Real samples inside a range take precedence
2629
+ * over its metadata. Line mode only. Default off.
2630
+ */
2631
+ lineGaps?: ChartGap[] | ChartGapsConfig;
2447
2632
  /**
2448
2633
  * Live trade fills for optional on-chart markers. Read on the UI thread only —
2449
2634
  * pass a `SharedValue` and update from JS via `.value` (same pattern as `data` / `value`).