Pivot - Designer component
SvPivotDesigner: self-contained, enterprise-ready pivot authoring with a left-rail field picker (search + grouped), four drop wells (Filters / Columns / Rows / Values), drag-and-drop between wells, per-chip aggregator + filter menus, presets toolbar, and an inline pivot grid driven by createPivotModel. Single bindable `layout` prop so the page can persist or restore it. (requires @svgrid/enterprise)
A live, editable Svelte 5 data grid example. Open the interactive demo or read the documentation.
What this example shows
`<SvPivotDesigner>` is a self-contained, enterprise-ready pivot authoring component. The consumer provides flat rows + a field list; the designer handles everything else:
- Left-rail field picker with search + dimension/measure grouping - Four drop wells (Filters, Columns, Rows, Values) - HTML5 drag-and-drop to move fields between wells - Per-chip menus: Values picks an aggregator, Filters picks allowed source values - Toolbar: Reset, Preset menu, Subtotal/Grand-total toggles, optional Export hook - Inline pivot grid driven by `createPivotModel` - A single bindable `layout` prop so the page can save it, restore it from URL, or sync it with other components.
Source code (168-pivot-designer.svelte)
<!-- Documented in: docs/help/pivot.md -->
<script lang="ts">
/**
* 168. Pivot designer (Enterprise) - SvPivotDesigner component
* --------------------------------------------------------------
* `<SvPivotDesigner>` is a self-contained, enterprise-ready pivot
* authoring component. The consumer provides flat rows + a field
* list; the designer handles everything else:
*
* - Left-rail field picker with search + dimension/measure grouping
* - Four drop wells (Filters, Columns, Rows, Values)
* - HTML5 drag-and-drop to move fields between wells
* - Per-chip menus: Values picks an aggregator, Filters picks
* allowed source values
* - Toolbar: Reset, Preset menu, Subtotal/Grand-total toggles,
* optional Export hook
* - Inline pivot grid driven by `createPivotModel`
* - A single bindable `layout` prop so the page can save it,
* restore it from URL, or sync it with other components.
*/
import { SvPivotDesigner, defaultLayoutFor, type PivotField, type PivotLayout, type PivotPreset } from '@svgrid/enterprise'
type Row = {
id: number
region: 'Americas' | 'EMEA' | 'APAC'
country: string
category: 'Electronics' | 'Apparel' | 'Home' | 'Outdoors'
quarter: 'Q1' | 'Q2' | 'Q3' | 'Q4'
salesperson: string
units: number
revenue: number
profit: number
}
const REGIONS: Row['region'][] = ['Americas', 'EMEA', 'APAC']
const COUNTRIES: Record<Row['region'], string[]> = {
Americas: ['USA', 'Canada', 'Brazil', 'Mexico'],
EMEA: ['UK', 'Germany', 'France', 'Spain', 'UAE'],
APAC: ['Japan', 'Australia', 'Singapore', 'India'],
}
const CATEGORIES: Row['category'][] = ['Electronics', 'Apparel', 'Home', 'Outdoors']
const QUARTERS: Row['quarter'][] = ['Q1', 'Q2', 'Q3', 'Q4']
const NAMES = ['Ada', 'Grace', 'Linus', 'Donald', 'Margaret', 'Brian', 'Barbara', 'Ken', 'Anita', 'Tim']
let seed = 0xc0ffee
const rnd = () => ((seed = (seed * 1103515245 + 12345) >>> 0) / 0xffffffff)
const rows: Row[] = Array.from({ length: 720 }, (_, id) => {
const region = REGIONS[id % 3]!
const country = COUNTRIES[region][id % COUNTRIES[region].length]!
const category = CATEGORIES[id % 4]!
const quarter = QUARTERS[id % 4]!
const units = Math.round(2 + rnd() * 38)
const price = category === 'Electronics' ? 280 : category === 'Apparel' ? 65 : category === 'Home' ? 120 : 45
const revenue = Math.round(units * price * (0.8 + rnd() * 0.5))
return {
id, region, country, category, quarter,
salesperson: NAMES[id % NAMES.length]!,
units, revenue,
profit: Math.round(revenue * (0.15 + rnd() * 0.25)),
}
})
const fields: PivotField<Row>[] = [
{ field: 'region', label: 'Region', kind: 'dimension', group: 'Geography' },
{ field: 'country', label: 'Country', kind: 'dimension', group: 'Geography' },
{ field: 'category', label: 'Category', kind: 'dimension', group: 'Product' },
{ field: 'quarter', label: 'Quarter', kind: 'dimension', group: 'Time' },
{ field: 'salesperson', label: 'Salesperson', kind: 'dimension', group: 'People' },
{ field: 'units', label: 'Units sold', kind: 'measure', defaultAgg: 'sum',
format: { type: 'number', options: { maximumFractionDigits: 0 } } },
{ field: 'revenue', label: 'Revenue', kind: 'measure', defaultAgg: 'sum',
format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } } },
{ field: 'profit', label: 'Profit', kind: 'measure', defaultAgg: 'sum',
format: { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } } },
]
// Saved layouts surfaced in the toolbar's Presets menu.
const presets: PivotPreset[] = [
{
name: 'Revenue by Region × Quarter',
layout: {
rows: ['region'], cols: ['quarter'],
values: [{ field: 'revenue', agg: 'sum', label: 'Revenue', format: fields.find((f) => f.field === 'revenue')!.format }],
filters: [],
},
},
{
name: 'Category drill: Region → Country',
layout: {
rows: ['region', 'country'], cols: ['category'],
values: [
{ field: 'units', agg: 'sum', label: 'Units' },
{ field: 'revenue', agg: 'sum', label: 'Revenue', format: fields.find((f) => f.field === 'revenue')!.format },
],
filters: [],
},
},
{
name: 'Salesperson scorecard',
layout: {
rows: ['salesperson'], cols: [],
values: [
{ field: 'revenue', agg: 'sum', label: 'Revenue', format: fields.find((f) => f.field === 'revenue')!.format },
{ field: 'profit', agg: 'sum', label: 'Profit', format: fields.find((f) => f.field === 'profit')!.format },
{ field: 'units', agg: 'avg', label: 'Avg units' },
],
filters: [],
},
},
]
// Bindable layout. Initial: revenue by region × quarter.
let layout = $state<PivotLayout>(presets[0]!.layout)
function onExport(layout: PivotLayout, rows: unknown[]) {
// Real apps would forward to @svgrid/enterprise `exportGrid` or
// a CSV helper. For the demo we just log + show a toast.
console.log('Export:', { layout, rowCount: rows.length })
alert(`Would export ${rows.length} pivot rows. See console for the layout payload.`)
}
</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);">
<SvPivotDesigner> — a self-contained pivot authoring component
</p>
<p class="mt-0.5 text-xs" style="color: var(--sg-muted);">
Drag a field from the rail into any well, or tick its checkbox to drop it in its default
well (dimensions → Rows, measures → Values). Click a Values chip to change the aggregator
(sum / avg / min / max / count …). Click a Filters chip to pick which source values pass.
Try a preset from the toolbar.
</p>
</div>
<div class="flex-1 min-h-0">
<SvPivotDesigner
data={rows}
{fields}
bind:layout
{presets}
{onExport}
/>
</div>
</section>More Pivot Grid examples
- Pivot mode grid - Built on <SvPivotDesigner panelPosition="right">: a docked tool panel with a PIVOT MODE toggle, field checklist, and Columns / Rows / Values wells (drag-and-drop). OFF renders the flat participant grid (column groups, flags, ratings, inline filter row); ON pivots Language -> Country x Game with heat-mapped avg measures.
- Pivot - Table + Designer - Drag-and-drop Pivot Designer with Filters / Rows / Columns / Values zones, multi-level column headers, subtotal + grand-total rows, row-header sort menu.
- Pivot - Sales pipeline - Polished pivot view: KPI strip, region/sales-person rows, quarter columns, two measures, expand-all/collapse-all toolbar, heatmap tinting.
- Pivot - Conditional cells - Function-valued cell and header templates on top of createPivotModel: traffic-light revenue pills, target chips, units data-bars, measure icons in headers, region color dots in row labels.
- Pivot - Drill-through - Click any pivot value cell - leaf, subtotal, or grand total - and the right rail opens with the source facts behind the aggregate. Total + count + average always match the cell.