Datafeed & realtime
Connect any backend - history, infinite scroll, realtime updates, aggregation, CSV and bar replay.
@reincharts/datafeed (also exported by reincharts) connects a chart to any backend. Write one small
adapter for your backend and useDatafeed gives the chart sorted bars, realtime updates and infinite scroll back in
time. This chart streams from the built-in demo feed:
"use client";
import { ChartCanvas, Pane } from "@reincharts/core";
import { CandlestickSeries } from "@reincharts/series";
import { Grid, XAxis, YAxis } from "@reincharts/axes";
import { Crosshair, LastValueLabel } from "@reincharts/coordinates";
import { Legend } from "@reincharts/tooltip";
import { createRandomDatafeed, useDatafeed } from "@reincharts/datafeed";
import { useState } from "react";
import { useDocsChartTheme } from "@/lib/use-docs-theme";
export default function RealtimeDatafeed() {
const theme = useDocsChartTheme();
// a seeded random walk with a realtime ticker; 60x speed = one minute of market time per second
const [feed] = useState(() =>
createRandomDatafeed({ symbols: ["DEMO"], seed: 7, speed: 60, realtimeIntervalMs: 500 }),
);
const { data, loadMore } = useDatafeed(feed, { symbol: "DEMO", resolution: "1" });
return (
// scrolling to the first bar loads the previous page of history
<ChartCanvas data={data} theme={theme} barSpacing={8} onReachStart={loadMore}>
<Pane>
<Grid />
<CandlestickSeries name="DEMO" />
<LastValueLabel line />
<YAxis />
<Legend title="DEMO · 1m · live" />
</Pane>
<XAxis />
<Crosshair />
</ChartCanvas>
);
}The Datafeed interface
Only getBars is required:
interface Datafeed {
/** Bars with time < `to`: the `countBack` most recent ones (or those >= `from`). */
getBars(request: GetBarsRequest): Promise<{ bars: Bar[]; noData?: boolean }>;
/** Realtime: same `time` as the last bar = update it, newer = append. Returns an unsubscribe function. */
subscribeBars?(symbol: string, resolution: Resolution, onBar: (bar: Bar) => void): () => void;
searchSymbols?(query: string): Promise<SymbolInfo[]>;
resolveSymbol?(symbol: string): Promise<SymbolInfo>;
}
interface Bar {
time: number; // bar open time, ms epoch (UTC)
open: number;
high: number;
low: number;
close: number;
volume?: number;
}Resolutions use a compact notation: "1S", "5S"... seconds, "1", "5", "15", "60", "240"... minutes,
"1D", "1W", "1M", "3M", "12M". RESOLUTIONS lists the common ones; normalizeResolution also accepts
"1h", "1d", "1y"...
Writing an adapter
A REST endpoint for history and a WebSocket for realtime:
import type { Datafeed } from "@reincharts/datafeed";
export const feed: Datafeed = {
async getBars({ symbol, resolution, to, countBack, signal }) {
const res = await fetch(`/api/bars?symbol=${symbol}&resolution=${resolution}&to=${to}&count=${countBack}`, {
signal,
});
const bars = await res.json(); // [{ time, open, high, low, close, volume }]
return { bars, noData: bars.length === 0 };
},
subscribeBars(symbol, resolution, onBar) {
const ws = new WebSocket(`wss://example.com/stream/${symbol}/${resolution}`);
ws.onmessage = (e) => onBar(JSON.parse(e.data));
return () => ws.close();
},
};Pass signal to fetch: requests that became stale (the symbol changed, the chart unmounted) are aborted.
useDatafeed
const { data, loadMore, loading, hasMore, symbolInfo } = useDatafeed(feed, { symbol, resolution });
<ChartCanvas data={data} onReachStart={loadMore} resetKey={`${symbol}:${resolution}`}>
{/* ... */}
</ChartCanvas>;datais sorted and de-duplicated, with adateon every bar, ready for<ChartCanvas>.- Pagination: wire
loadMoretoonReachStart. When the user scrolls to the first bar, the previous page (pageSize, default 500 bars) is prepended and the view stays where it was.hasMoreturns false once the feed answersnoData. - Realtime: when the feed implements
subscribeBars, incoming bars update the last bar or append a new one, and the chart follows the newest bar while it is visible. - Changing
symbolorresolutionreloads; pass the same key asresetKeyso the view resets too. loading/loadingMoreanderrordrive loading and error states,reset()reloads from scratch,realtime: falseskips the subscription andenabled: falsepauses loading (until a symbol is picked, say).
Demo and in-memory feeds
createRandomDatafeed({ symbols, seed, startPrice, volatility, speed, realtimeIntervalMs })is a deterministic random walk with a realtime ticker. It runs in the browser, which makes it great for demos, tests and prototyping.createMemoryDatafeed({ AAPL: bars }, { resolution: "1D" })serves arrays you already have, aggregates higher resolutions on demand and haspushBar()to simulate realtime updates.
See the in-memory datafeed example →
Aggregation and ticks
These helpers are pure functions that work without a datafeed:
| Function | Purpose |
|---|---|
aggregateBars(bars, "60") | Higher timeframe bars (1m → 1h, 1D → 1W / 1M), time zone aware. |
ticksToBars(ticks, "1S") | Seconds or minute bars from trades. |
applyTick(data, tick, res) | Merge one trade into the last bar (realtime from a trade stream). |
mergeBar(data, bar) | Realtime merge: update the last bar or append. |
mergeBars(older, newer) | Merge a history page into loaded data. |
alignSeries(base, other, "AAPL") | Align a compare symbol by time: plot d.AAPL?.close on a percent scale. |
tickToBar(bar, tick, res) | Update (or start) a single bar from a trade. |
toChartBar, sortBars | Add the date field; sort by time and drop duplicates. |
sliceBars(bars, request) | Answer a getBars request from an array (for your own in-memory feed). |
alignTime, nextBarTime, timeZoneOffset | Bar boundaries for a resolution and time zone. |
parseResolution, isResolution, normalizeResolution, resolutionToMs, formatResolution, compareResolutions | Resolution helpers. |
SECOND, MINUTE, HOUR, DAY, WEEK | Durations in milliseconds. |
CSV and TSV
parseCsv(text) reads CSV / TSV exports with a header row into bars sorted by time. The time column, delimiter and
OHLCV columns are detected; rename columns with columns, e.g. { close: "Adj Close" }. parseTimeValue is the
date parser it uses (ISO dates, epoch seconds or milliseconds; dates without a zone are read as UTC).
Bar replay
useBarReplay(data, { startIndex, speed, autoPlay }) reveals bars one by one. Pass its data to the chart and drive
it with play, pause, toggle, step(count), stepBack(count), setSpeed (bars per second), setStart(index)
and reset; index, playing and atEnd describe the current position. A negative startIndex counts from the
end.
Without a datafeed
The core handles live data on its own too: pass a new data array with the last bar replaced or a bar appended, use
onReachStart to prepend history, and change resetKey on symbol changes. See
Data & x modes.