Grouping your data by calculated values

Grouping your data by calculated values

Find out how to do custom grouping by calculated values

4 min readJuly 15, 2025Radu
When grouping data, most often, your group values are already in the data-set. However, there are some cases when your grouping logic is more complex than simply using existing fields in the data-set.
You might want to group by a computed field, or by using 2 fields at once.
In this article, we're going to use the olympic records data-set, which displays records about olympic medals - athlete name, country, year, season (winter or summer) and medal type.
Instead of grouping by year and then season, we want to group by the olympic game edition: eg: summer 2016. So effectively we need to group by year and season in the same group. Instead of heaving 2 grouping criteria, we'll have a single
<year,season>
criteria.
In addition, for second and third level grouping, lets group by
Medal
and then by
Team
.

Defining the computed group value#

Play time ahead, so let's dive into the code. We'll demo how you can have custom computed values in both AG Grid and Infinite Table.
In AG Grid we achieve grouping by a computed value by using the
keyCreator
column property. This let's you define the key to be used when grouping.
Grouping in AG Grid
View Mode
Fork
import { AgGridReact } from "ag-grid-react";

import data, { type OlympicRecord } from "@thedatagrid/data/olympics_10k";

import {
  AllEnterpriseModule,
  ColDef,
  ModuleRegistry,
  themeQuartz,
  colorSchemeDark,
} from "ag-grid-enterprise";

ModuleRegistry.registerModules([AllEnterpriseModule]);

// Column definitions
const columnDefs: ColDef<OlympicRecord>[] = [
  { field: "Name", headerName: "Name" },
  { field: "Medal", headerName: "Medal", rowGroupIndex: 1 },
  { field: "Sport", headerName: "Sport" },
  { field: "Team", headerName: "Team", rowGroupIndex: 2 },
  { field: "Year", headerName: "Year" },
  {
    field: "Season",
    headerName: "Olympic Season",
    keyCreator: (params) => `${params.data.Year}-${params.data.Season}`,
    valueFormatter: (params) => {
      return `${params.node?.key}`;
    },

    rowGroupIndex: 0,
  },
  { field: "City", headerName: "City" },
];

export default function () {
  return (
    <AgGridReact
      columnDefs={columnDefs}
      rowData={data}
      theme={themeQuartz.withPart(colorSchemeDark)}
    />
  );
}
To specify the grouping order, we leverage
rowGroupIndex
in the column definitions.
const columnDefs = [
  { field: "Name", headerName: "Name" },
  { field: "Medal", headerName: "Medal", rowGroupIndex: 1 },
  { field: "Sport", headerName: "Sport" },
  { field: "Team", headerName: "Team", rowGroupIndex: 2 },
  { field: "Year", headerName: "Year" },
  {
    field: "Season",
    rowGroupIndex: 0,
    headerName: "Olympic Season",
    keyCreator: (params) => `${params.data.Year}-${params.data.Season}`,
    valueFormatter: (params) => {
      return `${params.node?.key}`;
    },
  },
  { field: "City", headerName: "City" },
];
Otherwise, the column order would determine the grouping order, which we don't want.
For comparison, let's have a look at Infinite Table.
Infinite Table has a data-first approach, and the grouping is defined on the
<DataSource />
component and is not neccessarily bound to a column.
In order to define grouping in Infinite Table, you have to define a
groupBy
array, which lists the grouping criteria, in order.
For the computed grouping, you define a
valueGetter
for the custom group key. All other groups are simply bound to fields.
Grouping in Infinite Table
View Mode
Fork
import "@infinite-table/infinite-react/index.css";

import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
  DataSourceGroupBy,
  DataSourceProps,
} from "@infinite-table/infinite-react";

import data, { type OlympicRecord } from "@thedatagrid/data/olympics_10k";

const columns: Record<string, InfiniteTableColumn<OlympicRecord>> = {
  Name: { field: "Name" },
  Medal: { field: "Medal" },
  Sport: { field: "Sport" },
  Team: { field: "Team" },
  Year: { field: "Year" },
  Season: { field: "Season" },
  City: { field: "City" },
};

const defaultGrouping: DataSourceGroupBy<OlympicRecord>[] = [
  {
    field: "Season",
    valueGetter: (row) => `${row.data.Year}-${row.data.Season}`,
  },
  {
    field: "Medal",
  },
  {
    field: "Team",
  },
];

const groupRowsState: DataSourceProps<OlympicRecord>["groupRowsState"] = {
  expandedRows: [],
  collapsedRows: true,
};

