Live Line Chart
A streaming line chart — a time window that slides in real time over data as it arrives, with an interpolated live tip, a crosshair that reads between samples, and axes that glide with the domain.
Installation
pnpm add @blakeui/pro-reactnpm install @blakeui/pro-reactyarn add @blakeui/pro-reactbun add @blakeui/pro-reactUsage
LiveLineChart is built on visx time scales and the
motion library. It is a single component: grid, axes, tooltip, live dot,
and momentum colouring are all props, not composable children.
You own the stream. The chart holds no interval and generates no data — it takes a growing
data array and renders a moving window over it. Append points as they arrive from your
WebSocket, poll, or SSE connection.
import {LiveLineChart} from "@blakeui/pro-react";
const [data, setData] = useState([]);
const [value, setValue] = useState(0);
useEffect(() => {
const id = setInterval(() => {
const next = readLatestMetric();
setData((previous) => [...previous, {time: Date.now() / 1000, value: next}].slice(-240));
setValue(next);
}, 500);
return () => clearInterval(id);
}, []);
<LiveLineChart data={data} value={value} window={30} />;Data and value
data is an array of {time, value} sorted oldest-first, where time is Unix seconds — not
milliseconds. value is passed separately, and that separation is the point: data is committed
history, while value is the target the live tip eases toward. Between arrivals the dot keeps
moving toward value instead of sitting still and then jumping, which is what makes the chart read
as live rather than as a chart that redraws twice a second.
Updating value without appending to data is a normal, supported case — the tip, its readout, and
the dashed value rule all move; the committed line does not.
Cap your buffer
The chart never mutates data, so nothing stops it growing forever. Slice it on every append. A
good default is a few multiples of the visible window — enough history that a paused chart still has
real data behind it when it resumes, without unbounded growth on a page left open.
The time window
window is the visible span in seconds; older points scroll off the left edge. Rendering cost
is set by what is on screen, not by how much history you hold — the chart binary-searches to the
window start, so passing 10,000 points costs the same as passing 100.
nowOffsetUnits leaves a leading gap between the live tip and the right edge, measured in x-tick
units. At the default numXTicks={5} one unit is a quarter of the plot, so nowOffsetUnits={1}
puts the tip at 75% width and fades the line out across the gap ahead of it. At the default of 0
the tip sits hard against the right edge and there is no leading fade.
Pausing
Set paused to freeze the window. The "now" marker and the domain stop advancing, but new data
still appends and still widens the y-domain — so nothing is lost while paused. Releasing it snaps
the domain forward to the current time rather than replaying the backlog.
Momentum
Set momentum to colour the line, area, and live dot by short-term trend: --chart-1 while rising,
--chart-2 while falling, and the base stroke when flat. The trend compares the last five samples'
net change against 12% of the local peak-to-trough range, so a quiet series reads as flat instead of
flickering on noise.
Motion
The window slides on an animation frame loop, independent of when data arrives, so the chart scrolls smoothly rather than stepping. Interpolation is time-based, not per-frame, which keeps the scroll rate identical across refresh rates and through dropped frames. The y-domain is deliberately asymmetric: it snaps open the instant a new extreme arrives, so an incoming spike is never clipped, and eases closed when that extreme scrolls away, so the chart does not lurch every time a peak leaves the window.
The loop is suspended while the chart is scrolled out of view and snaps to the current state when it returns — an off-screen chart neither burns frames nor accumulates a backlog to animate through.
Under prefers-reduced-motion all continuous motion is suppressed: no sliding window, no
interpolation between arrivals, no pulsing dot. The chart still steps as each point lands. A live
chart has no final state to snap to, and freezing it would hide current data — accessibility must not
cost the reader information.
The chart exposes the live value, and the hovered value while scrubbing, to assistive tech through a polite live region. Left/right arrows move the crosshair, Home and End jump to the window edges, and Escape clears it.
Reading the crosshair
The hovered value is interpolated along the line rather than snapped to the nearest sample, and it is re-read every frame. A stationary pointer therefore reports a changing time and value as the window slides beneath it — that is the chart being live, not the tooltip drifting.
Connecting state
LiveLineChart has no loading status of its own — a live chart's loading state is just a line.
Render a loading LineChart while the stream connects, then swap in
the live chart once data starts arriving.
API Reference
LiveLineChart
The single chart component. Measures its container, slides a time-domained window over data,
interpolates the live tip toward value, and renders the series, axes, hover layer, and readout.
| Prop | Type | Default | Description |
|---|---|---|---|
data | LiveLineChartPoint[] | — | Streaming samples, oldest first: {time, value} with time in Unix seconds. The chart never mutates or caps this array — you own it. |
value | number | — | The latest value, separate from data so the live tip can interpolate toward it between arrivals instead of stepping. |
window | number | 30 | Visible time span in seconds. Older points scroll off the left edge. |
numXTicks | number | 5 | Number of x tick labels. Also sets the size of one nowOffsetUnits unit. |
nowOffsetUnits | number | 0 | Leading gap between the live tip and the right edge, in x-tick units. 1 at the default tick count leaves a quarter-plot gap that the line fades into. |
paused | boolean | false | Freeze the window. New data still appends and still widens the y-domain. |
exaggerate | boolean | false | Tighten the y-domain padding from 15% to 3%, exaggerating small moves. |
stroke | string | "var(--chart-1)" | Series color. Ignored for the line, area, and dot while momentum is active. |
strokeWidth | number | 2 | Series stroke width. |
curve | LiveLineCurve | curveMonotoneX | D3 curve interpolation factory. |
momentum | boolean | false | Colour the line, area, and dot by short-term trend — --chart-1 rising, --chart-2 falling, stroke flat. |
showArea | boolean | true | Render the gradient fill under the line. |
showGrid | boolean | true | Render horizontal grid lines, aligned to the y tick values. |
showAxes | boolean | true | Render the x and y axis labels. |
showTooltip | boolean | true | Render the hover crosshair and the floating tooltip. |
showLiveDot | boolean | true | Render the pulsing live dot, its rolling readout, and the dashed value rule. |
valueFormatter | (value: number) => string | — | Formats axis tick labels and the screen-reader readout. It does not format the live value or the tooltip value — those always roll through the odometer. |
timeFormatter | (date: Date) => string | HH:MM:SS | Formats x tick labels and the tooltip header. |
valuePrefix | string | — | Static text rendered before the rolling value (e.g. "$"). |
valueLabel | string | "Value" | Series name shown in the tooltip and announced to assistive tech. |
aspectRatio | string | "2 / 1" | CSS aspect ratio of the chart box. Ignored when height is set. |
height | number | — | Fixed pixel height. Overrides aspectRatio. |
margin | Partial<Margin> | {top: 16, right: 16, bottom: 32, left: 56} | Plot margins — a left gutter for value labels, room below for time labels. |
Also supports all native div HTML attributes.
LiveLineChartPoint
Each entry in data is one sample of the stream.
| Field | Type | Description |
|---|---|---|
time | number | Unix time in seconds — Date.now() / 1000, not Date.now(). Points must be sorted oldest-first. |
value | number | The sample's value. Participates in the y-domain while it is inside the window. |