# Group aggregators

Declarative per-column rollups for group rows via the aggregate column option: sum, avg, min, max, count, countDistinct, extent, first, or a custom (values, rows) reducer. Each rollup is formatted with the column format and shown in the group header.

Uses `@svgrid/grid` (MIT). Category: Sorting & Grouping. Live demo: https://svgrid.com/demos/142-group-aggregators/

Install: `npm i @svgrid/grid` (Svelte 5).

## About this example

Declarative per-column rollups for group rows in the Svelte 5 data grid. Set aggregate on a column and its group header shows the value formatted with the column's own format: sum for revenue with a currency format, avg for win rate as a percent, count, min, max, countDistinct, extent, first, or a custom (values, rows) reducer such as a median. The groupable shortcut switches grouping on.

Declarative per-column aggregation for group rows. Set `aggregate` on a column and the group header shows the rolled-up value, formatted with the column's own `format`:

```text
{ field: 'revenue', aggregate: 'sum',  format: { type: 'currency' } }
{ field: 'winRate', aggregate: 'avg',  format: { type: 'percent' } }
{ field: 'score',   aggregate: (vals) => median(vals) }   // custom
```

Built-ins: sum, avg, min, max, count, countDistinct, extent, first - plus any custom (values, rows) => value reducer.

## Imports, features and API used

- Imports: `@svgrid/grid`
- Table features registered: `columnGroupingFeature`
- Columns: `region` (Region), `rep` (Rep), `revenue` (Revenue), `deals` (Deals), `winRate` (Win rate), `score` (Median score)

## Frequently asked questions

### Which aggregators are built in?

sum, avg, min, max, count, countDistinct, extent and first. Set one as the column's aggregate and the group row displays it through the column's format, so a currency column shows a currency total.

### How do I write a custom aggregator?

Pass a function: aggregate: (values, rows) => median(values). It receives the leaf values of the group and the row objects, and its return value is rendered like a built-in.

### Do aggregates nest for multi-level groups?

Yes. Each group level is rolled up from its own leaf rows, so a region total and a rep subtotal both show correctly when grouped by region then rep.

## Related documentation

- [Group aggregators](https://svgrid.com/docs/help/grouping/aggregators.md)

## Related articles

- [Aggregation Functions Explained (Sum, Avg, Min, Max, Count)](https://svgrid.com/blog/aggregation-functions-explained/) - A practical look at SvGrid's five built-in aggregations - what they compute, where the math quietly breaks, and how to handle the server-paged case where client totals are meaningless.

## Source code (142-group-aggregators.svelte)

```svelte
<!-- Documented in: docs/help/grouping/aggregators.md -->
<script lang="ts">
  /**
   * 142. Group aggregators
   * ----------------------
   * Declarative per-column aggregation for group rows. Set `aggregate` on a
   * column and the group header shows the rolled-up value, formatted with the
   * column's own `format`:
   *
   *   { field: 'revenue', aggregate: 'sum',  format: { type: 'currency' } }
   *   { field: 'winRate', aggregate: 'avg',  format: { type: 'percent' } }
   *   { field: 'score',   aggregate: (vals) => median(vals) }   // custom
   *
   * Built-ins: sum, avg, min, max, count, countDistinct, extent, first -
   * plus any custom (values, rows) => value reducer.
   */
  import {
    SvGrid,
    tableFeatures,
    columnGroupingFeature,
    type GridColumns,
    type SvGridApi,
  } from '@svgrid/grid'

  const features = tableFeatures({ columnGroupingFeature })

  type Row = {
    id: number
    rep: string
    region: string
    revenue: number
    deals: number
    winRate: number
    score: number
  }

  let seed = 0xa11ce
  const rnd = () => ((seed = (seed * 1103515245 + 12345) >>> 0) / 0xffffffff)
  const REGIONS = ['Americas', 'EMEA', 'APAC']
  const NAMES = ['Ada', 'Grace', 'Alan', 'Margaret', 'Linus', 'Donald', 'Brian', 'Dennis', 'Barbara', 'Ken', 'Edsger', 'Tim']
  const rows: Row[] = NAMES.flatMap((name, i) =>
    REGIONS.map((region, j) => ({
      id: i * 3 + j,
      rep: name,
      region,
      revenue: Math.round(20_000 + rnd() * 200_000),
      deals: Math.round(2 + rnd() * 40),
      winRate: Math.round(rnd() * 100) / 100,
      score: Math.round(rnd() * 100),
    })),
  )

  const median = (vals: number[]): number => {
    if (!vals.length) return 0
    const s = [...vals].sort((a, b) => a - b)
    return s[Math.floor(s.length / 2)]!
  }

  const columns: GridColumns<Row> = [
    { field: 'region', header: 'Region', width: 140 },
    { field: 'rep', header: 'Rep', width: 130 },
    {
      field: 'revenue',
      header: 'Revenue',
      width: 150,
      aggregate: 'sum',
      format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } },
    },
    { field: 'deals', header: 'Deals', width: 110, aggregate: 'sum', align: 'right' },
    {
      field: 'winRate',
      header: 'Win rate',
      width: 120,
      aggregate: 'avg',
      align: 'right',
      format: { type: 'percent' },
    },
    {
      field: 'score',
      header: 'Median score',
      width: 130,
      aggregate: median,
      align: 'right',
    },
  ]

  let api = $state<SvGridApi<typeof features, Row> | null>(null)
</script>

<section class="flex flex-col flex-1 min-h-0 gap-3">
  <div
    class="shrink-0 rounded-lg border px-4 py-3"
    style="border-color: var(--sg-border); background: var(--sg-header-bg);"
  >
    <p class="text-sm font-semibold" style="color: var(--sg-fg);">
      Per-column rollups via <code>aggregate</code>
    </p>
    <p class="mt-1 text-xs" style="color: var(--sg-muted);">
      Grouped by Region. Revenue + Deals = sum, Win rate = avg, Median score =
      a custom reducer. Each rollup is formatted with its column's
      <code>format</code> and shown in the group header. Use the column menu to
      group by another field.
    </p>
  </div>

  <div class="flex-1 min-h-0">
    <SvGrid responsive={true}
      columnResize
      data={rows}
      columns={columns}
      features={features}
      groupable
      selectionMode="none"
      rowHeight={36}
      containerHeight="100%"
      fitColumns={true}
      onApiReady={(a) => {
        api = a
        queueMicrotask(() => a.setGroupBy(['region']))
      }}
    />
  </div>
</section>
```

[View this example on GitHub](https://github.com/sv-grid/sv-grid/blob/main/examples/src/demos/142-group-aggregators.svelte)
