Skip to content

Instantly share code, notes, and snippets.

@nickebbutt
Created August 15, 2019 09:48
Show Gist options
  • Save nickebbutt/c01527ce09fc04647d1180446389ca88 to your computer and use it in GitHub Desktop.
Save nickebbutt/c01527ce09fc04647d1180446389ca88 to your computer and use it in GitHub Desktop.
Ag grid fast feed using immutable store with deltaRowDataMode=true
"use strict";
import React, { Component } from "react";
import { render } from "react-dom";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-enterprise";
class GridExample extends Component {
constructor(props) {
super(props);
this.state = {
columnDefs: [
{
headerName: "Product",
field: "product",
enableRowGroup: true,
enablePivot: true,
rowGroupIndex: 0,
hide: true
},
{
headerName: "Portfolio",
field: "portfolio",
enableRowGroup: true,
enablePivot: true,
rowGroupIndex: 1,
hide: true
},
{
headerName: "Book",
field: "book",
enableRowGroup: true,
enablePivot: true,
rowGroupIndex: 2,
hide: true
},
{
headerName: "Trade",
field: "trade",
width: 100
},
{
headerName: "Deal Type",
field: "dealType",
enableRowGroup: true,
enablePivot: true
},
{
headerName: "Bid",
field: "bidFlag",
enableRowGroup: true,
enablePivot: true,
width: 100
},
{
headerName: "Comment",
field: "comment",
editable: true
},
{
headerName: "Batch",
field: "batch",
width: 100,
cellClass: "number",
aggFunc: "max",
enableValue: true,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "Current",
field: "current",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "Previous",
field: "previous",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "Change",
valueGetter: changeValueGetter,
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "PL 1",
field: "pl1",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "PL 2",
field: "pl2",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "Gain-DX",
field: "gainDx",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "SX / PX",
field: "sxPx",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "99 Out",
field: "_99Out",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "Submitter ID",
field: "submitterID",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
},
{
headerName: "Submitted Deal ID",
field: "submitterDealID",
width: 150,
aggFunc: "sum",
enableValue: true,
cellClass: "number",
valueFormatter: numberCellFormatter,
cellRenderer: "agAnimateShowChangeCellRenderer"
}
],
statusBar: { items: [{ component: "agAggregationComponent" }] },
rowGroupPanelShow: "always",
pivotPanelShow: "always",
getRowNodeId: function(data) {
return data.trade;
},
defaultColDef: {
width: 120,
sortable: true,
resizable: true
},
autoGroupColumnDef: { width: 200 }
};
}
onGridReady = params => {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
timeoutTarget(this.gridApi);
createRowData();
params.api.setRowData(globalRowData);
};
toggleFeed() {
feedActive = !feedActive;
var buttonText = feedActive ? "◼ Stop Feed" : "► Start Feed";
document.querySelector("#toggleInterval").innerHTML = buttonText;
}
doUpdate() {
updateUsingDeltas(this.gridApi)
}
render() {
return (
<div style={{ width: "100%", height: "100%" }}>
<div style={{ marginBottom: "5px" }}>
<button onClick={this.doUpdate.bind(this)}>Update Using Deltas</button>
<button onClick={this.toggleFeed.bind(this)} id="toggleInterval">
► Start Feed
</button>
</div>
<div style={{ height: "calc(100% - 25px)" }}>
<div
id="myGrid"
style={{
height: "100%",
width: "100%"
}}
className="ag-theme-balham"
>
<AgGridReact
columnDefs={this.state.columnDefs}
deltaRowDataMode={true}
statusBar={this.state.statusBar}
animateRows={true}
enableRangeSelection={true}
rowGroupPanelShow={this.state.rowGroupPanelShow}
pivotPanelShow={this.state.pivotPanelShow}
suppressAggFuncInHeader={true}
getRowNodeId={this.state.getRowNodeId}
defaultColDef={this.state.defaultColDef}
autoGroupColumnDef={this.state.autoGroupColumnDef}
onGridReady={this.onGridReady}
/>
</div>
</div>
</div>
);
}
}
var MIN_BOOK_COUNT = 2;
var MAX_BOOK_COUNT = 4;
var MIN_TRADE_COUNT = 1;
var MAX_TRADE_COUNT = 10;
var products = [
"Palm Oil",
"Rubber",
"Wool",
"Amber",
"Copper",
"Lead",
"Zinc",
"Tin",
"Aluminium",
"Aluminium Alloy",
"Nickel",
"Cobalt",
"Molybdenum",
"Recycled Steel",
"Corn",
"Oats",
"Rough Rice",
"Soybeans",
"Rapeseed",
"Soybean Meal",
"Soybean Oil",
"Wheat",
"Milk",
"Coca",
"Coffee C",
"Cotton No.2",
"Sugar No.11",
"Sugar No.14"
];
var portfolios = ["Aggressive", "Defensive", "Income", "Speculative", "Hybrid"];
var productToPortfolioToBooks = {};
var nextBookId = 62472;
var nextTradeId = 24287;
var nextBatchId = 101;
function changeValueGetter(params) {
return params.data.previous - params.data.current;
}
var globalRowData;
function createRowData() {
globalRowData = [];
var thisBatch = nextBatchId++;
for (var i = 0; i < products.length; i++) {
var product = products[i];
productToPortfolioToBooks[product] = {};
for (var j = 0; j < portfolios.length; j++) {
var portfolio = portfolios[j];
productToPortfolioToBooks[product][portfolio] = [];
var bookCount = randomBetween(MAX_BOOK_COUNT, MIN_BOOK_COUNT);
for (var k = 0; k < bookCount; k++) {
var book = createBookName();
productToPortfolioToBooks[product][portfolio].push(book);
var tradeCount = randomBetween(MAX_TRADE_COUNT, MIN_TRADE_COUNT);
for (var l = 0; l < tradeCount; l++) {
var trade = createTradeRecord(product, portfolio, book, thisBatch);
globalRowData.push(trade);
}
}
}
}
}
function randomBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function createTradeRecord(product, portfolio, book, batch) {
var current = Math.floor(Math.random() * 100000) + 100;
var previous = current + Math.floor(Math.random() * 10000) - 2000;
var trade = {
product: product,
portfolio: portfolio,
book: book,
trade: createTradeId(),
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(100, 1000),
pl2: randomBetween(100, 1000),
gainDx: randomBetween(100, 1000),
sxPx: randomBetween(100, 1000),
_99Out: randomBetween(100, 1000),
batch: batch
};
return trade;
}
function numberCellFormatter(params) {
return Math.floor(params.value)
.toString()
.replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}
function createBookName() {
nextBookId++;
return "GL-" + nextBookId;
}
function createTradeId() {
nextTradeId++;
return nextTradeId;
}
// function updateUsingTransaction(gridApi) {
// var itemsToRemove = removeSomeItems();
// var itemsToAdd = addSomeItems();
// var itemsToUpdate = updateSomeItems();
// gridApi.updateRowData({
// add: itemsToAdd,
// remove: itemsToRemove,
// update: itemsToUpdate
// });
// }
function updateUsingDeltas(gridApi) {
removeSomeItems();
addSomeItems();
updateSomeItems();
gridApi.setRowData(globalRowData);
}
function updateSomeItems() {
var updateCount = randomBetween(1, 500);
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 updatedItem = updateImmutableObject(itemToUpdate, {
previous: itemToUpdate.current,
current: itemToUpdate.current + randomBetween(0, 1000) - 500
});
globalRowData[indexToUpdate] = updatedItem;
itemsToUpdate.push(updatedItem);
}
return itemsToUpdate;
}
function addSomeItems() {
var addCount = randomBetween(0, 1);
var itemsToAdd = [];
var batch = nextBatchId++;
for (var j = 0; j < addCount; j++) {
var portfolio = portfolios[Math.floor(Math.random() * portfolios.length)];
var books = productToPortfolioToBooks["Palm Oil"][portfolio];
var book = books[Math.floor(Math.random() * books.length)];
var product = products[Math.floor(Math.random() * products.length)];
var trade = createTradeRecord(product, portfolio, book, batch);
itemsToAdd.push(trade);
globalRowData.push(trade);
}
return itemsToAdd;
}
function removeSomeItems() {
var removeCount = randomBetween(0, 1);
var itemsToRemove = [];
for (var i = 0; i < removeCount; i++) {
if (globalRowData.length === 0) {
continue;
}
var indexToRemove = randomBetween(0, globalRowData.length);
var itemToRemove = globalRowData[indexToRemove];
globalRowData.splice(indexToRemove, 1);
itemsToRemove.push(itemToRemove);
}
return itemsToRemove;
}
function updateImmutableObject(original, newValues) {
var newObject = {};
Object.keys(original).forEach(function(key) {
newObject[key] = original[key];
});
Object.keys(newValues).forEach(function(key) {
newObject[key] = newValues[key];
});
return newObject;
}
var feedActive = false;
function timeoutTarget(gridApi) {
if (feedActive) {
updateUsingDeltas(gridApi);
}
var millis = randomBetween(100, 200);
setTimeout(timeoutTarget, millis, gridApi);
}
render(<GridExample />, document.querySelector("#root"));
@nickebbutt
Copy link
Author

AG grid with a fast feed using 'immutable data' and deltaRowDataMode=true

This is a modified version of the ag grid Complex Immutable Store example here which seeks to demonstrate use of the 'deltaRowDataMode'

The 'feed' functionality has been modified to use the immutable setData() rather than a transaction
i.e. api.setRowData(immutableRows) rather than gridApi.updateRowData(transaction)

The speed of the feed has been increased to update several thousand rows per second to better reflect the requirements for a 'fast ticking pricing view'

Note on immutability

In this example, the row data is not strictly speaking immutable since it relies on standard js arrays and objects
Similar to the official example, this does not use immutable.js or equivalent immutable collections

The main row data array is in fact mutated on inserts/updates/deletes as per the original example.. this seems not to matter.
(Since this does not create problems I am guessing is iterated synchronously during the call to setData and no reference to it is maintained internally within ag grid afterwards)

The row data objects are not modified but copies are created with updated fields during an update transaction - so in this sense they are treated as immutable
(This presumably works because updated row objects then fail instance equality checking).
Clearly it is not as safe as would be use of a truly immutable collection since these objects permit mutation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment