Drill-down and charts
Both of these are headless. The grid gives you data; you render the
surface. EnterprisePivotGrid ships the components but wires neither — so a
first integration that "does nothing" is usually this, not a bug.
Drill-down: the rows behind a cell
Clicking a cell resolves the source records that produced it and hands them to
onCellClick.
import { useState } from 'react';
import { EnterprisePivotGrid, PivotDrillDownModal } from '@kanunilabs/pivotgrid-react-enterprise';
const [drill, setDrill] = useState<{ records: unknown[]; rowPath: string[]; colPath: string[] } | null>(null);
<EnterprisePivotGrid
licenseKey={key}
data={data}
initialFields={fields}
onCellClick={(e) => {
// Guard the cell type — headers and totals resolve too.
if (e.cellType !== 'data' || !e.records?.length) return;
setDrill({ records: e.records, rowPath: e.rowPath, colPath: e.colPath });
}}
/>
{drill && (
<PivotDrillDownModal
records={drill.records}
rowPath={drill.rowPath}
colPath={drill.colPath}
onClose={() => setDrill(null)}
/>
)}
e.records is populated by re-filtering the dataset — the grid does not keep
the rows behind each cell. See the limits.
Charts
Three pieces: a transformer turns the engine result into chart-shaped data, a hook keeps it in sync, and a factory renders it.
import { useMemo } from 'react';
import { EnterprisePivotGrid, ChartDataTransformer, ChartFactory } from '@kanunilabs/pivotgrid-react-enterprise';
// MEMOISE THIS. An inline literal recomputes the transform on every render.
const chartOptions = useMemo(
() => ({
transformer: ChartDataTransformer.transform, // required — there is no default
maxPoints: 100,
hideTotals: true,
inverted: false,
}),
[],
);
const [chart, setChart] = useState({ chartData: [], chartSeries: [] });
<EnterprisePivotGrid
licenseKey={key}
data={data}
initialFields={fields}
chartIntegrationOptions={chartOptions}
onChartSync={setChart}
/>
<div style={{ height: 320 }}>
<ChartFactory type="column" data={chart.chartData} series={chart.chartSeries} />
</div>
| Option | Type | Default |
|---|---|---|
transformer | (engineResult, …) => ChartNormalizedData | none — required in practice |
maxPoints | number | 100 |
hideTotals | boolean | true |
inverted | boolean | false |
Chart types: column, bar, line, area, pie, scatter, polar,
combination. The renderer is recharts, a peer dependency — install it
yourself. ResponsiveContainer fills 100% of its parent, so the parent needs a
height.
What these do not do
Both
- There is no server-side data source.
data: TRow[]in memory is the only input — to the grid, to drill-down and to charts. PivotGrid has no remote row model at all. Size your dataset accordingly. - Object identity is load-bearing.
chartIntegrationOptionsis in the memo's dependency list, so an inline literal recomputes the transform every render and re-firesonChartSync— which typically sets state, which re-renders. Memoise it. - No keyboard or screen-reader path into either. The drill-down modal has
no
role="dialog", no focus trap and no focus restore; the chart emits norole, noaria-labeland no tabular fallback, so its numbers exist only as SVG geometry. - Neither inherits the grid's theme, and in fullscreen the drill-down modal
portals to
document.body— outside the fullscreen element, so it is not visible. Wrap them in your ownPivotThemeProvider. - Everything is hardcoded English — the whole drill-down modal and the chart type selector. Neither goes through the dictionary.
Drill-down
- Clicking a grand-total cell hands you the entire filtered dataset. The
row path is empty, so both match loops run zero times and nothing narrows.
The modal then renders it unpaginated — one
<tr>per source row, with no cap and no warning. Guard oncellTypeand onrecords.length. - Every click is a full scan of the dataset, on the main thread, twice. The rows are re-filtered rather than remembered. Nothing is cached.
- Columns are inferred from the first record only —
Object.keys(records[0])— so heterogeneous records lose the keys the first one lacks. Headers are produced by capitalising the key. - An empty record set renders nothing and says nothing. The modal returns
null, so your state says a dialog is open and the screen shows none.
Charts
- No transformer means an empty chart, forever.
usePivotChartSyncshort-circuits and returns{ chartData: [], chartSeries: [] }with no warning, no console notice and no error. SettingmaxPointsand nothing else is the classic version of this. maxPointscaps the arguments, not the series. One chart element is emitted per visible column leaf, uncapped. A wide column tree is a large number of DOM nodes.- Truncation is invisible. The transformer reports
isSampled, and the hook's result type does not carry it — so you cannot tell the reader they are looking at a sample. dataFieldsDisplayMode: 'splitAxes'is computed and discarded. The transformer writesyAxisIdon every series; the factory never reads it.- Charts are never exported. The Enterprise exporter handles Excel and PDF and contains no reference to charts.
- Chart colours ignore the theme. An eight-entry array, wrapping — series nine repeats series one.
- The pie chart mis-assigns colours, indexing the series palette by slice index, and the scatter chart binds no axis to its values.
- Switching locale does not relabel the axes until something else changes:
localeis read by the transform but is not in the memo's dependencies.
Without a licence key both run watermarked.