Pivot chart
One drag-drop pivot layout, two synced views: <SvPivotDesigner chartable> renders the SAME Rows / Columns / Values as either an expandable pivot grid or a chart (Columns -> series, Values -> measure). Powered by the enterprise pivot engine. (requires @svgrid/enterprise)
A live, editable Svelte 5 data grid example from the SvGrid gallery (Pivot Grid). See the SvGrid documentation for the full API.
About this example
One drag-and-drop pivot layout rendered two ways in Svelte 5. SvPivotDesigner with the chartable prop renders the same Rows, Columns and Values as either an expandable pivot grid or a live chart, switched with a Table and Chart toggle in the toolbar: rows become chart categories with their full path so nested rows read Americas, Canada, columns become series and the values the measure. The enterprise pivot engine feeds pivotToChartSpec into the MIT SvGridChart.
One drag-drop pivot layout, two views. <SvPivotDesigner chartable> renders the SAME layout (Rows / Columns / Values) as either an expandable pivot grid or a live chart - flip Table <-> Chart in the toolbar. Rows -> chart categories (full path, so nested rows read "Americas · Canada"), Columns -> series, Values -> the measure. Powered by the enterprise pivot engine (createPivotModel -> pivotToChartSpec) over the free MIT SvGridChart.
Imports, features and API used
Imports: @svgrid/enterprise
Frequently asked questions
How do the wells map to the chart?
Each row path becomes a category on the axis, each column-axis member becomes a series and the first value well entry is the measure, so a Region by Quarter revenue pivot becomes grouped bars per region with one bar per quarter.
Is the chart component enterprise?
No. SvGridChart is in the MIT @svgrid/grid package; the pivot engine and pivotToChartSpec that feed it are in @svgrid/enterprise.
Does switching views lose the layout?
No. Both views read the same bound layout, so a change in the wells updates the table and the chart alike.
Related documentation
Related articles
- Sparkline Cells in a Svelte Data Grid - Show inline trend sparklines inside grid cells using SvGrid's built-in sparkline column property - no charting library needed, just a field that holds a number array.
- What's New in SvGrid Enterprise - Export, Pivot, Import, Print, and AI - A practical look at @svgrid/enterprise - pivot tables, Excel and PDF export, data import, printing, and an AI assistant. Covers when each feature earns its place and what to watch out for in production.
- Pivot Tables in Svelte - Summarize Data Without a Spreadsheet - Run a cross-tab pivot directly inside your Svelte app using @svgrid/enterprise createPivotModel - no Excel, no server-side aggregation, no stale exports.
Source code (359-pivot-chart.svelte)
<script lang="ts">
/**
* 359. Pivot chart (Enterprise) - the Excel PivotTable <-> PivotChart pairing
* ---------------------------------------------------------------------------
* One drag-drop pivot layout, two views. `<SvPivotDesigner chartable>` renders
* the SAME layout (Rows / Columns / Values) as either an expandable pivot grid
* or a live chart - flip Table <-> Chart in the toolbar. Rows -> chart
* categories (full path, so nested rows read "Americas · Canada"), Columns ->
* series, Values -> the measure. Powered by the enterprise pivot engine
* (createPivotModel -> pivotToChartSpec) over the free MIT SvGridChart.
*/
import { SvPivotDesigner, 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'],
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']
let seed = 0xc0ffee
const rnd = () => ((seed = (seed * 1103515245 + 12345) >>> 0) / 0xffffffff)
const pick = <T,>(list: readonly T[]): T => list[Math.floor(rnd() * list.length)]!
const rows: Row[] = Array.from({ length: 900 }, (_, id) => {
const region = REGIONS[id % 3]!
const country = pick(COUNTRIES[region])
const category = pick(CATEGORIES)
const price = category === 'Electronics' ? 280 : category === 'Apparel' ? 65 : category === 'Home' ? 120 : 45
const units = Math.round(2 + rnd() * 38)
const revenue = Math.round(units * price * (0.8 + rnd() * 0.5))
return {
id, region, country, category, quarter: pick(QUARTERS),
salesperson: NAMES[id % NAMES.length]!,
units, revenue,
profit: Math.round(revenue * (0.15 + rnd() * 0.25)),
}
})
const money = { type: 'currency', currency: 'USD', options: { maximumFractionDigits: 0 } } as const
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', kind: 'measure', defaultAgg: 'sum', format: { type: 'number', options: { maximumFractionDigits: 0 } } },
{ field: 'revenue', label: 'Revenue', kind: 'measure', defaultAgg: 'sum', format: money },
{ field: 'profit', label: 'Profit', kind: 'measure', defaultAgg: 'sum', format: money },
]
const presets: PivotPreset[] = [
{
name: 'Region → Country × Quarter',
layout: { rows: ['region', 'country'], cols: ['quarter'], values: [{ field: 'revenue', agg: 'sum', label: 'Revenue', format: money }], filters: [] },
},
{
name: 'Region × Category (revenue + profit)',
layout: { rows: ['region'], cols: ['category'], values: [{ field: 'revenue', agg: 'sum', label: 'Revenue', format: money }, { field: 'profit', agg: 'sum', label: 'Profit', format: money }], filters: [] },
},
{
name: 'Salesperson scorecard',
layout: { rows: ['salesperson'], cols: [], values: [{ field: 'revenue', agg: 'sum', label: 'Revenue', format: money }, { field: 'units', agg: 'avg', label: 'Avg units' }], filters: [] },
},
]
// Default: Region -> Country (nested, so the grid expands / collapses) x Quarter.
let layout = $state<PivotLayout>(presets[0]!.layout)
</script>
<div class="demo-page" style="height: 660px; display: flex; flex-direction: column;">
<p class="demo-lede">
One pivot layout, two views. The grid is <strong>expandable</strong> (click a Region to reveal its
countries); flip <strong>Table ↔ Chart</strong> in the toolbar and the SAME layout draws as a live
chart - Rows become <strong>categories</strong>, Columns <strong>series</strong>, Values the
<strong>measure</strong>. Drag fields between wells, or try a preset.
</p>
<div style="flex: 1; min-height: 0;">
<SvPivotDesigner
data={rows}
{fields}
bind:layout
{presets}
expandable
chartable
defaultView="table"
/>
</div>
</div>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.