export default function () {
  return (
    <DataSource
      primaryKey={"Id"}
      defaultGroupRowsState={groupRowsState}
      data={data}
      defaultGroupBy={defaultGrouping}
    >
      <InfiniteTable columns={columns} groupRenderStrategy="single-column" />
    </DataSource>
  );
}
By default, Infinite Table does not show the group count.

Customizing the group column & row rendering#

In the examples above, notice how Infinite Table does not show a group count. To do that, you have to customize the rendering of the
groupColumn
in order to include that. The snippet below shows how you can achieve this.
Customizing the group column rendering in Infinite Table
const groupColumn = {
  defaultWidth: 220,
  renderValue: (params) => {
    if (params.isGroupRow) {
      return `${params.value} (${params.rowInfo.groupCount})`;
    }
    return params.value;
  },
};
Now let's try and style the group row.
In Infinite Table, we can use the
rowStyle
function.

Row styling

In Infinite Table, the
rowStyle
prop can either be an object or a function.
rowStyle={({ rowInfo }) => {
  if (rowInfo.isGroupRow) {
    return {
      backgroundColor: "#9b6604",
    };
  }
}}
In addition, let's render all groups expanded.
Grouping in Infinite Table
View Mode
Fork
import "@infinite-table/infinite-react/index.css";

import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
  DataSourceGroupBy,
  DataSourceProps,
} from "@infinite-table/infinite-react";

import data, { type OlympicRecord } from "@thedatagrid/data/olympics_10k";

const columns: Record<string, InfiniteTableColumn<OlympicRecord>> = {
  Name: { field: "Name" },
  Medal: { field: "Medal" },
  Sport: { field: "Sport" },
  Team: { field: "Team" },
  Year: { field: "Year" },
  Season: { field: "Season" },
  City: { field: "City" },
};

const defaultGrouping: DataSourceGroupBy<OlympicRecord>[] = [
  {
    field: "Season",
    valueGetter: (row) => `${row.data.Year}-${row.data.Season}`,
  },
  {
    field: "Medal",
  },
  {
    field: "Team",
  },
];

const groupColumn: InfiniteTableColumn<OlympicRecord> = {
  defaultWidth: 220,
  renderValue: (params) => {
    if (params.isGroupRow) {
      return `${params.value} (${params.rowInfo.groupCount})`;
    }
    return params.value;
  },
};

export default function () {
  return (
    <DataSource primaryKey={"Id"} data={data} defaultGroupBy={defaultGrouping}>
      <InfiniteTable
        columns={columns}
        groupRenderStrategy="single-column"
        groupColumn={groupColumn}
        rowStyle={({ rowInfo }) => {
          if (rowInfo.isGroupRow) {
            return {
              backgroundColor: "#9b6604",
            };
          }
        }}
      />
    </DataSource>
  );
}
Let's do the same in AG Grid. AG Grid exposes rowStyle object and also the getRowStyle function to add styles dynamically.
To expand everything we'll add the
groupDefaultExpanded=-1
prop.
Adding styles to your custom AG Grid grouping
View Mode
Fork
import { AgGridReact } from "ag-grid-react";

import data, { type OlympicRecord } from "@thedatagrid/data/olympics_10k";

import {
  AllEnterpriseModule,
  ColDef,
  colorSchemeDark,
  ModuleRegistry,
  themeQuartz,
} from "ag-grid-enterprise";

ModuleRegistry.registerModules([AllEnterpriseModule]);

// Column definitions
const columnDefs: ColDef<OlympicRecord>[] = [
  { field: "Name", headerName: "Name" },
  { field: "Medal", headerName: "Medal", rowGroupIndex: 1 },
  { field: "Sport", headerName: "Sport" },
  { field: "Team", headerName: "Team", rowGroupIndex: 2 },
  { field: "Year", headerName: "Year" },
  {
    field: "Season",
    headerName: "Olympic Season",
    keyCreator: (params) => `${params.data.Year}-${params.data.Season}`,
    valueFormatter: (params) => {
      return `${params.node?.key}`;
    },

    rowGroupIndex: 0,
  },
  { field: "City", headerName: "City" },
];

export default function () {
  return (
    <AgGridReact
      columnDefs={columnDefs}
      rowData={data}
      theme={themeQuartz.withPart(colorSchemeDark)}
      groupDefaultExpanded={-1}
      getRowStyle={({ node }) => {
        if (node.group) {
          return {
            backgroundColor: "#9b6604",
          };
        }
      }}
    />
  );
}

Conclusions#

This sums up some of the lesser known functionalities about grouping on a computed value.
In addition to that, this article shows how you can easily achieve custom expand/collapse states for group rows and custom styling.
It also gives you an idea of how AG Grid and Infinite Table compare when specifying your grouping definitions.
We hope you've found this useful.

For more content like this, follow us on at @thedatagrid