AG Charts is optimised to handle large datasets with over 1 million points, while maintaining full, smooth interactivity. No additional configuration or modules required - it just works out of the box.
import React, { useState, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgCartesianAxisOptions,
AgCartesianChartOptions,
AgCartesianSeriesOptions,
AnimationModule,
AreaSeriesModule,
BarSeriesModule,
BubbleSeriesModule,
CandlestickSeriesModule,
CategoryAxisModule,
ContextMenuModule,
CrosshairModule,
HistogramSeriesModule,
LegendModule,
LineSeriesModule,
ModuleRegistry,
NavigatorModule,
NumberAxisModule,
OhlcSeriesModule,
OrdinalTimeAxisModule,
RangeAreaSeriesModule,
RangeBarSeriesModule,
ScatterSeriesModule,
TimeAxisModule,
ZoomModule,
} from "ag-charts-enterprise";
import { getData } from "./data";
import clone from "clone";
let dataLabel = "1K";
let seriesType = "Line";
let datapoints = 1e3;
const timeAxes: Record<string, AgCartesianAxisOptions> = {
x: { type: "ordinal-time", parentLevel: { enabled: true } },
};
const numberAxes: Record<string, AgCartesianAxisOptions> = {
x: { type: "number" },
};
const baseData = getData(1e6);
ModuleRegistry.registerModules([
AnimationModule,
AreaSeriesModule,
BarSeriesModule,
BubbleSeriesModule,
CandlestickSeriesModule,
CrosshairModule,
HistogramSeriesModule,
LegendModule,
LineSeriesModule,
NavigatorModule,
NumberAxisModule,
OhlcSeriesModule,
OrdinalTimeAxisModule,
RangeAreaSeriesModule,
RangeBarSeriesModule,
ScatterSeriesModule,
TimeAxisModule,
ZoomModule,
CategoryAxisModule,
ContextMenuModule,
]);
const ChartExample = () => {
const [options, setOptions] = useState<AgCartesianChartOptions>({
data: baseData.slice(-datapoints),
title: { text: `${seriesType} with ${dataLabel} datapoints` },
animation: { enabled: false },
zoom: {
enabled: true,
axes: "x",
anchorPointX: "pointer",
anchorPointY: "pointer",
autoScaling: {
enabled: true,
},
},
navigator: {
enabled: true,
miniChart: {
enabled: true,
},
},
series: [
{
type: "line",
xKey: "timestamp",
yKey: "close",
},
],
axes: timeAxes,
});
const setSeries = (type: string, label: string) => {
const nextOptions = { ...options };
seriesType = label;
let series: AgCartesianSeriesOptions[] = [];
switch (type) {
case "bar":
case "area":
case "line":
nextOptions.series = [
{
type,
xKey: "timestamp",
yKey: "high",
},
];
break;
case "stacked-bar":
case "stacked-area":
const stackedType = type === "stacked-bar" ? "bar" : "area";
nextOptions.series = [
{ type: stackedType, xKey: "timestamp", yKey: "open", stacked: true },
{
type: stackedType,
xKey: "timestamp",
yKey: "close",
stacked: true,
},
];
break;
case "range-area":
case "range-bar":
nextOptions.series = [
{
type,
xKey: "timestamp",
yLowKey: "low",
yHighKey: "high",
},
];
break;
case "candlestick":
case "ohlc":
nextOptions.series = [
{
type,
xKey: "timestamp",
lowKey: "low",
highKey: "high",
openKey: "open",
closeKey: "close",
},
];
break;
case "scatter":
nextOptions.series = [
{
type,
xKey: "x",
yKey: "y",
fillOpacity: 0.2,
strokeOpacity: 0.2,
},
];
break;
case "bubble":
nextOptions.series = [
{
type,
xKey: "x",
yKey: "y",
sizeKey: "size",
fillOpacity: 0.2,
strokeOpacity: 0.2,
},
];
break;
case "histogram":
nextOptions.series = [
{
type,
xKey: "close",
},
];
break;
default:
return;
}
const newDatapoints = (nextOptions.series?.[0] as any)?.stacked
? datapoints / 2
: datapoints;
if (nextOptions.data?.length !== newDatapoints) {
nextOptions.data = baseData.slice(-newDatapoints);
}
if (type == "bubble" || type == "scatter" || type == "histogram") {
nextOptions.zoom!.axes = "xy";
nextOptions.zoom!.autoScaling!.enabled = false;
nextOptions.navigator!.enabled = false;
nextOptions.axes = numberAxes;
} else {
nextOptions.zoom!.axes = "xy";
nextOptions.zoom!.autoScaling!.enabled = true;
nextOptions.navigator!.enabled = true;
nextOptions.axes = timeAxes;
}
nextOptions.title!.text = `${seriesType} with ${dataLabel} datapoints`;
setOptions(nextOptions);
};
const setData = (points: number, label: string) => {
const nextOptions = { ...options };
const newDatapoints = (nextOptions.series?.[0] as any)?.stacked
? points / 2
: points;
if (nextOptions.data?.length !== newDatapoints) {
nextOptions.data = baseData.slice(-newDatapoints);
}
dataLabel = label;
datapoints = points;
nextOptions.title!.text = `${seriesType} with ${dataLabel} datapoints`;
setOptions(nextOptions);
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<span>Series Type:</span>
<select
onChange={(event) =>
setSeries(
event.target.value,
event.target.selectedOptions[0].text,
)
}
>
<option value="line">Line</option>
<option value="area">Area</option>
<option value="bar">Bar</option>
<hr />
<option value="stacked-bar">Stacked Bar</option>
<option value="stacked-area">Stacked Area</option>
<hr />
<option value="range-area">Range Area</option>
<option value="range-bar">Range Bar</option>
<hr />
<option value="candlestick">Candlestick</option>
<option value="ohlc">OHLC</option>
<hr />
<option value="scatter">Scatter</option>
<option value="bubble">Bubble</option>
<hr />
<option value="histogram">Histogram</option>
</select>
<span className="gap-left">Data Size:</span>
<button onClick={() => setData(1e3, "1K")}>1K</button>
<button onClick={() => setData(1e4, "10K")}>10K</button>
<button onClick={() => setData(1e5, "100K")}>100K</button>
<button onClick={() => setData(5e5, "500K")}>500K</button>
<button onClick={() => setData(1e6, "1M")}>1M</button>
</div>
</div>
<AgCharts options={options} />
</Fragment>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
const startPrice = 100;
const maxDailyPriceChange = 5;
const maxRangeDelta = 1;
function sfc32(a: number, b: number, c: number, d: number) {
return function () {
a >>>= 0;
b >>>= 0;
c >>>= 0;
d >>>= 0;
let t = (a + b) | 0;
a = b ^ (b >>> 9);
b = (c + (c << 3)) | 0;
c = (c << 21) | (c >>> 11);
d = (d + 1) | 0;
t = (t + d) | 0;
c = (c + t) | 0;
return (t >>> 0) / 4294967296;
};
}
function seedRandom(seed = 1337): () => number {
const realSeed = seed ^ 0xdeadbeef; // 32-bit seed with optional XOR value
// Pad seed with Phi, Pi and E.
// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number
return sfc32(0x9e3779b9, 0x243f6a88, 0xb7e15162, realSeed);
}
const referenceDate = new Date(2024, 0, 1, 0).getTime();
export function getData(hours: number) {
let currentPrice = startPrice;
const random = seedRandom();
const period = 60 * 60 * 1000;
const startDate = new Date(2024, 0, 1, -hours);
return Array.from({ length: hours }, () => {
// Note time is reversed
const close = currentPrice;
const open = close + (random() * 2 - 1) * maxDailyPriceChange;
currentPrice = open;
const high = Math.max(open, close) + random() * maxRangeDelta;
const low = Math.min(open, close) - random() * maxRangeDelta;
startDate.setHours(startDate.getHours() + 1);
const timestamp = startDate.getTime();
let x = random();
let y = random();
x *= random();
y *= random();
const size = random();
return { timestamp, open, close, high, low, x, y, size };
});
}
In the above example:
- Use the controls to select different series types and data sizes.
- Use the mouse, Navigator or zoom controls to zoom, scroll and pan the data.
Performance may vary based on your specific use case, environment and hardware.
How it works Copy Link
Behind the scenes, AG Charts applies advanced data aggregation techniques, such as the M4 algorithm, to ensure accurate representation across scales. As you zoom and pan, the chart dynamically adapts to the visible range, preserving both performance and clarity.