A streaming line chart for live data such as heart rate, prices, and sensor readings, with a continuously scrolling time window and press-to-inspect.
LiveChartruns on the UI thread. Samples go into a buffer that lives there, and one frame loop per chart scrolls the window, moves the line's end and eases the y-range. A new sample never re-renders React. Rendering a chart requires the optionalreact-native-skiapeer (>=3.0.0).
Use it instead of passing a new data array to LineChart on every sample: that re-renders React per sample and morphs every point instead of scrolling.
The chart stays left-to-right in RTL layouts, since time runs left to right in every locale. Pair it with a text label, such as the latest value, when the values matter to screen reader users.
import { LiveChart, useLiveChartData } from 'heroui-native-pro';const data = useLiveChartData();
<LiveChart data={data}>
<LiveChart.Grid />
<LiveChart.YAxis />
<LiveChart.ReferenceLine value={...} />
<LiveChart.Line />
<LiveChart.Dot />
<LiveChart.Crosshair />
<LiveChart.Indicator />
<LiveChart.Tooltip />
</LiveChart>push appends a sample from the JS or the UI thread; reset replaces them all.window. Owns the canvas and the frame loop. Set its height with className.Push { time, value } samples, with time in milliseconds. Samples can arrive at any rate, evenly or not.
const data = useLiveChartData({ capacity: 600 });
useEffect(() => {
const id = setInterval(() => {
data.push({ time: Date.now(), value: readSensor() });
}, 250);
return () => clearInterval(id);
}, [data]);
<LiveChart data={data} window={30_000} className="h-56">
<LiveChart.Grid />
<LiveChart.YAxis />
<LiveChart.Line />
<LiveChart.Dot />
</LiveChart>;capacity (default 1000) is the most samples kept. Keep it above the number one window holds.
The right edge runs a little behind real time (delay, 'auto' by default: a bit more than the measured interval between samples), so the line's end moves between two known samples instead of jumping when one arrives. Pass a number of milliseconds to fix it, or 0 for no delay.
<LiveChart data={data} delay={500}>
...
</LiveChart>Without yDomain, the range fits the visible samples: it grows at once, so a sample is never clipped, and shrinks smoothly. Fix either end with yDomain.
<LiveChart data={data} yDomain={{ min: 40, max: 200 }}>
...
</LiveChart>data.latest is a shared value holding the newest sample's value. Show it with an animated text, or derive statistics from data.points with useDerivedValue: both stay on the UI thread.
const average = useDerivedValue(() => {
const points = data.points.get();
let sum = 0;
for (const point of points) sum += point.value;
return points.length ? sum / points.length : NaN;
});To react on the JS thread only when something meaningful changes (such as a zone), compare in useAnimatedReaction and call scheduleOnRN when it does.
isActive={false} stops the frame loop (for example when the chart scrolls off screen); the chart keeps its last frame. Samples pushed meanwhile are kept, and the line catches up when it resumes.
<LiveChart data={data} isActive={isFocused}>
...
</LiveChart>Press and drag on the chart to inspect a sample; Crosshair, Indicator and Tooltip show it. The finger's x maps to a time in the window and then to the nearest sample, all on the UI thread, so inspecting never re-renders React.
The chart stays live while pressed: a still finger keeps its x and samples scroll through it, so the tooltip follows whichever one is under the finger. To hold the pressed sample instead, pass freeze. The window then stops while the finger is down (new samples keep arriving) and glides back to live on release.
<LiveChart data={data} selection={{ freeze: true }}>
<LiveChart.Line />
<LiveChart.Crosshair />
<LiveChart.Indicator />
<LiveChart.Tooltip
format={(value) => {
'worklet';
return `${Math.round(value)} bpm`;
}}
/>
</LiveChart>activateAfter sets the press delay (100 ms by default) so a quick swipe still scrolls a parent list; selection={false} turns inspection off. Tooltip text runs on the UI thread, natively with react-native-animateable-text when it is installed, otherwise through an animated TextInput; format and formatTime must be worklets. Install react-native-animateable-text for the best tooltip performance (see Tooltip text on the UI thread).
animateIn on LiveChart.Line draws the line from left to right on mount; change resetKey to replay it.
<LiveChart.Line animateIn={1200} resetKey={replay} />import { Card } from 'heroui-native';
import { LiveChart, useLiveChartData } from 'heroui-native-pro';
import { useEffect } from 'react';
export default function HeartRate() {
const data = useLiveChartData({ capacity: 200 });
useEffect(() => {
let bpm = 120;
const id = setInterval(() => {
bpm += (Math.random() - 0.5) * 6;
data.push({ time: Date.now(), value: bpm });
}, 450);
return () => clearInterval(id);
}, [data]);
return (
<Card>
<Card.Body>
<LiveChart
data={data}
window={30_000}
yDomain={{ min: 100, max: 180 }}
className="h-[240px]"
>
<LiveChart.Grid dashed={[3, 5]} />
<LiveChart.YAxis count={5} />
<LiveChart.ReferenceLine value={150} />
<LiveChart.Line colorClassName="accent-danger" />
<LiveChart.Dot colorClassName="accent-danger" />
</LiveChart>
</Card.Body>
</Card>
);
}| prop | type | default | description |
|---|---|---|---|
children | ReactNode | - | Chart parts. |
data | LiveChartData | - | Buffer from useLiveChartData. |
window | number | 30000 | Visible time span, in ms. |
delay | number | 'auto' | 'auto' | How far the right edge runs behind real time, in ms. |
yDomain | { min?: number; max?: number } | fitted | Fixes either end of the y-axis. |
isActive | boolean | true | Runs the frame loop; false holds the last frame. |
className | string | - | Root view class. Set the height here. |
selection | { activateAfter?: number; freeze?: boolean } | false | { activateAfter: 100, freeze: false } | Press-and-drag inspection; freeze holds the window while pressed. |
animation | 'disable-all' | - | Snaps every eased value (line end, delay, y-range, release glide) and turns off the draw-in, the pulse and the tooltip glide. |
| prop | type | default | description |
|---|---|---|---|
colorClassName | string | 'accent-chart-3' | Uniwind color class for the stroke. |
curveType | CurveType | 'monotoneX' | Curve through the samples. |
strokeWidth | number | 2 | Stroke width. |
animateIn | boolean | number | - | Draws the line in on mount; a number sets the duration in ms. |
resetKey | unknown | - | Changing it replays animateIn. |
| prop | type | default | description |
|---|---|---|---|
colorClassName | string | muted at 30% | Uniwind color class for the lines. |
strokeWidth | number | 1 | Line width. |
dashed | boolean | number[] | - | Dash pattern, or true for 5-on/5-off. |
| prop | type | default | description |
|---|---|---|---|
count | number | 5 | Approximate number of ticks; the grid follows it. |
format | (value: number) => string | String(value) | Formats tick values. Must be a worklet. |
| prop | type | default | description |
|---|---|---|---|
value | number | - | Value the line marks. |
colorClassName | string | 'accent-warning' | Uniwind color class for the line. |
strokeWidth | number | 1.5 | Line width. |
dashed | boolean | number[] | true | Dash pattern, true for 6-on/4-off, false solid. |
| prop | type | default | description |
|---|---|---|---|
colorClassName | string | 'accent-chart-3' | Uniwind color class for the dot. |
radius | number | 4 | Dot radius. |
pulse | boolean | true | Draws an expanding ring around it. |
| prop | type | default | description |
|---|---|---|---|
strokeWidth | number | 1 | Line width. |
dashed | boolean | number[] | true | Dash pattern, true for 4-on/4-off, false solid. |
| prop | type | default | description |
|---|---|---|---|
colorClassName | string | 'accent-chart-3' | Uniwind color class for the dot. |
radius | number | 5 | Dot radius. |
ringRadius | number | 7 | Radius of the ring around it, in the background color. |
| prop | type | default | description |
|---|---|---|---|
format | (value: number) => string | 2 decimals | Formats the value. Must be a worklet. |
formatTime | (time: number) => string | HH:MM:SS | Formats the sample time (ms). Must be a worklet. |
showTime | boolean | true | Shows the time next to the value. |
placement | 'top' | 'bottom' | 'top' | Preferred side of the point; flips without room. |
gap | number | 16 | Space between the point and the tooltip. |
className | string | - | Tooltip view class. |
| option | type | default | description |
|---|---|---|---|
capacity | number | 1000 | Most samples kept; the oldest are dropped. |
initial | LiveChartPoint[] | - | Samples the buffer starts with, oldest first. |
Returns LiveChartData:
| field | type | description |
|---|---|---|
points | SharedValue<LiveChartPoint[]> | Samples, oldest first, on the UI thread. Changes in place. |
latest | SharedValue<number> | Newest value, NaN while empty. |
push | (point: LiveChartPoint) => void | Appends a sample. A worklet: call it from the JS or the UI thread. |
reset | (points?: LiveChartPoint[]) => void | Replaces every sample. A worklet. |
LiveChartPoint is { time: number; value: number }, with time in milliseconds.