Documentation

Prefilter and the node context menu

Two Enterprise surfaces that both attach to what is already on screen: a predicate applied to rows before anything is aggregated, and a right-click menu on a header node.

The prefilter

It runs earlier than everything else — before each field's own filterValues, before grouping, before aggregation. So it changes the totals, not just what you can see.

import { EnterprisePivotGrid } from '@kanunilabs/pivotgrid-react-enterprise';

<EnterprisePivotGrid
  licenseKey={key}
  data={data}
  initialFields={fields}
  layout={layout}
  onLayoutChange={setLayout}
/>

EnterprisePivotGrid supplies the builder; the footer's filter button opens it. The applied predicate lives at layout.prefilter.

The AST

type PivotPrefilter = FilterGroup;

interface FilterGroup {
  logicalOperator: 'and' | 'or';
  conditions: (FilterCondition | FilterGroup)[];
}

interface FilterCondition {
  fieldId: string;      // PivotField.id — not dataField
  operator: FilterOperator;
  value: PivotValue;
}

Set one from code without the dialog:

setLayout((l) => ({
  ...l,
  prefilter: {
    logicalOperator: 'and',
    conditions: [
      { fieldId: 'region', operator: '=', value: 'EMEA' },
      { fieldId: 'year', operator: '>=', value: 2024 },
    ],
  },
}));

Do not pass an inline layout={{ … }} literal. A fresh object every render overwrites whatever the dialog just applied. Hold it in state, as above.

Note this is not the DataGrid filter AST. PivotGrid uses logicalOperator / conditions / fieldId; DataGrid uses logic / nodes / columnId. They are not interchangeable.

The node context menu

Right-click a row or column header that has children:

ItemWhat it does
Expand / Collapsetoggles that node
Expand Allexpands one more level, across the whole tree
Collapse Allcollapses one level, across the whole tree

What this does not do

The prefilter is evaluated twice, by two different implementations

This is the one worth knowing before you rely on it. The predicate is interpreted by two separate switch statements:

  • engine/FlatPivotEngine.ts — drives everything the grid displays. On a string (dictionary-encoded) column it compares with string semantics: strVal > targetStr.
  • engine/filter.ts — drives drill-down and export. It coerces first: Number(val) > targetNum.

So > on a column of numeric-looking strings can order values differently in the grid than in the file you export from it. The two were written independently and nothing keeps them in step.

Two more consequences of the same split:

  • isnull on a measure treats zero as blank. The numeric branch returns isNaN(numVal) || numVal === 0, so a genuine 0 is reported as missing.
  • Text operators on a measure match everything. The numeric branch has no case for contains / startswith / endswith and falls through to default: return true.

The rest

  • A condition on a field that is in neither the Row nor the Column area is silently ignored — while the dialog's dropdown offers every field, with nothing to distinguish them.
  • The dialog offers 12 of the 14 operators. in and notin are implemented in both evaluators and absent from the UI. Worse: loading an existing prefilter that uses one corrupts it, because the select has no matching option and the next Apply writes whatever the browser selected instead.
  • Every value is a string. One text input for all types — no date picker, no numeric input, no between.
  • An empty condition blanks the grid. Apply only checks whether the root group is empty; a freshly added condition defaults to { operator: '=', value: '' } and matches nothing.
  • "Expand All" expands exactly one level, and it ignores the node you right-clicked. It walks the already-materialised tree, which only contains children of already-expanded nodes, and it is seeded from the root — so right-clicking EMEA also expands APAC and every other branch at that depth.
  • No keyboard path to the menu at all. The grid's key handler covers arrows, Home/End, PageUp/PageDown, Escape, Space and Enter — there is no ContextMenu-key binding. The dialog has no Escape handler, no focus trap and no role="dialog".
  • Parts are hardcoded English despite the dictionary being one hook away: all 12 operator labels, "Add Group", and the condition editor generally — that component never calls usePivotConfig, so it has no translator at all. The dialog's own title reads "Filter Area", which is the caption of a different feature.
  • You cannot add your own menu items. There is no items prop, and the only escape hatch replaces the whole component.
  • Neither feature is licence-gated. Without a key they run watermarked.

If any of these is load-bearing for you, say so — several are small, and knowing which one blocks real work is how they get prioritised.