Skip to content

The library

The live chart

A small animated chart for places where a price should look alive: a card, a ticker strip, the hero of a landing page. It ships in the same package and the same signed release as the full chart, under live/, and it sits beside that chart rather than replacing it.

What it is, and what it is not

One canvas, three modes: a line, candlesticks, or several series at once. The tip glides toward each new value, the time axis scrolls continuously instead of jumping a bar at a time, the price range eases when it changes, and a new candle grows from its open. It has no runtime dependencies and ships no stylesheet.

It is not a trading screen. There are no drawing tools, no indicators and no datafeed contract. It does have what a compact live view needs: a crosshair with tooltips, reference lines you can drag (working orders, alerts, targets), an order-ticket mode, markers for fills, scrolling back through history and zooming. Where a reader needs to study a market, mount the full chart; where they need to glance at one, or act on one price, mount this.

ImportFileUse it when
@warburgai/vp-charts/livelive/index.jsYou have a bundler. Exports createLiveChart and the LiveChartEngine class.
@warburgai/vp-charts/live/reactlive/react/index.jsA React app. Exports the LiveChart component. Imports react from your app: React 18 or later is an optional peer of the package, needed only by this entry.
@warburgai/vp-charts/live/globallive/vp-live.global.jsNo bundler. One file that defines window.VPLive, with the same exports as the module.
The React entry and the module share one engine chunk under live/chunks/, so a page using both downloads the engine once. Each entry is about 43 KB gzip, measured on the current release, and the build enforces a ceiling on all three. That is the cost of the full option set; a chart that only needs a line pays for the rest too.

Vanilla JavaScript

createLiveChart(element, options) mounts into any element that has a size. Keep the handle: update() merges new options in, and destroy() removes the chart, its observers and its animation frame.

A line that follows a price
import { createLiveChart } from '@warburgai/vp-charts/live';

const el = document.getElementById('btc-card');   // give it a height
const points = [];                                // { time, value }, oldest first

const chart = createLiveChart(el, {
  data: points,
  value: 0,
  color: '#3b82f6',
  theme: 'dark',
  window: 60,               // seconds of history on screen
  showValue: true,
  showChange: 'percent',
});

// On every tick from your own feed:
function onTick(price, unixSeconds) {
  points.push({ time: unixSeconds, value: price });
  chart.update({ data: points, value: price });
}

// When the card goes away:
chart.destroy();

Without a bundler, the global build defines VPLive and nothing else:

The script-tag build
<div id="btc-card" style="height: 160px"></div>

<script src="/vp-charts/live/vp-live.global.js"></script>
<script>
  const chart = VPLive.createLiveChart(document.getElementById('btc-card'), {
    data: [],
    value: 0,
    theme: 'light',
  });
</script>

React

The component mounts the engine once and hands it the full prop set on every render; the engine eases between them, so React never re-renders per frame and callbacks may be fresh closures. It fills its parent, so give the parent a height. Unmounting destroys the chart, which is the teardown the install page asks you to write by hand for the full chart.

A live price card
import { LiveChart } from '@warburgai/vp-charts/live/react';

export function PriceCard({ points, last }: {
  points: { time: number; value: number }[];
  last: number;
}) {
  return (
    <div style={{ height: 180 }}>
      <LiveChart
        data={points}
        value={last}
        theme="dark"
        windows={[
          { label: '1m', secs: 60 },
          { label: '5m', secs: 300 },
        ]}
        showValue
        showChange="percent"
      />
    </div>
  );
}
Hold the points in state you append to, not a new array built from scratch on every tick. The chart never mutates what it is given, so either works, but a list rebuilt each render is work the browser does for nothing.

Feeding it data

It fetches nothing. You pass the points you already have and it draws them; where they come from is your feed, your websocket or your own server. Times are unix SECONDS, fractional allowed. A millisecond timestamp puts every point thousands of years in the future and the plot reads empty.

