react-native-livechart 3.12.0 → 4.1.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 (140) hide show
  1. package/dist/components/BadgeOverlay.d.ts +9 -1
  2. package/dist/components/BadgeOverlay.d.ts.map +1 -1
  3. package/dist/components/ChartOverlayLayer.d.ts +19 -0
  4. package/dist/components/ChartOverlayLayer.d.ts.map +1 -0
  5. package/dist/components/CustomMarkerOverlay.d.ts +16 -6
  6. package/dist/components/CustomMarkerOverlay.d.ts.map +1 -1
  7. package/dist/components/CustomReferenceLineOverlay.d.ts +36 -0
  8. package/dist/components/CustomReferenceLineOverlay.d.ts.map +1 -0
  9. package/dist/components/CustomTooltipOverlay.d.ts +5 -2
  10. package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
  11. package/dist/components/DotOverlay.d.ts +7 -1
  12. package/dist/components/DotOverlay.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 +6 -3
  16. package/dist/components/MarkerOverlay.d.ts.map +1 -1
  17. package/dist/components/MultiSeriesDots.d.ts +4 -1
  18. package/dist/components/MultiSeriesDots.d.ts.map +1 -1
  19. package/dist/components/ReferenceLineGroupOverlay.d.ts +27 -0
  20. package/dist/components/ReferenceLineGroupOverlay.d.ts.map +1 -0
  21. package/dist/components/ReferenceLineOverlay.d.ts +25 -2
  22. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
  23. package/dist/components/XAxisOverlay.d.ts +7 -1
  24. package/dist/components/XAxisOverlay.d.ts.map +1 -1
  25. package/dist/components/YAxisOverlay.d.ts +12 -1
  26. package/dist/components/YAxisOverlay.d.ts.map +1 -1
  27. package/dist/constants.d.ts +13 -0
  28. package/dist/constants.d.ts.map +1 -1
  29. package/dist/core/liveChartEngineTick.d.ts +29 -0
  30. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  31. package/dist/core/liveChartSeriesEngineTick.d.ts +18 -0
  32. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
  33. package/dist/core/resolveConfig.d.ts +62 -2
  34. package/dist/core/resolveConfig.d.ts.map +1 -1
  35. package/dist/core/useLiveChartEngine.d.ts +58 -7
  36. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  37. package/dist/core/useLiveChartSeriesEngine.d.ts +8 -2
  38. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  39. package/dist/draw/grid.d.ts +13 -1
  40. package/dist/draw/grid.d.ts.map +1 -1
  41. package/dist/draw/markerAtlas.d.ts +20 -2
  42. package/dist/draw/markerAtlas.d.ts.map +1 -1
  43. package/dist/draw/volume.d.ts +25 -0
  44. package/dist/draw/volume.d.ts.map +1 -0
  45. package/dist/hooks/crosshairShared.d.ts +12 -6
  46. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  47. package/dist/hooks/overlayScale.d.ts +22 -0
  48. package/dist/hooks/overlayScale.d.ts.map +1 -0
  49. package/dist/hooks/resolveChartLayout.d.ts +9 -0
  50. package/dist/hooks/resolveChartLayout.d.ts.map +1 -1
  51. package/dist/hooks/useBadge.d.ts +22 -1
  52. package/dist/hooks/useBadge.d.ts.map +1 -1
  53. package/dist/hooks/useCandlePaths.d.ts +6 -0
  54. package/dist/hooks/useCandlePaths.d.ts.map +1 -1
  55. package/dist/hooks/useChartOverlayContext.d.ts +45 -0
  56. package/dist/hooks/useChartOverlayContext.d.ts.map +1 -0
  57. package/dist/hooks/useChartPaths.d.ts +8 -1
  58. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  59. package/dist/hooks/useCrosshair.d.ts +9 -2
  60. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  61. package/dist/hooks/useLiveDot.d.ts +6 -1
  62. package/dist/hooks/useLiveDot.d.ts.map +1 -1
  63. package/dist/hooks/useMarkers.d.ts +9 -4
  64. package/dist/hooks/useMarkers.d.ts.map +1 -1
  65. package/dist/hooks/usePanScroll.d.ts +82 -0
  66. package/dist/hooks/usePanScroll.d.ts.map +1 -0
  67. package/dist/hooks/usePinchZoom.d.ts +74 -0
  68. package/dist/hooks/usePinchZoom.d.ts.map +1 -0
  69. package/dist/hooks/useReferenceDrag.d.ts +24 -0
  70. package/dist/hooks/useReferenceDrag.d.ts.map +1 -0
  71. package/dist/hooks/useReferenceLine.d.ts +23 -4
  72. package/dist/hooks/useReferenceLine.d.ts.map +1 -1
  73. package/dist/hooks/useReferenceLinePress.d.ts +5 -1
  74. package/dist/hooks/useReferenceLinePress.d.ts.map +1 -1
  75. package/dist/hooks/useVisibleRange.d.ts +39 -0
  76. package/dist/hooks/useVisibleRange.d.ts.map +1 -0
  77. package/dist/hooks/useYAxis.d.ts +1 -1
  78. package/dist/hooks/useYAxis.d.ts.map +1 -1
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/math/markerCluster.d.ts +47 -0
  82. package/dist/math/markerCluster.d.ts.map +1 -0
  83. package/dist/math/markers.d.ts +10 -0
  84. package/dist/math/markers.d.ts.map +1 -1
  85. package/dist/math/referenceDrag.d.ts +24 -0
  86. package/dist/math/referenceDrag.d.ts.map +1 -0
  87. package/dist/math/referenceGroup.d.ts +31 -0
  88. package/dist/math/referenceGroup.d.ts.map +1 -0
  89. package/dist/math/referenceLines.d.ts +33 -7
  90. package/dist/math/referenceLines.d.ts.map +1 -1
  91. package/dist/types.d.ts +524 -21
  92. package/dist/types.d.ts.map +1 -1
  93. package/package.json +1 -1
  94. package/src/components/BadgeOverlay.tsx +26 -1
  95. package/src/components/ChartOverlayLayer.tsx +32 -0
  96. package/src/components/CustomMarkerOverlay.tsx +150 -57
  97. package/src/components/CustomReferenceLineOverlay.tsx +250 -0
  98. package/src/components/CustomTooltipOverlay.tsx +27 -7
  99. package/src/components/DotOverlay.tsx +9 -0
  100. package/src/components/LiveChart.tsx +550 -32
  101. package/src/components/LiveChartSeries.tsx +111 -5
  102. package/src/components/MarkerOverlay.tsx +92 -6
  103. package/src/components/MultiSeriesDots.tsx +12 -3
  104. package/src/components/ReferenceLineGroupOverlay.tsx +207 -0
  105. package/src/components/ReferenceLineOverlay.tsx +105 -32
  106. package/src/components/XAxisOverlay.tsx +9 -2
  107. package/src/components/YAxisOverlay.tsx +49 -28
  108. package/src/constants.ts +17 -0
  109. package/src/core/liveChartEngineTick.ts +81 -8
  110. package/src/core/liveChartSeriesEngineTick.ts +50 -9
  111. package/src/core/resolveConfig.ts +133 -2
  112. package/src/core/useLiveChartEngine.ts +111 -11
  113. package/src/core/useLiveChartSeriesEngine.ts +26 -3
  114. package/src/draw/grid.ts +71 -2
  115. package/src/draw/markerAtlas.ts +117 -3
  116. package/src/draw/volume.ts +190 -0
  117. package/src/hooks/crosshairShared.ts +37 -9
  118. package/src/hooks/overlayScale.ts +59 -0
  119. package/src/hooks/resolveChartLayout.ts +33 -0
  120. package/src/hooks/useBadge.ts +89 -21
  121. package/src/hooks/useCandlePaths.ts +104 -7
  122. package/src/hooks/useChartOverlayContext.ts +93 -0
  123. package/src/hooks/useChartPaths.ts +14 -1
  124. package/src/hooks/useCrosshair.ts +33 -2
  125. package/src/hooks/useLiveDot.ts +16 -5
  126. package/src/hooks/useMarkers.ts +39 -11
  127. package/src/hooks/usePanScroll.ts +253 -0
  128. package/src/hooks/usePinchZoom.ts +189 -0
  129. package/src/hooks/useReferenceDrag.ts +254 -0
  130. package/src/hooks/useReferenceLine.ts +56 -9
  131. package/src/hooks/useReferenceLinePress.ts +16 -2
  132. package/src/hooks/useVisibleRange.ts +119 -0
  133. package/src/hooks/useYAxis.ts +2 -0
  134. package/src/index.ts +17 -1
  135. package/src/math/markerCluster.ts +214 -0
  136. package/src/math/markers.ts +35 -3
  137. package/src/math/referenceDrag.ts +66 -0
  138. package/src/math/referenceGroup.ts +65 -0
  139. package/src/math/referenceLines.ts +76 -19
  140. package/src/types.ts +544 -21
