Live example:
app/demo/threshold.tsx
in the example app.threshold
colors the line itself above vs. below a value — green above, red below by default. The value
is either a single live benchmark (a SharedValue<number> that tracks on the UI thread without
re-rendering — break-even / average cost, VWAP, the previous close, a stablecoin peg) or a
time-varying series (a LiveChartPoint[] the split follows
point-for-point). Single-series LiveChart, line mode.
Green above, red below a live break-even — with an optional P/L fill band
{ value } splits the stroke at the threshold (palette green above, red
below; override with aboveColor / belowColor). Two opt-in extras share the same
SharedValue:
fill— tints the area between the line and the threshold (the profit/loss band) toward the above/below colors. It’s independent of the baselinegradientfill, so setgradient={false}for the band alone.trueuses the default band opacity (0.16, matching reference-line bands); pass{ opacity: 0.35 }to tune it.line— a dashed marker line at the threshold.truedraws just the line; pass aThresholdLineConfigto add a label badge (labelColoroverrides the label’s text color, likeReferenceLine.labelColor).includeInRange— fold the threshold into the Y-axis range fit, like reference lines do. Without it the range fits the data alone, so a benchmark outside the price range renders invisibly (marker off-plot, whole line one color). Defaultfalse.
labelPosition: "left", the default — clear of the y-axis labels) or the right
gutter, and is drawn on top of the line so it’s never painted over. For a
time-varying series, labelAnchor independently selects the threshold value and
Y position: "first" samples the visible window’s left edge and "last" (the
default) samples the live/right edge. This lets a left-positioned badge line up
with the threshold where it enters the plot:
line.color / line.colors and segment
recoloring for the main stroke.
Custom threshold badge
UserenderThresholdBadge when the badge needs React Native content the built-in
Skia pill cannot provide: a branded average-cost tag, icon, status chip, blur, or
an interactive child. The chart still owns the position and follows the live
threshold on the UI thread; your component owns only the badge’s contents and
chrome. The dashed marker line remains built in.
ThresholdBadgeRenderProps contains value, valueStr, y, and visible
SharedValues plus the resolved line config. Bind valueStr to animated text as
above so the badge value updates without a React render. Returning null or
undefined keeps the built-in badge. renderThresholdBadge has no effect unless
threshold.line is enabled.
Time-varying threshold (a series)
Pass aLiveChartPoint[] as value instead of a SharedValue<number> and the
threshold becomes a line, not a level — the stroke split, fill band and marker
all follow it point-for-point. This is the natural fit for a benchmark that
changes over time: a break-even / average cost that steps up as you DCA in, a
historical VWAP, a moving peg. For a series that updates live, pass a
SharedValue<LiveChartPoint[]> as series instead (below).
- The series is clamped to its first/last value outside its own time range. So a threshold whose last point sits behind the live edge extends as a flat line to “now” — carrying the last known break-even forward — and a window that starts before the first point holds the first value.
- Points are connected with straight segments. For a step function, emit two points at each boundary (same time, old then new value) as above; for a smooth benchmark, emit it as you’d sample it.
- The split renders from 64 samples across the plot (a GPU shader), so a hard step becomes a riser about 1/63 of the plot wide — visually a clean vertical at phone sizes. The stroke split, band and marker share the same samples, so they always agree at the riser.
aboveColor/belowColoraccept hex (#rgb/#rrggbb),rgb()andrgba()in series mode (anrgba()alpha carries into the stroke and scales the band). Named CSS colors and 8-digit hex are only supported by the constant form — in series mode they fall back to grey.- The marker
line’s badge shows the threshold’s live-edge value by default. SetlabelAnchor: "first"to show and align to the value at the visible window’s left edge instead;labelPositionstill controls the badge’s X side. - An empty series (
[]) renders as “no threshold yet”: plain line color, no band, no marker — handy while the threshold history is still loading. extendToNow: falseopts out of the flat extension 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.
SharedValue<number> form, a value series flows in on re-render
(it’s a plain array). Pass a stable, memoized array and update it when the
benchmark actually changes. For a threshold series that updates live — a VWAP
recomputed every tick — use the series form, a SharedValue<LiveChartPoint[]>
that works like the chart’s own data prop: update it with .set() /
.modify() and the split tracks on the UI thread without re-rendering.
Candle mode and
LiveChartSeries (multi-series) are unaffected — threshold is a
single-series line-mode feature in both forms.ThresholdConfig for every field.