Cross-filtering charts allow users to interact with data in an easy and intuitive way. Clicking on chart elements automatically filters values in both the grid and other cross-filter charts.
Cross-filtering in AG Grid is no longer being actively developed. For interactive dashboards and a more powerful cross-filtering experience, check out our purpose-built dashboarding tool AG Studio.

This built-in feature of integrated charts is particularly useful for creating interactive reports and dashboards.
Creating Cross-filter Charts Copy Link
Cross-Filter charts are created programmatically using createCrossFilterChart(params) on the grid's API.
Used to programmatically create cross filter charts from a range. |
The following snippet shows how a cross-filtering pie chart can be created:
api.createCrossFilterChart({
chartType: 'pie',
cellRange: {
columns: ['salesRep', 'sale'],
},
aggFunc: 'sum',
});Note in the snippet above that the sale values are aggregated by the salesRep category as aggFunc: 'sum' is specified.
A corresponding column configuration for the chart above is shown in the following snippet:
const gridOptions = {
columnDefs: [
{ field: 'salesRep', filter: 'agSetColumnFilter', chartDataType: 'category' },
{ field: 'sale', chartDataType: 'series' },
],
// other grid options ...
}Cross-filtering Charts only support Client-Side Row Model. Grid filtering needs to be enabled on the category column(s) with either a Set Filter or Multi Filter. It is also important to define the Chart Data Type as it's not possible to infer the type when all data is filtered out.
The following example shows how to create a simple cross-filtering pie chart. Note the following:
- Click on a sector of the pie chart to filter rows in the grid by the selected sales rep.
- Ctrl (Cmd) Click on another sector to additionally adds rows corresponding to the selected sales rep.
- Click Chart Background to remove / reset the filtering in the grid to restore all rows in the grid.
import { AgChartsEnterpriseModule } from "ag-charts-enterprise";
import {
ClientSideRowModelModule,
FirstDataRenderedEvent,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
FiltersToolPanelModule,
IntegratedChartsModule,
MultiFilterModule,
RowGroupingModule,
SetFilterModule,
} from "ag-grid-enterprise";
import { getData } from "./data";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
IntegratedChartsModule.with(AgChartsEnterpriseModule),
ColumnsToolPanelModule,
FiltersToolPanelModule,
ColumnMenuModule,
ContextMenuModule,
MultiFilterModule,
SetFilterModule,
RowGroupingModule,
]);
let gridApi: GridApi;
const gridOptions: GridOptions = {
columnDefs: [
{ field: "salesRep", chartDataType: "category" },
{ field: "handset", chartDataType: "category" },
{ field: "sale", chartDataType: "series" },
{ field: "saleDate", chartDataType: "category" },
],
defaultColDef: {
flex: 1,
filter: "agSetColumnFilter",
floatingFilter: true,
},
enableCharts: true,
onGridReady: (params: GridReadyEvent) => {
getData().then((rowData) => params.api.setGridOption("rowData", rowData));
},
onFirstDataRendered,
};
function onFirstDataRendered(params: FirstDataRenderedEvent) {
params.api.createCrossFilterChart({
chartType: "pie",
cellRange: {
columns: ["salesRep", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Sales by Representative ($)",
},
},
pie: {
series: {
title: {
enabled: false,
},
calloutLabel: {
enabled: false,
},
},
legend: {
position: "right",
},
},
},
sort: false,
chartContainer: document.querySelector("#pieChart") as any,
});
}
gridApi = createGrid(
document.querySelector<HTMLElement>("#myGrid")!,
gridOptions,
);
#wrapper {
height: 100%;
width: 100%;
display: grid;
grid-template-rows: 50% 50%;
gap: 10px;
padding: 10px;
box-sizing: border-box;
}
export async function getData(delay: number = 100): Promise<any[]> {
return new Promise((resolve) => setTimeout(() => resolve(generateData()), delay));
}
function generateData() {
const numRows = 500;
const names = [
'Aden Moreno',
'Alton Watson',
'Caleb Scott',
'Cathy Wilkins',
'Charlie Dodd',
'Jermaine Price',
'Reis Vasquez',
];
const phones = [
{ handset: 'Huawei P40', price: 599 },
{ handset: 'Google Pixel 5', price: 589 },
{ handset: 'Apple iPhone 12', price: 849 },
{ handset: 'Samsung Galaxy S10', price: 499 },
{ handset: 'Motorola Edge', price: 549 },
{ handset: 'Sony Xperia', price: 279 },
];
return Array.from({ length: numRows }, () => {
const phone = phones[getRandomNumber(0, phones.length - 1)];
const saleDate = randomDate(new Date(2020, 0, 1), new Date(2020, 11, 31));
return {
salesRep: names[getRandomNumber(0, names.length - 1)],
handset: phone.handset,
sale: phone.price,
saleDate,
};
});
}
function getRandomNumber(min: number, max: number): number {
return Math.floor(window.agRandom() * (max - min + 1)) + min;
}
function randomDate(start: Date, end: Date): string {
const date = new Date(start.getTime() + window.agRandom() * (end.getTime() - start.getTime()));
return date.toISOString().substring(0, 10);
}
<div id="wrapper">
<div id="pieChart"></div>
<div id="myGrid"></div>
</div>
Cross-filter API Copy Link
The cross-filter api shares a similar api to Range Chart, however there are different defaults which make sense for cross-filtering.
Used to programmatically create cross filter charts from a range. |
Properties available on the CreateCrossFilterChartParams interface.
The type of cross-filtering chart to create. |
Defines the list of columns to be charted. Note that cross-filter charts include all rows in the grid so there is no need to specify the range of rows. |
By default, when a cross-filter chart is displayed using the grid, the grid will not highlight the range the chart is charting when the chart gets focus. To show the chart range set suppressChartRanges=false. |
The aggregation function that should be applied to all series data. The built-in aggregation functions are 'sum', 'min', 'max', 'count', 'avg', 'first', 'last'. Alternatively, custom aggregation functions can be provided if they conform to the IAggFunc interface shown here. |
By default (or when true), the order in cross filter charts will match grid sorting. Set to false to disable sorting for this chart. Set to a SortModelItem[] to provide a custom sorting for this chart. |
The default theme to use for the created chart. In addition to the default options you listed, you can also provide your own custom chart themes. Options: 'ag-default', 'ag-default-dark', 'ag-material', 'ag-material-dark', 'ag-pastel', 'ag-pastel-dark', 'ag-vivid', 'ag-vivid-dark', 'ag-solar', 'ag-solar-dark' |
If the chart is to be displayed outside of the grid then a chart container should be provided. If the chart is to be displayed using the grid's popup window mechanism then leave as undefined. |
Allows specific chart options in the current theme to be overridden. |
When enabled the chart will be unlinked from the grid after creation, any updates to the data will not be reflected in the chart. |
Cross-filter Chart Types Copy Link
The following examples show the different chart types that support cross-filtering:
Example: Sales Dashboard #1 Copy Link
import { AgChartsEnterpriseModule } from "ag-charts-enterprise";
import {
ClientSideRowModelModule,
DateEditorModule,
FirstDataRenderedEvent,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
ValueFormatterParams,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
FiltersToolPanelModule,
IntegratedChartsModule,
MultiFilterModule,
RowGroupingModule,
SetFilterModule,
} from "ag-grid-enterprise";
import { getData } from "./data";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
IntegratedChartsModule.with(AgChartsEnterpriseModule),
ColumnsToolPanelModule,
FiltersToolPanelModule,
ColumnMenuModule,
ContextMenuModule,
MultiFilterModule,
SetFilterModule,
RowGroupingModule,
NumberFilterModule,
TextFilterModule,
TextEditorModule,
DateEditorModule,
NumberEditorModule,
]);
let gridApi: GridApi;
const gridOptions: GridOptions = {
columnDefs: [
{ field: "salesRep", chartDataType: "category" },
{ field: "handset", chartDataType: "category" },
{
headerName: "Sale Price",
field: "sale",
maxWidth: 160,
aggFunc: "sum",
filter: "agNumberColumnFilter",
chartDataType: "series",
},
{
field: "saleDate",
chartDataType: "category",
filter: "agSetColumnFilter",
filterParams: {
valueFormatter: (params: ValueFormatterParams) => `${params.value}`,
},
sort: "asc",
},
{
field: "quarter",
maxWidth: 160,
filter: "agSetColumnFilter",
chartDataType: "category",
},
],
defaultColDef: {
flex: 1,
editable: true,
filter: "agMultiColumnFilter",
floatingFilter: true,
},
enableCharts: true,
chartThemeOverrides: {
bar: {
axes: {
category: {
label: {
rotation: 0,
},
},
},
},
},
onGridReady: (params: GridReadyEvent) => {
getData().then((rowData) => params.api.setGridOption("rowData", rowData));
},
onFirstDataRendered,
};
function onFirstDataRendered(params: FirstDataRenderedEvent) {
createQuarterlySalesChart(params.api);
createSalesByRefChart(params.api);
createHandsetSalesChart(params.api);
}
function createQuarterlySalesChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "column",
cellRange: {
columns: ["quarter", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Quarterly Sales ($)",
},
legend: { enabled: false },
axes: {
category: {
label: {
rotation: 0,
},
},
number: {
label: {
formatter: (params: any) => {
return params.value / 1000 + "k";
},
},
},
},
},
},
sort: [{ colId: "quarter", sort: "asc" }],
chartContainer: document.querySelector("#columnChart") as any,
});
}
function createSalesByRefChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "pie",
cellRange: {
columns: ["salesRep", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Sales by Representative ($)",
},
},
pie: {
series: {
title: {
enabled: false,
},
calloutLabel: {
enabled: false,
},
},
legend: {
position: "right",
},
},
},
sort: false,
chartContainer: document.querySelector("#pieChart") as any,
});
}
function createHandsetSalesChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "bar",
cellRange: {
columns: ["handset", "sale"],
},
aggFunc: "count",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Handsets Sold (Units)",
},
legend: { enabled: false },
},
},
sort: [{ colId: "handset", sort: "asc" }],
chartContainer: document.querySelector("#barChart") as any,
});
}
gridApi = createGrid(
document.querySelector<HTMLElement>("#myGrid")!,
gridOptions,
);
#wrapper {
height: 100%;
width: 100%;
display: grid;
grid-template-rows: 300px 300px auto;
grid-template-columns: 50% 50%;
gap: 10px;
padding: 10px;
box-sizing: border-box;
}
#barChart {
grid-column: span 2;
}
#wrapper > div:last-child {
grid-column: span 2;
}
export const getData = async (delay = 100): Promise<any[]> =>
new Promise((resolve) => setTimeout(() => resolve(generateData()), delay));
const generateData = () => {
const numRows = 500;
const names = [
'Aden Moreno',
'Alton Watson',
'Caleb Scott',
'Cathy Wilkins',
'Charlie Dodd',
'Jermaine Price',
'Reis Vasquez',
];
const phones = [
{ handset: 'Huawei P40', price: 599 },
{ handset: 'Google Pixel 5', price: 589 },
{ handset: 'Apple iPhone 12', price: 849 },
{ handset: 'Samsung Galaxy S10', price: 499 },
{ handset: 'Motorola Edge', price: 549 },
{ handset: 'Sony Xperia', price: 279 },
];
return Array.from({ length: numRows }, () => {
const phone = phones[getRandomNumber(0, phones.length - 1)];
const saleDate = randomDate(new Date(2020, 0, 1), new Date(2020, 11, 31));
const quarter = `Q${Math.floor((saleDate.getMonth() + 3) / 3)}`;
return {
salesRep: names[getRandomNumber(0, names.length - 1)],
handset: phone.handset,
sale: phone.price,
saleDate,
quarter,
};
});
};
const getRandomNumber = (min: number, max: number): number => Math.floor(window.agRandom() * (max - min + 1) + min);
const randomDate = (start: Date, end: Date): Date =>
new Date(start.getTime() + window.agRandom() * (end.getTime() - start.getTime()));
<div id="wrapper">
<div id="columnChart"></div>
<div id="pieChart"></div>
<div id="barChart"></div>
<div id="myGrid"></div>
</div>
Example: Sales Dashboard #2 Copy Link
import { AgChartsEnterpriseModule } from "ag-charts-enterprise";
import {
ClientSideRowModelModule,
DateEditorModule,
FirstDataRenderedEvent,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
ValueFormatterParams,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
FiltersToolPanelModule,
IntegratedChartsModule,
MultiFilterModule,
RowGroupingModule,
SetFilterModule,
} from "ag-grid-enterprise";
import { getData } from "./data";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
IntegratedChartsModule.with(AgChartsEnterpriseModule),
ColumnsToolPanelModule,
FiltersToolPanelModule,
ColumnMenuModule,
ContextMenuModule,
MultiFilterModule,
SetFilterModule,
RowGroupingModule,
NumberFilterModule,
TextFilterModule,
NumberEditorModule,
TextEditorModule,
DateEditorModule,
]);
let gridApi: GridApi;
const gridOptions: GridOptions = {
columnDefs: [
{ field: "salesRep", chartDataType: "category" },
{ field: "handset", chartDataType: "category" },
{
headerName: "Sale Price",
field: "sale",
maxWidth: 160,
aggFunc: "sum",
filter: "agNumberColumnFilter",
chartDataType: "series",
},
{
field: "saleDate",
chartDataType: "category",
filter: "agSetColumnFilter",
filterParams: {
valueFormatter: (params: ValueFormatterParams) => `${params.value}`,
},
sort: "asc",
},
{
field: "quarter",
maxWidth: 160,
filter: "agSetColumnFilter",
chartDataType: "category",
},
],
defaultColDef: {
flex: 1,
editable: true,
filter: "agMultiColumnFilter",
floatingFilter: true,
},
enableCharts: true,
chartThemeOverrides: {
bar: {
axes: {
category: {
label: {
rotation: 0,
},
},
},
},
},
onGridReady: (params: GridReadyEvent) => {
getData().then((rowData) => params.api.setGridOption("rowData", rowData));
},
onFirstDataRendered,
};
function onFirstDataRendered(params: FirstDataRenderedEvent) {
createQuarterlySalesChart(params.api);
createSalesByRefChart(params.api);
createHandsetSalesChart(params.api);
}
function createQuarterlySalesChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "line",
cellRange: {
columns: ["quarter", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Quarterly Sales ($)",
},
axes: {
category: {
label: {
rotation: 0,
},
},
number: {
label: {
formatter: (params: any) => {
return params.value / 1000 + "k";
},
},
},
},
},
},
sort: [{ colId: "quarter", sort: "asc" }],
chartContainer: document.querySelector("#lineChart") as any,
});
}
function createSalesByRefChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "donut",
cellRange: {
columns: ["salesRep", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Sales by Representative ($)",
},
},
pie: {
legend: {
position: "right",
},
series: {
title: {
enabled: false,
},
calloutLabel: {
enabled: false,
},
},
},
},
sort: false,
chartContainer: document.querySelector("#donutChart") as any,
});
}
function createHandsetSalesChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "area",
cellRange: {
columns: ["handset", "sale"],
},
aggFunc: "count",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Handsets Sold (Units)",
},
padding: { left: 47, right: 80 },
},
},
sort: [{ colId: "handset", sort: "asc" }],
chartContainer: document.querySelector("#areaChart") as any,
});
}
gridApi = createGrid(
document.querySelector<HTMLElement>("#myGrid")!,
gridOptions,
);
#wrapper {
height: 100%;
width: 100%;
display: grid;
grid-template-rows: 300px 300px auto;
grid-template-columns: 50% 50%;
gap: 10px;
padding: 10px;
box-sizing: border-box;
}
#areaChart {
grid-column: span 2;
}
#wrapper > div:last-child {
grid-column: span 2;
}
export const getData = async (delay = 100): Promise<any[]> =>
new Promise((resolve) => setTimeout(() => resolve(generateData()), delay));
const generateData = () => {
const numRows = 500;
const names = [
'Aden Moreno',
'Alton Watson',
'Caleb Scott',
'Cathy Wilkins',
'Charlie Dodd',
'Jermaine Price',
'Reis Vasquez',
];
const phones = [
{ handset: 'Huawei P40', price: 599 },
{ handset: 'Google Pixel 5', price: 589 },
{ handset: 'Apple iPhone 12', price: 849 },
{ handset: 'Samsung Galaxy S10', price: 499 },
{ handset: 'Motorola Edge', price: 549 },
{ handset: 'Sony Xperia', price: 279 },
];
return Array.from({ length: numRows }, () => {
const phone = phones[getRandomNumber(0, phones.length - 1)];
const saleDate = randomDate(new Date(2020, 0, 1), new Date(2020, 11, 31));
const quarter = `Q${Math.floor((saleDate.getMonth() + 3) / 3)}`;
return {
salesRep: names[getRandomNumber(0, names.length - 1)],
handset: phone.handset,
sale: phone.price,
saleDate,
quarter,
};
});
};
const getRandomNumber = (min: number, max: number): number => Math.floor(window.agRandom() * (max - min + 1) + min);
const randomDate = (start: Date, end: Date): Date =>
new Date(start.getTime() + window.agRandom() * (end.getTime() - start.getTime()));
<div id="wrapper">
<div id="lineChart"></div>
<div id="donutChart"></div>
<div id="areaChart"></div>
<div id="myGrid"></div>
</div>
Example: Most Populous Cities Copy Link
import { AgChartsEnterpriseModule } from "ag-charts-enterprise";
import {
ClientSideRowModelModule,
FirstDataRenderedEvent,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
FiltersToolPanelModule,
IntegratedChartsModule,
MultiFilterModule,
RowGroupingModule,
SetFilterModule,
} from "ag-grid-enterprise";
import { getData } from "./data";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
IntegratedChartsModule.with(AgChartsEnterpriseModule),
ColumnsToolPanelModule,
FiltersToolPanelModule,
ColumnMenuModule,
ContextMenuModule,
MultiFilterModule,
SetFilterModule,
RowGroupingModule,
TextFilterModule,
TextEditorModule,
NumberFilterModule,
NumberEditorModule,
]);
let gridApi: GridApi;
const gridOptions: GridOptions = {
columnDefs: [
{ field: "city", chartDataType: "category" },
{ field: "country", chartDataType: "category" },
{ field: "longitude", chartDataType: "series" },
{ field: "latitude", chartDataType: "series" },
{ field: "population", chartDataType: "series" },
],
defaultColDef: {
flex: 1,
editable: true,
filter: "agMultiColumnFilter",
floatingFilter: true,
},
enableCharts: true,
onGridReady: (params: GridReadyEvent) => {
getData().then((rowData) => params.api.setGridOption("rowData", rowData));
},
onFirstDataRendered,
};
function onFirstDataRendered(params: FirstDataRenderedEvent) {
createColumnChart(params.api);
createBubbleChart(params.api);
}
function createColumnChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "column",
cellRange: {
columns: ["country", "population"],
},
aggFunc: "count",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Number of Most Populous Cities by Country",
},
legend: {
enabled: false,
},
},
bar: {
axes: {
category: {
label: {
rotation: 325,
},
},
},
},
},
sort: [{ colId: "country", sort: "asc" }],
chartContainer: document.querySelector("#barChart") as any,
});
}
function createBubbleChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "bubble",
cellRange: {
columns: ["longitude", "latitude", "population"],
},
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Latitude vs Longitude of Most Populous Cities",
},
legend: {
enabled: false,
},
},
},
sort: false,
chartContainer: document.querySelector("#bubbleChart") as any,
});
}
gridApi = createGrid(
document.querySelector<HTMLElement>("#myGrid")!,
gridOptions,
);
#wrapper {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
#barChart {
height: 400px;
}
#bubbleChart {
height: 300px;
}
#myGrid {
height: 400px;
}
export const getData = async (delay = 100): Promise<any[]> =>
new Promise((resolve) => setTimeout(() => resolve(data), delay));
const data = [
{
city: 'Tokyo',
latitude: 35.6897,
longitude: 139.6922,
country: 'Japan',
population: 37977000,
},
{
city: 'Jakarta',
latitude: -6.2146,
longitude: 106.8451,
country: 'Indonesia',
population: 34540000,
},
{
city: 'Delhi',
latitude: 28.66,
longitude: 77.23,
country: 'India',
population: 29617000,
},
{
city: 'Mumbai',
latitude: 18.9667,
longitude: 72.8333,
country: 'India',
population: 23355000,
},
{
city: 'Manila',
latitude: 14.5958,
longitude: 120.9772,
country: 'Philippines',
population: 23088000,
},
{
city: 'Shanghai',
latitude: 31.1667,
longitude: 121.4667,
country: 'China',
population: 22120000,
},
{
city: 'SĂŁo Paulo',
latitude: -23.5504,
longitude: -46.6339,
country: 'Brazil',
population: 22046000,
},
{
city: 'Seoul',
latitude: 37.5833,
longitude: 127,
country: 'Korea, South',
population: 21794000,
},
{
city: 'Mexico City',
latitude: 19.4333,
longitude: -99.1333,
country: 'Mexico',
population: 20996000,
},
{
city: 'Guangzhou',
latitude: 23.1288,
longitude: 113.259,
country: 'China',
population: 20902000,
},
{
city: 'Beijing',
latitude: 39.905,
longitude: 116.3914,
country: 'China',
population: 19433000,
},
{
city: 'Cairo',
latitude: 30.0561,
longitude: 31.2394,
country: 'Egypt',
population: 19372000,
},
{
city: 'New York',
latitude: 40.6943,
longitude: -73.9249,
country: 'United States',
population: 18713220,
},
{
city: 'KolkÄta',
latitude: 22.5411,
longitude: 88.3378,
country: 'India',
population: 17560000,
},
{
city: 'Moscow',
latitude: 55.7558,
longitude: 37.6178,
country: 'Russia',
population: 17125000,
},
{
city: 'Bangkok',
latitude: 13.75,
longitude: 100.5167,
country: 'Thailand',
population: 17066000,
},
{
city: 'Buenos Aires',
latitude: -34.5997,
longitude: -58.3819,
country: 'Argentina',
population: 16157000,
},
{
city: 'Shenzhen',
latitude: 22.535,
longitude: 114.054,
country: 'China',
population: 15929000,
},
{
city: 'Dhaka',
latitude: 23.7161,
longitude: 90.3961,
country: 'Bangladesh',
population: 15443000,
},
{
city: 'Lagos',
latitude: 6.45,
longitude: 3.4,
country: 'Nigeria',
population: 15279000,
},
{
city: 'Istanbul',
latitude: 41.01,
longitude: 28.9603,
country: 'Turkey',
population: 15154000,
},
{
city: 'Ćsaka',
latitude: 34.6936,
longitude: 135.5019,
country: 'Japan',
population: 14977000,
},
{
city: 'Karachi',
latitude: 24.86,
longitude: 67.01,
country: 'Pakistan',
population: 14835000,
},
{
city: 'Bangalore',
latitude: 12.9699,
longitude: 77.598,
country: 'India',
population: 13707000,
},
{
city: 'Tehran',
latitude: 35.7,
longitude: 51.4167,
country: 'Iran',
population: 13633000,
},
{
city: 'Ho Chi Minh City',
latitude: 10.8167,
longitude: 106.6333,
country: 'Vietnam',
population: 13312000,
},
{
city: 'Los Angeles',
latitude: 34.1139,
longitude: -118.4068,
country: 'United States',
population: 12750807,
},
{
city: 'Rio de Janeiro',
latitude: -22.9083,
longitude: -43.1964,
country: 'Brazil',
population: 12272000,
},
{
city: 'Nanyang',
latitude: 32.9987,
longitude: 112.5292,
country: 'China',
population: 12010000,
},
{
city: 'Chennai',
latitude: 13.0825,
longitude: 80.275,
country: 'India',
population: 11324000,
},
{
city: 'Chengdu',
latitude: 30.6636,
longitude: 104.0667,
country: 'China',
population: 11309000,
},
{
city: 'Lahore',
latitude: 31.5497,
longitude: 74.3436,
country: 'Pakistan',
population: 11021000,
},
{
city: 'Paris',
latitude: 48.8566,
longitude: 2.3522,
country: 'France',
population: 11020000,
},
{
city: 'London',
latitude: 51.5072,
longitude: -0.1275,
country: 'United Kingdom',
population: 10979000,
},
{
city: 'Linyi',
latitude: 35.0606,
longitude: 118.3425,
country: 'China',
population: 10820000,
},
{
city: 'Tianjin',
latitude: 39.1467,
longitude: 117.2056,
country: 'China',
population: 10800000,
},
{
city: 'Shijiazhuang',
latitude: 38.0422,
longitude: 114.5086,
country: 'China',
population: 10784600,
},
{
city: 'Baoding',
latitude: 38.8671,
longitude: 115.4845,
country: 'China',
population: 10700000,
},
{
city: 'Zhoukou',
latitude: 33.625,
longitude: 114.6418,
country: 'China',
population: 9901000,
},
{
city: 'HyderÄbÄd',
latitude: 17.3667,
longitude: 78.4667,
country: 'India',
population: 9746000,
},
{
city: 'Weifang',
latitude: 36.7167,
longitude: 119.1,
country: 'China',
population: 9373000,
},
{
city: 'Nagoya',
latitude: 35.1167,
longitude: 136.9333,
country: 'Japan',
population: 9113000,
},
{
city: 'Wuhan',
latitude: 30.5872,
longitude: 114.2881,
country: 'China',
population: 8962000,
},
{
city: 'Heze',
latitude: 35.2333,
longitude: 115.4333,
country: 'China',
population: 8750000,
},
{
city: 'Ganzhou',
latitude: 25.8292,
longitude: 114.9336,
country: 'China',
population: 8677600,
},
{
city: 'Tongshan',
latitude: 34.261,
longitude: 117.1859,
country: 'China',
population: 8669000,
},
{
city: 'Chicago',
latitude: 41.8373,
longitude: -87.6862,
country: 'United States',
population: 8604203,
},
{
city: 'Fuyang',
latitude: 32.8986,
longitude: 115.8045,
country: 'China',
population: 8360000,
},
{
city: 'Jining',
latitude: 35.4,
longitude: 116.5667,
country: 'China',
population: 8023000,
},
{
city: 'Dongguan',
latitude: 23.0475,
longitude: 113.7493,
country: 'China',
population: 7981000,
},
{
city: 'Hanoi',
latitude: 21.0245,
longitude: 105.8412,
country: 'Vietnam',
population: 7785000,
},
{
city: 'Pune',
latitude: 18.5196,
longitude: 73.8553,
country: 'India',
population: 7764000,
},
{
city: 'Chongqing',
latitude: 29.55,
longitude: 106.5069,
country: 'China',
population: 7739000,
},
{
city: 'Changchun',
latitude: 43.9,
longitude: 125.2,
country: 'China',
population: 7674439,
},
{
city: 'Zhumadian',
latitude: 32.9773,
longitude: 114.0253,
country: 'China',
population: 7640000,
},
{
city: 'Ningbo',
latitude: 29.875,
longitude: 121.5492,
country: 'China',
population: 7639000,
},
{
city: 'Onitsha',
latitude: 6.1667,
longitude: 6.7833,
country: 'Nigeria',
population: 7635000,
},
{
city: 'Hefei',
latitude: 31.8639,
longitude: 117.2808,
country: 'China',
population: 7457027,
},
{
city: 'AhmadÄbÄd',
latitude: 23.03,
longitude: 72.58,
country: 'India',
population: 7410000,
},
{
city: 'Nantong',
latitude: 31.9829,
longitude: 120.8873,
country: 'China',
population: 7282835,
},
{
city: 'Foshan',
latitude: 23.0292,
longitude: 113.1056,
country: 'China',
population: 7194311,
},
{
city: 'Hengyang',
latitude: 26.8968,
longitude: 112.5857,
country: 'China',
population: 7148344,
},
{
city: 'Xiâan',
latitude: 34.2667,
longitude: 108.9,
country: 'China',
population: 7135000,
},
{
city: 'Shenyang',
latitude: 41.8039,
longitude: 123.4258,
country: 'China',
population: 7105000,
},
{
city: 'Tangshan',
latitude: 39.6292,
longitude: 118.1742,
country: 'China',
population: 7100000,
},
{
city: 'Shaoyang',
latitude: 27.2418,
longitude: 111.4725,
country: 'China',
population: 7071000,
},
{
city: 'Changsha',
latitude: 28.1987,
longitude: 112.9709,
country: 'China',
population: 7044118,
},
{
city: 'Cangzhou',
latitude: 38.3037,
longitude: 116.8452,
country: 'China',
population: 6800000,
},
{
city: 'Maoming',
latitude: 21.6618,
longitude: 110.9178,
country: 'China',
population: 6706000,
},
{
city: 'Huanggang',
latitude: 30.45,
longitude: 114.875,
country: 'China',
population: 6667000,
},
{
city: 'Miami',
latitude: 25.7839,
longitude: -80.2102,
country: 'United States',
population: 6445545,
},
{
city: 'Sƫrat',
latitude: 21.17,
longitude: 72.83,
country: 'India',
population: 5807000,
},
{
city: 'Dallas',
latitude: 32.7936,
longitude: -96.7662,
country: 'United States',
population: 5743938,
},
];
<div id="wrapper">
<div id="barChart"></div>
<div id="bubbleChart"></div>
<div id="myGrid"></div>
</div>
Example: Custom Theme Copy Link
In this example, we use a custom theme to style the charts using customChartTheme and chartTheme. See the Custom Chart Themes section for more information.
Note that for cosmetic changes in cross-filtering charts, you must use a custom theme. Cross-filtering charts rely on a theme to derive filtered value colours from existing theme colours. Specifying your own colours in the theme overrides will disable this behaviour.
import { AgChartsEnterpriseModule } from "ag-charts-enterprise";
import {
ClientSideRowModelModule,
DateEditorModule,
FirstDataRenderedEvent,
GridApi,
GridOptions,
GridReadyEvent,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
ValueFormatterParams,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import {
ColumnMenuModule,
ColumnsToolPanelModule,
ContextMenuModule,
FiltersToolPanelModule,
IntegratedChartsModule,
MultiFilterModule,
RowGroupingModule,
SetFilterModule,
} from "ag-grid-enterprise";
import { getData, phones } from "./data";
// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
enableDevValidations();
}
ModuleRegistry.registerModules([
ClientSideRowModelModule,
IntegratedChartsModule.with(AgChartsEnterpriseModule),
ColumnsToolPanelModule,
FiltersToolPanelModule,
ColumnMenuModule,
ContextMenuModule,
MultiFilterModule,
SetFilterModule,
RowGroupingModule,
NumberFilterModule,
TextFilterModule,
TextEditorModule,
DateEditorModule,
NumberEditorModule,
]);
let gridApi: GridApi;
const gridOptions: GridOptions = {
columnDefs: [
{ field: "salesRep", chartDataType: "category" },
{ field: "handset", chartDataType: "category" },
{ field: "handsetIndex", chartDataType: "series", hide: true },
{ field: "quarterIndex", chartDataType: "series", hide: true },
{
headerName: "Sale Price",
field: "sale",
maxWidth: 160,
aggFunc: "sum",
filter: "agNumberColumnFilter",
chartDataType: "series",
},
{
field: "saleDate",
chartDataType: "category",
filter: "agSetColumnFilter",
filterParams: {
valueFormatter: (params: ValueFormatterParams) => `${params.value}`,
},
sort: "asc",
},
{
field: "quarter",
maxWidth: 160,
filter: "agSetColumnFilter",
chartDataType: "category",
},
],
defaultColDef: {
flex: 1,
editable: true,
filter: "agMultiColumnFilter",
floatingFilter: true,
},
enableCharts: true,
customChartThemes: {
"my-custom-theme-light": {
palette: {
fills: ["purple", "indigo", "blue", "green", "yellow", "orange", "red"],
},
},
"my-custom-theme-dark": {
palette: {
fills: ["red", "orange", "yellow", "green", "blue", "indigo", "purple"],
strokes: ["white"],
},
overrides: {
common: {
title: {
color: "white",
},
axes: {
category: {
label: {
color: "white",
},
},
number: {
label: {
color: "white",
},
},
},
legend: {
item: {
label: {
color: "white",
},
},
},
},
},
},
},
chartThemes: ["my-custom-theme-light", "my-custom-theme-dark"],
chartThemeOverrides: {
common: {
background: {
fill: "transparent",
},
zoom: {
enabled: false,
},
axes: {
number: {
crosshair: {
enabled: false,
},
},
category: {
crosshair: {
enabled: false,
},
},
},
},
bar: {
axes: {
category: {
label: {
rotation: 0,
},
},
},
},
},
onGridReady: (params: GridReadyEvent) => {
getData().then((rowData) => params.api.setGridOption("rowData", rowData));
},
onFirstDataRendered,
};
function onFirstDataRendered(params: FirstDataRenderedEvent) {
createQuarterlySalesChart(params.api);
createSalesByRefChart(params.api);
createHandsetSalesChart(params.api);
createBubbleChart(params.api);
}
function createQuarterlySalesChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "column",
cellRange: {
columns: ["quarter", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Quarterly Sales ($)",
},
legend: { enabled: false },
axes: {
category: {
label: {
rotation: 0,
},
},
number: {
label: {
formatter: (params: any) => {
return params.value / 1000 + "k";
},
},
},
},
},
},
sort: [{ colId: "quarter", sort: "asc" }],
chartContainer: document.querySelector("#columnChart") as any,
});
}
function createSalesByRefChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "pie",
cellRange: {
columns: ["salesRep", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Sales by Representative ($)",
},
},
pie: {
series: {
title: {
enabled: false,
},
calloutLabel: {
enabled: false,
},
},
legend: {
position: "right",
},
},
},
sort: false,
chartContainer: document.querySelector("#pieChart") as any,
});
}
function createHandsetSalesChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "area",
cellRange: {
columns: ["handset", "sale"],
},
aggFunc: "count",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Handsets Sold (Units)",
},
padding: { left: 47, right: 80 },
},
},
sort: [{ colId: "handset", sort: "asc" }],
chartContainer: document.querySelector("#areaChart") as any,
});
}
function createBubbleChart(api: GridApi) {
api.createCrossFilterChart({
chartType: "bubble",
cellRange: {
columns: ["quarterIndex", "handsetIndex", "sale"],
},
aggFunc: "sum",
chartThemeOverrides: {
common: {
title: {
enabled: true,
text: "Sales by Quarter and Handset",
},
legend: {
enabled: false,
},
seriesArea: {
padding: {
left: 8,
bottom: 8,
},
},
axes: {
number: {
label: {
formatter: (params: any) => {
// For this example only: format two number series differently with a single formatter
if (params.value < 10) {
if (Math.floor(params.value) !== params.value) {
return "";
}
return `Q${params.value}`;
} else {
return phones[params.value - 10]?.handset ?? "";
}
},
},
nice: false,
},
},
},
},
sort: false,
chartContainer: document.querySelector("#bubbleChart") as any,
});
}
gridApi = createGrid(
document.querySelector<HTMLElement>("#myGrid")!,
gridOptions,
);
#wrapper {
height: 100%;
width: 100%;
display: grid;
grid-template-columns: 100%;
grid-template-rows: 75% 25%;
box-sizing: border-box;
}
#charts {
height: 100%;
width: 100%;
display: grid;
grid-template-columns: 50% 50%;
grid-template-rows: 50% 50%;
gap: 10px;
padding-bottom: 20px;
padding-right: 10px;
box-sizing: border-box;
}
export const getData = async (delay = 100): Promise<any[]> =>
new Promise((resolve) => setTimeout(() => resolve(generateData()), delay));
const numRows = 500;
const names = [
'Aden Moreno',
'Alton Watson',
'Caleb Scott',
'Cathy Wilkins',
'Charlie Dodd',
'Jermaine Price',
'Reis Vasquez',
];
export const phones = [
{ handset: 'Huawei P40', price: 599 },
{ handset: 'Google Pixel 5', price: 589 },
{ handset: 'Apple iPhone 12', price: 849 },
{ handset: 'Samsung Galaxy S10', price: 499 },
{ handset: 'Motorola Edge', price: 549 },
{ handset: 'Sony Xperia', price: 279 },
];
const generateData = () => {
return Array.from({ length: numRows }, () => {
const handsetIndex = getRandomNumber(0, phones.length - 1);
const { handset, price } = phones[handsetIndex];
const saleDate = randomDate(new Date(2020, 0, 1), new Date(2020, 11, 31));
const quarterIndex = Math.floor((saleDate.getMonth() + 3) / 3);
const quarter = `Q${quarterIndex}`;
return {
salesRep: names[getRandomNumber(0, names.length - 1)],
handset,
handsetIndex: handsetIndex + 10,
quarterIndex,
sale: price,
saleDate,
quarter,
};
});
};
const getRandomNumber = (min: number, max: number): number => Math.floor(window.agRandom() * (max - min + 1) + min);
const randomDate = (start: Date, end: Date): Date =>
new Date(start.getTime() + window.agRandom() * (end.getTime() - start.getTime()));
<div id="wrapper">
<div id="charts">
<div id="columnChart"></div>
<div id="pieChart"></div>
<div id="areaChart"></div>
<div id="bubbleChart"></div>
</div>
<div id="myGrid"></div>
</div>