Calculated Fields & Filters
calculatedFields compute a measure from the other measures of each cell — totals included, so a ratio stays a ratio of the totals — and take the display modes of regular measures: percent of a total, the difference from the previous column, a running total. Row and column fields filter their members by label, by a measure’s total or to the Top N; the row headers lay out compact, outline or tabular.
calculatedFields topN labelFilter valueFilter rowHeaderLayout templates export-pdf
ratio of totals % of total Δ previous running cell template
Four calculated measures beside Amount and Units; a cell template colours falling years red.
import { ChangeDetectionStrategy, Component, viewChild } from '@angular/core';
import { OgePivotCellTemplate, OgePivotField, OgePivotGrid } from '@oge-ui/pivot';
import type { OgePivotCalculatedField } from '@oge-ui/pivot';
@Component({
selector: 'demo-root',
imports: [OgePivotCellTemplate, OgePivotField, OgePivotGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-pivot-grid #pivot [data]="sales" [calculatedFields]="calculated">
<oge-pivot-field dataField="region" area="row" />
<oge-pivot-field dataField="date" caption="Year" area="column" groupInterval="year" />
<oge-pivot-field dataField="amount" caption="Amount" area="data" summaryType="sum" />
<oge-pivot-field dataField="units" caption="Units" area="data" summaryType="sum" />
<!-- cell template: negative deltas in red -->
<span *ogePivotCellTemplate="let cell" [style.color]="negative(cell) ? 'crimson' : null">
{{ cell.text }}
</span>
</oge-pivot-grid>
<button type="button" (click)="pdf()">PDF</button>
`,
})
export class Demo {
protected readonly sales = [
{ region: 'Europe', country: 'Germany', date: '2025-03-14', amount: 1249, units: 9 },
{ region: 'Europe', country: 'France', date: '2026-05-02', amount: 890, units: 4 },
{ region: 'Asia', country: 'Japan', date: '2025-11-19', amount: 2140, units: 12 },
{ region: 'Americas', country: 'USA', date: '2026-01-08', amount: 1730, units: 8 },
];
protected readonly calculated: OgePivotCalculatedField[] = [
{
name: 'avgPrice', caption: 'Avg price',
// evaluated per cell — on totals too, so it stays sum(amount) / sum(units)
expression: (v) => (v['units'] ? (v['amount'] ?? 0) / v['units'] : null),
format: (value) => `€${Number(value).toFixed(2)}`,
},
{
name: 'share', caption: '% of total',
expression: (v) => v['amount'] ?? null,
displayMode: 'percentOfGrandTotal',
},
{
name: 'delta', caption: 'Δ prev. year',
expression: (v) => v['amount'] ?? null,
displayMode: 'absoluteVariation', // difference from the previous column
},
{
name: 'running', caption: 'Running',
expression: (v) => v['amount'] ?? null,
runningTotal: { direction: 'row' },
},
];
protected readonly negative = (cell: { measureId: string; value: unknown }): boolean =>
cell.measureId === 'delta' && Number(cell.value) < 0;
private readonly pivot = viewChild.required(OgePivotGrid);
// the grid's own cell text, headers repeated on every page, page numbers
protected async pdf(): Promise<void> {
const { exportPivotToPdf } = await import('@oge-ui/pivot/export-pdf');
await exportPivotToPdf(this.pivot(), { title: 'Sales', pageNumbers: true });
}
}Member filters & row-header layouts
topN, labelFilter and valueFilter on a row or column field keep whole members before aggregation, so totals follow what is shown. rowHeaderLayout gives each row field its own label column (outline) or repeats the ancestors on every line (tabular).
topN labelFilter valueFilter compact / outline / tabular
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgePivotField, OgePivotGrid } from '@oge-ui/pivot';
@Component({
selector: 'demo-root',
imports: [OgePivotField, OgePivotGrid],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Top-N, label and value filters run before aggregation, so totals follow -->
<oge-pivot-grid [data]="sales" rowHeaderLayout="tabular">
<oge-pivot-field dataField="region" area="row" />
<oge-pivot-field dataField="country" area="row"
[topN]="{ count: 3, measure: 'amount' }"
[labelFilter]="{ operator: 'contains', value: 'a' }" />
<oge-pivot-field dataField="date" caption="Year" area="column" groupInterval="year" />
<oge-pivot-field dataField="amount" caption="Amount" area="data" summaryType="sum" />
</oge-pivot-grid>
<!-- value filter: keep the members whose total of a measure passes -->
<oge-pivot-grid [data]="sales" rowHeaderLayout="outline">
<oge-pivot-field dataField="country" area="row"
[valueFilter]="{ measure: 'amount', operator: 'greaterThan', value: 1000 }" />
<oge-pivot-field dataField="amount" area="data" summaryType="sum" />
</oge-pivot-grid>
`,
})
export class Demo {
protected readonly sales = [
{ region: 'Europe', country: 'Germany', date: '2025-03-14', amount: 1249, units: 9 },
{ region: 'Europe', country: 'France', date: '2026-05-02', amount: 890, units: 4 },
{ region: 'Asia', country: 'Japan', date: '2025-11-19', amount: 2140, units: 12 },
{ region: 'Americas', country: 'USA', date: '2026-01-08', amount: 1730, units: 8 },
];
}Notes
- An
expressionreceives the cell’s measure values keyed by measure id (as displayed) plus where it runs (rowPath,columnPath, total flags); non-finite results become an empty cell. - Value and Top-N filters compare a member’s grand total (over all other fields), in area order; member filters apply to local data.
*ogePivotCellTemplate,*ogePivotRowHeaderTemplateand*ogePivotColumnHeaderTemplatereplace the content only — expanders, roles and keyboard navigation stay the grid’s.@oge-ui/pivot/export-pdfwrites the grid’s own cell text, its row-header layout and the field captions, with the column headers repeated on every page.