# Grouping



Grid supports grouping records by one or more fields with aggregation and custom footers.

```tsx
import { createModel } from "cx/data";
import { Controller, tpl } from "cx/ui";
import { Grid } from "cx/widgets";

interface Person {
  id: number;
  fullName: string;
  continent: string;
  browser: string;
  os: string;
  visits: number;
}

interface GroupData {
  $name: string;
  $level: number;
  fullName: number;
  browsers: number;
  os: number;
  visits: number;
}

interface PageModel {
  records: Person[];
  $record: Person;
  $group: GroupData;
}

const m = createModel<PageModel>();

class PageController extends Controller {
  onInit() {
    this.store.set(m.records, [
      {
        id: 1,
        fullName: "Alice Johnson",
        continent: "North America",
        browser: "Chrome",
        os: "Windows",
        visits: 45,
      },
      {
        id: 2,
        fullName: "Bob Smith",
        continent: "North America",
        browser: "Firefox",
        os: "macOS",
        visits: 32,
      },
      {
        id: 3,
        fullName: "Carol White",
        continent: "Europe",
        browser: "Chrome",
        os: "Linux",
        visits: 28,
      },
      {
        id: 4,
        fullName: "David Brown",
        continent: "Europe",
        browser: "Safari",
        os: "macOS",
        visits: 51,
      },
      {
        id: 5,
        fullName: "Eva Green",
        continent: "Europe",
        browser: "Chrome",
        os: "Windows",
        visits: 19,
      },
      {
        id: 6,
        fullName: "Frank Wilson",
        continent: "Asia",
        browser: "Edge",
        os: "Windows",
        visits: 37,
      },
      {
        id: 7,
        fullName: "Grace Lee",
        continent: "Asia",
        browser: "Chrome",
        os: "macOS",
        visits: 42,
      },
      {
        id: 8,
        fullName: "Henry Taylor",
        continent: "North America",
        browser: "Safari",
        os: "macOS",
        visits: 25,
      },
    ]);
  }
}

export default (
  <Grid
    controller={PageController}
    records={m.records}
    style="height: 500px"
    scrollable
    border
    grouping={[{ key: {}, showFooter: true }, "continent"]}
    columns={[
      {
        header: "Name",
        field: "fullName",
        aggregate: "count",
        footer: tpl(m.$group.fullName, "{0} people"),
      },
      {
        header: "Browser",
        field: "browser",
        aggregate: "distinct",
        aggregateAlias: "browsers",
        footer: tpl(m.$group.browsers, "{0} browsers"),
      },
      {
        header: "OS",
        field: "os",
        aggregate: "distinct",
        footer: tpl(m.$group.os, "{0} OS"),
      },
      {
        header: "Visits",
        field: "visits",
        align: "right",
        aggregate: "sum",
      },
    ]}
    recordAlias={m.$record}
  />
);

```

Records are grouped by continent with aggregate calculations shown in the footer.

## Basic Grouping

Use the `grouping` property to group records by a field:

```tsx
<Grid
  records={m.records}
  grouping="continent"
  columns={[
    { header: "Name", field: "fullName" },
    { header: "Continent", field: "continent" },
  ]}
/>
```

## Multi-Level Grouping

Pass an array for multiple grouping levels. The first level with an empty key groups all records (useful for totals):

```tsx
grouping={[
  { key: {}, showFooter: true },  // Total row
  "continent"                      // Group by continent
]}
```

## Aggregation

Configure aggregates on columns to calculate values per group:

```tsx
columns={[
  {
    header: "Name",
    field: "fullName",
    aggregate: "count",
    footer: tpl(m.$group.fullName, "{0} people"),
  },
  {
    header: "Visits",
    field: "visits",
    aggregate: "sum",
  },
]}
```

## Aggregate Functions

| Function | Description |
| -------- | ----------- |
| `count` | Count non-null values |
| `sum` | Sum numeric values |
| `avg` | Average of numeric values |
| `min` | Minimum value |
| `max` | Maximum value |
| `distinct` | Count unique values |

## Aggregate Alias

Use `aggregateAlias` to give the aggregate a custom name in `$group`:

