Pivot table
Summarises flat rows by the fields set on rows and columns, with subtotals, totals, sorting, a heatmap and a field list.
c2-pivot-table does what a spreadsheet’s pivot does: it groups flat rows by the fields set on rows and columns, and aggregates the values in every cell, with a subtotal for each group and grand totals.
Import
import '@c2n/components/pivot-table'Usage
| region / country | |||||
|---|---|---|---|---|---|
| Asia Pacific | |||||
| Australia | €144K | €151K | €149K | €162K | €606K |
| Japan | €298K | €305K | €320K | €341K | €1.26M |
| Asia Pacific total | €442K | €456K | €469K | €503K | €1.87M |
| Europe | |||||
| France | €356K | €371K | €360K | €398K | €1.49M |
| Germany | €412K | €438K | €455K | €502K | €1.81M |
| Spain | €188K | €201K | — | €236K | €625K |
| Europe total | €956K | €1.01M | €815K | €1.14M | €3.92M |
| North America | |||||
| Canada | €230K | €260K | €262K | €264K | €1.02M |
| United States | €980K | €1.04M | €1.01M | €1.12M | €4.15M |
| North America total | €1.21M | €1.3M | €1.27M | €1.38M | €5.17M |
| Grand total | €2.61M | €2.77M | €2.56M | €3.02M | €10.95M |
| region | ||
|---|---|---|
| Asia Pacific | €1.87M | 4,301 |
| Europe | €3.92M | 9,008 |
| North America | €5.17M | 11,882 |
| Grand total | €10.95M | 25,191 |
| cohort | ||||||
|---|---|---|---|---|---|---|
| 2026-01 | 100% | 62% | 48% | 41% | 37% | 35% |
| 2026-02 | 100% | 58% | 44% | 39% | 36% | — |
| 2026-03 | 100% | 65% | 51% | 45% | — | — |
| 2026-04 | 100% | 61% | 47% | — | — | — |
| 2026-05 | 100% | 67% | — | — | — | — |
| 2026-06 | 100% | — | — | — | — | — |
| section / account | ||||
|---|---|---|---|---|
| 4000 Revenue | ||||
| Product sales | €1.2M | €1.2M | €60K | 5.1% |
| Services | €215K | €190K | €25K | 13.2% |
| Subscriptions | €860K | €900K | -€40K | -4.4% |
| 4000 Revenue total | €2.3M | €2.3M | €45K | 2% |
| 5000 Cost of sales | ||||
| Hosting | -€182K | -€170K | -€12K | -7.1% |
| Support staff | -€240K | -€250K | €10K | 4% |
| 5000 Cost of sales total | -€422K | -€420K | -€2K | -0.5% |
| 6000 Operating expenses | ||||
| Marketing | -€305K | -€260K | -€45K | -17.3% |
| Rent | -€96K | -€96K | €0 | 0% |
| Salaries | -€910K | -€880K | -€30K | -3.4% |
| Software | -€64K | -€70K | €6K | 8.6% |
| 6000 Operating expenses total | -€1.4M | -€1.3M | -€69K | -5.3% |
| Grand total | €518K | €544K | -€26K | -4.8% |
Data and values
data is an array of plain rows (a dotted field such as customer.country reads a nested value). rows and columns list the grouping fields, outermost first; a missing value groups under “(blank)”. Each values entry is { field, aggregate, label, format }: the aggregate is sum (the default), count, avg, min, max or distinct, and format takes Intl.NumberFormat options. Two or more values split each column. field-labels names the fields in headers and in the field list.
Financial statements
A values entry with compute is worked out from two other values of the same cell instead of the records: { "field": "variance", "compute": "difference", "of": ["actual", "budget"] } is actual minus budget, percent-difference is that difference over the budget (shown as a percentage unless format says otherwise) and ratio divides them. Subtotals and totals compute from their own sums, so a section’s variance % is not an average of its accounts’. of names the first aggregated value of each field, so the data holds the compared figures side by side (actual and budget, or this year and last year) in each record.
invert-signs lists row groups whose numbers count with the opposite sign, by field: { "section": ["Cost of sales"] }. The sign flips before aggregation, so costs show negative, subtract in the grand total and an overspend reads as a negative variance. A computed cell carries the class negative or positive; colour them with --c2-pivot-table__computed__negative--color and --c2-pivot-table__computed__positive--color, and use "currencySign": "accounting" in format for parentheses.
pivot(data, config) is the same computation as a plain function, for a server, a test or an export.
Groups, totals and sorting
Row groups expand and collapse from their header, by click or with Left and Right; expand-depth sets how deep they start open, and expandAll() / collapseAll() change it from code. An open group has its subtotal under it (subtotals="top" puts it on the group’s own row, none drops it), and totals keeps the grand total row and column (both), one of them, or neither. Clicking a column header sorts the rows by it: largest first, then smallest first, then back to the labels. heatmap tints each cell by its value within its measure, the usual look for a cohort table.
Field list
With configurable, a field list beside the grid holds the data’s fields in four zones: unused, rows, columns and values. Drag a field between them, or use its menu, which also changes a value’s aggregate. pivot-change gives the new { rows, columns, values }, and the change is announced.
Drill-down and export
cell-click gives the row and column paths, the value and the records behind a cell, to open them in a table or a sheet. toCSV() returns the grid as shown.
Keyboard and screen readers
The grid is a treegrid: the arrow keys move between cells, Home and End go to the ends of a row (with Ctrl, of the grid), Left and Right close and open a group, and Enter on a value cell fires cell-click. Each row carries its level, position and expanded state. The grid renders every visible row: past a couple of thousand, keep groups closed.