ModeWhat you pass
Linedata: points { time, value }, oldest first, and value, the latest value the tip glides toward. Updating value alone between points is how the tip moves smoothly.
Candlesmode: 'candle', candles: closed buckets { time, open, high, low, close, volume? } with time the bucket's open, candleWidth in seconds, and liveCandle, the forming bucket, updated every tick.
Several seriesseries: a list of { id, data, value, color, label? }. Chips above the plot fade a line in and out, and onSeriesToggle tells you when.

Nothing the chart reports is an eased number. onHover and the tooltip read your own points, so a value a reader hovers is a value you supplied, never one the animation was passing through.

Options

Every option is optional. update() merges a partial set; the React component takes them as props. Rich options take true (the defaults), false (off) or a config object. The types and every default are in live/index.d.ts.

GroupOptions
Datadata, value; seriesOpacity dims the plotted data while a replacement loads
Looktheme, accentColor (or color), palette (override any colour: line, fill, grid, dots, candle bodies and wicks, tooltip, badge), metrics (badge, candle, grid, motion and empty-state sizes), font, line (width, colors, curve: 'monotone', simplify, join, cap), gradient, areaDots, dot (radius, ring, glow), pulse, badge (variant, tail, position, radius, border, font, offsets), valueLine, leftEdgeFade, canvasMode
Candlesmode, candles, candleWidth, liveCandle, lineMode, lineData, lineValue, volume (a reserved band, or a config with height, radius, opacity, colours), onModeChange, candleGaps, lineGaps
Several seriesseries (each with color, label, visible, style: 'dashed', strokeWidth, glow, kind: 'derived', valueLabel), legend (top or bottom, compact; false, or seriesToggle: false, hides the chips for a host that draws its own), onSeriesToggle, presentationTime, historyStartTime
Axes and rangexAxis (label spacing, vertical grid lines), yAxis (side, spacing, a fixed count, intervalScale, a right-anchored column, float, scaleGesture), gridStyle, axisAutoHide, topLabel and bottomLabel (the range bound, or pinned to the actual high and low), exaggerate, minRange, nonNegative, maxValue, yRangeScale, rangeAnimation
Motion and statesmoothing (or lerpSpeed), transitions, snapKey (land a timeframe switch in one frame), momentum, degen, loading (or a config: colour, amplitude, speed, skeleton labels, freezeAfterMs), paused, emptyText, static, autoSleep, frameLoopActive
TimetimeWindow (or window), windows, onWindowChange, windowStyle, windowBuffer, nowOverride (or a now clock)
HeadershowValue, valueMomentumColor, showChange with changeBase
OtherformatValue(value, tickStep), formatTime, padding (or insets), cursor, accessibilityLabel (or ariaLabel), accessibilityRole, forceMotion, brandMark
Motion follows the reader's prefers-reduced-motion setting: easing snaps and the pulse, particles and shake stop. forceMotion overrides it, and should be rare. The animation loop also stops while the chart is off screen or the tab is hidden; autoSleep stops it once nothing is moving, and static draws once with no loop at all, for grids of sparklines that still scrub on touch.

Interaction

WhatOptions
Scrubbingscrub or a config: crosshair colour, width, dash, cap and overshoot; dimming what lies ahead of the crosshair, or every candle but the one under it (dimTarget: 'otherCandles'); the tooltip along the top (default) or as a pill beside the line, at the top or bottom, or over the dot; snapToCandles; panGestureDelay for press-and-hold on touch; clampToPlot; hideOverlaysOnScrub; markers to report the fills under the crosshair; seriesTooltip for one pill per series. selectionDot styles the dot on the line.
CallbacksonScrub (time, value, position, and the candle, gap, markers or per-series values where they apply), onScrubCandleChange (once per candle entered), onGestureStart, onGestureEnd, onHover
HistorytimeScroll (drag to scroll back; the chart follows live again at the edge; fling, overscroll, axis-only drag), returnToLive, zoom (pinch, or ctrl and the wheel; bounds), viewport with onViewportChange to drive or sync the visible range, onVisibleRangeChange and onReachStart for paging older data, and resetZoom()
Order ticketscrubAction: a tap drops a price reticle, a drag fine-tunes it, and pressing its badge calls onScrubAction with the price at the reticle (snapped if you ask). Pair it with a draggable reference line to keep the order on the chart.

