Documentation

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>
OptionTypeDefault
transformer(engineResult, …) => ChartNormalizedDatanone — required in practice
maxPointsnumber100
hideTotalsbooleantrue
invertedbooleanfalse

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. chartIntegrationOptions is in the memo's dependency list, so an inline literal recomputes the transform every render and re-fires onChartSync — 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 no role, no aria-label and 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 own PivotThemeProvider.
  • 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 on cellType and on records.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 onlyObject.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. usePivotChartSync short-circuits and returns { chartData: [], chartSeries: [] } with no warning, no console notice and no error. Setting maxPoints and nothing else is the classic version of this.
  • maxPoints caps 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 writes yAxisId on 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: locale is read by the transform but is not in the memo's dependencies.

Without a licence key both run watermarked.