JavaScript
The PivotGrid does not need React. @kanunilabs/pivotgrid is a second renderer
over the same engine (@kanunilabs/pivotgrid-core) and the same
stylesheet — aggregation in the Web Worker, drag-and-drop between areas,
filters, sorting, expand/collapse, saved layouts and theming behave
identically, and a parity ledger checked in CI records, surface by surface,
where the two renderers agree and where they deliberately do not.
npm i @kanunilabs/pivotgrid
A pivot in one call
import { createPivotGrid, type PivotField } from '@kanunilabs/pivotgrid';
import '@kanunilabs/pivotgrid/styles.css';
const data = [
{ region: 'EMEA', quarter: 'Q1', revenue: 1200 },
{ region: 'EMEA', quarter: 'Q2', revenue: 980 },
{ region: 'APAC', quarter: 'Q1', revenue: 1500 },
];
const fields: PivotField<(typeof data)[number]>[] = [
{ id: 'region', dataField: 'region', caption: 'Region', dataType: 'string', area: 'row' },
{ id: 'quarter', dataField: 'quarter', caption: 'Quarter', dataType: 'string', area: 'column' },
{ id: 'revenue', dataField: 'revenue', caption: 'Revenue', dataType: 'number', area: 'data', summaryType: 'sum' },
];
const grid = createPivotGrid(document.getElementById('host'), {
gridId: 'sales',
data,
initialFields: fields,
locale: 'tr', // one of 12; 'ar' switches to RTL
theme: 'quartz',
});
grid.setData(nextRows); // live data
grid.setLocale('de'); // relabels chrome, chart axes and dialogs
grid.controller.expandAll(); // the engine, directly
grid.destroy(); // when the host goes away
The same data and fields as the React quick start
— the field vocabulary (area, dataType, summaryType, format,
groupInterval) is core's, not the renderer's, so it is identical in both.
The config mirrors the React props. Everything BasePivotGrid takes as a
prop, createPivotGrid takes as a key: layout, stateStoring, toolbar,
striped, dictionary, rtl, workerUrl, keyboardNavigation, disabled,
and the event callbacks (onCellClick, onFieldsChange, onLayoutChange,
onExporting, onError, …). One difference in the shape of one payload:
onCellClick receives the native MouseEvent, where React hands you a
synthetic one.
The overlap is not total, and the renderer tells you where it ends rather than
letting you guess: pass a React-only option and it says so in the console,
naming what to write instead. The React-only keys are the three render slots
(renderCalculatedFieldModal, renderPrefilterBuilder,
renderNodeContextMenu), onOpenFullScreenChart (deprecated in React too),
children and ref. Each has a plain-callback or Enterprise-package
counterpart, and the warning names it.
grid.controller is the engine itself — the same PivotController the React
binding exposes. Everything imperative (fields, layout, filters, expansion,
export, the cell-click resolver) lives there; see the
API reference. The JavaScript surface itself —
createPivotGrid
and PivotGridVanillaConfig
— has its own generated pages.
Three differences worth knowing
- Row headers are sticky, not a second table. One scroll container; the row-header cells pin themselves with computed insets, and their widths are budgeted against the viewport so the data never disappears behind them. A dragged row-header width is stored as the configured width, so a saved layout means the same thing at any window size.
- Sockets, not render props. Where React takes
renderCalculatedFieldModal, vanilla exposesonAddCalculatedField/onEditCalculatedField,onOpenPrefilterandonToggleChart. When a callback is present the footer draws the matching button; absent, nothing is drawn. The Enterprise package fills all four — an app may fill any. - Charts are a subscription.
createChartSync(controller, getLocale, options, onSync)is the React hookusePivotChartSyncas a plain subscription: 150 ms after the result, the expansion set or the locale changes, your transformer runs. The Community bridge ships no transformer; the Enterprise package does.
Script tag (UMD)
No bundler? The package ships a self-contained build:
<link rel="stylesheet" href="https://unpkg.com/@kanunilabs/pivotgrid/dist/styles.css" />
<script src="https://unpkg.com/@kanunilabs/pivotgrid/dist/pivotgrid.umd.js"></script>
<script>
const { createPivotGrid } = KanuniLabsPivotGrid;
createPivotGrid(document.getElementById('host'), { /* the same config */ });
</script>
The engine and its Web Worker are bundled in — the worker starts from a Blob
URL, so there is no file to serve. If a strict Content-Security-Policy blocks
blob: workers, pass workerUrl as described under
Installation.
Enterprise edition
@kanunilabs/pivotgrid-enterprise wraps the Community renderer — it does not
fork it — and fills the sockets it opens: the no-code calculated-field
designer, the visual prefilter builder, the drill-down modal, an eight-type
chart panel and styled Excel & PDF export. It is installed from the
private registry with your licence.
import { createEnterprisePivotGrid } from '@kanunilabs/pivotgrid-enterprise';
import '@kanunilabs/pivotgrid/styles.css'; // no separate Enterprise stylesheet
const grid = createEnterprisePivotGrid(host, {
licenseKey: 'KLAB1.…',
// everything createPivotGrid takes, plus — all default ON:
calculatedFields: true, // footer calculator → the designer
prefilter: true, // footer filter → the AND/OR builder
drillDown: true, // click a value cell → the source rows
charts: true, // footer chart button → the panel, hidden until pressed
chartType: 'column',
drillDownPageSize: 50,
data,
initialFields: fields,
});
grid.openCalculatedFieldDesigner(); // or (fieldId) to edit one
grid.openPrefilterBuilder();
grid.openDrillDown({ records, rowPath: ['EMEA'] });
grid.toggleChart();
grid.setChartType('pie');
grid.isEnterpriseLicensed();
Without a valid key the grid still works, watermarked — your app never breaks on a licensing problem. One licence key opens both Enterprise packages, React and plain JavaScript alike; see Enterprise & licensing.
drillDown chains your own onCellClick rather than replacing it: your
handler runs first and sees every click, whether or not a modal follows. Pass
drillDown: false to keep the click for yourself and call openDrillDown when
you want the dialog.
Charts without a charting library
The chart panel draws its eight types — column, bar, line, area, pie, scatter,
radar and combination — as SVG the package writes itself. There is no charting
dependency, for a reason that decided itself: a library marked external
survives into the script-tag bundle as a bare import('…') that a classic
script cannot resolve, and a second <script> tag defines a global that a
dynamic import never looks at. Bundling a library would have added it to every
customer's page; leaving it out would have made charts unreachable from the one
delivery path this package exists for.
The panel also carries what the React chart never had: axis captions in the
grid's locale (so the axis agrees with the cells above it), a note whenever
maxPoints or maxSeries dropped data, and a visually-hidden data table so a
screen reader gets the numbers, not just "graphic".
Excel and PDF from a script tag
The export peers are optional and never bundled. A bundler resolves them the usual way; a script-tag page provides them as globals, which is what their own UMD builds define:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/exceljs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.plugin.autotable.js"></script>
<script src="https://unpkg.com/@kanunilabs/pivotgrid-enterprise/dist/pivotgrid-enterprise.umd.js"></script>
The package looks for globalThis.ExcelJS and globalThis.jspdf first and
falls back to import() — a page that loads the tags in any order works, and a
click that lands before a tag has loaded is not remembered as a failure. If a
peer is nowhere, the export does nothing and the console says which tag to add.
A missing peer never throws into your click handler.
The Enterprise UMD bundles the Community renderer and the licensing core
(global KanuniLabsPivotGridEnterprise), so one stylesheet and one script tag
are enough.
Where it stops
The Enterprise parity ledger grades every paid surface against the React edition and is exported from the package, so the limits below are checked, not remembered:
- PDF has no embedded Unicode font (shared with React). Labels the built-in font cannot draw fall back to English rather than rendering as glyph soup; cell text outside Latin-1 is a known limit.
- Very large pivots export as SpreadsheetML (
.xls), above 150,000 engine cells, and that path carries no conditional formatting, freeze pane or outline levels (shared with React). Unlike React, the console says so when the switch happens. - Chart series colours ignore the theme (shared with React); chrome — axes, gridlines, labels — follows it.
- A prefilter on a date field with a
groupIntervalcompares the group key, not the date, in both editions. The builder therefore offers such a field a text box and no ordering operators, so what you type is what is compared.
What is the same, what is different
| React | JavaScript | |
|---|---|---|
| Engine, stylesheet, themes, worker | shared | shared |
| Config / props | <BasePivotGrid {...props} /> | createPivotGrid(host, props) |
| Enterprise surfaces | render props | plain callbacks (sockets) |
| Cell click payload | synthetic event | native MouseEvent |
| Imperative API | ref → controller | grid.controller |
| Charts | usePivotChartSync + recharts | createChartSync + built-in SVG panel |
| Server-side rendering | renders markup | constructs on mount (needs a document) |
| Enterprise | <EnterprisePivotGrid> | createEnterprisePivotGrid() |
SSR is the one honest difference: the JavaScript renderer produces DOM, so it is created when the host element exists. Everything else is held to parity by the ledgers and conformance suites that run on every commit.