Multi-level grouping and group summaries
Grouping itself is Community, and so is the group panel, and so are footer column totals. Enterprise adds exactly two things:
- a second and further grouping level,
- those same totals repeated on every group header.
Worth knowing before you reach for a licence key: if one level is enough, you already have everything.
One level — Community
import { DataGrid } from '@kanunilabs/datagrid-react';
<DataGrid
dataSource={orders}
rowKey="id"
columns={columns}
grouping={{ groupBy: 'region', panel: true }}
summaries={[
{ columnId: 'units', type: 'sum' },
{ columnId: 'revenue', type: 'sum' },
]}
/>
panel: true puts the drop strip above the grid; dragging a header into it
groups by that column. summaries draws the totals in the footer.
More levels — Enterprise
import { EnterpriseDataGrid } from '@kanunilabs/datagrid-react-enterprise';
<EnterpriseDataGrid
licenseKey={key}
dataSource={orders}
rowKey="id"
columns={columns}
grouping={{
groupBy: ['region', 'category', 'rep'], // outermost first
panel: true,
groupSummaries: true,
collapsedByDefault: true,
}}
summaries={[
{ columnId: 'units', type: 'sum' },
{ columnId: 'revenue', type: 'sum' },
]}
/>
| Option | Type | Default |
|---|---|---|
grouping.groupBy | string | string[] | null | null |
grouping.panel | boolean | false |
grouping.groupSummaries | boolean | false |
grouping.collapsedByDefault | boolean | false |
grouping.showGroupedColumns | boolean | false |
groupSummaries: true on its own shows nothing. The group header reuses the
summaries array, so an empty one produces empty headers. There is no separate
per-level or per-group definition list.
Totals land inside the header strip — ▸ Category: Toys (12) · Revenue: 1,240 — not in a footer row beneath the members, even though the header menu
item is worded "Show group footers".
Summary types are sum, avg, min, max, count and custom. avg,
min and max skip blanks; count counts non-empty cells.
summaries={[
{
columnId: 'margin',
type: 'custom',
calculate: (cells) => cells.reduce((a, c) => a + Number(c.value ?? 0), 0) / cells.length,
},
]}
Inside a group header, calculate receives only that group's cells.
Reacting to it
controller.events.on('groupChanged', ({ groupBy, levels }) => {
// groupBy = outermost level, levels = every level
});
columnStateChanged fires too, because the grouped column leaves the data area
and comes back when grouping is removed.
From code: controller.groupBy(id), controller.setGroupSummaries(true),
controller.expandAllGroups(), controller.collapseAllGroups().
What this does not do
- Community trims silently. Pass three levels to
DataGridand you get the first one;groupSummariesis forced tofalse. The warning is printed only underdebugMode: true. Adding a second level in the Community UI replaces the first rather than nesting — a deliberate swap, not a no-op. - Group keys are
String(value). No year/month/quarter bucketing for dates, nogroupInterval, nogroupValueGetter. Two values with the same text (1and'1') merge into one group;nullandundefinedcollect under a localised "(Blanks)". valueFormatteris not applied to the header caption. A formatter takes a row, and a group strip has no single row.lookupcolumns are the exception — those resolve to the label rather than the code.- The runtime "group footers" toggle is React-only. The vanilla header menu
has no such item; call
controller.setGroupSummaries(...)yourself. - Grouping outranks the user's sort. Group columns are pushed to the front of the sort, so a user's own sort becomes the secondary key. They can still flip a group column's direction, and that is kept.
- No auto group column. There is no
autoGroupColumnDefequivalent: the group row is one full-width cell pinned left, and depth is shown with 18px of indent. - Group headers cannot be selected. Space on a focused header toggles it; there is no "select every row in this group" checkbox.
groupable: falseonly closes the UI. The header menu item is disabled and the column drops out of the panel list, butcontroller.groupBy('thatOne')still works.- Export carries the label, not the totals. A group row exports as
"<label> (<count>)"in the first cell only. Styled Excel export does turn nesting into real outline levels; PDF only indents. - Group totals lag unsaved edits. Footer totals read pending edits; the cached group tree does not, so a group header can show the old number until the edit is committed.
- Tree wins over grouping. Configure both and
groupByis ignored, warned once underdebugMode. - Server-side grouping is the server's job. With a
DataSourceobject the client computes nothing: returngroups: RemoteGroupRow[]keyed ongroupKeyslength, with aggregates under${columnId}:${type}.type: 'custom'is never sent (a JS reducer cannot run there), andexpandAllGroups()is a deliberate no-op — it would mean fetching the whole table.collapseAllGroups()works.