LiveChartSeries draws multiple line series that share an axis, time window, and crosshair.
Pass a SharedValue<SeriesConfig[]>.
Live example:
app/demo/multi-series.tsx
in the example app.Multiple live series with a toggleable legend
series.modify(...) (mutating each entry’s data array and value), or replace the whole array
with series.set(...). Avoid series.value = ....
Ordinary live price updates use smoothing, including feeds that append a point
and set value together. The dot and newest drawn sample ease to the new value
together, so the line does not bend back from a snapped sample to a lagging dot.
This affects presentation only: source history and recorded scrub values stay
unchanged. Earlier samples retain their recorded geometry. smoothing={1} and
snapKey remain immediate; scrolling back retains its historical edge behavior.
With an explicit presentationTime, an already-recorded value at or before the
head is presented immediately because that clock/value pair already defines the
presentation.
Y-range contraction uses smoothing by default; outward bounds snap unless you
pass rangeAnimation={{ animateExpansion: true }}. Enable it to ease the chart
into a wider range when a series is revealed or a new extreme arrives. The
first visible data fits immediately; subsequent expansion follows this setting.
For a dataset or unit change (for example Price → Market Cap), replace each
series’ history array and live value together. Replacement entries reach drawing
on the next measured engine frame, alongside that frame’s Y range and tips.
History arrays remain shared, so this does not isolate in-place edits to existing
points. Normal live appends with series.modify(...) remain supported. Use
snapKey when the replacement should settle immediately instead of easing with
the configured smoothing.
Initial rendering
When history is already available, initializeseries with that history before
mounting the chart. This lets its lines, legend, and value-label layout initialize
together. Its first measured, visible data fits the Y-range immediately, even
with animateExpansion: true, so lines do not wait for a placeholder range to
reach their prices. Later live updates, series toggles, and replacements retain
the configured easing. Empty and hidden-only feeds wait for visible history
before this initial fit. For asynchronous history, keep the loading state active until the feed
is ready. Heavy controls or other surrounding UI can mount after the chart’s
initial frame, as the Multi-series demo does.
Replay clock
Drive reconstruction or replay with a SharedValue rather than updating a numericnowOverride prop every frame:
value alongside the clock. A head of
1050 produces viewport edge 1060. On a 320-point plot, the line and live dot end
at x=288, leaving 32 points of breathing room. Advancing the head to 1070 moves
the edge to 1080. Paths and range fitting ignore samples after an explicit head,
and scrubbing uses the same time limit. A historical viewport ends at its own
edge when that is earlier than the head. Panning into or past the buffer keeps
the presented value and range. Scrub callbacks and tooltip values interpolate
only through presented history and the current tip, with the selection dot
clamped to that head. Degen bursts use the same dot position and ignore queued
future momentum; an offscreen tip cannot spawn a burst.
All history
Set the authoritative start once to keep the complete history in a growing live window without updating thetimeWindow prop on every frame:
timeWindow remains the fallback and the
anchor for the default minimum zoom window. Pinch starts from the actual
painted window. Default maximum zoom includes the full anchored span even when
retained samples begin later; explicit zoom.minTimeWindow/maxTimeWindow take
precedence. Pan and pinch can park a narrower or historical view; call
ref.current?.resetZoom() to restore the growing live view.
Transforming the presentation range
For an overlay that needs temporary breathing room, transform the fitted range using a UI-thread reveal value instead of changing recorded prices:reveal from 0 to 1 with Reanimated when the overlay appears, then back
to 0 on release. The callback runs after normal fitting/easing and can also read
from and to, the visible Unix-second interval. Its output never feeds back
into fitting: bounds 17.6–42.4 become 7.6–52.4 at full reveal on every frame.
Releasing or removing the callback restores the original fit. Invalid ranges
are ignored. Recorded prices and scrub values stay unchanged; hard bounds apply
to the underlying fit before the presentation transform.
Series options
EachSeriesConfig supports:
Set
line={{ simplify: 1 }} on LiveChartSeries to denoise every rendered
path by roughly one screen pixel. A series can choose a different tolerance, or
set simplify: 0 to keep its exact point path. The shared axis, scrub values,
and live dots continue to use the original series data.
Dimming replacement data
For a timeframe change, retain the current series while the next range loads, then set one UI-thread opacity multiplier for every plotted series:1, then fade back in after
the plotted series returns to full opacity.
Legend
The legend renders toggle chips by default. Configure it with thelegend prop, and listen for
taps with onSeriesToggle:
Per-series dots
Thedot prop controls the live dots, their pulse, optional static glow,
value lines, and inline labels. Each dot has a crisp contrasting outer ring (in
the chart background color) by default, matching the single-series live dot so
overlapping series stay legible. The soft glow remains opt-in:
LiveChartSeries reference.
Per-series scrub tooltip
LiveChartSeries keeps its historical guide-only scrub by default. Opt into the
Morfi market-detail readout with scrub.seriesTooltip: a dashed vertical guide,
a top-centred bucket range, and one pill plus intersection dot for every visible
series. Close values stack instead of overlapping, wide pills flip to the
guide’s left near the right edge, and hidden legend series are omitted.
This feature is opt-in and backward compatible.
scrub={true},
scrub={{}}, and scrub={{ seriesTooltip: false }} all keep the existing
guide-only multi-series behavior. The existing scrub.tooltip property is
still the master switch; setting it to false also suppresses the per-series
pills.true to customize formatting and appearance:
formatSeriesValue and formatTimeRange run on the UI thread; keep them
worklet-safe, just like the chart’s formatValue and formatTime. Omit
bucketSeconds to infer the range width from the first visible series. The
range end is always clamped to the chart’s current-time anchor, so an
in-progress live bucket never displays a future end time.
Set alwaysShow to keep the value pills at the visible endpoints while the
user is not touching the chart. Idle mode deliberately hides the guide and
time pill; scrubbing restores the full readout.