# Computed columns

> Derive a column's value from the whole row, such as a total, a ratio or a P&L, and sort, filter, total and export it like any other column.

A computed column derives its value from the row instead of reading a field. Give the column a `value(row)` function: the result is shown, sorted, filtered, totalled, formatted conditionally, copied and exported exactly like a stored value. Use it for KPIs, ratios and deltas you would otherwise have to precompute and keep in sync.

Value and P&L below exist on no row; sort by either, and see the footer total them:

Example: Value and P&L derived per row (computed-columns.svelte)

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

  // Positions hold a quantity and a cost; value and P&L are derived per row.
  let rows = $state([
    { id: 1, symbol: 'VNM', qty: 1200, cost: 64.2, last: 68.5 },
    { id: 2, symbol: 'FPT', qty: 300, cost: 138.0, last: 132.0 },
    { id: 3, symbol: 'HPG', qty: 5000, cost: 25.1, last: 27.35 },
    { id: 4, symbol: 'VCB', qty: 800, cost: 93.4, last: 91.5 },
    { id: 5, symbol: 'MWG', qty: 1500, cost: 47.9, last: 51.4 },
  ]);

  const num = (row: GridRow, key: string) => Number(row[key]);
  const whole = (v: unknown) => Math.round(Number(v)).toLocaleString('en-US');

  const columns: ColumnDef[] = [
    { type: 'text', key: 'symbol', header: 'Symbol', width: 90 },
    { type: 'number', key: 'qty', header: 'Qty', width: 90, groupAgg: 'sum', format: whole },
    { type: 'price', key: 'cost', header: 'Avg cost', width: 96 },
    { type: 'price', key: 'last', header: 'Last', width: 90 },
    // No `value` or `pnl` field on the rows: derived, yet sortable,
    // filterable, totalled in the footer and exported like any column.
    { type: 'number', key: 'value', header: 'Value', width: 110, groupAgg: 'sum', format: whole, value: (r) => num(r, 'qty') * num(r, 'last') },
    {
      type: 'number',
      key: 'pnl',
      header: 'P&L',
      flex: 1,
      format: whole,
      groupAgg: 'sum',
      value: (r) => num(r, 'qty') * (num(r, 'last') - num(r, 'cost')),
      colorScale: { mid: 0 },
    },
  ];
</script>

<Grid {rows} {columns} footer height={220} />
```

## Defining one

```ts
const columns: ColumnDef[] = [
  { type: 'number', key: 'qty', header: 'Qty', decimals: 0 },
  { type: 'price', key: 'last', header: 'Last' },
  // No `value` field on the rows: derived, and still sortable and exportable.
  { type: 'number', key: 'value', header: 'Value', decimals: 0, groupAgg: 'sum', value: (r) => Number(r.qty) * Number(r.last) },
];
```

- `key` still names the column: sorting, filtering, hiding and saved layouts refer to it. It does not have to be a real field on the row.
- The column's `type` formats the result, so a computed price uses `type: 'price'`.
- `groupAgg` totals it on [group rows](https://bonguynvan.github.io/bo-grid/docs/grouping/) and in the grid's `footer`.

## Rules

- **Keep it cheap and pure.** `value()` runs while sorting and filtering, not only when a cell draws. Read the row and return a value; don't fetch or mutate anything.
- **It is read-only.** There is no field to write an edit back to, so computed columns are not editable.
- **In memory only.** With a server [data source](https://bonguynvan.github.io/bo-grid/docs/server-data/), the server computes its own derived fields.

## Reference

- `value`: `(row: GridRow) => unknown` — Computed value: derive this cell's value from the whole row instead of reading `row[key]` (KPIs, ratios, deltas). Flows through display, sort, filter, aggregation, export and conditional formatting. `key` still names the column (and is the sort/filter key) but need not be a real row field. Keep it cheap and pure — it's called during sort/filter. Computed columns aren't editable. In-memory mode (a server source owns its own derivations).

  ```ts
  { type: 'percent', key: 'chg', header: 'Chg %', value: (row) => ((Number(row.last) - Number(row.ref)) / Number(row.ref)) * 100 }
  ```

- `groupAgg`: `AggKind` (default `undefined`) — When grouping is active (or with `footer`), show this aggregate of the column on group headers and the totals row.

  ```ts
  { type: 'volume', key: 'qty', header: 'Qty', groupAgg: 'sum' }
  ```

- `format`: `(value: unknown, row?: GridRow) => string` — Custom display formatter, overriding the built-in type formatter. Applies to display, tooltip, copy and (formatted) export. `row` is absent for aggregate cells.

  ```ts
  { type: 'number', key: 'qty', header: 'Qty', format: (v) => Number(v).toLocaleString('vi-VN') }
  ```


- `footer`: `boolean` (default `false`) — Show a pinned totals row: each column with a `groupAgg` shows that aggregate over all (filtered) rows. In-memory mode only.

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