package/src/types.ts CHANGED
@@ -79,6 +79,15 @@ export interface ReferenceLine {
79
79
  strokeWidth?: number;
80
80
  /** Dash pattern as `[dashLength, gapLength]` in pixels (line stroke + band border). */
81
81
  intervals?: [number, number];
82
+ /**
83
+ * Span the **full chart width** — edge to edge through the Y-axis gutter, not
84
+ * clipped at the plot's right edge — so the line/band visually connects to its
85
+ * value on the axis (like a price tag). Only the line/band extends; any
86
+ * `label`/`badge` stays anchored inside the plot. For a Form-A line with a
87
+ * `badge`, the full-width line replaces the dashed connector. No effect on a
88
+ * vertical time band. Default `false` (stops at the plot edge). Form A / B.
89
+ */
90
+ fullWidth?: boolean;
82
91
  /** Line / band color override. Defaults to palette `refLine`. */
83
92
  color?: string;
84
93
  /** Fill opacity for a value / time band (0–1). Default `0.16`. */
@@ -126,6 +135,86 @@ export interface ReferenceLine {
126
135
  badgeBorderColor?: string;
127
136
  /** Badge pill corner radius in pixels. Default `5`. (Fallback for `badge.radius`.) */
128
137
  badgeRadius?: number;
138
+
139
+ // ── Draggable (Form A only) ───────────────────────────────────────────────
140
+ /**
141
+ * Make this Form-A line **draggable** along the Y-axis — grab its handle / badge
142
+ * and drag to set a new value (a working-order price, alert level, or target).
143
+ * The line tracks the finger on the UI thread; {@link onChange} / {@link onCommit}
144
+ * report the value to JS. The line is *uncontrolled* by default (it stays where
145
+ * you drop it); pair `onCommit` with writing the value back into `value` to make
146
+ * it controlled. No effect on bands / time bands. Default `false`.
147
+ */
148
+ draggable?: boolean;
149
+ /**
150
+ * Snap the dragged value to this increment (e.g. `0.01` for cents, `0.5` for a
151
+ * tick size) so drops land on round levels. Omit for free dragging. Applies only
152
+ * while {@link draggable}.
153
+ */
154
+ snap?: number;
155
+ /**
156
+ * Hard-clamp the draggable value to `[min, max]` in Y-axis units — the line
157
+ * can't be dragged past either end. Omit for unbounded (clamped only to the
158
+ * visible range). Reaching a bound fires {@link onDragOut}. Applies only while
159
+ * {@link draggable}.
160
+ */
161
+ bounds?: [number, number];
162
+ /**
163
+ * Fired on the JS thread *while* dragging, each time the (snapped, clamped)
164
+ * value changes. De-duplicated to value changes — not every frame. Form-A
165
+ * draggable only.
166
+ */
167
+ onChange?: (value: number) => void;
168
+ /**
169
+ * Fired once on the JS thread when the drag ends (finger up), with the final
170
+ * (snapped, clamped) value. Pair with a controlled `value` to persist the move.
171
+ * Form-A draggable only.
172
+ */
173
+ onCommit?: (value: number) => void;
174
+ /**
175
+ * Fired on the JS thread when the line's value crosses **into** its watched
176
+ * interval — the visible Y-range, or {@link bounds} when set. Triggers from
177
+ * dragging *or* the axis rescaling under a fixed value (e.g. a target scrolling
178
+ * back into view). Edge-triggered: once per crossing. Form-A only.
179
+ */
180
+ onDragIn?: (value: number) => void;
181
+ /**
182
+ * Fired on the JS thread when the line's value crosses **out of** its watched
183
+ * interval (the visible Y-range, or {@link bounds}) — dragged/scrolled off-screen
184
+ * or pinned at a bound. Edge-triggered: once per crossing. Form-A only.
185
+ */
186
+ onDragOut?: (value: number) => void;
187
+ }
188
+
189
+ /**
190
+ * Shared **style & shape** knobs for every badge pill — the value `badge`
191
+ * ({@link BadgeConfig}), reference-line badges ({@link ReferenceLineBadgeConfig}),
192
+ * and the grouping count pill. Each badge interface extends this and adds its own
193
+ * anchor (`position`) / content fields. Unset values fall back to that badge's own
194
+ * defaults (documented on each interface). One source of truth so every badge is
195
+ * configured identically.
196
+ */
197
+ export interface BadgeStyleConfig {
198
+ /** Pill background color. */
199
+ background?: string;
200
+ /** Pill border color. */
201
+ borderColor?: string;
202
+ /** Border stroke width in pixels. Default `1`. */
203
+ borderWidth?: number;
204
+ /** Pill corner radius in pixels. */
205
+ radius?: number;
206
+ /** Label / icon text color override. */
207
+ textColor?: string;
208
+ /** Badge font size in pixels. Falls back to the chart `font`. */
209
+ fontSize?: number;
210
+ /** Badge font family. Falls back to the chart `font`. */
211
+ fontFamily?: string;
212
+ /** Badge font weight. Falls back to the chart `font`. */
213
+ fontWeight?: FontWeight;
214
+ /** Nudge the whole badge horizontally from its anchor, in pixels. Default `0`. */
215
+ offsetX?: number;
216
+ /** Nudge the whole badge vertically from its anchor, in pixels. Default `0`. */
217
+ offsetY?: number;
129
218
  }
130
219
 
131
220
  /**
@@ -133,10 +222,20 @@ export interface ReferenceLine {
133
222
  * price alert, or target tag. The badge sits at the line's value (pinned to
134
223
  * `position`) with a dashed connector to the opposite edge, and pins to the
135
224
  * nearest edge with a chevron once the value scrolls off-screen.
225
+ *
226
+ * Extends {@link BadgeStyleConfig} for the style/shape knobs. Their reference-line
227
+ * fallbacks: `background` → `badgeBackground` → theme `tooltipBg`; `borderColor` →
228
+ * `badgeBorderColor` → the line `color`; `radius` → `badgeRadius` → `5`; `textColor`
229
+ * → `labelColor` → the line `color` → theme `refLabel`; the `font*` knobs → the
230
+ * chart `font`.
136
231
  */
137
- export interface ReferenceLineBadgeConfig {
138
- /** Which plot edge the badge pins to. Default `"left"`. */
139
- position?: "left" | "right";
232
+ export interface ReferenceLineBadgeConfig extends BadgeStyleConfig {
233
+ /**
234
+ * Where the badge pins horizontally. `"left"` / `"right"` pin to that plot edge
235
+ * with a dashed connector running to the opposite edge; `"center"` floats the
236
+ * pill centered in the plot at the line's value (no connector). Default `"left"`.
237
+ */
238
+ position?: "left" | "center" | "right";
140
239
  /**
141
240
  * Leading glyph drawn in the badge — rendered with the chart font, so pass an
142
241
  * emoji-capable font (via the chart `font` prop) for emoji. Like `Marker.icon`.
@@ -147,12 +246,71 @@ export interface ReferenceLineBadgeConfig {
147
246
  * Default `true`. Set `false` for an **icon-only** badge.
148
247
  */
149
248
  text?: boolean;
150
- /** Pill background. Falls back to `badgeBackground`, then theme `tooltipBg`. */
151
- background?: string;
152
- /** Pill border color. Falls back to `badgeBorderColor`, then the line `color`. */
153
- borderColor?: string;
154
- /** Pill corner radius in pixels. Falls back to `badgeRadius`, then `5`. */
249
+ }
250
+
251
+ /**
252
+ * Context passed to a custom {@link LiveChartProps.renderReferenceLine}. The chart
253
+ * floats the element you return over the canvas and pins it to the line's value on
254
+ * the UI thread (vertically centered on the line, horizontally at the badge /
255
+ * label position) — so it tracks the rescaling axis and any drag smoothly without
256
+ * JS re-renders, just like {@link TooltipRenderProps}. Replaces the built-in pill
257
+ * badge / gutter label for that line; return `null`/`undefined` to keep the
258
+ * built-in. Bind the SharedValues to animated text (e.g. an animated `TextInput`)
259
+ * for the value to update on the UI thread too.
260
+ */
261
+ export interface ReferenceLineRenderProps {
262
+ /** The reference line being rendered. */
263
+ line: ReferenceLine;
264
+ /** Its index in the `referenceLines` array. */
265
+ index: number;
266
+ /** Live Y-axis value of the line — tracks dragging when {@link ReferenceLine.draggable}. */
267
+ value: SharedValue<number>;
268
+ /** The value formatted with the chart's `formatValue` (computed UI-side). */
269
+ valueStr: SharedValue<string>;
270
+ /** Canvas Y pixel of the line, recomputed each frame (`-1` when not laid out). */
271
+ y: SharedValue<number>;
272
+ /** Whether the value currently sits within the visible plot range. */
273
+ inRange: SharedValue<boolean>;
274
+ /**
275
+ * Which visible edge the value is pinned to when off-screen: `"above"` (past the
276
+ * top), `"below"` (past the bottom), or `"in"`. Use it to flip a directional
277
+ * chevron on a custom off-axis handle.
278
+ */
279
+ edge: SharedValue<"above" | "in" | "below">;
280
+ /** Whether this line is currently being dragged. */
281
+ dragging: SharedValue<boolean>;
282
+ }
283
+
284
+ /**
285
+ * Grouping behavior for reference lines (single-series). When enabled, Form-A
286
+ * lines whose handles fall within {@link ReferenceLineGroupingConfig.radius} px of
287
+ * each other collapse into a single count handle, so a cluster of nearby orders /
288
+ * alerts reads as one tag instead of an unreadable pile. Pass `true` for defaults
289
+ * or an object to tune the proximity radius.
290
+ */
291
+ export interface ReferenceLineGroupingConfig {
292
+ /**
293
+ * Collapse lines whose value-Y positions are within this many px of each other.
294
+ * Default `18`.
295
+ */
155
296
  radius?: number;
297
+ /**
298
+ * Styling for the collapsed **count pill** — the same style/shape config as a
299
+ * reference-line badge ({@link ReferenceLineBadgeConfig}): `position`
300
+ * (`"left"` / `"center"` / `"right"`), `icon` (a leading glyph before the count),
301
+ * `background`, `borderColor`, `borderWidth`, `radius` (corner radius),
302
+ * `textColor`, `fontSize` / `fontFamily` / `fontWeight`, and `offsetX` / `offsetY`.
303
+ * Omit for the theme defaults (left-pinned, `tooltipBg` fill, `refLine` border,
304
+ * `refLabel` text). The badge's `text: false` hides the count for an icon-only pill.
305
+ */
306
+ badge?: ReferenceLineBadgeConfig;
307
+ /**
308
+ * Format the collapsed count for the pill label, e.g. `n => \`×${n}\`` or
309
+ * `n => \`${n} orders\``. Default `String(n)` (the bare count). **Must be a
310
+ * worklet** (add the `"worklet"` directive) — it runs on the UI thread each
311
+ * frame, like the chart's `formatValue`. A plain JS closure throws.
312
+ */
313
+ format?: (count: number) => string;
156
314
  }
157
315
 
158
316
  /**
@@ -356,22 +514,79 @@ export interface AreaDotsConfig {
356
514
  opacity?: number;
357
515
  }
358
516
 
359
- /** Value badge pill configuration. */
360
- export interface BadgeConfig {
517
+ /**
518
+ * Value badge pill configuration. Extends {@link BadgeStyleConfig} for the
519
+ * style/shape knobs (`background`, `borderColor` / `borderWidth`, `radius`,
520
+ * `textColor`, `font*`, `offsetX/Y`). Value-badge specifics: `radius` defaults to a
521
+ * full capsule (clamped to `[0, pillHeight / 2]`); `borderColor` unset → no border;
522
+ * `textColor` unset → the `variant` / theme rule.
523
+ */
524
+ export interface BadgeConfig extends BadgeStyleConfig {
361
525
  /** Visual style of the badge pill. Default `"default"`. */
362
526
  variant?: BadgeVariant;
363
527
  /** Show the pointed tail toward the live dot. Default `true`. */
364
528
  tail?: boolean;
365
- /** Badge background color override. */
366
- background?: string;
367
529
  /** Which side of the chart the badge appears on. Default `"right"`. */
368
530
  position?: "right" | "left";
531
+ /**
532
+ * When the chart is scrolled back (see `timeScroll`), move the live-price
533
+ * indicators — the badge, the value line, and the live dot — to the price at
534
+ * the visible window's right edge instead of the live price, so they track the
535
+ * last visible price as you pan. Default `false`.
536
+ *
537
+ * @experimental
538
+ */
539
+ followViewEdge?: boolean;
540
+ }
541
+
542
+ /**
543
+ * Volume-bar configuration (candle mode only — see {@link LiveChartProps.volume}).
544
+ * Bars are drawn in a reserved band below the candles; the candle/price plot
545
+ * shrinks by {@link maxHeight} to make room (the x-axis stays at the bottom).
546
+ * Each bar's height is its candle's `volume` normalized to the largest visible
547
+ * volume, so the tallest visible bar fills the band.
548
+ */
549
+ export interface VolumeConfig {
550
+ /** Bar color for up (close ≥ open) candles. Default: `palette.candleUp`. */
551
+ upColor?: string;
552
+ /** Bar color for down (close < open) candles. Default: `palette.candleDown`. */
553
+ downColor?: string;
554
+ /**
555
+ * Height (px) of the reserved band — the tallest a bar can be. Reserved out of
556
+ * the plot below the candles. Default `48`.
557
+ */
558
+ maxHeight?: number;
559
+ /** Corner radius (px) of bar tops. `0` = sharp. Default `2`. */
560
+ radius?: number;
561
+ /** Opacity (0..1) applied to the whole band. Default `0.6`. */
562
+ opacity?: number;
369
563
  }
370
564
 
371
565
  /** Y-axis grid configuration. */
372
566
  export interface YAxisConfig {
373
567
  /** Minimum pixel gap between grid lines. Default `36`. */
374
568
  minGap?: number;
569
+ /**
570
+ * Show a fixed number of price labels instead of the dynamic nice-interval
571
+ * grid. When set (≥ 2), exactly `count` labels are spaced evenly **in pixels**
572
+ * across the plot — top label = current high, bottom = current low — so the
573
+ * count never changes as data streams in. Values are not rounded to "nice"
574
+ * numbers; they track the live range each frame.
575
+ *
576
+ * `minGap` still acts as a floor: if `count` labels won't fit at least
577
+ * `minGap` px apart, the count is reduced to what fits. Clamped to at most 15
578
+ * (the label pool size). Omit (or `0`) for the default dynamic grid.
579
+ */
580
+ count?: number;
581
+ /**
582
+ * Float the price axis over a full-width plot instead of reserving a right
583
+ * gutter for it. The line/candles run all the way to the right edge, and the
584
+ * price labels (and the live-value badge) float on top — so the chart isn't
585
+ * cut off short of the edge, especially while time-scrolling. Default `false`.
586
+ *
587
+ * @experimental
588
+ */
589
+ float?: boolean;
375
590
  }
376
591
 
377
592
  /**
@@ -480,11 +695,13 @@ export interface ScrubConfig {
480
695
  * Where the tooltip pill sits relative to the vertical scrub line.
481
696
  * `"side"` (default) offsets it to the right of the line and flips left near
482
697
  * the right edge. `"top"` / `"bottom"` center it horizontally over the line,
483
- * clamped into the plot and pinned to the plot's top or bottom. This also
484
- * drives a custom {@link LiveChartProps.renderTooltip} so it gets the same
485
- * placement for free.
698
+ * clamped into the plot and pinned to the plot's top or bottom. `"point"`
699
+ * centers it over the line and floats it just above the scrub dot, flipping
700
+ * below the dot when there isn't room above (single-series line mode; candle
701
+ * mode keeps its top-pinned OHLC stack). This also drives a custom
702
+ * {@link LiveChartProps.renderTooltip} so it gets the same placement for free.
486
703
  */
487
- tooltipPlacement?: "side" | "top" | "bottom";
704
+ tooltipPlacement?: "side" | "top" | "bottom" | "point";
488
705
  /**
489
706
  * Gap in px between the tooltip and the plot edge it's pinned to — the top for
490
707
  * `"side"`/`"top"`, the bottom for `"bottom"`. Applies to the built-in pill and
@@ -547,6 +764,13 @@ export interface ScrubActionConfig {
547
764
  * instead of moving it. Default `false` (an empty-plot tap re-places the reticle).
548
765
  */
549
766
  dismissOnTapOutside?: boolean;
767
+ /**
768
+ * Dismiss the reticle once the action badge is pressed (and
769
+ * {@link LiveChartProps.onScrubAction} fires), so no crosshair lingers after the
770
+ * action — e.g. clear the order reticle once the order has been placed. Default
771
+ * `false` (the reticle stays until tapped away).
772
+ */
773
+ dismissOnAction?: boolean;
550
774
  }
551
775
 
552
776
  /**
@@ -626,6 +850,92 @@ export interface TooltipRenderProps {
626
850
  candle: SharedValue<CandlePoint | null>;
627
851
  }
628
852
 
853
+ /** Inner plot rectangle in canvas pixels (a snapshot field of {@link ChartScale}). */
854
+ export interface ChartPlotRect {
855
+ /** Inner plot left edge (`padding.left`). */
856
+ left: number;
857
+ /** Inner plot top edge (`padding.top`). */
858
+ top: number;
859
+ /** Inner plot right edge (`canvasWidth - padding.right`). */
860
+ right: number;
861
+ /** Inner plot bottom edge (`canvasHeight - padding.bottom`). */
862
+ bottom: number;
863
+ /** Full canvas width. */
864
+ width: number;
865
+ /** Full canvas height. */
866
+ height: number;
867
+ }
868
+
869
+ /**
870
+ * A snapshot of the chart's live scale — the value range, time window, and plot
871
+ * rect at one frame. Carried by {@link ChartOverlayContext.scale} (a `SharedValue`
872
+ * recomputed each frame) and consumed by the pure mapping functions. Reading
873
+ * `scale.get()` inside your worklet is what subscribes the overlay to per-frame
874
+ * updates, so it tracks the chart as it scrolls and rescales.
875
+ */
876
+ export interface ChartScale {
877
+ /** Live Y-axis lower bound (price at the plot bottom). */
878
+ min: number;
879
+ /** Live Y-axis upper bound (price at the plot top). */
880
+ max: number;
881
+ /** Visible time window in seconds. */
882
+ window: number;
883
+ /** Right-edge timestamp (unix seconds) — "now". */
884
+ now: number;
885
+ /** Inner plot rectangle in canvas px. */
886
+ plot: ChartPlotRect;
887
+ }
888
+
889
+ /**
890
+ * The price↔pixel / time↔pixel bridge handed to a custom
891
+ * {@link LiveChartProps.renderOverlay}.
892
+ *
893
+ * **Easiest path — the `usePriceY` / `useTimeX` hooks.** They project a price / time
894
+ * to a `SharedValue<number>` that tracks the live axis for you; just read it in
895
+ * your `useAnimatedStyle` like any SharedValue:
896
+ *
897
+ * ```tsx
898
+ * function PriceLevel({ ctx, price }) {
899
+ * const y = usePriceY(ctx, price); // reactive — tracks the rescaling axis
900
+ * const style = useAnimatedStyle(() => ({ transform: [{ translateY: y.get() }] }));
901
+ * return <Animated.View style={style} />;
902
+ * }
903
+ * ```
904
+ *
905
+ * **Manual path — {@link scale} + the pure mappings.** For one-off math (e.g. a
906
+ * gesture handler) or when you want everything in one worklet, read `scale.get()`
907
+ * inside your worklet — that read subscribes it to the per-frame scale — then pass
908
+ * the snapshot to the pure mappings (`priceToY` / `yToPrice` / `timeToX` /
909
+ * `xToTime`). The mappings do **not** read `scale` themselves, so they only reflect
910
+ * the chart live when you feed them `scale.get()`:
911
+ *
912
+ * ```tsx
913
+ * const style = useAnimatedStyle(() => {
914
+ * const s = scale.get(); // subscribe to the live scale
915
+ * const y = priceToY(142.5, s); // project a price to its pixel
916
+ * return { transform: [{ translateY: y }] };
917
+ * });
918
+ * ```
919
+ */
920
+ export interface ChartOverlayContext {
921
+ /**
922
+ * The live {@link ChartScale} snapshot, recomputed each frame. Read `.get()`
923
+ * inside your worklet to subscribe it to chart updates.
924
+ */
925
+ scale: SharedValue<ChartScale>;
926
+ /** Maps a price (Y-axis value) → canvas Y px for a {@link ChartScale}. Worklet. -1 when not laid out. */
927
+ priceToY: (price: number, scale: ChartScale) => number;
928
+ /**
929
+ * Inverse of {@link priceToY}: canvas Y px → price. Worklet. Clamps to the
930
+ * visible range; `null` when not laid out.
931
+ */
932
+ yToPrice: (y: number, scale: ChartScale) => number | null;
933
+ /** Maps a unix-seconds timestamp → canvas X px for a {@link ChartScale}. Worklet. -1 when not laid out. */
934
+ timeToX: (time: number, scale: ChartScale) => number;
935
+ /** Inverse of {@link timeToX}: canvas X px → unix-seconds timestamp. Worklet. */
936
+ xToTime: (x: number, scale: ChartScale) => number;
937
+ }
938
+
629
939
  /** Outer ring drawn around the built-in selection dot (the subtle halo). */
630
940
  export interface SelectionDotRingConfig {
631
941
  /** Ring color. Defaults to the dot color. */
@@ -783,14 +1093,60 @@ export interface Marker {
783
1093
  pill?: boolean;
784
1094
  /** Icon / image box size in px (icon font size or image width+height). Default `16`. */
785
1095
  size?: number;
786
- /** Pass-through payload surfaced on `onMarkerHover`. */
1096
+ /**
1097
+ * Sit the glyph above / below its anchor instead of centered on it. Default
1098
+ * `"center"` (on the line/value, today's behavior). Also the stack direction
1099
+ * when `markerCluster: "stacked"` — e.g. buys `"below"`, sells `"above"`.
1100
+ */
1101
+ side?: MarkerSide;
1102
+ /** Pass-through payload surfaced on `onMarkerPress`. */
787
1103
  data?: unknown;
788
1104
  }
789
1105
 
790
- /** Payload for `onMarkerHover` the marker and its screen position. */
791
- export interface MarkerHoverEvent {
1106
+ /** Where a marker glyph sits relative to its anchor (and the stack direction). */
1107
+ export type MarkerSide = "above" | "below" | "center";
1108
+
1109
+ /**
1110
+ * Object form of {@link LiveChartProps.markerCluster} for tuning the stacked
1111
+ * collision behavior. Passing this object implies `mode: "stacked"` unless you
1112
+ * set `mode` explicitly.
1113
+ */
1114
+ export interface MarkerClusterConfig {
1115
+ /** Defaults to `"stacked"` when this object form is used. */
1116
+ mode?: "anchored" | "stacked";
1117
+ /**
1118
+ * How much adjacent co-located glyphs overlap when fanned, `0` (just touching)
1119
+ * to `1` (fully stacked). Default `0.75`. The on-screen overlap is approximate
1120
+ * (the fan step is estimated from the glyph size, not its exact pixels).
1121
+ */
1122
+ overlap?: number;
1123
+ /** Collapse a co-located run to a single count badge once it exceeds this many.
1124
+ * Default `5`. */
1125
+ maxBeforeGroup?: number;
1126
+ }
1127
+
1128
+ /** Context passed to `renderMarker` alongside the marker (cluster / position state). */
1129
+ export interface MarkerRenderContext {
1130
+ /** Index of the marker in the `markers` array. */
1131
+ index: number;
1132
+ /** `true` when this marker is the representative of a collapsed cluster. */
1133
+ isGrouped: boolean;
1134
+ /** Number of markers in the collapsed cluster (`0` when not grouped). */
1135
+ groupCount: number;
1136
+ /** Resolved side the glyph is drawn on. */
1137
+ side: MarkerSide;
1138
+ }
1139
+
1140
+ /** Payload for `onMarkerPress` — the marker and its screen position. */
1141
+ export interface MarkerPressEvent {
792
1142
  marker: Marker;
793
1143
  point: { x: number; y: number };
1144
+ /** Index of the pressed marker in the `markers` array. */
1145
+ index: number;
1146
+ /** `true` when the press landed on a collapsed cluster (count badge). */
1147
+ isGrouped: boolean;
1148
+ /** The cluster's markers when `isGrouped`, so the consumer can list them. */
1149
+ members?: Marker[];
794
1150
  }
795
1151
 
796
1152
  /** Particle burst + screen shake on momentum swings ("degen mode"). */
@@ -1001,6 +1357,13 @@ export interface CandlePoint {
1001
1357
  low: number;
1002
1358
  /** Closing price. */
1003
1359
  close: number;
1360
+ /**
1361
+ * Traded volume in the bucket. Drives the optional volume bars
1362
+ * (see {@link LiveChartProps.volume}); bar heights are normalized to the
1363
+ * largest visible volume, so only relative values matter. Omitted candles
1364
+ * contribute no bar. Ignored when `volume` is off or in line mode.
1365
+ */
1366
+ volume?: number;
1004
1367
  }
1005
1368
 
1006
1369
  // ── Metrics (sizing & motion tokens) ─────────────────────────────────────────
@@ -1048,6 +1411,10 @@ export interface CandleMetrics {
1048
1411
  maxBodyPx: number;
1049
1412
  /** Body width as a fraction of the per-candle slot width (0–1). Default `0.8`. */
1050
1413
  bodyWidthRatio: number;
1414
+ /** Corner radius (px) of candle bodies. `0` = sharp corners. Default `0`. */
1415
+ bodyRadius: number;
1416
+ /** Wick (high–low line) stroke width in px. Default `1`. */
1417
+ wickWidth: number;
1051
1418
  }
1052
1419
 
1053
1420
  /** Grid-line and axis-label fade animation speeds. */
@@ -1091,6 +1458,61 @@ export interface LiveChartMetricsOverride {
1091
1458
 
1092
1459
  // ── Component Props ──────────────────────────────────────────────────────────
1093
1460
 
1461
+ /**
1462
+ * Time-scroll activation (see {@link LiveChartCoreProps.timeScroll}).
1463
+ *
1464
+ * @experimental Prototype — gesture model and API may change.
1465
+ */
1466
+ export interface TimeScrollConfig {
1467
+ /**
1468
+ * Which gesture pans the timeline:
1469
+ * - `"holdToScrub"` (default) — a one-finger drag anywhere scrolls; scrub
1470
+ * moves to press-and-hold (Rainbow-style). See {@link scrubHoldMs} for the
1471
+ * hold duration.
1472
+ * - `"axisDrag"` — a one-finger drag starting in the bottom X-axis band
1473
+ * ("grab the time ruler"); the plot area stays free for one-finger scrub.
1474
+ */
1475
+ gesture?: "holdToScrub" | "axisDrag";
1476
+ /**
1477
+ * `holdToScrub` only: press-and-hold duration (ms) before scrub engages, so a
1478
+ * quicker one-finger drag scrolls instead. Higher = more deliberate scrub and
1479
+ * fewer accidental scrubs while scrolling. Falls back to `scrub.panGestureDelay`
1480
+ * if set, otherwise `500`.
1481
+ */
1482
+ scrubHoldMs?: number;
1483
+ }
1484
+
1485
+ /**
1486
+ * Pinch-to-zoom the visible time window (see {@link LiveChartCoreProps.zoom}).
1487
+ *
1488
+ * @experimental Prototype — gesture model and API may change.
1489
+ */
1490
+ export interface ZoomConfig {
1491
+ /**
1492
+ * Tightest visible window in seconds (max zoom-in). Default `timeWindow / 8`.
1493
+ */
1494
+ minTimeWindow?: number;
1495
+ /**
1496
+ * Widest visible window in seconds (max zoom-out). Defaults to the full data
1497
+ * span (you can zoom out to all retained history), never below `timeWindow`.
1498
+ */
1499
+ maxTimeWindow?: number;
1500
+ }
1501
+
1502
+ /**
1503
+ * The visible time range reported by {@link LiveChartCoreProps.onVisibleRangeChange}.
1504
+ *
1505
+ * @experimental
1506
+ */
1507
+ export interface VisibleRange {
1508
+ /** Left-edge time of the visible window (unix seconds). */
1509
+ startSec: number;
1510
+ /** Right-edge time of the visible window (unix seconds). */
1511
+ endSec: number;
1512
+ /** True when the window is at the live edge (not scrolled back / paused). */
1513
+ following: boolean;
1514
+ }
1515
+
1094
1516
  /** Props shared between `LiveChart` and `LiveChartSeries`. */
1095
1517
  export interface LiveChartCoreProps {
1096
1518
  /** Color scheme. Default `"dark"`. */
@@ -1168,9 +1590,19 @@ export interface LiveChartCoreProps {
1168
1590
  */
1169
1591
  markers?: SharedValue<Marker[]>;
1170
1592
  /** Fires when a marker is tapped; `null` when a tap misses every marker. */
1171
- onMarkerHover?: (event: MarkerHoverEvent | null) => void;
1593
+ onMarkerPress?: (event: MarkerPressEvent | null) => void;
1172
1594
  /** Tap hit-test radius in px. Default `16` (≈ 44px touch target with the glyph). */
1173
1595
  markerHitRadius?: number;
1596
+ /**
1597
+ * Collision handling for co-located markers. `"anchored"` (default) keeps
1598
+ * today's behavior — glyphs draw at their anchor and overlap. `"stacked"` fans
1599
+ * co-located markers apart horizontally (overlapping, left-over-right) along
1600
+ * their {@link Marker.side}, collapsing a cluster into a single count-badge
1601
+ * marker once it exceeds {@link MarkerClusterConfig.maxBeforeGroup}. Grouping is
1602
+ * recomputed per frame, so a cluster fans back out as you zoom/scroll in. Pass a
1603
+ * {@link MarkerClusterConfig} object to tune the `overlap` and group threshold.
1604
+ */
1605
+ markerCluster?: "anchored" | "stacked" | MarkerClusterConfig;
1174
1606
  /**
1175
1607
  * Render a marker as a custom **React Native** element instead of a built-in
1176
1608
  * Skia glyph — e.g. an `expo-blur` glass badge or any non-Skia view that the
@@ -1183,8 +1615,15 @@ export interface LiveChartCoreProps {
1183
1615
  * each custom marker is its own animated view + projection, whereas built-in
1184
1616
  * glyphs batch into a single draw call. Custom-rendered markers skip the atlas
1185
1617
  * glyph entirely (no double-draw).
1618
+ *
1619
+ * The second argument carries cluster / position state (see
1620
+ * {@link MarkerRenderContext}) — e.g. render a distinct collapsed look when
1621
+ * `ctx.isGrouped` (showing `ctx.groupCount`).
1186
1622
  */
1187
- renderMarker?: (marker: Marker) => ReactElement | null | undefined;
1623
+ renderMarker?: (
1624
+ marker: Marker,
1625
+ ctx: MarkerRenderContext,
1626
+ ) => ReactElement | null | undefined;
1188
1627
  /**
1189
1628
  * Render a fully custom scrub tooltip as a **React Native** element instead of
1190
1629
  * the built-in pill — the same idea as `renderMarker`. The chart floats the
@@ -1226,6 +1665,31 @@ export interface LiveChartCoreProps {
1226
1665
  * and `timeWindow = maxT - minT` to fill the canvas edge-to-edge with historical data.
1227
1666
  */
1228
1667
  nowOverride?: number;
1668
+ /**
1669
+ * Enable horizontal time-scrolling: drag (or fling) to pan back through history.
1670
+ * The chart stops auto-scrolling while panned and resumes once you reach the
1671
+ * live edge again. One-finger plot-area scrub is unchanged. Requires retained
1672
+ * history in `data` / `candles` to scroll into.
1673
+ *
1674
+ * `true` uses the default drag-to-scroll gesture (`"holdToScrub"`); pass a
1675
+ * {@link TimeScrollConfig} to pick the activation (`"holdToScrub"` or
1676
+ * `"axisDrag"`). Default `false`.
1677
+ *
1678
+ * @experimental Prototype — gesture model and API may change.
1679
+ */
1680
+ timeScroll?: boolean | TimeScrollConfig;
1681
+ /**
1682
+ * Pinch-to-zoom the visible time window. Two-finger pinch in/out narrows or
1683
+ * widens the window, anchored at the focal point between your fingers. Composes
1684
+ * with `timeScroll` (zoom level and scroll position are independent). Seed extra
1685
+ * history in `data` / `candles` to have room to zoom out into.
1686
+ *
1687
+ * `true` uses sensible default bounds; pass a {@link ZoomConfig} to set
1688
+ * `minTimeWindow` / `maxTimeWindow`. Default `false`.
1689
+ *
1690
+ * @experimental Prototype — gesture model and API may change.
1691
+ */
1692
+ zoom?: boolean | ZoomConfig;
1229
1693
  /** Accessibility label for the chart container. */
1230
1694
  accessibilityLabel?: string;
1231
1695
  /** Accessibility role for the chart container. Default `"image"`. */
@@ -1242,6 +1706,22 @@ export interface LiveChartCoreProps {
1242
1706
  onGestureStart?: () => void;
1243
1707
  /** Called once when the user stops scrubbing/panning the chart. */
1244
1708
  onGestureEnd?: () => void;
1709
+ /**
1710
+ * Called as the visible time window changes (throttled to ~1 Hz), with the
1711
+ * window's edges in unix seconds and whether the chart is at the live edge.
1712
+ * Useful for paging data sources alongside `timeScroll` / `zoom`.
1713
+ *
1714
+ * @experimental
1715
+ */
1716
+ onVisibleRangeChange?: (range: VisibleRange) => void;
1717
+ /**
1718
+ * Called once when the visible window's left edge comes within one
1719
+ * window-width of the earliest retained data — the cue to lazily load older
1720
+ * history. Re-arms after the edge moves back out.
1721
+ *
1722
+ * @experimental
1723
+ */
1724
+ onReachStart?: () => void;
1245
1725
  /**
1246
1726
  * Fade out chart content on the left (destination-out style). `true` = defaults, `false` = off,
1247
1727
  * or pass `LeftEdgeFadeConfig`. Default `true`.
@@ -1324,6 +1804,19 @@ export interface LiveChartProps extends LiveChartCoreProps {
1324
1804
  * in a list. Pulse/scrub/degen and the entry animation are disabled. Frame the data
1325
1805
  * with `timeWindow` + `nowOverride` (see the historical-data-fill pattern). */
1326
1806
  static?: boolean;
1807
+ /**
1808
+ * Render a custom overlay floated over the chart canvas, handed a price↔pixel /
1809
+ * time↔pixel {@link ChartOverlayContext} so it can track the auto-rescaling axis
1810
+ * on the UI thread. Return any React Native view tree (e.g. order / avg-entry /
1811
+ * liquidation price tags) and position its pieces with the context's worklet
1812
+ * mappings inside `useAnimatedStyle`; return `null`/`undefined` for no overlay.
1813
+ *
1814
+ * The returned tree is mounted as an RN sibling of the `<Canvas>` (like
1815
+ * `renderMarker` / `renderTooltip`), full-bleed with `pointerEvents="box-none"`
1816
+ * so empty areas still scrub while an interactive leaf can receive touches.
1817
+ * Single-series only.
1818
+ */
1819
+ renderOverlay?: (ctx: ChartOverlayContext) => ReactElement | null | undefined;
1327
1820
  /** Called when the user scrubs the crosshair. `null` when scrub ends. */
1328
1821
  onScrub?: (point: ScrubPoint | null) => void;
1329
1822
  /**
@@ -1349,6 +1842,36 @@ export interface LiveChartProps extends LiveChartCoreProps {
1349
1842
  * routed here, not to reticle placement). Single-series only.
1350
1843
  */
1351
1844
  onReferenceLinePress?: (line: ReferenceLine, index: number) => void;
1845
+ /**
1846
+ * Render a reference line's tag as a custom **React Native** element instead of
1847
+ * the built-in Skia pill / gutter label — the same model as `renderMarker` /
1848
+ * `renderTooltip`. The chart floats the returned element over the canvas and
1849
+ * pins it to the line's value on the UI thread (see {@link ReferenceLineRenderProps}),
1850
+ * so it tracks the rescaling axis and any drag without JS re-renders. Called per
1851
+ * Form-A line; return `null`/`undefined` to keep that line's built-in tag. Works
1852
+ * with `badge: false` too (replace the plain gutter label). Single-series only.
1853
+ */
1854
+ renderReferenceLine?: (
1855
+ ctx: ReferenceLineRenderProps,
1856
+ ) => ReactElement | null | undefined;
1857
+ /**
1858
+ * Collapse Form-A reference lines whose handles sit near the same value into a
1859
+ * single count handle (e.g. a stack of working orders at adjacent prices reads
1860
+ * as one "×3" tag). `true` = defaults, `false`/omitted = off, or pass a
1861
+ * {@link ReferenceLineGroupingConfig} to tune the proximity radius. Lines a
1862
+ * {@link LiveChartProps.renderReferenceLine} owns are excluded (their custom tag
1863
+ * draws itself), so the count reflects only collapsed built-in tags. Single-series
1864
+ * only. Default off.
1865
+ */
1866
+ referenceLineGrouping?: boolean | ReferenceLineGroupingConfig;
1867
+ /**
1868
+ * Volume bars below the candles (candle mode only). `true` = defaults,
1869
+ * `false`/omitted = off, or pass a {@link VolumeConfig} for colors, band
1870
+ * height, rounding, and opacity. The candle/price plot shrinks by the band
1871
+ * height to make room; the x-axis stays pinned to the bottom. Reads each
1872
+ * candle's `CandlePoint.volume`. Ignored in line mode. Default off.
1873
+ */
1874
+ volume?: boolean | VolumeConfig;
1352
1875
  /**
1353
1876
  * Called on the JS thread when degen chart shake starts (momentum swing with shake enabled).
1354
1877
  * Not called when `degen` is off or `DegenOptions.shake` is `false`.