This section introduces Integrated Charts that are created programmatically within an application.
import { AgChartsEnterpriseModule } from "ag-charts-enterprise";
import {
CellStyleModule,
ChartToolbarMenuItemOptions,
ChartType,
ClientSideRowModelApiModule,
ClientSideRowModelModule,
ColDef,
ColumnApiModule,
GetRowIdParams,
GridApi,
GridOptions,
HighlightChangesModule,
ModuleRegistry,
NumberEditorModule,
NumberFilterModule,
TextEditorModule,
TextFilterModule,
ValueFormatterParams,
createGrid,
enableDevValidations,
} from "ag-grid-community";
import { IntegratedChartsModule, RowGroupingModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
ModuleRegistry.registerModules([
ColumnApiModule,
ClientSideRowModelApiModule,
TextEditorModule,
TextFilterModule,
NumberEditorModule,
CellStyleModule,
ClientSideRowModelModule,
IntegratedChartsModule.with(AgChartsEnterpriseModule),
RowGroupingModule,
HighlightChangesModule,
NumberFilterModule,
]);
declare let __basePath: string;
// Types
interface WorkerMessage {
type: string;
records?: any[];
}
// Global variables
let chartRef: any;
let gridApi: GridApi;
let worker: Worker;
// Column Definitions
function getColumnDefs(): ColDef[] {
return [
{ field: "product", chartDataType: "category", minWidth: 110 },
{ field: "book", chartDataType: "category", minWidth: 100 },
{ field: "current", type: "measure" },
{ field: "previous", type: "measure" },
{ headerName: "PL 1", field: "pl1", type: "measure" },
{ headerName: "PL 2", field: "pl2", type: "measure" },
{ headerName: "Gain-DX", field: "gainDx", type: "measure" },
{ headerName: "SX / PX", field: "sxPx", type: "measure" },
{ field: "trade", type: "measure" },
{ field: "submitterID", type: "measure" },
{ field: "submitterDealID", type: "measure" },
{ field: "portfolio" },
{ field: "dealType" },
{ headerName: "Bid", field: "bidFlag" },
];
}
// Grid Options
const gridOptions: GridOptions = {
columnDefs: getColumnDefs(),
defaultColDef: {
editable: true,
flex: 1,
minWidth: 140,
filter: true,
},
columnTypes: {
measure: {
chartDataType: "series",
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer",
},
},
enableCharts: true,
suppressAggFuncInHeader: true,
getRowId: (params: GetRowIdParams) => String(params.data.trade),
getChartToolbarItems: (): ChartToolbarMenuItemOptions[] => [],
onFirstDataRendered,
};
// Initial Chart Creation
function onFirstDataRendered(params: any) {
chartRef = params.api.createRangeChart({
chartContainer: document.querySelector("#myChart") as any,
cellRange: {
columns: [
"product",
"current",
"previous",
"pl1",
"pl2",
"gainDx",
"sxPx",
],
},
suppressChartRanges: true,
chartType: "groupedColumn",
aggFunc: "sum",
chartThemeOverrides: {
common: {
animation: {
enabled: false,
},
},
},
});
}
function updateChart(chartType: ChartType) {
gridApi!.updateChart({
type: "rangeChartUpdate",
chartId: chartRef.chartId,
chartType,
});
}
function numberCellFormatter(params: ValueFormatterParams) {
return Math.floor(params.value)
.toString()
.replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}
function startWorker(): void {
worker = new Worker(`${__basePath || "."}/dataUpdateWorker.js`);
worker.addEventListener("message", handleWorkerMessage);
worker.postMessage("start");
}
function handleWorkerMessage(e: any): void {
if (e.data.type === "setRowData") {
gridApi!.setGridOption("rowData", e.data.records);
}
if (e.data.type === "updateData") {
gridApi!.applyTransactionAsync({ update: e.data.records });
}
}
// create the grid, then start streaming updates from the web worker
const eGridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(eGridDiv, gridOptions);
startWorker();
// Worker Commands
function onStartLoad(): void {
worker.postMessage("start");
}
function onStopMessages(): void {
worker.postMessage("stop");
}
if (typeof window !== "undefined") {
// Attach external event handlers to window so they can be called from index.html
(<any>window).updateChart = updateChart;
(<any>window).onStartLoad = onStartLoad;
(<any>window).onStopMessages = onStopMessages;
}
.number {
text-align: right;
}
.wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.my-grid {
flex-grow: 1;
}
.my-chart {
flex-grow: 1;
height: 135px;
display: inline-block;
margin-top: 0.5rem;
}
// NOTE: The details of this web worker are not important it's just used to simulate streaming updates in the grid.
// Constants
const UPDATES_PER_MESSAGE = 100;
const MILLISECONDS_BETWEEN_MESSAGES = 100;
const BOOK_COUNT = 5;
const TRADE_COUNT = 2;
const VALUE_FIELDS = ['current', 'previous', 'pl1', 'pl2', 'gainDx', 'sxPx', '_99Out'];
const PRODUCTS = [
'Cobalt',
'Rubber',
'Wool',
'Amber',
'Corn',
'Nickel',
'Copper',
'Oats',
'Coffee',
'Wheat',
'Lead',
'Zinc',
'Tin',
'Coca',
];
const PORTFOLIOS = ['Aggressive', 'Defensive', 'Income', 'Speculative', 'Hybrid'];
// Global Variables
let globalRowData;
let nextBookId = 62472;
let nextTradeId = 24287;
let nextBatchId = 101;
let latestUpdateId = 0;
let intervalId;
/**
* Generates a random number between min and max
*/
function randomBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// build up the test data
function createRowData() {
globalRowData = [];
var thisBatch = nextBatchId++;
for (var k = 0; k < BOOK_COUNT; k++) {
for (var j = 0; j < PORTFOLIOS.length; j++) {
var portfolio = PORTFOLIOS[j];
for (var i = 0; i < PRODUCTS.length; i++) {
var product = PRODUCTS[i];
var book = 'GL-' + ++nextBookId;
for (var l = 0; l < TRADE_COUNT; l++) {
var trade = createTradeRecord(product, portfolio, book, thisBatch);
globalRowData.push(trade);
}
}
}
}
// console.log('Total number of records sent to grid = ' + globalRowData.length);
}
function createTradeRecord(product, portfolio, book, batch) {
var current = Math.floor(Math.random() * 10000) + (Math.random() < 0.45 ? 500 : 19000);
var previous = current + (Math.random() < 0.5 ? 500 : 19000);
return {
product: product,
portfolio: portfolio,
book: book,
trade: ++nextTradeId,
submitterID: randomBetween(10, 1000),
submitterDealID: randomBetween(10, 1000),
dealType: Math.random() < 0.2 ? 'Physical' : 'Financial',
bidFlag: Math.random() < 0.5 ? 'Buy' : 'Sell',
current: current,
previous: previous,
pl1: randomBetween(10000, 30000),
pl2: randomBetween(8000, 35000),
gainDx: randomBetween(35000, 1000),
sxPx: randomBetween(10000, 30000),
batch: batch,
};
}
function updateSomeItems(updateCount) {
var itemsToUpdate = [];
for (var k = 0; k < updateCount; k++) {
if (globalRowData.length === 0) {
continue;
}
var indexToUpdate = Math.floor(Math.random() * globalRowData.length);
var itemToUpdate = globalRowData[indexToUpdate];
var field = VALUE_FIELDS[Math.floor(Math.random() * VALUE_FIELDS.length)];
itemToUpdate[field] += randomBetween(-8000, 8200);
itemsToUpdate.push(itemToUpdate);
}
return itemsToUpdate;
}
function startUpdates(thisUpdateId) {
// Cancel any interval a previous 'start' left running so only one is ever active.
clearInterval(intervalId);
postMessage({
type: 'start',
updateCount: UPDATES_PER_MESSAGE,
interval: MILLISECONDS_BETWEEN_MESSAGES,
});
function intervalFunc() {
// Check for cancellation before posting so Stop takes effect without an extra batch.
if (thisUpdateId !== latestUpdateId) {
clearInterval(intervalId);
return;
}
postMessage({
type: 'updateData',
records: updateSomeItems(UPDATES_PER_MESSAGE),
});
}
intervalId = setInterval(intervalFunc, MILLISECONDS_BETWEEN_MESSAGES);
}
function stopUpdates() {
clearInterval(intervalId);
}
// Initialize Row Data
createRowData();
// Notify that row data is ready
postMessage({
type: 'setRowData',
records: globalRowData,
});
// Event Listener for incoming messages
self.addEventListener('message', function (e) {
latestUpdateId++;
if (e.data === 'start') {
startUpdates(latestUpdateId);
} else if (e.data === 'stop') {
stopUpdates();
}
});
<div id="myApp" class="wrapper">
<div style="padding-bottom: 4px">
<span>
<button onclick="onStopMessages()">■ Stop</button>
<button onclick="onStartLoad()">► Start</button>
</span>
<span style="margin-left: 30px">
<button onclick="updateChart('stackedColumn')">Stacked Column Chart</button>
<button onclick="updateChart('groupedColumn')">Grouped Column Chart</button>
<button onclick="updateChart('line')">Line Chart</button>
</span>
</div>
<div id="myGrid" class="my-grid"></div>
<div id="myChart" class="my-chart"></div>
</div>
The dummy financial application above shows some of the grid's integrated charting capabilities. Note the following:
- Pre-Defined Chart: A pre-defined chart is shown in a separate chart container below the grid.
- Dynamic Charts: Buttons positioned above the grid dynamically create different chart types.
- High Performance: 100 rows are randomly updated 10 times a second (1,000 updates per second). Try updating the example via Plunker with higher update frequencies and more data.
Charts created through the Grid API do not require the enableCharts grid option. That option governs the user-initiated path only — whether the grid offers users the chartRange and pivotChart Context Menu items by default.
To learn how to create charts in your applications see the following sections for details:
- Range Chart API - create Range Charts using the Grid API
- Pivot Chart API - create Pivot Charts using the Grid API
- Cross Filter Chart API - create cross-filter charts where element clicks auto-filter grid and charts