import type { ComponentType, ReactElement } from "react"; import type { ViewStyle } from "react-native"; import type { SharedValue } from "react-native-reanimated"; import type { DataSourceParam, SkFontMgr, SkImage } from "@shopify/react-native-skia"; /** A single data point on the chart timeline. */ export interface LiveChartPoint { /** Unix timestamp in seconds. */ time: number; /** Numeric value at this point in time. */ value: number; } /** Direction of recent price movement, used for dot/badge coloring and degen effects. */ export type Momentum = "up" | "down" | "flat"; /** Fine-tune auto-detected momentum sensitivity. */ export interface MomentumConfig { /** Fraction of the lookback range the tail delta must exceed to register as directional. Default `0.12`. */ threshold?: number; /** Number of recent data points used for range calculation. Default `20`. */ lookback?: number; } /** Font weight values matching React Native's supported set. */ export type FontWeight = "normal" | "bold" | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900"; /** Color scheme for the chart background, grid, and derived palette colors. */ export type ThemeMode = "light" | "dark"; /** Skia canvas composition mode. Android uses TextureView for `"transparent"` * and an opaque SurfaceView for `"opaque"`. */ export type CanvasMode = "transparent" | "opaque"; /** * Badge pill style. * - `"default"` — accent-colored background with white text. * - `"minimal"` — white/grey background with subdued text. */ export type BadgeVariant = "default" | "minimal"; /** * A reference line or band drawn into the chart. Four mutually-exclusive forms, * with precedence A > B > C > D when fields from more than one are present: * - **Form A** — horizontal line at `value`. * - **Form B** — time-varying line following `series`. * - **Form C** — horizontal band between `valueFrom` and `valueTo`. * - **Form D** — vertical time band between `from` and `to` (unix seconds). */ export interface ReferenceLine { /** * Stable identifier for this line. Supply a unique value when `referenceLines` * may be reordered so the chart preserves the line's rendered identity. */ id?: string; /** Form A — the Y-axis value where the horizontal line is drawn. */ value?: number; /** * Form B — a time-varying reference line. Points use unix-second timestamps * and should be sorted oldest to newest. The first value extends to the left * edge unless {@link extendToStart} is `false`; the last value extends to the * live edge unless {@link extendToNow} is `false`. Supported by line and * candle charts. */ series?: LiveChartPoint[]; /** * Form B — extend the series' first value flat to the chart's left edge. * Set `false` to start at the first point. Default `true`. */ extendToStart?: boolean; /** * Form B — extend the series' last value flat to the chart's live edge. * Set `false` to stop at the last point. Default `true`. */ extendToNow?: boolean; /** Form C — horizontal band lower Y bound (paired with `valueTo`). */ valueFrom?: number; /** Form C — horizontal band upper Y bound (paired with `valueFrom`). */ valueTo?: number; /** Form D — vertical time-band start, unix seconds (paired with `to`). */ from?: number; /** Form D — vertical time-band end, unix seconds (paired with `from`). */ to?: number; /** Optional right-gutter label (e.g. `"Entry"`). */ label?: string; /** * Stroke thickness in pixels. For a line it's the line width (default `1`). * For a band, setting this also renders a dashed border along the band edges * (top/bottom for value bands, left/right for time bands); omit for no border. */ strokeWidth?: number; /** Dash pattern as `[dashLength, gapLength]` in pixels (line/series stroke + band border). */ intervals?: [number, number]; /** * Span the **full chart width** — edge to edge through the Y-axis gutter, not * clipped at the plot's right edge — so the line/band visually connects to its * value on the axis (like a price tag). Only the line/band extends; any * `label`/`badge` stays anchored inside the plot. For a Form-A line with a * `badge`, the full-width line replaces the dashed connector. No effect on a * vertical time band. Default `false` (stops at the plot edge). Form A / C. */ fullWidth?: boolean; /** Line / band color override. Defaults to palette `refLine`. */ color?: string; /** * Band fill color override. Defaults to {@link color}, then palette `refLine`. * Form C / D only. */ fillColor?: string; /** Fill opacity for a value / time band (0–1). Default `0.16`. */ fillOpacity?: number; /** Opacity for the line, series, or band border stroke (0–1). Default `1`. */ strokeOpacity?: number; /** Label text color. Defaults to `color`, then palette `refLabel`. */ labelColor?: string; /** * Horizontal label placement. For a Form-A line: `"left" | "center" | "right"` * (default `"right"`, the legacy gutter position). For a band: `"left" | "right"` * (default `"left"`). */ labelPosition?: "left" | "center" | "right"; /** Append the formatted value to the label (Form A/B). Default `false`. */ showValue?: boolean; /** * Exclude this line's value(s) from the Y-axis range computation, so it may sit * off-axis instead of forcing the axis to expand. Per-line; other lines are * unaffected. Default `false`. */ excludeFromRange?: boolean; /** * Render the Form-A value as a **pill badge** — an icon and/or text tag pinned * to a plot edge at the line's value, with a dashed connector running to the * opposite edge (instead of a plain gutter label that collides with the Y-axis * ticks). Auto-pins to the nearest edge with a directional chevron once the * value scrolls off-screen. Ideal for working orders, price alerts, and targets. * * `true` = defaults (left-pinned, the line's label/value), or a * {@link ReferenceLineBadgeConfig} for an `icon`, `text` toggle (icon-only), and * `position`. Supersedes the legacy `offAxisBadge`. Default off. */ badge?: boolean | ReferenceLineBadgeConfig; /** * Legacy: when a Form-A `value` falls outside the visible plot, render a pinned * edge badge with a directional chevron instead of culling the off-screen line. * Prefer {@link badge} (which also shows the tag in-range and supports an icon). * Typically paired with `excludeFromRange`. Default `false`. */ offAxisBadge?: boolean; /** Localized word shown in the legacy off-axis badge (e.g. "Target"). Falls back to `label`. */ offAxisBadgeLabel?: string; /** Badge pill background. Default: theme `tooltipBg`. (Fallback for `badge.background`.) */ badgeBackground?: string; /** Badge pill border color. Default: the line `color`. (Fallback for `badge.borderColor`.) */ badgeBorderColor?: string; /** Badge pill corner radius in pixels. Default `5`. (Fallback for `badge.radius`.) */ badgeRadius?: number; /** * Make this Form-A line **draggable** along the Y-axis — grab its handle / badge * and drag to set a new value (a working-order price, alert level, or target). * The line tracks the finger on the UI thread; {@link onChange} / {@link onCommit} * report the value to JS. The line is *uncontrolled* by default (it stays where * you drop it); pair `onCommit` with writing the value back into `value` to make * it controlled. No effect on bands / time bands. Default `false`. */ draggable?: boolean; /** * Snap the dragged value to this increment (e.g. `0.01` for cents, `0.5` for a * tick size) so drops land on round levels. Omit for free dragging. Applies only * while {@link draggable}. */ snap?: number; /** * Hard-clamp the draggable value to `[min, max]` in Y-axis units — the line * can't be dragged past either end. Omit for unbounded (clamped only to the * visible range). Reaching a bound fires {@link onDragOut}. Applies only while * {@link draggable}. */ bounds?: [number, number]; /** * Fired on the JS thread *while* dragging, each time the (snapped, clamped) * value changes. De-duplicated to value changes — not every frame. Form-A * draggable only. */ onChange?: (value: number) => void; /** * Fired once on the JS thread when the drag ends (finger up), with the final * (snapped, clamped) value. Pair with a controlled `value` to persist the move. * Form-A draggable only. */ onCommit?: (value: number) => void; /** * Fired on the JS thread when the line's value crosses **into** its watched * interval — the visible Y-range, or {@link bounds} when set. Triggers from * dragging *or* the axis rescaling under a fixed value (e.g. a target scrolling * back into view). Edge-triggered: once per crossing. Form-A only. */ onDragIn?: (value: number) => void; /** * Fired on the JS thread when the line's value crosses **out of** its watched * interval (the visible Y-range, or {@link bounds}) — dragged/scrolled off-screen * or pinned at a bound. Edge-triggered: once per crossing. Form-A only. */ onDragOut?: (value: number) => void; } /** * Shared **style & shape** knobs for every badge pill — the value `badge` * ({@link BadgeConfig}), reference-line badges ({@link ReferenceLineBadgeConfig}), * and the grouping count pill. Each badge interface extends this and adds its own * anchor (`position`) / content fields. Unset values fall back to that badge's own * defaults (documented on each interface). One source of truth so every badge is * configured identically. */ export interface BadgeStyleConfig { /** Pill background color. */ background?: string; /** Pill border color. */ borderColor?: string; /** Border stroke width in pixels. Default `1`. */ borderWidth?: number; /** Pill corner radius in pixels. */ radius?: number; /** Label / icon text color override. */ textColor?: string; /** Badge font size in pixels. Falls back to the chart `font`. */ fontSize?: number; /** Badge font family. Falls back to the chart `font`. */ fontFamily?: string; /** Badge font weight. Falls back to the chart `font`. */ fontWeight?: FontWeight; /** Nudge the whole badge horizontally from its anchor, in pixels. Default `0`. */ offsetX?: number; /** Nudge the whole badge vertically from its anchor, in pixels. Default `0`. */ offsetY?: number; } /** * Pill-badge presentation for a Form-A {@link ReferenceLine} — a working order, * price alert, or target tag. The badge sits at the line's value (pinned to * `position`) with a dashed connector to the opposite edge, and pins to the * nearest edge with a chevron once the value scrolls off-screen. * * Extends {@link BadgeStyleConfig} for the style/shape knobs. Their reference-line * fallbacks: `background` → `badgeBackground` → theme `tooltipBg`; `borderColor` → * `badgeBorderColor` → the line `color`; `radius` → `badgeRadius` → `5`; `textColor` * → `labelColor` → the line `color` → theme `refLabel`; the `font*` knobs → the * chart `font`. */ export interface ReferenceLineBadgeConfig extends BadgeStyleConfig { /** * Where the badge pins horizontally. `"left"` / `"right"` pin to that plot edge * with a dashed connector running to the opposite edge; `"center"` floats the * pill centered in the plot at the line's value (no connector). Default `"left"`. */ position?: "left" | "center" | "right"; /** * Leading glyph drawn in the badge — rendered with the chart font, so pass an * emoji-capable font (via the chart `font` prop) for emoji. Like `Marker.icon`. */ icon?: string; /** * Show the text label (the line's `label`, plus the value when `showValue`). * Default `true`. Set `false` for an **icon-only** badge. */ text?: boolean; } /** * Context passed to a custom {@link LiveChartProps.renderReferenceLine} or * {@link LiveChartProps.renderOffAxisReferenceLine} (and their * `LiveChartSeries` counterparts). The chart floats the element you return over * the canvas and pins it to the line's value on the UI thread (vertically centered * on the line, horizontally at the badge / label position) — so it tracks the * rescaling axis and any drag smoothly without JS re-renders, just like * {@link TooltipRenderProps}. `renderReferenceLine` replaces the built-in pill / * gutter label in every state; `renderOffAxisReferenceLine` replaces it only while * the value is pinned above or below the visible plot. Bind the SharedValues to * animated text (e.g. an animated `TextInput`) for the value to update on the UI * thread too. */ export interface ReferenceLineRenderProps { /** The reference line being rendered. */ line: ReferenceLine; /** Its index in the `referenceLines` array. */ index: number; /** Live Y-axis value of the line — tracks dragging when {@link ReferenceLine.draggable}. */ value: SharedValue; /** The value formatted with the chart's `formatValue` (computed UI-side). */ valueStr: SharedValue; /** Canvas Y pixel of the line, recomputed each frame (`-1` when not laid out). */ y: SharedValue; /** Whether the value currently sits within the visible plot range. */ inRange: SharedValue; /** * Which visible edge the value is pinned to when off-screen: `"above"` (past the * top), `"below"` (past the bottom), or `"in"`. Use it to flip a directional * chevron on a custom off-axis handle. */ edge: SharedValue<"above" | "in" | "below">; /** Whether this line is currently being dragged. */ dragging: SharedValue; } /** * Grouping behavior for reference lines (single-series). When enabled, Form-A * lines whose handles fall within {@link ReferenceLineGroupingConfig.radius} px of * each other collapse into a single count handle, so a cluster of nearby orders / * alerts reads as one tag instead of an unreadable pile. Pass `true` for defaults * or an object to tune the proximity radius. */ export interface ReferenceLineGroupingConfig { /** * Collapse lines whose value-Y positions are within this many px of each other. * Default `18`. */ radius?: number; /** * Styling for the collapsed **count pill** — the same style/shape config as a * reference-line badge ({@link ReferenceLineBadgeConfig}): `position` * (`"left"` / `"center"` / `"right"`), `icon` (a leading glyph before the count), * `background`, `borderColor`, `borderWidth`, `radius` (corner radius), * `textColor`, `fontSize` / `fontFamily` / `fontWeight`, and `offsetX` / `offsetY`. * Omit for the theme defaults (left-pinned, `tooltipBg` fill, `refLine` border, * `refLabel` text). The badge's `text: false` hides the count for an icon-only pill. */ badge?: ReferenceLineBadgeConfig; /** * Format the collapsed count for the pill label, e.g. `n => \`×${n}\`` or * `n => \`${n} orders\``. Default `String(n)` (the bare count). **Must be a * worklet** (add the `"worklet"` directive) — it runs on the UI thread each * frame, like the chart's `formatValue`. A plain JS closure throws. */ format?: (count: number) => string; } /** * A time-range segment of the chart — e.g. a pre-market / regular / after-hours * session — distinguished with a **scrub-focus** interaction (Robinhood-style * extended-hours segmentation). At rest the whole line is one uniform color; * while the user scrubs (or when a segment is `active`) the focused segment keeps * the base color and every other segment is de-emphasized by recoloring the line * stroke itself (no overlay). An optional dashed `divider` + `label` mark a * segment's leading edge. */ export interface ChartSegment { /** Segment start, unix seconds. Omit to extend to the chart's left edge. */ from?: number; /** Segment end, unix seconds. Omit to extend to the live edge (now). */ to?: number; /** * Participate in scrub-focus line styling. At rest the line is one uniform * color; while scrubbing (or when a segment is `active`), the focused segment * keeps the base line color and every OTHER `recolorLine` segment is * de-emphasized with `mutedColor` / `mutedColors`. Default `true`. */ recolorLine?: boolean; /** De-emphasis line color, used when this segment is NOT the focused one. An * alpha-reduced color (e.g. `"rgba(154,160,166,0.4)"`) fades the line — it * paints the stroke directly, not a layer on top. Defaults to the chart's muted * palette color (`palette.gridLabel`). */ mutedColor?: string; /** * Two or more CSS colors → horizontal gradient across the segment's sub-range * (left → right) for the de-emphasized state, mirroring `LineConfig.colors`. * Takes precedence over `mutedColor` when set. */ mutedColors?: string[]; /** Force this segment to be the focused one without scrubbing — it stays full * while the others are de-emphasized (e.g. the session is currently after-hours). */ active?: boolean; /** Draw a vertical dashed divider at the `from` edge (market-close marker). Default `false`. */ divider?: boolean; /** Divider color. Defaults to the chart's reference-line color (`palette.refLine`). */ dividerColor?: string; /** Optional label captioning the divider at the top of the segment. Shown only * when `divider` is set; drawn in the chart's reference-label color * (`palette.refLabel`). */ label?: string; /** Label horizontal anchor within the segment. Default `"left"`. */ labelPosition?: "left" | "right"; } /** Per-instance grid-line styling for the horizontal value-axis grid. */ export interface GridStyleConfig { /** Stroke color. Defaults to palette `gridLine`. */ color?: string; /** Stroke width in pixels. Default `1`. */ strokeWidth?: number; /** Dash pattern as `[dash, gap, …]`. Default `[1, 3]` (dotted). Pass `[]` for solid. */ intervals?: number[]; /** Global alpha multiplier (0–1). Default `1`. */ opacity?: number; } /** Configuration for the horizontal dashed line at the current live value. */ export interface ValueLineConfig { /** Line thickness in pixels. Default `1`. */ strokeWidth?: number; /** Dash pattern as `[dashLength, gapLength]` in pixels. */ intervals?: [number, number]; /** Line color override. Defaults to palette `dashLine`. */ color?: string; } /** * Shared styling for a simple straight line — color, thickness, and an optional * dash. The common shape behind the chart's secondary lines (the `valueLine`, * the extrema-label `connector`, etc.), so they configure the same way. */ export interface LineStyleConfig { /** Line color. Defaults per usage (e.g. the connector uses the label color). */ color?: string; /** Line thickness in pixels. Default `1`. */ strokeWidth?: number; /** Dash pattern as `[dashLength, gapLength]` in pixels. Omit for a solid line. */ intervals?: [number, number]; } /** Main chart line styling. */ export interface LineConfig { /** Stroke width of the main line in pixels. Default `2`. */ width?: number; /** * Interpolation between points. `"monotone"` (default) draws a smooth monotone * cubic; `"linear"` draws straight segments for an angular, hard-edged line — * pair with `join: "miter"` + `cap: "butt"` for true sharp corners (no rounding). */ curve?: "monotone" | "linear"; /** * Screen-space path simplification tolerance in pixels. Values above `0` * remove intermediate points that deviate by no more than this amount while * retaining endpoints and larger peaks/valleys. This changes only the drawn * line/fill; axis fitting, live values, markers, and scrubbing still use the * original data. Default `0` (off). Start around `0.75`–`1.5` for sub-pixel * noise reduction. */ simplify?: number; /** Line color override. Defaults to palette-derived accent. */ color?: string; /** * Two or more CSS color strings → horizontal gradient along the stroke * (left → right). Takes precedence over `color` when set. */ colors?: string[]; /** * Stroke line-join — how corners between segments render. `"round"` (default) * softens every peak; `"miter"` gives sharp, angular ("edgy") peaks; `"bevel"` * flattens them. */ join?: "round" | "miter" | "bevel"; /** * Stroke line-cap at the path's start/end. `"round"` (default) | `"butt"` | * `"square"`. Pair `"butt"` with `join: "miter"` for a fully hard-edged line. */ cap?: "round" | "butt" | "square"; } /** * Color the line above vs. below a threshold — green above, red below by * default. The threshold is either a single **live benchmark** (a * `SharedValue` that tracks on the UI thread without re-rendering) or a * **time-varying series** (a `LiveChartPoint[]` the split follows point-for-point * — a stepped break-even, a historical VWAP). Drives a hard-split line stroke * and, optionally, a tinted profit/loss fill band and a marker line at the * threshold. * * The split stroke supersedes `LineConfig.color`/`colors` and segment recoloring * for the main line while a threshold is set. */ export interface ThresholdConfig { /** * The split value, in Y-axis (price) units. Two forms: * * - **`SharedValue`** — a single live benchmark. Update it with * `.set()` and the horizontal split tracks on the UI thread without * re-rendering (break-even / average cost, VWAP, the previous close, a peg). * - **`LiveChartPoint[]`** — a *time-varying* threshold (e.g. a historical * break-even that steps up as you average in). The stroke split, fill band * and marker line follow the series point-for-point. The series clamps to its * first/last value outside its own time range, so it extends flat to the * visible edges by default (see {@link extendToStart} and * {@link extendToNow}). Flows in on re-render — pass a stable (memoized) * array and reserve it for thresholds that change occasionally; for a * threshold series that updates live, use {@link series} instead. * * Provide `value` or {@link series} (not both) — `series` wins if both are set. */ value?: SharedValue | LiveChartPoint[]; /** * A **live** time-varying threshold: like the `LiveChartPoint[]` form of * {@link value}, but a `SharedValue` — update it with `.set()`/`.modify()` and * the split tracks on the UI thread without re-rendering (a VWAP that updates * every tick, mirroring how the chart's own `data` prop works). Points must be * sorted by `time`, like `data`. Takes precedence over {@link value}. */ series?: SharedValue; /** * Stroke color where the line is at/above `value`. Default: palette up-green * (`candleUp`). With a series `value`, use hex (`#rgb`/`#rrggbb`), `rgb()` or * `rgba()` — the split shader parses these; named CSS colors and 8-digit hex * are only supported by the constant form. */ aboveColor?: string; /** Stroke color where the line is below `value`. Default: palette down-red * (`candleDown`). Same format support as `aboveColor`. */ belowColor?: string; /** * Tint the area between the line and the threshold (the profit/loss band) * toward the above/below colors. Independent of the baseline `gradient` fill — * set `gradient={false}` for the threshold band alone. `true` → default band * opacity (`0.16`), or an object to tune it. Default `false`. */ fill?: boolean | ThresholdFillConfig; /** * Fold the threshold into the Y-axis range fit — like reference lines — so a * benchmark outside the data's own range stays on-plot instead of rendering * invisibly (marker off-plot, whole line one color). For a series, the values * visible in the current window count (respecting {@link extendToStart} and * {@link extendToNow}). * Default `false` (range fits the data only). */ includeInRange?: boolean; /** * Series forms only: extend the threshold **flat before its first point to the * visible window's left edge**, carrying the first known benchmark backward. * Set `false` when the benchmark did not exist yet (for example, a break-even * before the first trade) — left of the first point the stroke keeps its plain * line color and the band / marker do not begin. Default `true`. */ extendToStart?: boolean; /** * Series forms only: extend the threshold **flat past its last point to * "now"**, carrying the last known benchmark forward. Set `false` for a * benchmark that must not project into the future (a closed session's VWAP) — * right of the last point the stroke keeps its plain line color and the band / * marker / badge end. Default `true`. */ extendToNow?: boolean; /** * Dashed marker line + optional gutter label at the threshold. `true` → a dashed * line in the palette reference color; object → styled; omit/`false` → none. * Default off. */ line?: boolean | ThresholdLineConfig; } /** Dashed marker line drawn at a {@link ThresholdConfig} value. */ export interface ThresholdLineConfig { /** Label text, e.g. `"Break-even"`. */ label?: string; /** * Label side. `"left"` sits just inside the plot at the line's left edge — * clear of the y-axis labels and the live badge; `"right"` uses the right * gutter like a legacy reference line (may overlap y-axis labels). Default `"left"`. */ labelPosition?: "left" | "right"; /** * Time-varying threshold series only: which visible endpoint supplies the * label badge's Y position and optional value. `"first"` samples the * threshold at the visible window's left edge; `"last"` uses the live/right * edge. Independent of {@link labelPosition}. Default `"last"`. */ labelAnchor?: "first" | "last"; /** Line + label color. Defaults to palette `refLine` / `refLabel`. */ color?: string; /** Dash pattern `[dashLength, gapLength]` in pixels. Default `[4, 4]`. */ intervals?: [number, number]; /** Line thickness in pixels. Default `1`. */ strokeWidth?: number; /** Append the formatted threshold value to the label. Default `false`. */ showValue?: boolean; /** Label text color. Defaults to {@link color}, then palette `refLabel` — * mirroring `ReferenceLine.labelColor`. */ labelColor?: string; } /** * Context passed to {@link LiveChartProps.renderThresholdBadge}. The chart * floats the returned React Native element over the canvas and pins it to the * live threshold on the UI thread. Bind the SharedValues to animated content * when its displayed value must update without React re-renders. */ export interface ThresholdBadgeRenderProps { /** Resolved marker-line config for the badge being rendered. */ line: ThresholdLineConfig; /** Live threshold value in Y-axis units. */ value: SharedValue; /** The threshold value formatted with the chart's `formatValue`. */ valueStr: SharedValue; /** Canvas Y pixel of the threshold (`NaN` when geometry is unavailable). */ y: SharedValue; /** Whether the threshold badge currently belongs inside the visible plot. */ visible: SharedValue; } /** Object form of {@link ThresholdConfig.fill} — band tuning. */ export interface ThresholdFillConfig { /** Band fill opacity (0–1), applied to the above/below colors. Multiplies an * `rgba()` color's own alpha. Default `0.16` (matching reference-line bands). */ opacity?: number; } /** Area fill gradient beneath the chart line. */ export interface GradientConfig { /** Opacity at the top of the gradient (near the line). Default `0.35`. */ topOpacity?: number; /** Opacity at the bottom of the gradient. Default `0`. */ bottomOpacity?: number; /** Explicit gradient color stops (top → bottom) for the area fill. Overrides * topOpacity/bottomOpacity when provided. Must have at least 2 entries. */ colors?: string[]; /** Optional stop positions (0..1, ascending) matching `colors` length. */ positions?: number[]; } /** * Dot-lattice fill of the area beneath the line — a screen-fixed grid of dots * clipped to the region between the line and the baseline. Composes with * `gradient` (both paint), or use it alone with `gradient={false}`. */ export interface AreaDotsConfig { /** Lattice pitch (px) between dots, both axes. Default `12`. */ spacing?: number; /** Dot diameter (px). Default `1.6`. */ size?: number; /** Dot color. Omit to derive a faint tint from the line/accent color. */ color?: string; /** Overall opacity (0..1) applied to the whole field. Default `1`. */ opacity?: number; } /** * Value badge pill configuration. Extends {@link BadgeStyleConfig} for the * style/shape knobs (`background`, `borderColor` / `borderWidth`, `radius`, * `textColor`, `font*`, `offsetX/Y`). Value-badge specifics: `radius` defaults to a * full capsule (clamped to `[0, pillHeight / 2]`); `borderColor` unset → no border; * `textColor` unset → the `variant` / theme rule. */ export interface BadgeConfig extends BadgeStyleConfig { /** Visual style of the badge pill. Default `"default"`. */ variant?: BadgeVariant; /** * Show the pointed tail toward the live dot. When `false`, no tail space is * reserved: the pill body sits flush at the gutter edge (right after the dot * gap) and the auto right padding shrinks accordingly. Default `true`. */ tail?: boolean; /** Which side of the chart the badge appears on. Default `"right"`. */ position?: "right" | "left"; /** * When the chart is scrolled back (see `timeScroll`), move the live-price * indicators — the badge, the value line, and the live dot — to the price at * the visible window's right edge instead of the live price, so they track the * last visible price as you pan. The badge's momentum color is derived at that * same historical edge, so incoming live ticks do not recolor it. Default * `false`. * * @experimental */ followViewEdge?: boolean; } /** * Volume-bar configuration (candle mode only — see {@link LiveChartProps.volume}). * Bars are drawn in a reserved band below the candles; the candle/price plot * shrinks by {@link maxHeight} to make room (the x-axis stays at the bottom). * Each bar's height is its candle's `volume` normalized to the largest visible * volume, so the tallest visible bar fills the band. */ export interface VolumeConfig { /** Bar color for up (close ≥ open) candles. Default: `palette.candleUp`. */ upColor?: string; /** Bar color for down (close < open) candles. Default: `palette.candleDown`. */ downColor?: string; /** * Height (px) of the reserved band — the tallest a bar can be. Reserved out of * the plot below the candles. Default `48`. */ maxHeight?: number; /** Corner radius (px) of bar tops. `0` = sharp. Default `2`. */ radius?: number; /** Opacity (0..1) applied to the whole band. Default `0.6`. */ opacity?: number; } /** Y-axis grid configuration. */ export interface YAxisConfig { /** Minimum pixel gap between grid lines. Default `36`. */ minGap?: number; /** * Multiplier used to choose and align dynamic "nice" intervals without * changing plotted values. Pair it with a `formatValue` that applies the * same multiplier. Must be positive and finite; invalid values use `1`. * If applying the multiplier to the live range would overflow or underflow, * interval selection falls back to source units for that range. * Ignored when {@link count} is set. Default `1`. */ intervalScale?: number; /** * Show a fixed number of price labels instead of the dynamic nice-interval * grid. When set (≥ 2), exactly `count` labels are spaced evenly **in pixels** * across the plot — top label = current high, bottom = current low — so the * count never changes as data streams in. Values are not rounded to "nice" * numbers; they track the live range each frame. * * `minGap` still acts as a floor: if `count` labels won't fit at least * `minGap` px apart, the count is reduced to what fits. Clamped to at most 15 * (the label pool size). Omit (or `0`) for the default dynamic grid. */ count?: number; /** * Place price labels in a shared left-aligned column whose right edge sits * this many pixels from the canvas edge. When omitted, labels keep the * default centered-gutter placement. */ labelRightMargin?: number; /** * Gap (px) between the shared price-label column and the end of each grid or * plain reference line. Only applies when {@link labelRightMargin} is set. * Default `0`. */ gridEndGap?: number; /** * Float the price axis over a full-width plot instead of reserving a right * gutter for it. The line/candles run all the way to the right edge, and the * price labels (and the live-value badge) float on top — so the chart isn't * cut off short of the edge, especially while time-scrolling. Default `false`. * * @experimental */ float?: boolean; } /** * Axis edge label — the value floated at the plot's top or bottom edge * (Robinhood-style high/low). Pass `topLabel`/`bottomLabel` as `true` for the * batteries-included value label (the chart's current top/bottom Y-axis bound, * updated each frame), an object to configure it, or `{ render }` to float a * fully custom element instead. */ export interface AxisLabelConfig { /** Formatter for the built-in value. Defaults to the chart's `formatValue`. */ format?: (v: number) => string; /** Text color. Defaults to a muted label color (`palette.gridLabel`). */ color?: string; /** * Where the label sits. * - `"left"` / `"right"` (default `"right"`) — pin to that edge of the plot, * horizontally aligned. * - `"extrema"` — float at the **actual data point** where the value occurs * (`topLabel` tracks the highest point, `bottomLabel` the lowest), anchored * over the point with a marker dot, so you can see *when* the high / low * happened. The dot and label track the point on the UI thread. * - `"extrema-edge"` — like `"extrema"`, but the value label is pinned to the * **top / bottom edge**, horizontally aligned with the extremum (not floated * over the point). The marker dot still sits on the data point, joined to the * edge label by a {@link AxisLabelConfig.connector} line. Keeps the readout on * a clean rail while still showing where the extremum is. */ position?: "left" | "right" | "extrema" | "extrema-edge"; /** Built-in value text size in px. Default `11`. */ fontSize?: number; /** Built-in value text weight. Default the platform `` default. */ fontWeight?: FontWeight; /** Built-in value text font family (e.g. a loaded monospace face). */ fontFamily?: string; /** * Extrema modes only — color of the marker dot at the data point. Defaults to * `color`. Lets the dot and the value text differ. */ dotColor?: string; /** Extrema modes only — marker dot diameter in px. Default `7`. */ dotSize?: number; /** * Extrema modes only — draw the marker dot at the data point. Default `true`. * Set `false` for a value label with no dot. */ dot?: boolean; /** * `"extrema-edge"` only — the line joining the marker dot (on the data point) * to the edge value label. `true` = a dashed default, `false` = none, or pass a * {@link LineStyleConfig} to style it (`color` defaults to the label `color`). * Default on (dashed) in `"extrema-edge"` mode. */ connector?: boolean | LineStyleConfig; /** * Full custom element, floated at the edge (or, in an extrema mode, centered * over the extremum point). Overrides the built-in value label (and the * `fontSize` / `fontWeight` / `fontFamily` / `dot*` knobs above — you own the * styling). */ render?: () => ReactElement | null; } /** X-axis (time) configuration. */ export interface XAxisConfig { /** Minimum pixel gap between time labels. Default `60`. */ minGap?: number; } /** * Auto-hide both axes while idle (see {@link LiveChartCoreProps.axisAutoHide}). * * @experimental */ export interface AxisAutoHideConfig { /** Fade-in duration (ms) when interaction starts — near-instant. Default `60`. */ fadeInMs?: number; /** Fade-out duration (ms) back to idle. Default `250`. */ fadeOutMs?: number; /** Axis opacity while idle (0 = fully hidden). Default `0`. */ idleOpacity?: number; /** Untouched time (ms) before the axes fade back out. Default `3000`. */ hideAfterMs?: number; } /** * Morfi-style on-canvas tooltip for {@link LiveChartSeries}: a time-range pill * above the guide plus one value pill at every visible series intersection. * * Enable it with `scrub={{ seriesTooltip: true }}` or pass this object to style * and format the readout. It is ignored by the single-series {@link LiveChart}. */ export interface PerSeriesTooltipConfig { /** * Keep the value pills pinned to the visible series endpoints while idle. * The guide and time pill remain hidden until a scrub starts. Default `false`. */ alwaysShow?: boolean; /** * Bucket width used by the time-range pill, in seconds. Omit to infer it from * the latest positive interval in the first visible series. */ bucketSeconds?: number; /** * Format a value pill. The second argument identifies the series so one chart * can dispatch to different unit formatters. Defaults to the chart's * `formatValue`. * * Runs on the UI thread and must be worklet-safe. */ formatSeriesValue?: (value: number, seriesId: string) => string; /** * Format the time pill from the bucket's start/end unix seconds. The end is * clamped to the chart's current-time anchor, so the live bucket never claims * to end in the future. Defaults to two chart `formatTime` labels joined by * an en dash. * * Runs on the UI thread and must be worklet-safe. */ formatTimeRange?: (from: number, to: number) => string; /** Truncate labels longer than this many characters with `…`. Default `14`. */ maxLabelChars?: number; /** Guide color. Omit to use `scrub.crosshairLineColor`, then the theme. */ guideColor?: string; /** Guide stroke width in px. Default `1`. */ guideWidth?: number; /** * Guide dash pattern. `true` uses `[3, 3]`; an array supplies explicit Skia * dash intervals; `false` draws a solid guide. Default `[3, 3]`. */ guideDashPattern?: boolean | number[]; /** Time-pill background. Omit to use `scrub.tooltipBackground`, then the theme. */ timePillBackground?: string; /** Time-pill text color. Omit to use `scrub.tooltipColor`, then the theme. */ timePillColor?: string; /** Time-pill border color. Omit to use `scrub.tooltipBorderColor`, then the theme. */ timePillBorderColor?: string; /** Time-pill corner radius in px. Default `6`. */ timePillRadius?: number; /** Time-pill horizontal padding in px. Default `8`. */ timePillPaddingX?: number; /** Time-pill vertical padding in px. Default `4`. */ timePillPaddingY?: number; /** Series-pill background. Omit to use `scrub.tooltipBackground`, then the theme. */ seriesPillBackground?: string; /** Series label color. Omit to use the theme tooltip text. */ seriesPillLabelColor?: string; /** Formatted series value color. Omit to use `seriesPillLabelColor`. */ seriesPillValueColor?: string; /** Series-pill border color. Omit to use `scrub.tooltipBorderColor`, then the theme. */ seriesPillBorderColor?: string; /** Series-pill corner radius in px. Default `6`. */ seriesPillRadius?: number; /** Series-pill horizontal padding in px. Default `8`. */ seriesPillPaddingX?: number; /** Series-pill vertical padding in px. Default `4`. */ seriesPillPaddingY?: number; /** Diameter of the series-colour dot inside each pill, in px. Default `8`. */ seriesPillDotSize?: number; /** Gap between the colour dot and label, in px. Default `6`. */ seriesPillDotGap?: number; /** Gap between the label and formatted value, in px. Default `6`. */ seriesPillLabelValueGap?: number; /** Diameter of the dot drawn at each line/guide intersection. Default `8`. */ intersectionDotSize?: number; } /** Crosshair scrub configuration. */ export interface ScrubConfig { /** Show the value/time tooltip pill while scrubbing. Default `true`. */ tooltip?: boolean; /** * Opt into the Morfi-style per-series pill tooltip on `LiveChartSeries`. * `true` uses defaults; an object enables and configures it; `false`/omitted * preserves the existing guide-only multi-series scrub. Ignored by * single-series `LiveChart`. Default `false`. */ seriesTooltip?: boolean | PerSeriesTooltipConfig; /** * Which content {@link dimOpacity} fades while scrubbing. * * - `"future"` fades all chart content to the right of the crosshair. * - `"otherCandles"` keeps the candle under the finger at full strength and * fades every other candle body and wick. Outside candle mode it falls back * to the standard `"future"` behavior. When the finger is in a gap between * candle buckets no candle is selected. Volume bars are unchanged. The * vertical crosshair and scrub selection dot are hidden so the focused * candle is the sole position indicator. * * Default `"future"`. */ dimTarget?: "future" | "otherCandles"; /** * Opacity of the content selected by {@link dimTarget} while scrubbing — `0` * fully fades it out, `1` disables the dim. Values are clamped to `[0, 1]`. * * With the default `dimTarget: "future"`, the fade is implemented by erasing * the trailing content's alpha (`dstOut`), so it reveals the real background * and works on any background color. Default `0.3`. `crosshairDimColor` * overrides this opacity only for the `"future"` target. */ dimOpacity?: number; /** * Duration in milliseconds for other candles to ease toward * {@link dimOpacity} when a focused-candle scrub starts, and back to full * strength on release. Used only with `dimTarget: "otherCandles"`. * Set `0` for an instant change. Negative values clamp to `0`. Default `60`. */ dimFadeMs?: number; /** Vertical crosshair line stroke. Omit to use theme `crosshairLine`. */ crosshairLineColor?: string; /** Vertical crosshair line width in px. Default `1`. */ crosshairStrokeWidth?: number; /** * Extend the vertical crosshair line past the plot's top and bottom edges by * this many px. A measured top custom-tooltip stop remains unchanged so the * line does not draw through the tooltip. Negative values are clamped to `0`. * Default `0`. */ crosshairOvershoot?: number; /** * Fade the crosshair as it approaches the live edge. Set `false` to keep the * line, selection dot, and tooltip fully opaque while scrubbing. The trailing * content dim keeps its own edge fade. Default `true`. */ crosshairFade?: boolean; /** * Distance in px over which the visible crosshair fades as it approaches the * live edge. Applies to the line, selection dot, and tooltip when * `crosshairFade` is enabled; the trailing-content dim keeps its existing * fade. Negative values are clamped to `0`. Default `4`. */ crosshairFadeDistance?: number; /** * Cap style for the vertical crosshair line. Omit to preserve the existing * Skia default. */ crosshairLineCap?: "butt" | "round" | "square"; /** * Dash the vertical crosshair line. `true` → a default `[4, 4]` dash; an array * sets explicit Skia dash intervals `[on, off, …]` in px. Omit / `false` → a * solid line. */ crosshairDash?: number[] | boolean; /** * Legacy: fill the region right of the crosshair with this solid (usually * semi-transparent) color — a mask painted *over* the chart, so it only looks * right when it matches the background. Prefer `dimOpacity`. When set, it * overrides the `dimOpacity` fade. */ crosshairDimColor?: string; /** Tooltip pill background. Omit to use theme `tooltipBg`. */ tooltipBackground?: string; /** Tooltip text color. Omit to use theme `tooltipText`. */ tooltipColor?: string; /** Tooltip pill border color. Omit to use theme `tooltipBorder`. */ tooltipBorderColor?: string; /** Tooltip pill corner radius in px. Default `5`. */ tooltipBorderRadius?: number; /** * Where the tooltip pill sits relative to the vertical scrub line. * `"side"` (default) offsets it to the right of the line and flips left near * the right edge. `"top"` / `"bottom"` center it horizontally over the line, * clamped into the plot and pinned to the plot's top or bottom. `"point"` * centers it over the line and floats it just above the scrub dot, flipping * below the dot when there isn't room above (single-series line mode; candle * mode keeps its top-pinned OHLC stack). This also drives a custom * {@link LiveChartProps.renderTooltip} so it gets the same placement for free. */ tooltipPlacement?: "side" | "top" | "bottom" | "point"; /** * Gap in px between the tooltip and the plot edge it's pinned to — the top for * `"side"`/`"top"`, the bottom for `"bottom"`. Applies to the built-in pill and * a custom {@link LiveChartProps.renderTooltip}. Default `8`. */ tooltipMargin?: number; /** Show the value row in the default tooltip body. Default `true`. */ tooltipShowValue?: boolean; /** Show the time row in the default tooltip body. Default `true`. */ tooltipShowTime?: boolean; /** * Press-and-hold delay in milliseconds before scrubbing activates — think of * it as "press and hold to scrub." During the delay the pan is not captured, * so a quick horizontal swipe falls through to a parent gesture (e.g. a * navigator's swipe-back-to-previous-route). `0` = scrub immediately on drag. * Default `0`. */ panGestureDelay?: number; /** * Fade the annotation overlays — buy/sell **markers** and **reference lines** * (both the built-in Skia tags/lines and any custom `renderMarker` / * `renderReferenceLine` RN views) — out while scrubbing, so they don't clutter * the crosshair read-out. Reverses on release. * * The fade is driven by the **scrub-active** state (not the crosshair's * edge-proximity fade, which would resurface the overlays as the crosshair * nears the live dot) and eased on the UI thread over `SCRUB_OVERLAY_FADE_MS`. * It animates only a **group opacity** — the marker atlas and reference-line * geometry are left intact (still one batched draw each), so it's far cheaper * than emptying / rebuilding overlay data per scrub. The leading dot is * governed separately by `selectionDot`. * * `false` / omitted keeps the overlays visible while scrubbing (default). */ hideOverlaysOnScrub?: boolean; /** * Keep plain scrubbing inside the horizontal plot bounds. A gesture that * starts in the Y-axis gutter (or beyond either horizontal edge) is ignored; * one that starts inside remains active and clamps to the nearest plot edge * while dragging. Scrub-action gestures retain their existing behavior. * Default `false`. */ clampToPlot?: boolean; /** * Candle mode: snap the crosshair to candle centers. The scrub X is * quantized to the hovered candle's center before it drives the crosshair, * so the line — and everything derived from it (the time, tooltip, and * trailing dim) — jumps from candle to candle instead of gliding, the * tick-to-tick feel of pro charting tools. A position in a gap between * candles keeps the raw finger X; no effect in line mode. Default `false`. */ snapToCandles?: boolean; } /** * Scrub-action ("order ticket") mode for the single-series `LiveChart` (line and * candle). Tap to drop a locked crosshair, drag to fine-tune a **price level**, * then press the right-gutter action badge to fire {@link LiveChartProps.onScrubAction}. * * The reported price is the value at the reticle's **Y position** (a free price * level you choose — the inverse of the value→pixel mapping), NOT the line/candle * value at the reticle's X. That matches how a limit price works (a horizontal * level) and the crosshair badge in pro charting tools. Opt-in (default off). */ export interface ScrubActionConfig { /** Glyph drawn in the action badge (rendered as chart text, like `Marker.icon`). Default `"+"`. */ icon?: string; /** Action-badge background color. Defaults to the accent / badge color. */ background?: string; /** Action-badge icon + price text color. Defaults to the badge text color. */ iconColor?: string; /** Horizontal level-line color. Defaults to `palette.crosshairLine`. */ lineColor?: string; /** * Show the price readout inside the action badge. Default `true`. Set `false` * for an **icon-only** pill (mirrors {@link ReferenceLineBadgeConfig.text}). */ text?: boolean; /** * Show a date/time pill where the reticle's vertical line meets the x-axis, * formatted by the chart's `formatTime`. Off by default — for order entry the * reticle's X (time) is incidental to a price *level*; enable it when the time * under the reticle is meaningful (annotations, time-relevant actions, or a full * crosshair readout). Reuses `background` / `iconColor`. Default `false`. */ timeBadge?: boolean; /** * Round the reported price to this increment (e.g. `0.01` for cents, `0.5` for * a tick size) so the badge reads round numbers. Omit for the raw value. */ snap?: number; /** * A tap on empty plot (outside the reticle + action badge) dismisses the lock * instead of moving it. Default `false` (an empty-plot tap re-places the reticle). */ dismissOnTapOutside?: boolean; /** * Dismiss the reticle once the action badge is pressed (and * {@link LiveChartProps.onScrubAction} fires), so no crosshair lingers after the * action — e.g. clear the order reticle once the order has been placed. Default * `false` (the reticle stays until tapped away). */ dismissOnAction?: boolean; } /** * Payload for {@link LiveChartProps.onScrubAction} — the chosen price **level** * (from the locked reticle's Y), not the line value at that time. * * The library asserts no buy/sell semantics: derive the side from `price` versus * your own current price (`price < last` is the usual buy-below / sell-above * convention, but stop orders invert it). */ export interface ScrubActionPoint { /** Chosen price level — the value at the reticle Y, optionally `snap`-rounded. */ price: number; /** Unix timestamp in seconds at the reticle X. */ time: number; /** Canvas X coordinate of the reticle. */ x: number; /** Canvas Y coordinate of the reticle. */ y: number; /** In candle mode, the OHLC data of the candle under the reticle X (context only). */ candle?: CandlePoint; } /** * Props passed to a custom {@link SelectionDotConfig.component} — the dot drawn * at the scrub intersection while scrubbing. All positional inputs are * SharedValues so the dot animates on the UI thread without re-renders. */ export interface SelectionDotProps { /** Scrub X in canvas px. */ x: SharedValue; /** Scrub Y in canvas px (the line/value intersection). */ y: SharedValue; /** Whether scrubbing is active. */ active: SharedValue; /** Crosshair fade opacity (0..1), already ramped. */ opacity: SharedValue; /** Resolved dot color (accent/series color). */ color: string; /** Suggested dot radius in px. */ size: number; } /** * Context passed to a custom {@link LiveChartProps.renderTooltip}. The element * you return is a React Native view that the chart floats over the canvas and * positions on the UI thread (per `scrub.tooltipPlacement`) — so movement stays * smooth without JS re-renders, unlike rebuilding the tooltip from the JS-thread * `onScrub` callback. Bind the SharedValues here to animated text (e.g. an * `Animated.createAnimatedComponent(TextInput)` driven by `useAnimatedProps`) * for the value/date to update on the UI thread too. */ export interface TooltipRenderProps { /** * Value under the crosshair; `null` when none. In line mode this is the * interpolated value at the scrub time; in candle mode it's the scrubbed * candle's close (use {@link TooltipRenderProps.candle} for full OHLC). */ value: SharedValue; /** Window time (unix seconds) under the crosshair. */ time: SharedValue; /** * Value formatted with the chart's `formatValue` (computed UI-side). In candle * mode this is the formatted close. */ valueStr: SharedValue; /** Time formatted with the chart's `formatTime` (computed UI-side). */ timeStr: SharedValue; /** Whether scrubbing is currently active. */ active: SharedValue; /** * In candle mode, the OHLC candle under the crosshair (`null` when none or * while inactive). Always `null` in line mode — bind it to render OHLC in a * custom candlestick tooltip. Format the individual prices with your own * worklet-safe formatter (e.g. the chart's `formatValue`). */ candle: SharedValue; /** * Explicit line or candle gap under the crosshair (`null` outside a * configured gap). A gap never fabricates {@link candle}; inspect this field * for no-trade, unavailable, or unknown-data treatment. */ gap: SharedValue; } /** Inner plot rectangle in canvas pixels (a snapshot field of {@link ChartScale}). */ export interface ChartPlotRect { /** Inner plot left edge (`padding.left`). */ left: number; /** Inner plot top edge (`padding.top`). */ top: number; /** Inner plot right edge (`canvasWidth - padding.right`). */ right: number; /** Inner plot bottom edge (`canvasHeight - padding.bottom`). */ bottom: number; /** Full canvas width. */ width: number; /** Full canvas height. */ height: number; } /** * A snapshot of the chart's live scale — the value range, time window, and plot * rect at one frame. Carried by {@link ChartOverlayContext.scale} (a `SharedValue` * recomputed each frame) and consumed by the pure mapping functions. Reading * `scale.get()` inside your worklet is what subscribes the overlay to per-frame * updates, so it tracks the chart as it scrolls and rescales. */ export interface ChartScale { /** Live Y-axis lower bound (price at the plot bottom). */ min: number; /** Live Y-axis upper bound (price at the plot top). */ max: number; /** Visible time window in seconds. */ window: number; /** Right-edge timestamp (unix seconds) — "now". */ now: number; /** Inner plot rectangle in canvas px. */ plot: ChartPlotRect; } /** * The price↔pixel / time↔pixel bridge handed to a custom * {@link LiveChartProps.renderOverlay} or * {@link LiveChartSeriesProps.renderOverlay}. * * **Easiest path — the `usePriceY` / `useTimeX` hooks.** They project a price / time * to a `SharedValue` that tracks the live axis for you; just read it in * your `useAnimatedStyle` like any SharedValue: * * ```tsx * function PriceLevel({ ctx, price }) { * const y = usePriceY(ctx, price); // reactive — tracks the rescaling axis * const style = useAnimatedStyle(() => ({ transform: [{ translateY: y.get() }] })); * return ; * } * ``` * * **Manual path — {@link scale} + the pure mappings.** For one-off math (e.g. a * gesture handler) or when you want everything in one worklet, read `scale.get()` * inside your worklet — that read subscribes it to the per-frame scale — then pass * the snapshot to the pure mappings (`priceToY` / `yToPrice` / `timeToX` / * `xToTime`). The mappings do **not** read `scale` themselves, so they only reflect * the chart live when you feed them `scale.get()`: * * ```tsx * const style = useAnimatedStyle(() => { * const s = scale.get(); // subscribe to the live scale * const y = priceToY(142.5, s); // project a price to its pixel * return { transform: [{ translateY: y }] }; * }); * ``` */ export interface ChartOverlayContext { /** * The live {@link ChartScale} snapshot, recomputed each frame. Read `.get()` * inside your worklet to subscribe it to chart updates. */ scale: SharedValue; /** Maps a price (Y-axis value) → canvas Y px for a {@link ChartScale}. Worklet. -1 when not laid out. */ priceToY: (price: number, scale: ChartScale) => number; /** * Inverse of {@link priceToY}: canvas Y px → price. Worklet. Clamps to the * visible range; `null` when not laid out. */ yToPrice: (y: number, scale: ChartScale) => number | null; /** Maps a unix-seconds timestamp → canvas X px for a {@link ChartScale}. Worklet. -1 when not laid out. */ timeToX: (time: number, scale: ChartScale) => number; /** Inverse of {@link timeToX}: canvas X px → unix-seconds timestamp. Worklet. */ xToTime: (x: number, scale: ChartScale) => number; } /** Outer ring drawn around the built-in selection dot (the subtle halo). */ export interface SelectionDotRingConfig { /** Ring color. Defaults to the dot color. */ color?: string; /** Ring thickness in pixels — how far the halo extends past the dot. Default `2`. */ width?: number; } /** * Selection-dot styling — the dot drawn at the scrub intersection while * scrubbing. Pass `selectionDot={false}` to hide it, an object to configure the * built-in dot, or `{ component }` to supply a fully custom Skia dot. */ export interface SelectionDotConfig { /** Dot radius in px. Default `4`. */ size?: number; /** Dot color. Defaults to the line / leading-series color. */ color?: string; /** * Outer ring around the dot. `true`/config = on, `false` = off. Default on. */ ring?: boolean | SelectionDotRingConfig; /** * Fully custom Skia dot — receives the scrub position as SharedValues. When * set, the `size` / `color` / `ring` knobs are ignored. */ component?: ComponentType; } /** Left-edge fade — soft erase so the chart blends into the left gutter (web liveline parity). */ export interface LeftEdgeFadeConfig { /** * Horizontal fade band in pixels; gradient runs from the chart inner edge (`padding.left`) * across this width. Default `40` (same as liveline). */ width?: number; /** * Gradient color at the left (canvas edge side). With default `dstOut` blending, **alpha** * controls how strongly content is erased (opaque = full fade). When omitted, defaults match * the chart background RGB (`palette.bgRgb`) at alpha 1. */ startColor?: string; /** * Gradient color at the right (chart side). When omitted, defaults to the same RGB as the * chart background at alpha 0 (no erase). */ endColor?: string; } /** Pulsing ring animation on the live dot. */ export interface PulseConfig { /** Time between pulse starts in milliseconds. Default `2400`. */ interval?: number; /** Duration of each pulse expansion in milliseconds. Default `1600`. */ duration?: number; /** Maximum radius the pulse ring expands to in pixels. Default `20`. */ maxRadius?: number; /** Peak opacity of the pulse ring (0–1). Default `0.4`. */ opacity?: number; /** Stroke width of the pulse ring in pixels. Default `1.5`. */ strokeWidth?: number; } /** Font configuration for chart labels, badges, and axis text. */ export interface FontConfig { /** Font family name. Default `"Menlo"`. */ fontFamily?: string; /** Base font size in pixels. Default `11`. */ fontSize?: number; /** Font weight. Default `"normal"`. */ fontWeight?: FontWeight; /** * Load this typeface from a Metro asset or URI (`require("./Font.ttf")`, path string, or * `Uint8Array`). When set, Skia uses `useFont` for that file; `fontWeight` does not alter the * outlines (use a bold file or `fontManager` for multiple weights). While the asset loads, * the chart falls back to `matchFont` with `fontFamily` / defaults. Prefer either this or * `fontManager`, not both, unless you intentionally want a registered family as fallback. */ typeface?: DataSourceParam; /** * Custom Skia font manager from `useFonts` (e.g. bundled `.ttf` files registered under family * names). Passed as the second argument to `matchFont`. When `null` or omitted, the system * font manager is used. */ fontManager?: SkFontMgr | null; } /** Padding insets for the chart drawing area. */ export interface ChartInsets { /** Top padding in pixels. Default `12`. */ top?: number; /** Right padding in pixels. Auto-calculated based on badge/grid presence. */ right?: number; /** Bottom padding in pixels. Default `28`. */ bottom?: number; /** Left padding in pixels. Default `12`. */ left?: number; } /** A single trade fill event for on-chart markers. */ export interface TradeEvent { /** Trade direction. */ side: "buy" | "sell"; /** Execution price. */ price: number; /** Trade size / quantity. */ size: number; /** Unix timestamp in seconds. */ time: number; /** Optional ticker symbol for simulated / custom feeds. */ symbol?: string; } /** Built-in marker glyph kinds drawn into the chart canvas. */ export type MarkerKind = "trade" | "boost" | "graduation" | "winner" | "clawback"; /** * A marker rendered into the chart at `(time, y)`. Exactly one of `seriesId` * (anchor y to a series line at the marker's time, multi-series only) or * `value` (absolute y in data space) should be provided. */ export interface Marker { /** Stable identifier. */ id: string; /** Unix timestamp in seconds. */ time: number; /** Glyph kind. */ kind: MarkerKind; /** Anchor y to this series' line at `time` (multi-series). */ seriesId?: string; /** * Absolute y value in data space. Takes precedence over `seriesId`. Omit it on * a single-series chart to anchor the marker to the line at `time` * (interpolated from the chart's `data`) — so the glyph always sits on the line. */ value?: number; /** Glyph color override. Defaults to a kind-specific palette accent. */ color?: string; /** * Text / emoji glyph drawn centered at the marker, overriding the built-in * `kind` shape. Rendered with the chart font, so pass an emoji-capable font * (via `font`) if you use emoji. */ icon?: string; /** * Image icon drawn centered at the marker (e.g. from Skia `useImage`). Takes * precedence over `icon` and the built-in `kind` shape. */ image?: SkImage; /** * Draw the `icon` inside a filled circular badge in the marker `color` (icon * rendered in white) — e.g. a green `+` buy / red `−` sell tag. Requires * `icon`; ignored without it. The badge sizes itself to the icon glyph. */ pill?: boolean; /** Icon / image box size in px (icon font size or image width+height). Default `16`. */ size?: number; /** * Sit the glyph above / below its anchor instead of centered on it. Default * `"center"` (on the line/value, today's behavior). Also the stack direction * when `markerCluster: "stacked"` — e.g. buys `"below"`, sells `"above"`. */ side?: MarkerSide; /** Pass-through payload surfaced on `onMarkerPress`. */ data?: unknown; } /** Where a marker glyph sits relative to its anchor (and the stack direction). */ export type MarkerSide = "above" | "below" | "center"; /** * Object form of {@link LiveChartProps.markerCluster} for tuning the stacked * collision behavior. Passing this object implies `mode: "stacked"` unless you * set `mode` explicitly. */ export interface MarkerClusterConfig { /** Defaults to `"stacked"` when this object form is used. */ mode?: "anchored" | "stacked"; /** * Stack direction when `mode: "stacked"`. `"horizontal"` (default) fans * co-located glyphs sideways into an overlapping row (the "( ) ) )" * stacked-coins look). `"vertical"` piles them into a **column** that grows * away from the line in each glyph's `side` direction — `side: "above"` climbs * up, `"below"` descends, `"center"` climbs up from the line. This is the * transactions-stacked-on-the-candle look; raise `maxBeforeGroup` for taller * columns before they collapse to a count badge. Default `"horizontal"`. */ direction?: "horizontal" | "vertical"; /** * How much adjacent co-located glyphs overlap when fanned, `0` (just touching) * to `1` (fully stacked). Default `0.75`. The on-screen overlap is approximate * (the fan step is estimated from the glyph size, not its exact pixels). */ overlap?: number; /** Collapse a co-located run to a single count badge once it exceeds this many. * Default `5`. */ maxBeforeGroup?: number; /** * `"vertical"` only: cap the column at this many glyphs instead of letting it * grow unbounded (and off the plot). The oldest `maxVisible` glyphs keep * their stack; the newest overflow is hidden. Default unbounded. */ maxVisible?: number; /** * What a collapsed cluster draws in place of its members: * - `"count"` (default) — the built-in round count badge (a circle with the * member count inside). * - `"marker"` — the **representative marker's own glyph** (its `image`, `icon` * /`pill`, or `kind` shape). The representative is the newest marker in the run, * so a run of buy pills collapses to a buy pill — handy when the group should * look like its members. * - {@link MarkerGroupBadge} — a **dedicated group badge you supply** (your own * Skia `image`, or an `icon`/`pill`), independent of the member markers. Use * this when the collapse should look *different* from the individual markers — * e.g. tiny dots that collapse into a distinct "Buy 5" badge image. * * All three are Skia-drawn in the same `drawAtlas` batch (not a `renderMarker` * RN overlay). Pair any non-`"count"` form with {@link showGroupCount} for a * corner count. Default `"count"`. */ groupBadge?: "count" | "marker" | MarkerGroupBadge; /** * When {@link groupBadge} is `"marker"` or a {@link MarkerGroupBadge}, also stamp * the member count as a small badge in the glyph's top-right corner — the * "Buy **5**" look. Ignored when `groupBadge` is `"count"` (the count *is* the * badge). Default `false`. */ showGroupCount?: boolean; } /** * A dedicated badge drawn for a collapsed marker cluster — your own Skia design, * independent of the member markers. Pass it as * {@link MarkerClusterConfig.groupBadge}. Glyph precedence mirrors a single marker: * `image` → `icon` (`pill`) → the built-in count badge. An object containing only * {@link letterSpacing} customizes that built-in count badge. */ export interface MarkerGroupBadge { /** * Extra spacing in px between digits in the built-in count badge. Also applies * to the optional corner count on a custom group badge. Default `0`. */ letterSpacing?: number; /** Custom Skia image for the collapsed group (e.g. from `useImage`). Takes * precedence over {@link icon}. */ image?: SkImage; /** Text / emoji glyph for the collapsed group (used when {@link image} is unset). * Rendered with the chart font. */ icon?: string; /** Color of the `icon` (and its `pill`). Defaults to a palette accent. */ color?: string; /** Wrap the `icon` in a filled circular badge in {@link color} (icon drawn white). */ pill?: boolean; /** Glyph box size in px (icon font size, or image width+height). Default `16`. */ size?: number; } /** Context passed to `renderMarker` alongside the marker (cluster / position state). */ export interface MarkerRenderContext { /** Index of the marker in the `markers` array. */ index: number; /** `true` when this marker is the representative of a collapsed cluster. */ isGrouped: boolean; /** Number of markers in the collapsed cluster (`0` when not grouped). */ groupCount: number; /** Resolved side the glyph is drawn on. */ side: MarkerSide; } /** Payload for `onMarkerPress` — the marker and its screen position. */ export interface MarkerPressEvent { marker: Marker; point: { x: number; y: number; }; /** Index of the pressed marker in the `markers` array. */ index: number; /** `true` when the press landed on a collapsed cluster (count badge). */ isGrouped: boolean; /** The cluster's markers when `isGrouped`, so the consumer can list them. */ members?: Marker[]; } /** Particle burst + screen shake on momentum swings ("degen mode"). */ export interface DegenOptions { /** Scale multiplier for particle size and speed. Default `1`. */ scale?: number; /** Also trigger on downward momentum (not just upward). Default `false`. */ downMomentum?: boolean; /** When `false`, particle bursts still run but the chart does not shake. Default `true`. */ shake?: boolean; /** Multiplier on default shake amplitude (`1` matches built-in behavior). */ shakeIntensity?: number; /** How long the shake envelope runs, in seconds. Default `0.45`. */ shakeDurationSec?: number; /** Ring-buffer slots (clamped 4–80). Default `60`. */ particleSlotCount?: number; /** How long each particle stays visible, in seconds. Default `1.0`. */ particleBurstDurationSec?: number; /** Particles spawned per momentum burst (clamped 1–`particleSlotCount`). Default `20`. */ burstParticleCount?: number; /** Velocity drag per frame (0–1, higher = less drag). Default `0.95`. */ drag?: number; /** Minimum particle radius in pixels. Default `1`. */ particleSizeMin?: number; /** Maximum particle radius in pixels. Default `2.2`. */ particleSizeMax?: number; /** Peak particle opacity (0–1). Default `0.55`. */ particleOpacity?: number; /** Angular spread in radians for the burst semicircle. Default `π * 1.2`. */ spreadAngle?: number; /** Horizontal position jitter in pixels (±half). Default `24`. */ positionJitterX?: number; /** Vertical position jitter in pixels (±half). Default `8`. */ positionJitterY?: number; /** Minimum initial speed in px/s. Default `60`. */ speedMin?: number; /** Maximum initial speed in px/s. Default `160`. */ speedMax?: number; /** * Particle color(s). A single string or an array of colors. * When an array is provided, each particle picks one at random. * Default: chart accent / `palette.line`. */ colors?: string | string[]; } /** Payload for {@link LiveChartProps.onDegenShake}. */ export interface DegenShakePayload { direction: "up" | "down"; } /** Optional soft, static color glow drawn behind a live dot. */ export interface DotGlowConfig { /** Glow color. Defaults to the dot's resolved fill color. */ color?: string; /** Radius of the glow's source circle in pixels. Default `7`. */ radius?: number; /** Skia blur radius in pixels. Default `5`. */ blur?: number; /** Glow opacity (0–1). Default `0.18`. */ opacity?: number; } /** Contrasting outer ring drawn behind each series dot — the crisp backing * circle the single-series live dot has, so dots stand out against the lines * and one another. */ export interface DotRingConfig { /** Ring color. Default: theme `badgeOuterBg` (a near-background backing). */ color?: string; /** Ring thickness in pixels — how far the halo extends past the dot. Default `2.5`. */ width?: number; } /** * Shared live-dot styling, used by both `LiveChart` (`dot`) and * `LiveChartSeries` (`dot`, which extends this). A dot is a color-filled circle * of `radius` with an optional contrasting outer `ring` and opt-in soft * `glow`. The glow is static; use `pulse` for an animated heartbeat. */ export interface DotConfig { /** Radius of the (color-filled) dot in pixels. Default `3.5`. */ radius?: number; /** * Contrasting outer ring (halo) behind the dot, so it reads clearly against * the line(s). `true` = defaults, `false` = a flat circle, or pass * `DotRingConfig`. Default `true`. */ ring?: boolean | DotRingConfig; /** * Soft static glow behind the dot. `true` = restrained defaults, `false` = * off, or pass `DotGlowConfig` to tune its color, radius, blur, and opacity. * Default `false`. */ glow?: boolean | DotGlowConfig; /** * Show the dot. `false` hides it (line, badge, and labels still render). Default `true`. * * @deprecated Pass `dot={false}` to hide the dot — the uniform `boolean | Config` * convention. `show` still works and is equivalent to `dot={{ show: false }}`. */ show?: boolean; /** Dot fill color. Defaults to the chart line color (per series for multi-series). */ color?: string; /** * Keep the dot on the **true live point** while scrolled back or overscrolled * with `timeScroll`: it tracks the live point's x (instead of staying pinned * to the plot's right edge), keeps its heartbeat pulse (the dot still marks * an honest live position), stays visible under * `timeScroll.hideLiveOnScrollBack`, and hides once the live point leaves the * visible window. Ignored when `badge.followViewEdge` is set — an edge-pinned * dot must stay aligned with its badge. Single-series `LiveChart` only * (multi-series dots already ride each line's end while scrolled). Default * `false` (the dot stays pinned to the right edge with its pulse suppressed * while scrolled back). * * @experimental */ trackWhileParked?: boolean; } /** Live dot configuration for multi-series charts (extends the shared {@link DotConfig}). */ export interface MultiSeriesDotConfig extends DotConfig { /** Pulsing ring animation on each series dot. `true` = defaults, or pass `PulseConfig`. Default `true`. */ pulse?: boolean | PulseConfig; /** Horizontal dashed line at each series' live value. `true` = defaults, or pass `ValueLineConfig`. Default `false`. */ valueLine?: boolean | ValueLineConfig; /** Show series label (e.g. "Yes", "No") to the right of each dot. Default `true`. */ valueLabel?: boolean; } /** Visual style overrides for the legend (toggle chips). All fields optional. */ export interface LegendStyle { /** Font size for chip labels (px). Default `13` (`11` when compact). */ fontSize?: number; /** Chip corner radius (px). Default `8`. */ borderRadius?: number; /** Colored swatch diameter (px). Default `8`. */ dotSize?: number; /** Chip background when the series is visible. */ activeBackground?: string; /** Chip background when the series is hidden. */ hiddenBackground?: string; /** Label color when the series is visible. */ activeColor?: string; /** Label color when the series is hidden. */ hiddenColor?: string; } /** Legend (toggle chips) configuration for multi-series charts. */ export interface LegendConfig { /** Show the legend. Default `true`. */ visible?: boolean; /** Use a smaller, denser chip layout (tighter padding + font). Default `false`. */ compact?: boolean; /** Position of the legend relative to the chart. Default `"top"`. */ position?: "top" | "bottom"; /** Visual style overrides for the chip row. */ style?: LegendStyle; } /** Configuration for a single series in a multi-series chart. */ export interface SeriesConfig { /** Unique identifier for this series. */ id: string; /** Array of data points for this series. */ data: LiveChartPoint[]; /** Latest (live) value for smooth interpolation. */ value: number; /** Line color. Defaults to the built-in series color palette. */ color?: string; /** Display label shown in toggle chips. */ label?: string; /** Whether this series is visible. Default `true`. */ visible?: boolean; /** Stroke style. `"dashed"` applies `intervals`. Default `"solid"`. */ style?: "solid" | "dashed"; /** Dash pattern as `[dashLength, gapLength]` when `style` is `"dashed"`. Default `[6, 4]`. */ intervals?: [number, number]; /** * Interpolation for this series' line, mirroring {@link LineConfig.curve}. * - `"monotone"` (default): Fritsch-Carlson monotone cubic spline. * - `"linear"`: straight segments between samples (sharp vertices). Markers * anchored to this series snap to the straight chord to match. */ curve?: "monotone" | "linear"; /** * Per-series screen-space path simplification tolerance in pixels. Overrides * `LiveChartSeries.line.simplify`; `0` disables simplification for this series. * Only rendered geometry is simplified — values, range fitting, and scrubbing * continue to use the original points. */ simplify?: number; /** Per-series stroke width override (px). Falls back to the chart line width. */ strokeWidth?: number; /** Render a soft glow behind this series' line. Default `false`. */ glow?: boolean; /** * Semantic role. `"derived"` series (e.g. a conviction trajectory) render a * subdued / dashed legend chip. Default `"outcome"`. */ kind?: "outcome" | "derived"; /** Value text shown next to the label inside the legend chip. */ valueLabel?: string; } /** Per-series value at a scrub position, used in the multi-series `onScrub` callback. */ export interface ScrubSeriesValue { /** Series identifier matching `SeriesConfig.id`. */ id: string; /** Series label, if provided. */ label?: string; /** Interpolated value at the scrub time for this series. */ value: number; } /** Base fields shared by single and multi-series scrub callback payloads. */ export interface ScrubPointCore { /** Unix timestamp in seconds at the scrub position. */ time: number; /** Interpolated value at the scrub position. */ value: number; /** Canvas X coordinate of the crosshair. */ x: number; /** Canvas Y coordinate of the interpolated value. */ y: number; } /** Scrub callback payload for single-series charts. */ export interface ScrubPoint extends ScrubPointCore { /** In candle mode, the OHLC data of the candle under the crosshair. */ candle?: CandlePoint; /** Explicit chart gap under the crosshair, when a bridged gap supplies the value. */ gap?: ChartGap; } /** Scrub callback payload for multi-series charts. */ export interface ScrubPointMulti extends ScrubPointCore { /** Interpolated values for each visible series at the scrub time. */ seriesValues: ScrubSeriesValue[]; } /** OHLC candlestick data for a single time bucket. */ export interface CandlePoint { /** Bucket start time as Unix timestamp in seconds. */ time: number; /** Opening price. */ open: number; /** Highest price during the bucket. */ high: number; /** Lowest price during the bucket. */ low: number; /** Closing price. */ close: number; /** * Traded volume in the bucket. Drives the optional volume bars * (see {@link LiveChartProps.volume}); bar heights are normalized to the * largest visible volume, so only relative values matter. Omitted candles * contribute no bar. Ignored when `volume` is off or in line mode. */ volume?: number; } /** Why a chart time range contains no observed market sample. */ export type CandleGapKind = "no-trades" | "unavailable" | "unknown"; /** * Explicit metadata for a chart interval without an observed sample. The range * is half-open (`[from, to)`) and uses Unix seconds, matching line points and * {@link CandlePoint.time}. Gaps do not replace or mutate real data. */ export interface CandleGap { /** Inclusive range start as a Unix timestamp in seconds. */ from: number; /** Exclusive range end as a Unix timestamp in seconds. Must be greater than {@link from}. */ to: number; /** Semantic reason the interval has no observation. */ kind: CandleGapKind; /** Optional localized label, e.g. `"Exchange maintenance"`. */ label?: string; } /** Previous-value bridge styling for a chart gap. */ export interface CandleGapBridgeStyle { /** Mark color. Defaults to palette `refLine`. */ color?: string; /** Mark opacity. Default `0.7` when enabled (`0.55` for unavailable gaps). */ opacity?: number; /** Mark stroke width in pixels. Default `2`. */ strokeWidth?: number; /** Mark cap style. Default `"round"`. */ strokeCap?: "butt" | "round" | "square"; } /** Full-height time-band styling for a chart gap. */ export interface CandleGapBandStyle { /** Band fill color. Defaults to palette `refLine`. */ fillColor?: string; /** Band fill opacity. Default `0.12` when enabled (`0.08` for unknown gaps). */ fillOpacity?: number; /** Band-edge color. Defaults to palette `refLine`. */ borderColor?: string; /** Band-edge opacity. Default `1`. */ borderOpacity?: number; /** * Band-edge width in pixels. `0` hides the border. Default `2` (`1` for * unknown gaps). */ borderWidth?: number; /** Band-edge dash pattern. Default `[4, 4]`. */ intervals?: [number, number]; } /** Label styling for a chart-gap time band. Text comes from {@link CandleGap.label}. */ export interface CandleGapLabelStyle { /** Label color. Defaults to palette `refLabel`. */ color?: string; /** Label position inside the band. Default `"left"`. */ position?: "left" | "right"; } /** Per-kind presentation overrides for {@link CandleGapsConfig}. */ export interface CandleGapStyle { /** * Previous-close marks. `false` disables them; an object enables and styles * them. Omission preserves the semantic default for the gap kind. */ bridge?: false | CandleGapBridgeStyle; /** * Full-height time band. `false` disables it; an object enables and styles it. * Omission preserves the semantic default for the gap kind. */ band?: false | CandleGapBandStyle; /** * Band label. `false` hides it; an object enables and styles it. Omission * preserves the semantic default for the gap kind. */ label?: false | CandleGapLabelStyle; } /** * Data and optional per-kind styling for explicit chart gaps. Gap metadata is * low-frequency React data (like `referenceLines`); live market data remains in * its SharedValue. */ export interface CandleGapsConfig { /** Sorted, non-overlapping gap ranges. Invalid ranges are ignored. */ gaps: CandleGap[]; /** Presentation overrides keyed by semantic gap kind. */ styles?: Partial>; } /** Semantic reason for a chart interval with no observed market sample. */ export type ChartGapKind = CandleGapKind; /** Shared gap metadata used by both line and candlestick charts. */ export type ChartGap = CandleGap; /** Previous-value bridge styling shared by line and candlestick gaps. */ export type ChartGapBridgeStyle = CandleGapBridgeStyle; /** Full-height time-band styling shared by line and candlestick gaps. */ export type ChartGapBandStyle = CandleGapBandStyle; /** Time-band label styling shared by line and candlestick gaps. */ export type ChartGapLabelStyle = CandleGapLabelStyle; /** Per-kind gap presentation shared by line and candlestick charts. */ export type ChartGapStyle = CandleGapStyle; /** Gap data and per-kind styles shared by line and candlestick charts. */ export type ChartGapsConfig = CandleGapsConfig; /** * Spatial sizing & motion tokens — the geometry/timing analogue of * {@link LiveChartPalette}. Where `palette` controls *color*, `metrics` controls * *shape* (badge geometry, candle bounds) and *feel* (fade/lerp speeds). Override * via the `metrics` prop; only the namespaces/keys you set are replaced. */ export interface LiveChartMetrics { /** Value-badge pill geometry. */ badge: BadgeMetrics; /** Candlestick body/wick geometry. */ candle: CandleMetrics; /** Grid + axis-label fade animation. */ grid: GridMetrics; /** Per-frame lerp speeds for value/color transitions. */ motion: MotionMetrics; /** Empty-state (no-data) layout. */ emptyState: EmptyStateMetrics; } /** Value-badge pill geometry (the "metrics" analogue of badge colors). */ export interface BadgeMetrics { /** Horizontal padding inside the pill, each side of the label. Default `10`. */ padX: number; /** Vertical padding above and below the label. Default `3`. */ padY: number; /** Length of the pointed tail toward the live dot. Default `5`. */ tailLength: number; /** Gap between the pill's outer edge and the canvas edge. Default `4`. */ marginEdge: number; /** Gap between the live dot and the badge tail tip. Default `12`. */ dotGap: number; /** Vertical spread of the tail curve's control points. Default `2.5`. */ tailSpread: number; } /** Candlestick body/wick geometry. */ export interface CandleMetrics { /** Minimum candle body height in pixels (so dojis stay visible). Default `1`. */ minBodyPx: number; /** Maximum candle body width in pixels. Default `40`. */ maxBodyPx: number; /** Body width as a fraction of the per-candle slot width (0–1). Default `0.8`. */ bodyWidthRatio: number; /** Minimum horizontal gap (px) enforced between adjacent candle bodies. `0` lets `bodyWidthRatio` alone control body width on dense charts. Default `2`. */ minGapPx: number; /** Corner radius (px) of candle bodies. `0` = sharp corners. Default `0`. */ bodyRadius: number; /** Wick (high–low line) stroke width in px. Default `1`. */ wickWidth: number; } /** Grid-line and axis-label fade animation speeds. */ export interface GridMetrics { /** Per-frame alpha lerp speed when a grid line / label fades in. Default `0.18`. */ fadeInSpeed: number; /** Per-frame alpha lerp speed when a grid line / label fades out. Default `0.12`. */ fadeOutSpeed: number; } /** Per-frame lerp speeds for value/color transitions. */ export interface MotionMetrics { /** Per-frame lerp speed for the badge background color transition. Default `0.08`. */ badgeColorSpeed: number; /** Extra catch-up speed added to `smoothing` when the live value lags its target. Default `0.12`. */ adaptiveSpeedBoost: number; } /** Empty-state (no-data) layout. */ export interface EmptyStateMetrics { /** Opacity of the empty-state label. Default `0.35`. */ labelOpacity: number; /** Half-padding (px) around the empty text for the squiggle "gap" erase band. Default `20`. */ gapPad: number; /** Horizontal fade width (px) on each side of the empty-text gap. Default `30`. */ gapFadeWidth: number; } /** * Caller-supplied `metrics` override — every namespace and field is optional, and * only the keys you set replace the resolved default (per-namespace shallow merge, * same model as `palette`). */ export interface LiveChartMetricsOverride { badge?: Partial; candle?: Partial; grid?: Partial; motion?: Partial; emptyState?: Partial; } /** * Time-scroll activation (see {@link LiveChartCoreProps.timeScroll}). * * @experimental Prototype — gesture model and API may change. */ export interface TimeScrollConfig { /** * Which gesture pans the timeline: * - `"holdToScrub"` (default) — a one-finger drag anywhere scrolls; scrub * moves to press-and-hold (Rainbow-style). See {@link scrubHoldMs} for the * hold duration. * - `"axisDrag"` — a one-finger drag starting in the bottom X-axis band * ("grab the time ruler"); the plot area stays free for one-finger scrub. */ gesture?: "holdToScrub" | "axisDrag"; /** * `holdToScrub` only: press-and-hold duration (ms) before scrub engages, so a * quicker one-finger drag scrolls instead. Higher = more deliberate scrub and * fewer accidental scrubs while scrolling. Falls back to `scrub.panGestureDelay` * if set, otherwise `500`. */ scrubHoldMs?: number; /** * Hide the live badge, dot, and dashed value line while scrolled back through * history. Default `true`: all three mark the LIVE price, which is off-screen * once the window is frozen behind the live edge, so they'd point at a price * that isn't in view. Set `false` to keep them visible while scrolled back. * Ignored when `badge.followViewEdge` is on — there the group tracks the * visible edge price, which IS in view. */ hideLiveOnScrollBack?: boolean; /** * How far pan / fling / pinch may travel past the data bounds, as a fraction * of the visible window (`0`–`1`). With overscroll you can drag the latest * candle toward the middle of the plot (blank future space on the right) or * pan left past the oldest point into blank history — TradingView-style free * dragging. A released drag that settles within a small zone of the live edge * re-attaches to live. Default `0`: the window hard-stops at the oldest data * and the live edge (the classic behavior). Values outside `[0, 1)` are * clamped. Applies to the pinch-zoom (`zoom`) clamps too. */ overscroll?: number; /** * Fling inertia on release: a fast drag keeps scrolling and decays to a * stop (the classic momentum feel). Set `false` to stop the window dead * where the finger lifts — a deliberate, TradingView-style hard stop. A * release that lands within the snap zone of the live edge still * re-attaches to live. Default `true`. */ fling?: boolean; } /** * Tunes the "return to live" glide — the eased animation that carries the window * back to the live edge when `timeScroll` is switched off while scrolled back. See * {@link TimeScrollConfig.returnToLive}. * * @experimental */ export interface ReturnToLiveConfig { /** * Glide duration in milliseconds. Default `450`. The window decelerates onto the * live edge (ease-out cubic). Lower = snappier; `0` is treated as no animation * (an instant snap). */ duration?: number; } /** * Pinch-to-zoom the visible time window (see {@link LiveChartCoreProps.zoom}). * * @experimental Prototype — gesture model and API may change. */ export interface ZoomConfig { /** * Tightest visible window in seconds (max zoom-in). Default `timeWindow / 8`. */ minTimeWindow?: number; /** * Widest visible window in seconds (max zoom-out). Defaults to the full data * span (you can zoom out to all retained history), never below `timeWindow`. */ maxTimeWindow?: number; } /** Imperative methods exposed by `LiveChart` and `LiveChartSeries`. */ export interface LiveChartHandle { /** * Reset built-in pinch/time-window zoom to the configured `timeWindow` and * clear the focal-point offset so the chart follows the live edge again. * Safe to call when already reset. A paused chart remains paused. * * ```tsx * const chartRef = useRef(null); *