Group rows by one or more columns using the rowGroupModel
property. This property accepts an array
of column id
strings, where each id
represents a groupable column.
rowGroupModel
defines how the grid groups data. Each entry creates a group level, producing group
rows that can contain child rows. This creates a tree-like view of your data.
Note: Group rows are not aggregated by default, so their cell values appear empty. To aggregate group rows, see the Row Aggregation guide.
"use client";
import { Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import {
ChevronDownIcon,
ChevronRightIcon,
} from "@1771technologies/lytenyte-pro/icons";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", type: "number" },
{ id: "job" },
{ id: "balance", type: "number" },
{ id: "education" },
{ id: "marital" },
{ id: "default" },
{ id: "housing" },
{ id: "loan" },
{ id: "contact" },
{ id: "day", type: "number" },
{ id: "month" },
{ id: "duration" },
{ id: "campaign" },
{ id: "pdays" },
{ id: "previous" },
{ id: "poutcome" },
{ id: "y" },
];
export default function RowGrouping() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
rowGroupModel: ["job", "education"],
rowGroupColumn: {
cellRenderer: ({ grid, row, column }) => {
if (!grid.api.rowIsGroup(row)) return null;
const field = grid.api.columnField(column, row);
const isExpanded = grid.api.rowGroupIsExpanded(row);
return (
<div
className="flex items-center gap-2 w-full h-full"
style={{ paddingLeft: row.depth * 16 }}
>
<button
className="flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
grid.api.rowGroupToggle(row);
}}
>
{!isExpanded && <ChevronRightIcon />}
{isExpanded && <ChevronDownIcon />}
</button>
<div>{`${field}`}</div>
</div>
);
},
},
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}
Any column can be added to rowGroupModel
. It's your responsibility to group only on columns that
make sense for your data.
Instead of grouping by a column, you can use a custom field object. Pass an object with a field
property to perform arbitrary grouping unrelated to any single column.
export interface RowGroupField<T> {
readonly kind: "field";
readonly id: string;
readonly field: FieldRowGroup<T>;
readonly name?: string;
}
For example, you can group by an age bucket rather than the exact age:
"use client";
import {
Grid,
GROUP_COLUMN_SINGLE_ID,
useClientRowDataSource,
} from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import {
ChevronDownIcon,
ChevronRightIcon,
} from "@1771technologies/lytenyte-pro/icons";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", type: "number" },
{ id: "job" },
{ id: "balance", type: "number" },
{ id: "education" },
{ id: "marital" },
{ id: "default" },
{ id: "housing" },
{ id: "loan" },
{ id: "contact" },
{ id: "day", type: "number" },
{ id: "month" },
{ id: "duration" },
{ id: "campaign" },
{ id: "pdays" },
{ id: "previous" },
{ id: "poutcome" },
{ id: "y" },
];
export default function RowGrouping() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
sortModel: [{ sort: { kind: "string" }, columnId: GROUP_COLUMN_SINGLE_ID }],
rowGroupModel: [
{
kind: "field",
id: "age-buckets",
field: ({ data }) => {
if (!data) return "-";
if (data.age < 20) return "0-20";
if (data.age < 30) return "20-30";
if (data.age < 40) return "30-40";
if (data.age < 50) return "40-50";
return "50+";
},
},
],
rowGroupColumn: {
cellRenderer: ({ grid, row, column }) => {
if (!grid.api.rowIsGroup(row)) return null;
const field = grid.api.columnField(column, row);
const isExpanded = grid.api.rowGroupIsExpanded(row);
return (
<div
className="flex items-center gap-2 w-full h-full"
style={{ paddingLeft: row.depth * 16 }}
>
<button
className="flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
grid.api.rowGroupToggle(row);
}}
>
{!isExpanded && <ChevronRightIcon />}
{isExpanded && <ChevronDownIcon />}
</button>
<div>{`${field}`}</div>
</div>
);
},
},
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}
When row groups are applied, LyteNyte Grid creates extra columns for these groups. Control their
display with the rowGroupDisplayMode
property:
"single-column"
- Default. One group column expands/collapses all groups; most space-efficient."multi-column"
- Creates a separate group column for each item in the row group model."custom"
- Lets you provide your own expand/collapse column. LyteNyte Grid won't create one."use client";
import { Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import {
ChevronDownIcon,
ChevronRightIcon,
} from "@1771technologies/lytenyte-pro/icons";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", type: "number" },
{ id: "job" },
{ id: "balance", type: "number" },
{ id: "education" },
{ id: "marital" },
{ id: "default" },
{ id: "housing" },
{ id: "loan" },
{ id: "contact" },
{ id: "day", type: "number" },
{ id: "month" },
{ id: "duration" },
{ id: "campaign" },
{ id: "pdays" },
{ id: "previous" },
{ id: "poutcome" },
{ id: "y" },
];
export default function RowGroupingMultiColumns() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
rowGroupModel: ["job", "education"],
rowGroupExpansions: { "root:unemployed": true },
rowGroupDisplayMode: "multi-column",
rowGroupColumn: {
headerRenderer: ({ grid, column }) => {
const index = grid.api.rowGroupColumnIndex(column);
const group = grid.state.rowGroupModel.useValue()[index];
const def =
typeof group === "string" ? grid.api.columnById(group)! : group;
const name = def.name ?? def.id;
return (
<div className="flex items-center w-full h-full px-2">{name}</div>
);
},
cellRenderer: ({ grid, row, column }) => {
if (!grid.api.rowIsGroup(row)) return null;
const index = grid.api.rowGroupColumnIndex(column);
if (row.depth !== index) return null;
const field = grid.api.columnField(column, row);
const isExpanded = grid.api.rowGroupIsExpanded(row);
return (
<div
className="flex items-center gap-2 w-full h-full"
style={{ paddingLeft: row.depth * 16 }}
>
<button
className="flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
grid.api.rowGroupToggle(row);
}}
>
{!isExpanded && <ChevronRightIcon />}
{isExpanded && <ChevronDownIcon />}
</button>
<div>{`${field}`}</div>
</div>
);
},
},
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}
Use the rowGroupColumn
property to customize group columns. This property accepts a simplified
column configuration that LyteNyte Grid applies when creating group columns.
The rowGroupExpansions
property tracks which groups are expanded and controls when child rows are
visible.
If a group isn't listed in rowGroupExpansions
, the grid uses the rowGroupDefaultExpansion
property, which can be:
true
) or none (false
) of the groups by default.Depth refers to the group's position in the rowGroupModel
.
"use client";
import { Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import {
ChevronDownIcon,
ChevronRightIcon,
} from "@1771technologies/lytenyte-pro/icons";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", type: "number" },
{ id: "job" },
{ id: "balance", type: "number" },
{ id: "education" },
{ id: "marital" },
{ id: "default" },
{ id: "housing" },
{ id: "loan" },
{ id: "contact" },
{ id: "day", type: "number" },
{ id: "month" },
{ id: "duration" },
{ id: "campaign" },
{ id: "pdays" },
{ id: "previous" },
{ id: "poutcome" },
{ id: "y" },
];
export default function RowGroupingDefaultExpansions() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
rowGroupModel: ["job", "education"],
rowGroupDefaultExpansion: 0,
rowGroupColumn: {
cellRenderer: ({ grid, row, column }) => {
if (!grid.api.rowIsGroup(row)) return null;
const field = grid.api.columnField(column, row);
const isExpanded = grid.api.rowGroupIsExpanded(row);
return (
<div
className="flex items-center gap-2 w-full h-full"
style={{ paddingLeft: row.depth * 16 }}
>
<button
className="flex items-center justify-center"
onClick={(e) => {
e.stopPropagation();
grid.api.rowGroupToggle(row);
}}
>
{!isExpanded && <ChevronRightIcon />}
{isExpanded && <ChevronDownIcon />}
</button>
<div>{`${field}`}</div>
</div>
);
},
},
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}