```tsx
{
  header: "Browser",
  field: "browser",
  aggregate: "distinct",
  aggregateAlias: "browsers",
  footer: tpl(m.$group.browsers, "{0} browsers"),
}
```

## Group Captions

Use `showCaption: true` in the grouping configuration to display a caption row for each group. Define `caption` on columns to customize what appears in the caption row:

```tsx
import { createModel } from "cx/data";
import { Controller, tpl } from "cx/ui";
import { Grid } from "cx/widgets";

interface Person {
  id: number;
  fullName: string;
  continent: string;
  browser: string;
  visits: number;
}

interface GroupData {
  $name: string;
  $level: number;
  continent: string;
  fullName: number;
  browsers: number;
  visits: number;
}

interface PageModel {
  records: Person[];
  $record: Person;
  $group: GroupData;
}

const m = createModel<PageModel>();

class PageController extends Controller {
  onInit() {
    this.store.set(m.records, [
      {
        id: 1,
        fullName: "Alice Johnson",
        continent: "North America",
        browser: "Chrome",
        visits: 45,
      },
      {
        id: 2,
        fullName: "Bob Smith",
        continent: "North America",
        browser: "Firefox",
        visits: 32,
      },
      {
        id: 3,
        fullName: "Carol White",
        continent: "Europe",
        browser: "Chrome",
        visits: 28,
      },
      {
        id: 4,
        fullName: "David Brown",
        continent: "Europe",
        browser: "Safari",
        visits: 51,
      },
      {
        id: 5,
        fullName: "Eva Green",
        continent: "Europe",
        browser: "Chrome",
        visits: 19,
      },
      {
        id: 6,
        fullName: "Frank Wilson",
        continent: "Asia",
        browser: "Edge",
        visits: 37,
      },
      {
        id: 7,
        fullName: "Grace Lee",
        continent: "Asia",
        browser: "Chrome",
        visits: 42,
      },
      {
        id: 8,
        fullName: "Henry Taylor",
        continent: "North America",
        browser: "Safari",
        visits: 25,
      },
    ]);
  }
}

export default (
  <Grid
    controller={PageController}
    records={m.records}
    style="height: 500px"
    scrollable
    fixedFooter
    grouping={[
      { key: {}, showFooter: true },
      { key: { continent: { bind: "$record.continent" } }, showCaption: true },
    ]}
    columns={[
      {
        header: "Name",
        field: "fullName",
        aggregate: "count",
        footer: tpl(m.$group.fullName, "{0} people"),
        caption: tpl(m.$group.continent, m.$group.fullName, "{0} ({1} people)"),
      },
      {
        header: "Browser",
        field: "browser",
        aggregate: "distinct",
        aggregateAlias: "browsers",
        footer: tpl(m.$group.browsers, "{0} browsers"),
        caption: tpl(m.$group.browsers, "{0} browsers"),
      },
      {
        header: "Visits",
        field: "visits",
        align: "right",
        aggregate: "sum",
        caption: m.$group.visits,
      },
    ]}
    recordAlias={m.$record}
  />
);

```

```tsx
grouping={[{ key: { continent: { bind: "$record.continent" } }, showCaption: true }]}
columns={[
  {
    header: "Name",
    field: "fullName",
    aggregate: "count",
    caption: tpl(m.$group.continent, m.$group.fullName, "{0} ({1} people)"),
  },
  // ...
]}
```

## Group Data

The `$group` object contains:

| Property | Description |
| -------- | ----------- |
| `$name` | Group name/label |
| `$level` | Nesting level (0 for deepest) |
| `$records` | Records in the group |
| `$key` | Serialized group key |
| `[field]` | Aggregate value for each field |

## Grouping Configuration

| Property | Type | Description |
| -------- | ---- | ----------- |
| `key` | `object` | Fields to group by. Empty object groups all records. |
| `showHeader` | `boolean` | Show group header row. |
| `showFooter` | `boolean` | Show group footer row. |
| `showCaption` | `boolean` | Show group caption. |
| `caption` | `StringProp` | Caption template. |
| `text` | `StringProp` | Group name template. |

See also: [Dynamic Grouping](/docs/tables/dynamic-grouping), [GroupAdapter](/docs/tables/group-adapter)