Liveline

Liveline is a real-time animated line chart component for React. One <canvas>, no dependencies beyond React 18, smooth interpolation at 60fps.

Degen mode (chart shake and particles) with momentum arrows.

I built this because every charting library I tried was either too heavy for a simple live feed, or too rigid to feel alive. Liveline does one thing: draw a line that moves smoothly as new data arrives. Everything else is opt-in.


Getting started

npm install liveline

The component fills its parent container. Set a height on the wrapper.

import { Liveline } from 'liveline'
 
function Chart({ data, value }) {
  return (
    <div style={{ height: 200 }}>
      <Liveline data={data} value={value} />
    </div>
  )
}

data is an array of { time, value } points. value is the latest number.

Two props. That's it.

Feed it data however you like. WebSocket, polling, random walk. Liveline interpolates between updates so even infrequent data looks smooth. It works for anything with a value that changes over time.

Resting heart rate. Custom formatter, exaggerated Y-axis.


Momentum

The momentum prop adds directional arrows and a glow to the live dot. Green for up, red for down, grey for flat. Pass true to auto-detect direction, or force it with "up", "down", or "flat".

Arrows fade out fully before the new direction fades in.


Value overlay

showValue renders the current value as a large number over the chart. It updates at 60fps through direct DOM manipulation, not React re-renders. Pair it with valueMomentumColor to tint the number based on direction.

60fps value overlay with momentum colouring.


Time windows

Pass a windows array to render time horizon buttons. Each entry has a label and secs value. Three styles are available via windowStyle: "default", "rounded", and "text".

<Liveline
  windows={[
    { label: '1m', secs: 60 },
    { label: '5m', secs: 300 },
  ]}
  windowStyle="rounded"
/>

CPU usage with occasional spikes. Rounded time windows.


Reference line

referenceLine draws a horizontal line at a fixed value. Pass an object with value and an optional label.

Polymarket-style prediction line. "Will Bitcoin stay above $67,500?"


Orderbook

Pass an orderbook prop with bids and asks arrays to render streaming order labels behind the line. Each entry is a [price, size] tuple. Labels spawn at the bottom, drift upward, and fade out. Green for bids, red for asks. Bigger orders appear brighter.

The stream speed reacts to price momentum and orderbook churn (how much the bid/ask totals are changing). Calm markets drift slowly, volatile ones rush.

Kalshi-style orderbook stream. Bid and ask sizes float upward behind the price line.


Theming

Pass any CSS colour string to color and Liveline derives the full palette. Line, fill gradient, glow, badge, grid labels. It converts the input to HSL and generates every variant from there.

Dark theme. Same component, different colour.


More features

Everything is off by default or has sensible defaults. A few more things you can turn on:

  • exaggerate tightens the Y-axis range so small movements fill the full chart height. Useful for values that move in tiny increments, like the heart rate demo above.
  • scrub shows a crosshair with time and value tooltips on hover. On by default.
  • degen enables burst particles and chart shake on momentum swings. For when subtlety is not the goal.
  • badgeVariant="minimal" renders a quieter white pill instead of the accent-colored default. Or badge={false} to remove it entirely.

How it works

One <canvas>, one requestAnimationFrame loop. When a new value arrives, nothing jumps. The chart lerps toward the new state at 8% per frame (lerpSpeed). The Y-axis range, the badge, the grid labels all use the same lerp. The range snaps outward instantly when data exceeds it, so the line is never clipped. That's why it feels like one thing breathing rather than a bunch of parts updating independently.


Props

Data

PropTypeDefault
dataLivelinePoint[]required
valuenumberrequired

Appearance

PropTypeDefault
theme'light' | 'dark''dark'
colorstring'#3b82f6'
gridbooleantrue
badgebooleantrue
badgeVariant'default' | 'minimal''default'
badgeTailbooleantrue
fillbooleantrue
pulsebooleantrue

Features

PropTypeDefault
momentumboolean | Momentumtrue
scrubbooleantrue
exaggeratebooleanfalse
showValuebooleanfalse
valueMomentumColorbooleanfalse
degenboolean | DegenOptionsfalse

Time

PropTypeDefault
windownumber30
windowsWindowOption[]
onWindowChange(secs: number) => void
windowStyle'default' | 'rounded' | 'text'

Crosshair

PropTypeDefault
tooltipYnumber14
tooltipOutlinebooleantrue

Orderbook

PropTypeDefault
orderbookOrderbookData

Advanced

PropTypeDefault
referenceLineReferenceLine
formatValue(v: number) => stringv.toFixed(2)
formatTime(t: number) => stringHH:MM:SS
lerpSpeednumber0.08
paddingPadding{ top: 12, right: 80, bottom: 28, left: 12 }
onHover(point: HoverPoint | null) => void
cursorstring'crosshair'
classNamestring
styleCSSProperties

Stress testing

A chart that only looks good on calm data isn't much use. These demos throw the worst stuff I could think of at it: wild volatility, sharp direction changes, isolated spikes on flat lines, and irregular data arrival with random gaps.

Wild swings, fast updates (100ms)

Near-flat, ultra-low volatility, exaggerate (150ms)

Chaotic, huge spikes (80ms)

Sharp reversals are the classic breaking point. The first chart hammers the line with frequent direction changes at 60ms. The second holds nearly flat, then fires massive isolated spikes. The third is just chaos.

Frequent sharp reversals (60ms updates)

Near-flat with massive isolated spikes (120ms)

Rapid zigzag oscillation (50ms)

Real-world data doesn't arrive at regular intervals. WebSocket connections drop, batch updates land all at once, mobile networks stall. This one simulates that: long quiet stretches of 1-3 seconds between points, then sudden bursts at 40-80ms. The tick interval itself is random.


Just a line

Liveline can do a lot. Momentum arrows, particles, orderbooks, scrubbing, time windows. But at the end of the day, if you just want a line that moves when a number changes, it does that just fine too.