Annotations

WhatOptions
Reference linesreferenceLines: a level (value), a line that moves with time (series), a value band (valueFrom, valueTo) or a time band (from, to). Labels, dashes, colours, fullWidth, excludeFromRange; a pill badge that pins to the edge with a chevron when the level is off screen; a separate valueBadge with the price; draggable with snap, bounds and onChange, onCommit, onDragIn, onDragOut; referenceLineGrouping to collapse or stack nearby tags; onReferenceLinePress
Markersmarkers pinned to a time (and a value, a series, or the line): built-in glyphs, an icon, an image or a filled pill, above or below the line; markerCluster fans or stacks co-located markers and collapses a burst into a count; onMarkerPress. tradeStream draws fills sized by trade.
Levels and sessionsthreshold colours the line above and below a level or a moving benchmark, with an optional profit and loss band and a labelled marker. segments label sessions and, while scrubbing, mute every session but the one under the crosshair. zones shade price bands.
Data gapslineGaps and candleGaps declare intervals with no trades, unavailable data or unknown data. The line breaks there, carries the last price across where that is true, and shades the band; nothing is invented and a real sample always wins.
Orderbookorderbook: bid and ask sizes drift up from their price behind the line and fade, brighter for larger orders.

Your own elements

Where the built-in drawing is not enough, hand the chart an element and it positions it every frame: renderTooltip, renderMarker, renderReferenceLine (and renderOffAxisReferenceLine for the off-screen state only), renderThresholdBadge, and render on topLabel or bottomLabel. Each function receives the live values (the price, its formatted text, whether it is in range, which edge it is pinned to) and is called again only when they change, so return the same element after updating it.

A custom tooltip
const tip = document.createElement('div');
tip.className = 'my-tooltip';

createLiveChart(el, {
  data, value,
  scrub: { tooltipPlacement: 'top' },
  renderTooltip: (ctx) => {
    tip.textContent = `${ctx.valueStr} · ${ctx.timeStr}`;
    return tip;
  },
});

renderOverlay(ctx) mounts one full-size element and hands it the price and time mapping: ctx.priceToY, ctx.yToPrice, ctx.timeToX, ctx.xToTime, and ctx.onFrame(cb), called after every frame with that frame's scale, so your own tags follow the axis as it rescales. In React, useChartFrame, usePriceY and useTimeX do the same from a component without re-rendering it. LiveChartTransition cross-fades between charts by key, for switching symbols or timeframes.

The credit

Every chart carries a small VerticalProp logo at the bottom-left of the plot, at half opacity, linking to verticalprop.com. Its accessible name is "Chart by VerticalProp".

It is part of the licence, as the full chart's mark is: there is no option that hides it, in the library or on the embed. It sits inside the plot at 10 by 12 pixels so it does not take space from your layout.

Saving a frame

chart.snapshot() returns the current frame as a PNG data URL, for a share image or a thumbnail. The window buttons, chips and your own elements are HTML over the canvas, so they are not in it. In React, a ref on the component reaches snapshot(), resetZoom(), scale() and the engine; engineRef still works:

A snapshot from React
const engine = useRef<LiveChartEngine | null>(null);

<LiveChart data={points} value={last} engineRef={engine} />;

const png = engine.current?.snapshot();

LiveChartEngine is exported from @warburgai/vp-charts/live; the React entry imports its types from there, so the class is declared once and a handle from either entry has the same type.

Verifying it

The live/ files are listed in the same release-manifest.json as the rest of the package and covered by the same signature, so the verification step you already run checks them too. There is nothing separate to pin.