Table Workflows
Grouping & Pivot
Grouping is a row model, summaries are an aggregate object, and a pivot is a data transformation. All three are separate entry points that cost nothing until imported.
- Fixture
- Benchmark
On this page
The engine takes an optional getRowModel; groupBy() is one. It runs the
same search, filters and sorting as the default model, buckets the sorted rows by one or more columns,
computes the aggregates the columns declare, and hands the result to the same pagination. Group rows
are nodes with a stable id derived from the group path, so an expanded group stays expanded when the
rows underneath re-sort.
This is what "supporting a feature does not mean shipping it" looks like in a build: the
grouping entry measures 2148 B gzip and the pivot entry 526 B gzip. A production build of a plain DataTable contains neither module: the table-basic fixture asserts both exclusions in its module graph.
import { groupBy, summarize, aggregate } from '@svelte-lean/table/grouping';
import { pivot } from '@svelte-lean/table/pivot';
// A grouped row model: register it on the table.
<DataTable getRowModel={groupBy(['team', 'region'])} … />Row grouping
Derive group rows and aggregates with the optional grouping entry point.
| Revenue: 60531Progress: 45 | ||||
|---|---|---|---|---|
Amara Okafor | Engineering | Europe | $4,501 | 20% |
Selin Aydın | Engineering | Americas | $36,177 | 72% |
Hana Kowalska | Engineering | Asia | $19,853 | 43% |
| Revenue: 84288Progress: 58 | ||||
| Revenue: 60045Progress: 71 | ||||
| Revenue: 83802Progress: 57 | ||||
<script lang="ts">
import { DataTable } from '@svelte-lean/table';
import { groupBy } from '@svelte-lean/table/grouping';
import { memberColumns, pick } from '$lib/demo/columns';
import { people } from '$lib/demo/data';
const data = people(12);
// revenue carries aggregate: 'sum' and progress aggregate: 'mean'; group rows show both.
const columns = pick(memberColumns(), ['name', 'team', 'region', 'revenue', 'progress']);
</script>
<DataTable
{data}
{columns}
getRowId={(row) => row.id}
getRowModel={groupBy(['team'])}
state={{ expanded: ['__group:["team:string:\"Engineering\""]'] }}
pagination={false}
label="Members by team"
/>- Nested grouping is a list of column ids in order; each level adds a depth.
- Tree input (
getSubRows) and grouping are separate row models; combining them throws. - Search and filters apply to the rows before grouping, so an empty group disappears.
Summaries
Render totals aligned with visible and pinned columns.
Amara Okafor | Engineering | $4,501 | 20% |
Jonas Lindqvist | Design | $12,420 | 33% |
Mei Tanaka | Operations | $20,339 | 46% |
Rafael Duarte | Growth | $28,258 | 59% |
Selin Aydın | Engineering | $36,177 | 72% |
Noah Brenner | Design | $44,096 | 85% |
Priya Raman | Operations | $4,015 | 98% |
Elias Moreau | Growth | $11,934 | 30% |
| Total | $161,740 | 55% mean |
<script lang="ts">
import { DataTable } from '@svelte-lean/table';
import { summarize } from '@svelte-lean/table/grouping';
import { memberColumns, pick } from '$lib/demo/columns';
import { money, people } from '$lib/demo/data';
const data = people(8);
const columns = pick(memberColumns(), ['name', 'team', 'revenue', 'progress']);
// summarize() returns one value per column with an aggregate; format it for the tfoot.
const totals = summarize(data, columns);
const summary = {
name: 'Total',
revenue: money.format(Number(totals.revenue)),
progress: `${Math.round(Number(totals.progress))}% mean`
};
</script>
<DataTable {data} {columns} getRowId={(row) => row.id} {summary} pagination={false} label="Members" />Pivot
Turn long-form records into a compact cross-tab using an optional pure helper.
Engineering | $10,587 | $105,615 | $56,643 | $172,845 |
Design | $80,400 | $34,344 | $129,372 | $244,116 |
Operations | $9,129 | $104,157 | $58,101 | $171,387 |
Growth | $81,858 | $32,886 | $127,914 | $242,658 |
<script lang="ts">
import { DataTable } from '@svelte-lean/table';
import { pivot } from '@svelte-lean/table/pivot';
import { money, people } from '$lib/demo/data';
// Long-form members become one row per team with one column per region.
const result = pivot(people(36), {
row: (item) => item.team,
column: (item) => item.region,
value: (item) => item.revenue,
aggregate: 'sum',
rowLabel: 'Team'
});
const columns = result.columns.map((column) =>
column.id === 'label' ? column : { ...column, format: (v: unknown) => money.format(Number(v)) }
);
</script>
<DataTable data={result.data} {columns} getRowId={(row) => row.id} bordered pagination={false} label="Revenue by team and region" />API
| Name | Of | Type | Default | Description |
|---|---|---|---|---|
getRowModel | Data and identity | (context: ModelContext<T>) => RowModel<T> | createRowModel | Replaces the client row model; groupBy() from the grouping entry is the shipped alternative. |
summary | Events and composition | Record<string, string | number> | undefined | A <tfoot> row keyed by leaf column id; summarize() from the grouping entry produces one. |
aggregate | Sort, filter and aggregate | 'sum' | 'mean' | 'min' | 'max' | 'count' | ((values: unknown[]) => unknown) | undefined | Aggregate used by groupBy() and summarize(). |
expanded | TableState | RowId[] | [] | Expanded details, tree and group row ids. |
Aggregate | Events and positions | 'sum' | 'mean' | 'min' | 'max' | 'count' | ((values: unknown[]) => unknown) | – | An aggregate name or function. |
RowNode<T> | Row model | { id; data?; depth; children; kind: 'row' | 'group'; label?; count?; aggregates? } | – | A rendered row or a group row. |
@svelte-lean/table/grouping | Entry points | groupBy · summarize · aggregate | – | A grouped row model for getRowModel, a totals object for summary, and the aggregate function both use. |
@svelte-lean/table/pivot | Entry points | pivot | – | Long-form records to a cross-tab: rows, columns and totals for a second DataTable. |