Asynchronous Data allows charts to load data on demand, supporting progressive detail loading and server-side paging when used with Zoom or the Scrollbar.
Data Source Copy Link
Use the dataSource option to provide an asynchronous getData callback. The chart calls this function on initial load and then whenever the visible window changes, and displays a loading overlay until the returned promise resolves.
import React, { useState, useRef, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgCartesianChartOptions,
AgChartsInstance,
AnimationModule,
ContextMenuModule,
CrosshairModule,
DataSourceModule,
LegendModule,
LineSeriesModule,
ModuleRegistry,
NavigatorModule,
NumberAxisModule,
TimeAxisModule,
ZoomModule,
} from "ag-charts-enterprise";
import { Database } from "./data";
import { FakeServer } from "./fakeServer";
import clone from "clone";
ModuleRegistry.registerModules([
AnimationModule,
CrosshairModule,
DataSourceModule,
LegendModule,
LineSeriesModule,
NavigatorModule,
NumberAxisModule,
TimeAxisModule,
ZoomModule,
ContextMenuModule,
]);
const ChartExample = () => {
const chartRef = useRef<AgChartsInstance>(null);
const [options, setOptions] = useState<AgCartesianChartOptions>({
dataSource: {
getData: ({ windowStart, windowEnd, source }) => {
// Request the data from the server, this is an asynchronous call which may take up to 2500ms. In your
// application, replace this with a call to your server api.
// The navigator mini chartRef.current! requests a coarse, full-range overview; the main chartRef.current! requests the visible
// window, and the server returns higher-resolution data as the window narrows.
return source === "mini-chart"
? FakeServer.get({})
: FakeServer.get({ windowStart, windowEnd });
},
},
navigator: {
enabled: true,
miniChart: {},
},
zoom: {
enabled: true,
},
initialState: {
zoom: {
ratioX: { start: 0.7, end: 1 },
},
},
series: [
{
type: "line",
xKey: "time",
yKey: "price",
yName: "Price",
},
],
axes: {
y: {
type: "number",
min: 400,
max: 1600,
},
x: {
type: "time",
min: new Date("2019-01-01 00:00:00"),
max: new Date("2024-12-30 23:59:59"),
interval: {
minSpacing: 100,
maxSpacing: 200,
},
label: {
formatter: ({ value }) =>
Intl.DateTimeFormat("en-GB", {
day: "2-digit",
month: "short",
year: "2-digit",
}).format(new Date(value)),
},
},
},
});
// Refresh the underlying server data, then call updateDelta({}) to re-trigger getData for the
// current window. The chartRef.current! shows its loading overlay while the new data is fetched.
const reload = () => {
Database.refresh();
chartRef.current!.updateDelta({});
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
<button onClick={reload}>Reload Data</button>
</div>
</div>
<AgCharts ref={chartRef} options={options} />
</Fragment>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
import { createSeededRandom } from "./seededRandom";
/**
* This fake database generates and returns randomised data of objects with time, price and quantity. If you are a
* frontend developer you can safely ignore this part of the example.
*/
export const Database = {
get: () => (data ??= generate(seed)),
// Re-generate the dataset with a new seed so a reload returns a visibly different price series, mimicking
// fresh data arriving from a real server.
refresh: () => {
seed += 1;
data = generate(seed);
},
};
export const minute = 1000 * 60;
export const hour = minute * 60;
export const day = hour * 24;
export const week = day * 7;
export const month = day * 30;
export const dataStart = new Date("2019-01-01 00:00:00").getTime();
export const dataEnd = new Date("2024-12-30 23:59:59").getTime();
let data: Array<Datum> | undefined;
let seed = 1;
const center = 1000;
function generate(seed: number): Array<Datum> {
const random = createSeededRandom(seed);
const result: Array<Datum> = [];
for (let time = dataStart; time < dataEnd; time += hour) {
let price;
if (result.length === 0) {
price = center + random() * 100;
} else if (result.length < 5) {
price = result[result.length - 1].price + random() * 20 - 10;
} else {
const avg =
result.slice(result.length - 5).reduce((a, v) => a + v.price, 0) / 5;
// Mean-reverting random walk: a gentle pull back towards the centre keeps the series within the
// chart's fixed y-axis range for any seed, so each reload looks different without clipping.
price = avg + (random() * 50 - 25) + (center - avg) * 0.002;
}
result.push({ time, price });
}
return result;
}
export type Datum = { time: number; price: number };
import { Database, Datum, dataEnd, dataStart, day, hour, week } from "./data";
import { random } from "./seededRandom";
/**
* This fake server mimics how a real server api chart service may get and format data for charts. If you are a
* frontend developer you can safely ignore this part of the example.
*/
export const FakeServer = {
get: async function (params: {
windowStart?: Date | number | string;
windowEnd?: Date | number | string;
}) {
// Simulate a real server with a random 2000-2500ms delay
const delayTime = 2000 + Math.floor(random() * 500);
await delay(delayTime);
// Fetch the data from the fake database
const data = Database.get();
// Format the data ready for the chart
const formattedData = formatData(
data,
toTimestamp(params.windowStart) ?? dataStart,
toTimestamp(params.windowEnd) ?? dataEnd,
);
return formattedData;
},
};
function toTimestamp(
value: Date | number | string | undefined,
): number | undefined {
if (value === undefined) return undefined;
if (value instanceof Date) return value.getTime();
if (typeof value === "number") return value;
return new Date(value).getTime();
}
function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function formatData(data: Datum[], windowStart: number, windowEnd: number) {
const diff = windowEnd - windowStart;
let granularity = week * 4;
if (diff < week * 2) {
granularity = hour;
} else if (diff < week * 13) {
granularity = day;
} else if (diff < week * 52) {
granularity = week;
}
return data.filter(({ time }) => {
const isCoarse = (time - dataStart) % (week * 4) === 0;
const isFineWithinWindow =
(time - dataStart) % granularity === 0 &&
time >= windowStart &&
time <= windowEnd;
return isCoarse || isFineWithinWindow;
});
}
export function createSeededRandom(seed = 42): () => number {
let state = seed;
return () => {
state = (state * 16807) % 2147483647;
return (state - 1) / 2147483646;
};
}
export const random = createSeededRandom();
{
dataSource: {
getData: ({ windowStart, windowEnd }) => {
return FakeServer.get({ windowStart, windowEnd });
},
},
}In this example:
- The fake server returns coarse data covering the full date range alongside finer-grained data for the visible window.
- As the user zooms in,
getDatais re-invoked with updatedwindowStartandwindowEndvalues and the server returns higher-resolution data for that range.
The getData callback receives an AgDataSourceCallbackParams object with:
windowStart: the start of the visible window.windowEnd: the end of the visible window.source: what triggered the request, such as'mini-chart','user-interaction'or'chart-update'.context: the chart context object, if one has been provided.
The returned data must include the first and last data points so the chart can establish the axis domain, unless the axis has explicit min and max values.
When the Navigator Mini Chart is enabled, it issues its own getData call with source: 'mini-chart' to load a full-range overview, independent of the main chart's windowed fetches. This is only done on initial load or options update.
Triggering a Reload Copy Link
Calling updateDelta({}) with an empty object triggers getData to be called again, which is useful for refreshing data on demand. The Reload Data button in the example above does exactly this; the loading overlay is shown while the request is in progress, and the chart updates with the freshly fetched data once it resolves.
chart.updateDelta({}); Loading Overlay Copy Link
While getData is in progress, the chart displays a Loading Overlay automatically. The overlay can be customised through the overlays.loading option; see Overlays for text customisation and custom renderers.
Manual Control Copy Link
Set loading on the chart options to control the overlay independently of dataSource.
import React, { useState, useRef, Fragment } from "react";
import { createRoot } from "react-dom/client";
import { AgCharts } from "ag-charts-react";
import {
AgChartOptions,
AgChartsInstance,
AnimationModule,
ContextMenuModule,
CrosshairModule,
DataSourceModule,
LegendModule,
LineSeriesModule,
ModuleRegistry,
NumberAxisModule,
} from "ag-charts-enterprise";
import clone from "clone";
const datasets = [
[120, 145, 98, 160, 135],
[200, 175, 220, 190, 210],
[80, 95, 70, 110, 85],
[155, 130, 180, 145, 165],
];
let loadIndex = 0;
function getData() {
const values = datasets[loadIndex % datasets.length];
loadIndex++;
return values.map((spending, i) => ({ year: 2020 + i, spending }));
}
ModuleRegistry.registerModules([
AnimationModule,
CrosshairModule,
DataSourceModule,
LegendModule,
LineSeriesModule,
NumberAxisModule,
ContextMenuModule,
]);
const ChartExample = () => {
const chartRef = useRef<AgChartsInstance>(null);
const [options, setOptions] = useState<AgChartOptions>({
dataSource: {
getData: () =>
new Promise((resolve) => setTimeout(() => resolve(getData()), 2000)),
},
series: [
{
type: "line",
xKey: "year",
yKey: "spending",
},
],
axes: {
x: { type: "number", title: { text: "Year" } },
y: { type: "number", title: { text: "Spending" } },
},
});
const setLoading = (value: boolean | undefined) => {
chartRef.current!.updateDelta({ loading: value });
};
const reload = () => {
chartRef.current!.updateDelta({});
};
return (
<Fragment>
<div className="example-controls">
<div className="controls-row">
Loading:
<button onClick={() => setLoading(true)}>
<code>true</code>
</button>
<button onClick={() => setLoading(false)}>
<code>false</code>
</button>
<button onClick={() => setLoading(undefined)}>
<code>undefined</code>
</button>
</div>
<div className="controls-row">
<button onClick={reload}>Reload</button>
</div>
</div>
<AgCharts ref={chartRef} options={options} />
</Fragment>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(<ChartExample />);
Use the buttons to override the loading state.
{
loading: true,
}true- force the overlay on.false- force the overlay off.undefined- automatic, shown whilegetDatais pending and hidden when it resolves.