# Pivot tables

> Turn flat rows into a pivot table, with row groups down the side, one column per value of a field, and an aggregate in each cell.

`pivot()` turns flat rows into a pivot table: it groups rows by one or more fields, spreads the values of another field into columns, and aggregates a measure into each cell. It returns ordinary `rows` and `columns`, so the result goes straight into `<Grid>` with sorting, selection and export working as usual.

Trades by desk and book, one column per month plus a Total, in thousands. Change the aggregate to recompute every cell.

Example: Notional by desk and month (pivot.svelte)

```svelte
<script lang="ts">
  import { Grid, pivot, type AggKind } from 'bo-grid';
  import { trades } from './pivot-data';

  const data = trades();
  let agg = $state<AggKind>('sum');

  // Rows by desk and book, a column per month (plus Total), notional ($k) in each cell.
  const table = $derived(pivot(data, { rowFields: ['desk', 'book'], columnField: 'month', measure: 'notional', agg }));
</script>

<p style="margin: 0 0 10px">
  <label>
    Aggregate
    <select bind:value={agg}>
      <option value="sum">Sum</option>
      <option value="avg">Average</option>
      <option value="max">Max</option>
      <option value="count">Count</option>
    </select>
  </label>
</p>
<Grid rows={table.rows} columns={table.columns} fitColumns height={180} />
```

pivot-data.ts:

```ts
// Trades for the pivot example: one row per fill, by desk and month, notional in $ thousands.
export type Trade = { id: number; desk: string; book: string; month: string; notional: number };

const DESKS = [
  ['Equities', 'Cash'],
  ['Equities', 'Derivatives'],
  ['Rates', 'Govies'],
  ['FX', 'Spot'],
  ['FX', 'Forwards'],
] as const;
const MONTHS = ['2026-01', '2026-02', '2026-03', '2026-04'];

export function trades(): Trade[] {
  const out: Trade[] = [];
  let id = 1;
  for (const [desk, book] of DESKS) {
    for (const month of MONTHS) {
      // Two fills a month, sized from the ids so the table is the same on every load.
      for (let k = 0; k < 2; k++, id++) out.push({ id, desk, book, month, notional: ((id * 7919) % 900) + 50 });
    }
  }
  return out;
}
```

## Building a pivot

```svelte
<script lang="ts">
  import { Grid, pivot } from 'bo-grid';

  const table = $derived(
    pivot(trades, {
      rowFields: ['desk', 'book'], // leading text columns, one row per combination
      columnField: 'month',        // each distinct month becomes a column
      measure: 'notional',         // the numeric field aggregated into the cells
      agg: 'sum',
    }),
  );
</script>

<Grid rows={table.rows} columns={table.columns} height={480} />
```

`pivot` is a pure function of its input. Call it once for a snapshot, or in `$derived` (as above) so the table follows the data and the settings.

## Options

| Option | Meaning |
| --- | --- |
| `rowFields` | Fields to group rows by (at least one); each becomes a leading text column |
| `columnField` | The field whose distinct values become columns, in sorted order; omit it for a single value column |
| `measure` | The numeric field to aggregate |
| `agg` | `'sum'` (default), `'avg'`, `'count'`, `'min'` or `'max'` |
| `decimals` | Decimal places for the value columns (default 0) |
| `totals` | Append a Total column across all column values (default `true`) |

## Pivot or grouping?

A pivot reshapes the data into a new table with a column per value, which suits a report: months across, desks down. To keep the original rows and columns and fold them into collapsible sections with subtotals, use [Row grouping](https://bonguynvan.github.io/bo-grid/docs/grouping/).

## Reference

`pivot(source, config)` returns `{ rows, columns }` (`PivotResult`). Its config is the `PivotConfig` type above.

---
Page: https://bonguynvan.github.io/bo-grid/docs/pivot/ · All pages: https://bonguynvan.github.io/bo-grid/llms.txt
