Highlight changes by flashing or animating cells.
"use client";
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
CellStyleModule,
ClientSideRowModelModule,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
GridReadyEvent,
HighlightChangesModule,
ModuleRegistry,
RowApiModule,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
RowApiModule,
CellStyleModule,
ClientSideRowModelModule,
HighlightChangesModule,
];
let updateInterval: ReturnType<typeof setInterval>;
function formatNumber(number: number) {
return Math.floor(number).toLocaleString();
}
function createRowData() {
const rowData = [];
for (let i = 0; i < 20; i++) {
rowData.push({
a: Math.floor(((i + 323) * 145045) % 10000),
b: Math.floor(((i + 323) * 543020) % 10000),
c: Math.floor(((i + 323) * 305920) % 10000),
d: Math.floor(((i + 323) * 204950) % 10000),
e: Math.floor(((i + 323) * 103059) % 10000),
f: Math.floor(((i + 323) * 468276) % 10000),
});
}
return rowData;
}
const GridExample = () => {
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [rowData, setRowData] = useState<any[]>(createRowData());
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{ field: "a", enableCellChangeFlash: true },
{ field: "b", enableCellChangeFlash: true },
{ field: "c", cellRenderer: "agAnimateShowChangeCellRenderer" },
{ field: "d", cellRenderer: "agAnimateShowChangeCellRenderer" },
{ field: "e", cellRenderer: "agAnimateSlideCellRenderer" },
{ field: "f", cellRenderer: "agAnimateSlideCellRenderer" },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
flex: 1,
cellClass: "align-right",
valueFormatter: (params) => {
return formatNumber(params.value);
},
};
}, []);
const onGridReady = useCallback((params: GridReadyEvent) => {
const updateValues = () => {
// The timer outlives the grid, so stop once it is gone rather than calling a destroyed API.
if (params.api!.isDestroyed()) {
clearInterval(updateInterval);
return;
}
const rowCount = params.api!.getDisplayedRowCount();
// pick 2 cells at random to update
for (let i = 0; i < 2; i++) {
const row = Math.floor(window.agRandom() * rowCount);
const rowNode = params.api!.getDisplayedRowAtIndex(row)!;
const col = ["a", "b", "c", "d", "e", "f"][
Math.floor(window.agRandom() * 6)
];
rowNode.setDataValue(col, Math.floor(window.agRandom() * 10000));
}
};
updateInterval = setInterval(updateValues, 250);
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div style={gridStyle}>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
onGridReady={onGridReady}
/>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
.align-right {
text-align: right;
}
The example above shows changing values:
- Columns A and B use Flashing Cells.
- Columns C and D use the Animate Show Change cell renderer.
- Columns E and F use the Animate Slide Cell cell renderer.
Flashing Cells Copy Link
You can trigger cells to flash either though the Grid API or by enabling cells to flash when the data changes.
Enable Flashing on Data Change Copy Link
Set Column attribute enableCellChangeFlash=true to flash the cells when data changes.
"use client";
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
CellStyleModule,
ClientSideRowModelModule,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
HighlightChangesModule,
ModuleRegistry,
RowApiModule,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
RowApiModule,
HighlightChangesModule,
CellStyleModule,
ClientSideRowModelModule,
];
function formatNumber(number: number) {
return Math.floor(number).toLocaleString();
}
function createRowData() {
const rowData = [];
for (let i = 0; i < 20; i++) {
rowData.push({
a: Math.floor(((i + 323) * 25435) % 10000),
b: Math.floor(((i + 323) * 23221) % 10000),
c: Math.floor(((i + 323) * 468276) % 10000),
d: 0,
e: 0,
f: 0,
});
}
return rowData;
}
const GridExample = () => {
const gridRef = useRef<AgGridReact>(null);
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [rowData, setRowData] = useState<any[]>(createRowData());
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{ field: "a" },
{ field: "b" },
{ field: "c" },
{ field: "d" },
{ field: "e" },
{ field: "f" },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
flex: 1,
cellClass: "align-right",
enableCellChangeFlash: true,
valueFormatter: (params) => {
return formatNumber(params.value);
},
};
}, []);
const onUpdateSomeValues = useCallback(() => {
const rowCount = gridRef.current!.api.getDisplayedRowCount();
// pick 20 cells at random to update
for (let i = 0; i < 20; i++) {
const row = Math.floor(window.agRandom() * rowCount);
const rowNode = gridRef.current!.api.getDisplayedRowAtIndex(row)!;
const col = ["a", "b", "c", "d", "e", "f"][i % 6];
rowNode.setDataValue(col, Math.floor(window.agRandom() * 10000));
}
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div
style={{ height: "100%", display: "flex", flexDirection: "column" }}
>
<div style={{ marginBottom: "4px" }}>
<button onClick={onUpdateSomeValues}>Update Some Data</button>
</div>
<div style={{ flexGrow: "1" }}>
<div style={gridStyle}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
/>
</div>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
.align-right {
text-align: right;
}
In the example above:
- All columns have
enableCellChangeFlash=trueso changes to the cell values will flash the cell. - Clicking Update Some Data will randomly update some data. The grid will then flash the cells where data has changed.
To change the length of the effect, use the grid options cellFlashDuration and cellFadeDuration.
By default, value changes caused by updates to column filters are not highlighted with cell flashing. This behaviour can be toggled by enabling the grid option allowShowChangeAfterFilter.
Flash Cells using the API Copy Link
Alternatively flash cells using the grid API flashCells(params). The params object takes a list of columns and rows to flash, together with the flashDuration and the fadeDuration values.
Flash rows, columns or individual cells. |
When calling flashCells(), pass the flashDuration and fadeDuration values (in milliseconds) as params to change the duration of cell flashing.
"use client";
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
CellStyleModule,
ClientSideRowModelModule,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
HighlightChangesModule,
ModuleRegistry,
RowApiModule,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
HighlightChangesModule,
RowApiModule,
CellStyleModule,
ClientSideRowModelModule,
];
function formatNumber(number: number) {
return Math.floor(number).toLocaleString();
}
function createRowData() {
const rowData = [];
for (let i = 0; i < 20; i++) {
rowData.push({
a: Math.floor(((i + 323) * 25435) % 10000),
b: Math.floor(((i + 323) * 23221) % 10000),
c: Math.floor(((i + 323) * 468276) % 10000),
d: 0,
e: 0,
f: 0,
});
}
return rowData;
}
const GridExample = () => {
const gridRef = useRef<AgGridReact>(null);
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [rowData, setRowData] = useState<any[]>(createRowData());
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{ field: "a" },
{ field: "b" },
{ field: "c" },
{ field: "d" },
{ field: "e" },
{ field: "f" },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
flex: 1,
cellClass: "align-right",
valueFormatter: (params) => {
return formatNumber(params.value);
},
};
}, []);
const onFlashOneCell = useCallback(() => {
// pick fourth row at random
const rowNode = gridRef.current!.api.getDisplayedRowAtIndex(4)!;
// pick 'c' column
gridRef.current!.api.flashCells({ rowNodes: [rowNode], columns: ["c"] });
}, []);
const onFlashTwoColumns = useCallback(() => {
// flash whole column, so leave row selection out
gridRef.current!.api.flashCells({ columns: ["c", "d"] });
}, []);
const onFlashTwoRows = useCallback(() => {
// pick fourth and fifth row at random
const rowNode1 = gridRef.current!.api.getDisplayedRowAtIndex(4)!;
const rowNode2 = gridRef.current!.api.getDisplayedRowAtIndex(5)!;
// flash whole row, so leave column selection out
gridRef.current!.api.flashCells({ rowNodes: [rowNode1, rowNode2] });
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div
style={{ height: "100%", display: "flex", flexDirection: "column" }}
>
<div style={{ marginBottom: "4px" }}>
<button onClick={onFlashOneCell} style={{ marginLeft: "15px" }}>
Flash One Cell
</button>
<button onClick={onFlashTwoRows}>Flash Two Rows</button>
<button onClick={onFlashTwoColumns}>Flash Two Columns</button>
</div>
<div style={{ flexGrow: "1" }}>
<div style={gridStyle}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
/>
</div>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
.align-right {
text-align: right;
}
In the example above, all three buttons use the flashCells(params) API. Note the following:
- Clicking Flash One Cell uses parameters with one column and one row to flash the intersecting cell.
- Clicking Flash Two Rows uses parameters consisting of two row nodes, causing those rows to flash.
- Clicking Flash Two Columns uses parameters consisting of two columns, causing those columns to flash.
Customise Flash Colour Copy Link
Each time the cell value is changed, the grid adds the CSS class ag-cell-data-changed for 500ms by default, and then the CSS class ag-cell-data-changed-animation for 1,000ms by default. The grid-provided themes use this to apply a background colour. To override the flash background colour, override the relevant CSS class.
"use client";
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./style.css";
import {
CellStyleModule,
ClientSideRowModelModule,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
HighlightChangesModule,
ModuleRegistry,
RowApiModule,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
RowApiModule,
HighlightChangesModule,
CellStyleModule,
ClientSideRowModelModule,
];
function formatNumber(number: number) {
return Math.floor(number).toLocaleString();
}
function createRowData() {
const rowData = [];
for (let i = 0; i < 20; i++) {
rowData.push({
a: Math.floor(((i + 323) * 25435) % 10000),
b: Math.floor(((i + 323) * 23221) % 10000),
c: Math.floor(((i + 323) * 468276) % 10000),
d: 0,
e: 0,
f: 0,
});
}
return rowData;
}
const GridExample = () => {
const gridRef = useRef<AgGridReact>(null);
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [rowData, setRowData] = useState<any[]>(createRowData());
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{ field: "a" },
{ field: "b" },
{ field: "c" },
{ field: "d" },
{ field: "e" },
{ field: "f" },
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
flex: 1,
cellClass: "align-right",
enableCellChangeFlash: true,
valueFormatter: (params) => {
return formatNumber(params.value);
},
};
}, []);
const onUpdateSomeValues = useCallback(() => {
const rowCount = gridRef.current!.api.getDisplayedRowCount();
// pick 20 cells at random to update
for (let i = 0; i < 20; i++) {
const row = Math.floor(window.agRandom() * rowCount);
const rowNode = gridRef.current!.api.getDisplayedRowAtIndex(row)!;
const col = ["a", "b", "c", "d", "e", "f"][i % 6];
rowNode.setDataValue(col, Math.floor(window.agRandom() * 10000));
}
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div
style={{ height: "100%", display: "flex", flexDirection: "column" }}
>
<div style={{ marginBottom: "4px" }}>
<button onClick={onUpdateSomeValues}>Update Some Data</button>
</div>
<div style={{ flexGrow: "1" }}>
<div style={gridStyle}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
/>
</div>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
body {
--ag-value-change-value-highlight-background-color: #cc222244;
}
.align-right {
text-align: right;
}
The example above demonstrates customising the flashing cell background colour using the --ag-value-change-value-highlight-background-color CSS variable.
Animated Cell Renderers Copy Link
Interesting animations for data changes can be achieved using Cell Components. You can create your own or use one of the provided Show Change Cell Components. The grid provides two such components out of the box.
Animate Show Changed Cells Copy Link
The difference between the previous and new value is temporarily shown beside the new value and is then faded out. This difference is shown in either green or red, for an increase or decrease in value respectively, alongside an arrow indicating the direction of change.
"use client";
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
CellStyleModule,
ClientSideRowModelModule,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
HighlightChangesModule,
ModuleRegistry,
RowApiModule,
TextEditorModule,
ValueParserParams,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
TextEditorModule,
RowApiModule,
CellStyleModule,
ClientSideRowModelModule,
HighlightChangesModule,
];
function numberValueParser(params: ValueParserParams) {
return Number(params.newValue);
}
function formatNumber(number: number) {
return Math.floor(number).toLocaleString();
}
function createRowData() {
const rowData = [];
for (let i = 0; i < 20; i++) {
rowData.push({
a: Math.floor(((i + 323) * 25435) % 10000),
b: Math.floor(((i + 323) * 23221) % 10000),
c: Math.floor(((i + 323) * 468276) % 10000),
d: 0,
});
}
return rowData;
}
const GridExample = () => {
const gridRef = useRef<AgGridReact>(null);
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [rowData, setRowData] = useState<any[]>(createRowData());
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{
headerName: "Editable A",
field: "a",
editable: true,
valueParser: numberValueParser,
},
{
headerName: "Editable B",
field: "b",
editable: true,
valueParser: numberValueParser,
},
{
headerName: "API C",
field: "c",
minWidth: 135,
valueParser: numberValueParser,
cellRenderer: "agAnimateShowChangeCellRenderer",
},
{
headerName: "API D",
field: "d",
minWidth: 135,
valueParser: numberValueParser,
cellRenderer: "agAnimateShowChangeCellRenderer",
},
{
headerName: "Total",
valueGetter: "data.a + data.b + data.c + data.d",
minWidth: 135,
cellRenderer: "agAnimateShowChangeCellRenderer",
},
{
headerName: "Average",
valueGetter: "(data.a + data.b + data.c + data.d) / 4",
minWidth: 135,
cellRenderer: "agAnimateShowChangeCellRenderer",
},
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
minWidth: 105,
flex: 1,
cellClass: "align-right",
valueFormatter: (params) => {
return formatNumber(params.value);
},
};
}, []);
const onUpdateSomeValues = useCallback(() => {
const rowCount = gridRef.current!.api.getDisplayedRowCount();
for (let i = 0; i < 10; i++) {
const row = Math.floor(window.agRandom() * rowCount);
const rowNode = gridRef.current!.api.getDisplayedRowAtIndex(row)!;
rowNode.setDataValue("c", Math.floor(window.agRandom() * 10000));
rowNode.setDataValue("d", Math.floor(window.agRandom() * 10000));
}
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div className="example-wrapper">
<div style={{ marginBottom: "5px" }}>
<button onClick={onUpdateSomeValues}>
Update Some C & D Values
</button>
</div>
<div style={gridStyle}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
/>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
.align-right {
text-align: right;
}
The example above demonstrates the following:
- Columns A, B are editable.
- Columns C and D are updated via clicking the button.
- Changes to any of the first 4 columns results in animations in the Total and Average column.
- This can be set as a cell renderer in the column definitions:
const [columnDefs, setColumnDefs] = useState([
// set the cell renderer in the column definition
{ cellRenderer: "agAnimateShowChangeCellRenderer" },
]);
<AgGridReact columnDefs={columnDefs} /> Animate Slide Cells Copy Link
The previous value is shown in a faded fashion and slides, giving a ghosting effect as the old value fades and slides away.
"use client";
import React, {
useCallback,
useMemo,
useRef,
useState,
StrictMode,
} from "react";
import { createRoot } from "react-dom/client";
import { AgGridReact, AgGridProvider } from "ag-grid-react";
import "./styles.css";
import {
CellStyleModule,
ClientSideRowModelModule,
ColDef,
ColGroupDef,
GridApi,
GridOptions,
HighlightChangesModule,
ModuleRegistry,
RowApiModule,
TextEditorModule,
ValueParserParams,
enableDevValidations,
} from "ag-grid-community";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableDevValidations();
}
const modules = [
TextEditorModule,
RowApiModule,
CellStyleModule,
ClientSideRowModelModule,
HighlightChangesModule,
];
function numberValueParser(params: ValueParserParams) {
return Number(params.newValue);
}
function formatNumber(number: number) {
return Math.floor(number).toLocaleString();
}
function createRowData() {
const rowData = [];
for (let i = 0; i < 20; i++) {
rowData.push({
a: Math.floor(((i + 323) * 25435) % 10000),
b: Math.floor(((i + 323) * 23221) % 10000),
c: Math.floor(((i + 323) * 468276) % 10000),
d: 0,
});
}
return rowData;
}
const GridExample = () => {
const gridRef = useRef<AgGridReact>(null);
const containerStyle = useMemo(() => ({ width: "100%", height: "100%" }), []);
const gridStyle = useMemo(() => ({ height: "100%", width: "100%" }), []);
const [rowData, setRowData] = useState<any[]>(createRowData());
const [columnDefs, setColumnDefs] = useState<ColDef[]>([
{
headerName: "Editable A",
field: "a",
editable: true,
valueParser: numberValueParser,
},
{
headerName: "Editable B",
field: "b",
editable: true,
valueParser: numberValueParser,
},
{
headerName: "API C",
field: "c",
minWidth: 135,
valueParser: numberValueParser,
cellRenderer: "agAnimateSlideCellRenderer",
},
{
headerName: "API D",
field: "d",
minWidth: 135,
valueParser: numberValueParser,
cellRenderer: "agAnimateSlideCellRenderer",
},
{
headerName: "Total",
valueGetter: "data.a + data.b + data.c + data.d",
minWidth: 135,
cellRenderer: "agAnimateSlideCellRenderer",
},
{
headerName: "Average",
valueGetter: "(data.a + data.b + data.c + data.d) / 4",
minWidth: 135,
cellRenderer: "agAnimateSlideCellRenderer",
},
]);
const defaultColDef = useMemo<ColDef>(() => {
return {
minWidth: 105,
flex: 1,
cellClass: "align-right",
valueFormatter: (params) => {
return formatNumber(params.value);
},
};
}, []);
const onUpdateSomeValues = useCallback(() => {
const rowCount = gridRef.current!.api.getDisplayedRowCount();
for (let i = 0; i < 10; i++) {
const row = Math.floor(window.agRandom() * rowCount);
const rowNode = gridRef.current!.api.getDisplayedRowAtIndex(row)!;
rowNode.setDataValue("c", Math.floor(window.agRandom() * 10000));
rowNode.setDataValue("d", Math.floor(window.agRandom() * 10000));
}
}, []);
return (
<AgGridProvider modules={modules}>
<div style={containerStyle}>
<div className="example-wrapper">
<div style={{ marginBottom: "5px" }}>
<button onClick={onUpdateSomeValues}>
Update Some C & D Values
</button>
</div>
<div style={gridStyle}>
<AgGridReact
ref={gridRef}
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
/>
</div>
</div>
</div>
</AgGridProvider>
);
};
const root = createRoot(document.getElementById("root")!);
root.render(
<StrictMode>
<GridExample />
</StrictMode>,
);
.example-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
#myGrid {
flex: 1 1 0px;
width: 100%;
}
.align-right {
text-align: right;
}
The example above demonstrates the following:
- Columns A, B are editable.
- Columns C and D are updated via clicking the button.
- Changes to any of the first 4 columns results in animations in the Total and Average column.
- This can be set as a cell renderer in the column definitions:
const [columnDefs, setColumnDefs] = useState([
// set the cell renderer in the column definition
{ cellRenderer: "agAnimateSlideCellRenderer" },
]);
<AgGridReact columnDefs={columnDefs} />