Working with data in React Chart
17 Aug 202624 minutes to read
The React Charts component supports multiple data binding approaches to suit different application scenarios. Data can be bound from local JSON arrays, dynamically loaded on demand with lazy loading, fetched from remote services using various adaptor patterns, or handled when the dataset is missing or contains empty points. This guide covers all available data binding methods and additional scenarios, helping you choose the right approach for your use case based on data size, performance requirements, and backend architecture.
Choosing a data binding approach
| Method | Best for | Advantages | Considerations |
|---|---|---|---|
| Local data | Small to medium datasets | Simple setup, no network latency, instant rendering | All data must be in memory |
| Common datasource | Multiple series sharing data | Reduces redundancy, single update point | Limited to data common across series |
| Lazy loading | Large datasets with scrolling | Loads only visible data, better performance | Requires server-side pagination |
| Remote data (OData / Web API) | Backend-driven data | Scalable, centralized data management, real-time updates | Network latency, requires service setup |
| Custom adaptor | Non-standard backends | Full control over request/response handling | More code to maintain |
| Offline mode | Data caching with client-side actions | Eliminates repeated requests, faster interactions | Initial load time, memory constraints |
| Empty points | Datasets with missing values | Gracefully renders gaps instead of errors | Requires configuration of emptyPointSettings
|
| No data template | Empty or loading states | Displays a custom message or layout | Replaced automatically when data arrives |
Local data
Bind simple JSON data to the chart using the dataSource property in the series configuration. The dataSource accepts an array of objects, an array of arrays, or the result of a JSON parse. Map fields on each data object to the xName and yName properties to specify which data fields represent the x and y axis values.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Category, ChartComponent, ColumnSeries, Inject, Legend, LineSeries, SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { data } from './datasource';
function App() {
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis}>
<Inject services={[ColumnSeries, Legend, LineSeries, Category]}/>
<SeriesCollectionDirective>
<SeriesDirective dataSource={data} xName='month' type='Column' yName='sales' name='Sales'/>
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { AxisModel,Category,ChartComponent, ColumnSeries, Inject, Legend, LineSeries,
SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { data } from './datasource';
function App() {
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis}>
<Inject services={[ColumnSeries, Legend, LineSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={data} xName='month' type='Column' yName='sales' name='Sales' />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));export let data = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: 28 },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: 32 },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];export let data: Object[] = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: 28 },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: 32 },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];Lazy loading
Lazy loading enables on-demand data retrieval, loading only the data required for the currently visible range of the chart. The chart fires the scrollEnd event at the end of every scroll interaction and the scrollStart event at the beginning. To enable lazy loading, wire up both events and, in the scrollEnd handler, read the currentRange minimum and maximum values, request the corresponding data from the server, and append the result to the existing data source. This approach is ideal for large datasets that would be expensive to load entirely into memory.
How lazy loading works:
- Scroll the chart to view a different data range.
- The
scrollEndevent fires with the current axis range information. - Fetch the corresponding data from the server using the minimum and maximum values from the axis range.
- Update the React state with the combined dataset (existing data + newly fetched data).
- The chart re-renders automatically with the updated data.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { ChartComponent, SeriesCollectionDirective, SeriesDirective, Inject, ScrollBar, Zoom, LineSeries, Tooltip, DateTime, Crosshair } from '@syncfusion/ej2-react-charts';
import { Internationalization } from '@syncfusion/ej2-base';
function App() {
let chart;
let intl = new Internationalization();
function GetDateTimeData(start, end) {
let series1 = [];
let date;
let value = 30;
let option = {
skeleton: 'full',
type: 'dateTime'
};
let dateParser = intl.getDateParser(option);
let dateFormatter = intl.getDateFormat(option);
for (let i = 0; start <= end; i++) {
date = Date.parse(dateParser(dateFormatter(start)));
if (Math.random() > .5) {
value += (Math.random() * 10 - 5);
}
else {
value -= (Math.random() * 10 - 5);
}
if (value < 0) {
value = getRandomInt(20, 40);
}
let point1 = { x: new Date(date), y: Math.round(value) };
new Date(start.setDate(start.getDate() + 1));
series1.push(point1);
}
return series1;
}
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
return <ChartComponent id='charts' ref={chart => chart = chart} primaryXAxis={{
title: 'Day',
valueType: 'DateTime',
edgeLabelPlacement: 'Shift',
skeleton: 'yMMM',
skeletonType: 'Date',
scrollbarSettings: {
range: {
minimum: new Date(2009, 0, 1),
maximum: new Date(2014, 0, 1)
},
enable: true,
}
}} primaryYAxis={{
title: 'Server Load',
labelFormat: '{value}MB'
}} crosshair={{ enable: true, lineType: 'Vertical' }} chartArea={{ border: { width: 0 } }} tooltip={{ enable: true }} legendSettings={{ visible: true }}
// scrollStart={this.scrollStart.bind(this)}
// scrollEnd={this.scrollEnd.bind(this)}
title='Network Load' height='450' width='100%'>
<Inject services={[LineSeries, DateTime, Tooltip, ScrollBar, Zoom, Crosshair]}/>
<SeriesCollectionDirective>
<SeriesDirective dataSource={GetDateTimeData(new Date(2009, 0, 1), new Date(2009, 8, 1))} xName='x' yName='y' type='Line' animation={{ enable: false }}>
</SeriesDirective>
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { ChartComponent, SeriesCollectionDirective, SeriesDirective, Inject,
ChartTheme, ScrollBar, Zoom, IScrollEventArgs, LineSeries, Tooltip,
DateTime, ILoadedEventArgs, Chart, Crosshair, ColumnSeries } from '@syncfusion/ej2-react-charts';
import { Internationalization } from '@syncfusion/ej2-base';
function App() {
let chart: ChartComponent;
let intl: Internationalization = new Internationalization();
function GetDateTimeData(start: Date, end: Date): { x: Date, y: number }[] {
let series1: { x: Date, y: number }[] = [];
let date: number;
let value: number = 30;
let option: DateFormatOptions = {
skeleton: 'full',
type: 'dateTime'
};
let dateParser: Function = intl.getDateParser(option);
let dateFormatter: Function = intl.getDateFormat(option);
for (let i: number = 0; start <= end; i++) {
date = Date.parse(dateParser(dateFormatter(start)));
if (Math.random() > .5) {
value += (Math.random() * 10 - 5);
} else {
value -= (Math.random() * 10 - 5);
}
if (value < 0) {
value = getRandomInt(20, 40);
}
let point1: { x: Date, y: number } = { x: new Date(date), y: Math.round(value) };
new Date(start.setDate(start.getDate() + 1));
series1.push(point1);
}
return series1;
}
function getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
return <ChartComponent id='charts'
ref={chart => chart = chart}
primaryXAxis={{
title: 'Day',
valueType: 'DateTime',
edgeLabelPlacement: 'Shift',
skeleton: 'yMMM',
skeletonType: 'Date',
scrollbarSettings: {
range: {
minimum: new Date(2009, 0, 1),
maximum: new Date(2014, 0, 1)
},
enable: true,
}
}}
primaryYAxis={{
title: 'Server Load',
labelFormat: '{value}MB'
}}
crosshair={{ enable: true, lineType: 'Vertical' }}
chartArea={{ border: { width: 0 } }}
tooltip={{ enable: true }}
legendSettings={{ visible: true }}
// scrollStart={this.scrollStart.bind(this)}
// scrollEnd={this.scrollEnd.bind(this)}
title='Network Load' height='450' width='100%' >
<Inject services={[LineSeries, DateTime, Tooltip, ScrollBar, Zoom, Crosshair]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={GetDateTimeData(new Date(2009, 0, 1), new Date(2009, 8, 1))} xName='x' yName='y'
type='Line' animation={{ enable: false }}>
</SeriesDirective>
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));Common datasource
Bind the same JSON data to all series by setting the dataSource property at the chart level instead of the series level. This approach is useful when multiple series share the same dataset and you want to update data from a single source. Each series independently maps its own xName and yName properties to the common data fields.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Category, ChartComponent, ColumnSeries, Inject, Legend, LineSeries, SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { chartData } from './datasource';
function App() {
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} dataSource={chartData}>
<Inject services={[ColumnSeries, Legend, LineSeries, Category]}/>
<SeriesCollectionDirective>
<SeriesDirective xName='month' type='Column' yName='sales'/>
<SeriesDirective xName='month' type='Column' yName='sales1'/>
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { AxisModel,Category,ChartComponent, ColumnSeries, Inject, Legend, LineSeries,
SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { chartData } from './datasource';
function App() {
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} dataSource={chartData}>
<Inject services={[ColumnSeries, Legend, LineSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective xName='month' type='Column' yName='sales' />
<SeriesDirective xName='month' type='Column' yName='sales1' />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));export let chartData = [
{ month: 'Jan', sales: 35, sales1: 28 }, { month: 'Feb', sales: 28, sales1: 35 },
{ month: 'Mar', sales: 34, sales1: 32 }, { month: 'Apr', sales: 32, sales1: 34 },
{ month: 'May', sales: 40, sales1: 32 }, { month: 'Jun', sales: 32, sales1: 40 },
{ month: 'Jul', sales: 35, sales1: 55 }, { month: 'Aug', sales: 55, sales1: 35 },
{ month: 'Sep', sales: 38, sales1: 30 }, { month: 'Oct', sales: 30, sales1: 38 },
{ month: 'Nov', sales: 25, sales1: 32 }, { month: 'Dec', sales: 32, sales1: 25 }
];export let chartData: Object[] = [
{ month: 'Jan', sales: 35, sales1: 28 }, { month: 'Feb', sales: 28, sales1: 35 },
{ month: 'Mar', sales: 34, sales1: 32 }, { month: 'Apr', sales: 32, sales1: 34 },
{ month: 'May', sales: 40, sales1: 32 }, { month: 'Jun', sales: 32, sales1: 40 },
{ month: 'Jul', sales: 35, sales1: 55 }, { month: 'Aug', sales: 55, sales1: 35 },
{ month: 'Sep', sales: 38, sales1: 30 }, { month: 'Oct', sales: 30, sales1: 38 },
{ month: 'Nov', sales: 25, sales1: 32 }, { month: 'Dec', sales: 32, sales1: 25 }
];Remote data
Bind remote data from a web service by using the DataManager class. The DataManager simplifies communication with REST APIs, OData services, and custom web endpoints. It requires minimal configuration—typically just the service URL and an appropriate adaptor—and then handles all request and response processing. Import the class from @syncfusion/ej2-data, assign a DataManager instance to the dataSource property in the series, and map the response fields to xName and yName. Use the optional query property of the DataManager to filter, sort, or paginate data on the server.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query } from '@syncfusion/ej2-data';
import { Category, ChartComponent, ColumnSeries, Inject, Legend, LineSeries, SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders'
});
const query = new Query().take(5).where('Estimate', 'lessThan', 3, false);
const primaryxAxis = { valueType: 'Category' };
const primaryyAxis = { title: 'Freight rate in U.S. dollars' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} primaryYAxis={primaryyAxis} title="Container freight rate">
<Inject services={[ColumnSeries, Legend, Category, LineSeries]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} xName='CustomerID' type='Column' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager,Query} from '@syncfusion/ej2-data'
import { AxisModel,Category,ChartComponent, ColumnSeries, Inject, Legend, LineSeries, SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders'
});
const query = new Query().take(5).where('Estimate', 'lessThan', 3, false);
const primaryxAxis: AxisModel = { valueType: 'Category' };
const primaryyAxis: AxisModel = { title: 'Freight rate in U.S. dollars' };
return <ChartComponent id='charts'
primaryXAxis={primaryxAxis}
primaryYAxis={primaryyAxis}
title="Container freight rate">
<Inject services={[ColumnSeries, Legend, Category, LineSeries]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} xName='CustomerID' type='Column' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));Binding data using ODataAdaptor
OData is a standardized protocol for creating and consuming data via REST APIs. Use the ODataAdaptor with DataManager to retrieve data from OData v3 services. The adaptor automatically constructs the correct query syntax and handles all standard OData conventions. Construct it as new DataManager({ url: 'https://service/odata/', adaptor: new ODataAdaptor() }).
Example use case: Querying a product sales service that implements OData v3 filtering, sorting, and pagination.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data';
import { ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.odata.org/V3/Northwind/Northwind.svc/Orders/',
adaptor: new ODataAdaptor(),
crossDomain: true
});
const query = new Query();
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} xName='CustomerID' type='Column' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data'
import { AxisModel, ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.odata.org/V3/Northwind/Northwind.svc/Orders/',
adaptor: new ODataAdaptor(),
crossDomain: true
});
const query = new Query();
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts'
primaryXAxis={primaryxAxis}
title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));Binding data using ODataV4Adaptor
ODataV4 is an improved and more standardised version of the OData protocol. It provides enhanced query capabilities and improved JSON support over OData v3. Use the ODataV4Adaptor to consume ODataV4 services. For more information on ODataV4 specifications, refer to the OData v4 documentation.
When to use ODataV4Adaptor: If your backend service implements OData v4, prefer this adaptor over the older ODataAdaptor for better compliance and features.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataV4Adaptor } from '@syncfusion/ej2-data';
import { ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders',
adaptor: new ODataV4Adaptor()
});
const query = new Query();
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataV4Adaptor } from '@syncfusion/ej2-data'
import { AxisModel, ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.odata.org/V4/Northwind/Northwind.svc/Orders',
adaptor: new ODataV4Adaptor()
});
const query = new Query();
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts'
primaryXAxis={primaryxAxis}
title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));Web API adaptor
Use the WebApiAdaptor to consume custom REST APIs that follow a standard response format. This adaptor is ideal for backends that do not implement OData, but provide REST endpoints returning JSON data.
Expected response format
The Web API must return a JSON object with two properties:
-
Items- Array of data objects for the chart. -
Count- Total number of records (useful for pagination).
import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, WebApiAdaptor } from '@syncfusion/ej2-data';
import { ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders',
adaptor: new WebApiAdaptor()
});
const query = new Query();
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, WebApiAdaptor } from '@syncfusion/ej2-data'
import { AxisModel, ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders',
adaptor: new WebApiAdaptor()
});
const query = new Query();
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts'
primaryXAxis={primaryxAxis}
title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));Example response:
The Web API should structure its response as shown below. The Items array contains the actual data records, and Count indicates the total available records (supporting server-side pagination).
{
"Items": [{..}, {..}, {..}, ...],
"Count": 830
}
Custom adaptor
Create a custom adaptor by extending one of the built-in adaptors (typically ODataAdaptor) to add custom logic for request and response handling. Override the processResponse(data, ds, query, xhr) method to transform or enrich the response data. The method receives the raw server response and must return the processed data array. Common use cases include adding a serialNumber field, reformatting dates, or adding computed fields before the chart renders the data.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data';
import { ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
import { SerialNoAdaptor } from './serialNoAdaptor';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders',
adaptor: new SerialNoAdaptor()
});
const query = new Query();
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Sno' query={query} />
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data'
import { AxisModel, ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
import { SerialNoAdaptor } from './serialNoAdaptor';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders',
adaptor: new SerialNoAdaptor()
});
const query = new Query();
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts'
primaryXAxis={primaryxAxis}
title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Sno' query={query} />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import { setValue } from '@syncfusion/ej2-base';
import { ODataAdaptor } from '@syncfusion/ej2-data';
export class SerialNoAdaptor extends ODataAdaptor {
processResponse() {
let i = 0;
// calling base class processResponse function
const original = super.processResponse.apply(this, arguments);
if (!(original instanceof Array)) {
// adding serial number
original.result.forEach((item) => setValue('Sno', ++i, item));
}
return original;
}
}import { setValue } from '@syncfusion/ej2-base';
import { ODataAdaptor } from '@syncfusion/ej2-data';
export class SerialNoAdaptor extends ODataAdaptor {
public processResponse() {
let i: number = 0;
// calling base class processResponse function
const original: any = super.processResponse.apply(this, arguments);
if (!(original instanceof Array)) {
// adding serial number
original.result.forEach((item: object) => setValue('Sno', ++i, item));
}
return original;
}
}Offline mode
When using remote data binding, all filtering, sorting, and pagination normally happen on the server. To improve responsiveness and reduce server load, enable offline mode: the chart loads all data once during initialization, then handles all interactions client-side. Set the offline property of DataManager to true to activate this behavior.
Use offline mode when:
- Your dataset is relatively small (fits comfortably in browser memory).
- You want instant filtering and sorting without server round-trips.
- Network latency is a significant usability concern.
- You prefer to minimize server requests during user interactions.
Caution: Offline mode loads the entire dataset at once, which may impact initial load time and memory usage for large datasets.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data';
import { ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders',
adaptor: new ODataAdaptor(),
offline: true
});
const query = new Query();
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis} title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { DataManager, Query, ODataAdaptor } from '@syncfusion/ej2-data'
import { AxisModel, ChartComponent, Inject, SeriesCollectionDirective, SeriesDirective, Category, ColumnSeries } from '@syncfusion/ej2-react-charts';
function App() {
const dataManager = new DataManager({
url: 'https://services.syncfusion.com/react/production/api/orders',
adaptor: new ODataAdaptor(),
offline: true
});
const query = new Query();
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts'
primaryXAxis={primaryxAxis}
title="Order Details">
<Inject services={[ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={dataManager} type='Column' xName='CustomerID' yName='Freight' query={query} />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));Empty points
Data points with null or undefined values are interpreted as empty points. Empty data points are skipped and not rendered in the chart. Customize empty points with the emptyPointSettings property in the series configuration. By default, empty points create a gap in the series line or bar.
Default behavior: Empty points use the Gap mode, which leaves a blank space in the chart visualization.
Available modes
The mode property of emptyPointSettings accepts the following values:
-
Gap- Leaves a blank space in the chart. -
Zero- Treats the empty point as zero. -
Average- Replaces the empty point with the average of neighboring points. -
Drop- Drops the data point from the chart entirely.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Category, ChartComponent, ColumnSeries, Inject, LineSeries, SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { data } from './datasource';
function App() {
const primaryxAxis = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis}>
<Inject services={[ColumnSeries, LineSeries, ColumnSeries, Category]}/>
<SeriesCollectionDirective>
<SeriesDirective dataSource={data} xName='month' type='Column' yName='sales' name='Sales'/>
<SeriesDirective dataSource={data} xName='month' type='Line' yName='sales' name='Sales'/>
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { AxisModel,Category,ChartComponent, ColumnSeries, Inject, LineSeries,
SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { data } from './datasource';
function App() {
const primaryxAxis: AxisModel = { valueType: 'Category' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis}>
<Inject services={[ColumnSeries, LineSeries, ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={data} xName='month' type='Column' yName='sales' name='Sales' />
<SeriesDirective dataSource={data} xName='month' type='Line' yName='sales' name='Sales' />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));export let data = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: undefined },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];export let data: Object[] = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: undefined },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];Empty point color
Assign a specific color to empty points by setting the fill property in the emptyPointSettings object. This allows you to visually distinguish empty data points from regular data in your chart.
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Category, ChartComponent, ColumnSeries, Inject, LineSeries, SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { data } from './datasource';
function App() {
const primaryxAxis = { valueType: 'Category' };
const emptyPointSettings1 = { mode: 'Average', fill: 'pink' };
const emptyPointSettings2 = { mode: 'Zero', fill: 'green' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis}>
<Inject services={[ColumnSeries, LineSeries, ColumnSeries, Category]}/>
<SeriesCollectionDirective>
<SeriesDirective dataSource={data} xName='month' type='Column' yName='sales' name='Sales' emptyPointSettings={emptyPointSettings1}/>
<SeriesDirective dataSource={data} xName='month' type='Line' yName='sales' name='Sales' emptyPointSettings={emptyPointSettings2}/>
</SeriesCollectionDirective>
</ChartComponent>;
}
;
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import * as ReactDOM from "react-dom";
import { AxisModel,Category,ChartComponent, ColumnSeries, EmptyPointSettingsModel,
Inject, LineSeries, SeriesCollectionDirective, SeriesDirective } from '@syncfusion/ej2-react-charts';
import { data } from './datasource';
function App() {
const primaryxAxis: AxisModel = { valueType: 'Category' };
const emptyPointSettings1: EmptyPointSettingsModel = { mode: 'Average', fill: 'pink' };
const emptyPointSettings2: EmptyPointSettingsModel = { mode: 'Zero', fill: 'green' };
return <ChartComponent id='charts' primaryXAxis={primaryxAxis}>
<Inject services={[ColumnSeries, LineSeries, ColumnSeries, Category]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={data} xName='month' type='Column' yName='sales' name='Sales' emptyPointSettings={emptyPointSettings1} />
<SeriesDirective dataSource={data} xName='month' type='Line' yName='sales' name='Sales' emptyPointSettings={emptyPointSettings2} />
</SeriesCollectionDirective>
</ChartComponent>
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));export let data = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: undefined },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];export let data: Object[] = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: undefined },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];Handling no data
When the chart has no data available to render, use the noDataTemplate property to display a custom layout within the chart area. This template can include messages, images, icons, or interactive elements (such as a load button) to guide the user. The template maintains design consistency and improves user experience when data is unavailable. When data becomes available, the chart automatically replaces the template with the visualization.
import * as React from "react";
import { createRoot } from 'react-dom/client';
import { useEffect, useRef, useState } from 'react';
import * as ReactDOM from "react-dom";
import { ChartComponent, SeriesCollectionDirective, SeriesDirective, LineSeries, SplineSeries, Category, Tooltip, Inject, SplineAreaSeries } from '@syncfusion/ej2-react-charts';
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
import { loadChartTheme } from './theme-color';
export let data1 = [
{ x: 'January', y: 19173 },
{ x: 'February', y: 17726 },
{ x: 'March', y: 19874 },
{ x: 'April', y: 19391 },
{ x: 'May', y: 20072 },
{ x: 'June', y: 19233 }
];
const SAMPLE_CSS = `
#noDataTemplateContainer {
height: inherit;
width: inherit;
}
.dark-bg {
background-color: #000000;
color: #ffffff;
}
.material3-dark .dark-bg, .fluent2-highcontrast .dark-bg {
background-color: #1c1b1f;
}
.fluent2-dark .dark-bg {
background-color: #1f1f1f;
}
.tailwind3-dark .dark-bg {
background-color: #111827;
}
.bootstrap5_3-dark .dark-bg {
background-color: #212529;
}
.light-bg {
background-color: #fafafa;
color: #000000;
}
.template-align img {
max-width: 150px;
/* Adjust size as needed */
max-height: 150px;
margin-top: 55px;
}
.load-data-btn {
border-radius: 4px;
}
.template-align {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
flex-direction: column;
}
.control-fluid {
padding: 0px !important;
}
#syncfusionButtonContainer {
margin-top: 5px;
}`;
function App() {
const [hasData, setHasData] = useState(false);
let chartInstance = useRef(null);
const buttonContainerRef = useRef(null);
const loadData = () => {
if (chartInstance.current) {
chartInstance.current.series[0].dataSource = data1;
setHasData(true);
const values = data1.map(d => d.y);
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min;
chartInstance.current.primaryYAxis.minimum = Math.floor(min - range * 0.1);
chartInstance.current.primaryYAxis.maximum = Math.ceil(max + range * 0.1);
chartInstance.current.primaryYAxis.interval = Math.ceil(range / 5);
chartInstance.current.series[0].animation.enable = true;
chartInstance.current.refresh();
}
};
const noDataTemplate = `
<div id="noDataTemplateContainer" class="light-bg">
<div class="template-align">
<img src="no-data.png" alt="No Data"/>
</div>
<div class="template-align">
<p style="font-size: 15px; margin: 10px 0 10px;"><strong>No data available to display.</strong></p>
</div>
<div class="template-align">
<div id="syncfusionButtonContainer"></div>
</div>
</div>
`;
const load = (args) => {
loadChartTheme(args);
};
const loaded = (args) => {
let selectedTheme = loadChartTheme(args);
const noDataDiv = document.getElementById("noDataTemplateContainer");
if (noDataDiv) {
noDataDiv.className = selectedTheme.indexOf("Dark") > -1 || selectedTheme.indexOf("HighContrast") > -1 ? 'dark-bg' : 'light-bg';
}
if (!hasData) {
const buttonContainer = document.getElementById("syncfusionButtonContainer");
if (buttonContainer && !buttonContainer.hasChildNodes()) {
// Create the button element using React.createElement
const buttonElement = React.createElement(ButtonComponent, {
id: "loadDataButton",
content: "Load Data",
iconCss: "e-icons e-refresh",
cssClass: "load-data-btn e-outline",
isPrimary: false,
onClick: loadData
});
const root = createRoot(buttonContainer);
root.render(buttonElement);
}
}
};
useEffect(() => {
if (hasData) {
const buttonContainer = document.getElementById("syncfusionButtonContainer");
if (buttonContainer) {
ReactDOM.unmountComponentAtNode(buttonContainer);
}
}
}, [hasData]);
return (<div className='control-pane'>
<style>{SAMPLE_CSS}</style>
<div className='control-section row'>
<ChartComponent id='charts' ref={chartInstance} primaryXAxis={{
valueType: 'Category',
majorGridLines: {
width: 0
},
majorTickLines: {
width: 0
},
}} chartArea={{ border: { width: 0 } }} primaryYAxis={{
title: 'Production (in million pounds)',
titleStyle: {
fontWeight: '600'
},
majorTickLines: {
width: 0
},
lineStyle: {
width: 0
}
}} loaded={loaded} load={load.bind(this)} tooltip={{ enable: true, format: '${point.x} : <b>${point.y}M</b>', header: 'Milk Production', }} noDataTemplate={noDataTemplate} title="Milk Production in US - 2025" subTitle="Source: nass.usda.gov" width='100%'>
<Inject services={[LineSeries, Category, Tooltip, SplineSeries, SplineAreaSeries]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={hasData ? data1 : []} xName='x' marker={{ visible: true, width: 7, height: 7 }} animation={{ enable: true }} yName='y' type='Line' width={2} />
</SeriesCollectionDirective>
</ChartComponent>
</div>
</div>);
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));import * as React from "react";
import { createRoot } from 'react-dom/client';
import { useEffect, useRef, useState } from 'react';
import * as ReactDOM from "react-dom";
import { ChartComponent, SeriesCollectionDirective, SeriesDirective, LineSeries, ChartTheme, SplineSeries, Category, Tooltip, ILoadedEventArgs, Inject, SplineAreaSeries } from '@syncfusion/ej2-react-charts';
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
import { loadChartTheme } from './theme-color';
export let data1: any[] = [
{ x: 'January', y: 19173 },
{ x: 'February', y: 17726 },
{ x: 'March', y: 19874 },
{ x: 'April', y: 19391 },
{ x: 'May', y: 20072 },
{ x: 'June', y: 19233 }
];
const SAMPLE_CSS = `
#noDataTemplateContainer {
height: inherit;
width: inherit;
}
.dark-bg {
background-color: #000000;
color: #ffffff;
}
.material3-dark .dark-bg, .fluent2-highcontrast .dark-bg {
background-color: #1c1b1f;
}
.fluent2-dark .dark-bg {
background-color: #1f1f1f;
}
.tailwind3-dark .dark-bg {
background-color: #111827;
}
.bootstrap5_3-dark .dark-bg {
background-color: #212529;
}
.light-bg {
background-color: #fafafa;
color: #000000;
}
.template-align img {
max-width: 150px;
/* Adjust size as needed */
max-height: 150px;
margin-top: 55px;
}
.load-data-btn {
border-radius: 4px;
}
.template-align {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
flex-direction: column;
}
.control-fluid {
padding: 0px !important;
}
#syncfusionButtonContainer {
margin-top: 5px;
}`;
function App() {
const [hasData, setHasData] = useState(false);
let chartInstance = useRef<ChartComponent>(null);
const buttonContainerRef = useRef<HTMLDivElement>(null);
const loadData = () => {
if (chartInstance.current) {
chartInstance.current.series[0].dataSource = data1;
setHasData(true);
const values = data1.map(d => d.y);
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min;
chartInstance.current.primaryYAxis.minimum = Math.floor(min - range * 0.1);
chartInstance.current.primaryYAxis.maximum = Math.ceil(max + range * 0.1);
chartInstance.current.primaryYAxis.interval = Math.ceil(range / 5);
chartInstance.current.series[0].animation.enable = true;
chartInstance.current.refresh();
}
};
const noDataTemplate: string = `
<div id="noDataTemplateContainer" class="light-bg">
<div class="template-align">
<img src="no-data.png" alt="No Data"/>
</div>
<div class="template-align">
<p style="font-size: 15px; margin: 10px 0 10px;"><strong>No data available to display.</strong></p>
</div>
<div class="template-align">
<div id="syncfusionButtonContainer"></div>
</div>
</div>
`;
const load = (args: ILoadedEventArgs): void => {
loadChartTheme(args);
};
const loaded = (args: ILoadedEventArgs): void => {
let selectedTheme = loadChartTheme(args);
const noDataDiv: Element = document.getElementById("noDataTemplateContainer");
if (noDataDiv) {
noDataDiv.className = selectedTheme.indexOf("Dark") > -1 || selectedTheme.indexOf("HighContrast") > -1 ? 'dark-bg' : 'light-bg';
}
if (!hasData) {
const buttonContainer = document.getElementById("syncfusionButtonContainer");
if (buttonContainer && !buttonContainer.hasChildNodes()) {
// Create the button element using React.createElement
const buttonElement = React.createElement(ButtonComponent, {
id: "loadDataButton",
content: "Load Data",
iconCss: "e-icons e-refresh",
cssClass: "load-data-btn e-outline",
isPrimary: false,
onClick: loadData
});
const root = createRoot(buttonContainer);
root.render(buttonElement);
}
}
};
useEffect(() => {
if (hasData) {
const buttonContainer = document.getElementById("syncfusionButtonContainer");
if (buttonContainer) {
ReactDOM.unmountComponentAtNode(buttonContainer);
}
}
}, [hasData]);
return (
<div className='control-pane'>
<style>{SAMPLE_CSS}</style>
<div className='control-section row'>
<ChartComponent
id='charts'
ref={chartInstance}
primaryXAxis={{
valueType: 'Category',
majorGridLines: {
width: 0
},
majorTickLines: {
width: 0
},
}}
chartArea={{ border: { width: 0 } }}
primaryYAxis={{
title: 'Production (in million pounds)',
titleStyle: {
fontWeight: '600'
},
majorTickLines: {
width: 0
},
lineStyle: {
width: 0
}
}} loaded={loaded} load={load.bind(this)} tooltip={{ enable: true, format: '${point.x} : <b>${point.y}M</b>', header: 'Milk Production', }} noDataTemplate={noDataTemplate} title="Milk Production in US - 2025" subTitle="Source: nass.usda.gov" width='100%'>
<Inject services={[LineSeries, Category, Tooltip, SplineSeries, SplineAreaSeries]} />
<SeriesCollectionDirective>
<SeriesDirective dataSource={hasData ? data1 : []} xName='x' marker={{ visible: true, width: 7, height: 7 }} animation={{ enable: true }} yName='y' type='Line' width={2} />
</SeriesCollectionDirective>
</ChartComponent>
</div>
</div >
)
};
export default App;
ReactDOM.render(<App />, document.getElementById("charts"));export let data = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: undefined },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];export let data: Object[] = [
{ month: 'Jan', sales: 35 }, { month: 'Feb', sales: null },
{ month: 'Mar', sales: 34 }, { month: 'Apr', sales: 32 },
{ month: 'May', sales: 40 }, { month: 'Jun', sales: undefined },
{ month: 'Jul', sales: 35 }, { month: 'Aug', sales: 55 },
{ month: 'Sep', sales: 38 }, { month: 'Oct', sales: 30 },
{ month: 'Nov', sales: 25 }, { month: 'Dec', sales: 32 }
];Troubleshooting
CORS errors when binding remote data
The browser blocks the request if the remote service does not return appropriate CORS headers. Enable CORS on the server or proxy the request through your application.
DataManager is not defined
DataManager and the adaptors are exported from @syncfusion/ej2-data. Install the package and import the symbols you use, for example import { DataManager, ODataAdaptor } from '@syncfusion/ej2-data';.
Empty chart with no errors
Verify that the response field names match the values set on xName and yName. Field matching is case-sensitive.
Lazy loading never appends new data
Make sure the scrollEnd event is wired up and that you are updating the React state with the combined dataset, not mutating the array in place. The chart re-renders only when the data source reference changes.