Skip to content

Pivot Overview

Pivot turns a flat fact table into an analytical view. You choose which fields define the row hierarchy, which fields define generated columns, and which fields become aggregated measures. RevoGrid stays responsible for rendering and interaction, while Pivot builds the grouped rows, generated headers, totals, and summary cells.

For raw type signatures, see Pivot API and Pivot Config API. Use this guide set for behavior, defaults, and how the parts work together.

Source code
TypeScriptts
// src/components/pivot/Pivot.ts

import { defineCustomElements } from '@revolist/revogrid/loader';
defineCustomElements();

import { PivotPlugin } from '@revolist/revogrid-enterprise';
import { AdvanceFilterPlugin, RowOddPlugin } from '@revolist/revogrid-pro';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import { ECOMMERCE_PIVOT } from '../sys-data/ecommerce.pivot';
import { currentTheme } from '../composables/useRandomData';
import type { DataType } from '@revolist/revogrid';

export function load(parentSelector: string, rows: DataType[]) {
  const { isDark } = currentTheme();
  const grid = document.createElement('revo-grid');
  grid.className = 'grow h-full w-full cell-border';


  grid.range = true;
  grid.resize = true;
  grid.filter = true;
  grid.colSize = 200;
  grid.readonly = true;
  grid.hideAttribution = true;
  grid.theme = isDark() ? 'darkCompact' : 'compact';
  grid.source = rows;

  grid.columnTypes = {
    integer: new NumberColumnType(),
    currency: new NumberColumnType('$0,0.00'),
  };

  grid.plugins = [PivotPlugin, AdvanceFilterPlugin, RowOddPlugin];

  Object.assign(grid, {
    pivot: { ...ECOMMERCE_PIVOT },
  })

  document.querySelector(parentSelector)?.appendChild(grid);
}
Vuevue
// src/components/pivot/Pivot.vue
<template>
    <RevoGrid
    class="grow h-full cell-border"
      hide-attribution
      range
      resize
      filter
      :colSize="200"
      :source="rows"
      :pivot.prop="pivot"
      :theme="isDark ? 'darkCompact' : 'compact'"
      :plugins="plugins"
      :column-types="columnTypes"
      readonly
    />
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { currentThemeVue } from '../composables/useRandomData';
const { isDark } = currentThemeVue();

import NumberColumnType from '@revolist/revogrid-column-numeral';
import RevoGrid, {
  type DataType,
  type GridPlugin,
} from '@revolist/vue3-datagrid';

import {
  PivotPlugin,
} from '@revolist/revogrid-enterprise';
import {
  AdvanceFilterPlugin,
  RowOddPlugin,
} from '@revolist/revogrid-pro';

import { ECOMMERCE_PIVOT } from '../sys-data/ecommerce.pivot';

const props = defineProps<{
  rows: DataType[];
  hasConfigurator?: boolean;
}>();

const columnTypes = ref({
  integer: new NumberColumnType(),
  currency: new NumberColumnType('$0,0.00'),
});

const plugins: GridPlugin[] = [PivotPlugin, AdvanceFilterPlugin, RowOddPlugin];
const pivot = computed(() => {
  return {
    ...ECOMMERCE_PIVOT,
    hasConfigurator: props.hasConfigurator,
  };
});

</script>
Reacttsx
// src/components/pivot/Pivot.tsx

import { PivotPlugin } from '@revolist/revogrid-enterprise';
import { AdvanceFilterPlugin, RowOddPlugin } from '@revolist/revogrid-pro';
import { useMemo } from 'react';
import { RevoGrid, type DataType } from '@revolist/react-datagrid';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import { ECOMMERCE_PIVOT } from '../sys-data/ecommerce.pivot';
import { currentTheme } from '../composables/useRandomData';

interface PivotProps {
  rows?: DataType[];
}

function Pivot({ rows }: PivotProps) {
  const { isDark } = currentTheme();
  const data = useMemo(() => (Array.isArray(rows) && rows.length > 0 ? rows : []), [rows]);

  const columnTypes = useMemo(
    () => ({
      integer: new NumberColumnType(),
      currency: new NumberColumnType('$0,0.00'),
    }),
    []
  );

  const plugins = useMemo(() => [PivotPlugin, AdvanceFilterPlugin, RowOddPlugin], []);
  const pivot = useMemo(
    () => ({ ...ECOMMERCE_PIVOT }),
    []
  );

  return (
    <RevoGrid
      className="grow h-full cell-border"
      hideAttribution
      range
      resize
      filter
      colSize={200}
      source={data}
      columns={[]}
      pivot={pivot}
      theme={isDark() ? 'darkCompact' : 'compact'}
      plugins={plugins}
      columnTypes={columnTypes}
      readonly
    />
  );
}

export default Pivot;
Angularts
// src/components/pivot/PivotAngular.ts

import { Component, ViewEncapsulation, Input, NO_ERRORS_SCHEMA } from '@angular/core';
import { RevoGrid, type DataType } from '@revolist/angular-datagrid';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import { PivotPlugin } from '@revolist/revogrid-enterprise';
import { AdvanceFilterPlugin, RowOddPlugin } from '@revolist/revogrid-pro';
import { ECOMMERCE_PIVOT } from '../sys-data/ecommerce.pivot';
import { currentTheme } from '../composables/useRandomData';

@Component({
  selector: 'pivot-grid',
  standalone: true,
  imports: [RevoGrid],
  template: `
    <revo-grid
      class="grow h-full cell-border"
      [hideAttribution]="true"
      [range]="true"
      [resize]="true"
      [filter]="true"
      [colSize]="200"
      [source]="rows"
      [pivot]="pivot"
      [theme]="theme"
      [plugins]="plugins"
      [columnTypes]="columnTypes"
      [readonly]="true"
    ></revo-grid>
  `,
  encapsulation: ViewEncapsulation.None,
  // Allows Angular demos to bind RevoGrid plugin props that are not wrapper inputs.
  schemas: [NO_ERRORS_SCHEMA],
})
export class PivotGridComponent {
  @Input() rows: DataType[] = [];

  theme = currentTheme().isDark() ? 'darkCompact' : 'compact';

  columnTypes = {
    integer: new NumberColumnType(),
    currency: new NumberColumnType('$0,0.00'),
  };

  plugins = [PivotPlugin, AdvanceFilterPlugin, RowOddPlugin];

  pivot = { ...ECOMMERCE_PIVOT };
}
Pivot Configts
import type { GridPlugin } from '@revolist/revogrid';
import {
  type PivotConfigDimension,
  type PivotConfig,
  PivotPlugin,
} from '@revolist/revogrid-enterprise';
import {
  commonAggregators,
  ratingStarRenderer,
  progressLineWithValueRenderer,
  progressLineRenderer,
  changeRenderer,
  thresholdRenderer,
  mergeCellProperties,
  RowOddPlugin,
  AdvanceFilterPlugin,
  SameValueMergePlugin,
  RowSelectPlugin,
  FilterHeaderPlugin,
  ColumnCollapsePlugin,
} from '@revolist/revogrid-pro';

export const PIVOT_PLUGINS: GridPlugin[] = [FilterHeaderPlugin, RowSelectPlugin, SameValueMergePlugin, PivotPlugin, AdvanceFilterPlugin, RowOddPlugin] as any[]; 
export const PIVOT_SHOWCASE_PLUGINS: GridPlugin[] = [
  FilterHeaderPlugin,
  RowSelectPlugin,
  SameValueMergePlugin,
  PivotPlugin,
  ColumnCollapsePlugin,
  AdvanceFilterPlugin,
  RowOddPlugin,
] as any[];

export const ECOMMERCE_PIVOT: PivotConfig = {
  dimensions: [
    {
      prop: 'Age',
      columnType: 'integer',
      size: 100,
      sortable: true,
      merge: true,
      filterPlaceholder: 'Age?',
    },
    {
      prop: 'Time',
      columnType: 'time',
      size: 100,
      sortable: true,
      merge: true,
      columnProperties: (column) => {
        if (column.children && column.name === '00:00') {
          return {
            highlight: true,
          };
        }
      }
    },
    {
      prop: 'City',
      sortable: true,
      merge: true,
      filter: ['string', 'selection'],
      order: 'asc',
    },
    {
      prop: 'Gender',
      filter: ['string', 'selection'],
      sortable: true,
      merge: true,
    },
    {
      prop: 'Membership Type',
      filter: ['string', 'selection'],
      sortable: true,
      merge: true,
    },
    {
      prop: 'Total Spend',
      sortable: true,
      columnType: 'currency',
      filter: ['number'],
      filterPlaceholder: 'Total Spend?',
      thresholds: [
        { value: 900, className: 'high' },
        { value: 600, className: 'medium' },
      ],
      cellProperties: mergeCellProperties(thresholdRenderer, ({ value }) => ({
        class: {
          highlight: value > 20000,
          'align-right': true,
        },
      })),
      aggregators: {
        sum: commonAggregators.sum,
        avg: commonAggregators.avg,
        min: commonAggregators.min,
        max: commonAggregators.max,
      },
    },
    {
      name: 'Spend Change %',
      prop: 'Spend Change (%)',
      sortable: true,
      filter: ['number'],
      columnType: 'percent',
      cellTemplate: changeRenderer,
    },
    {
      name: 'Avg Rating',
      prop: 'Average Rating',
      filter: ['number', 'slider'],
      filterPlaceholder: 'Avg Rating?',
      sortable: true,
      maxStars: 5,
      maxValue: 5,
      thresholds: [
        { value: 4, className: 'high' },
        { value: 3, className: 'medium' },
        { value: 0, className: 'low' },
      ],
      cellParser: (model, column) => {
        const value = model[column.prop];
        if (Number(value)) {
          return value.toFixed(2);
        }
        return value;
      },
      cellTemplate(...args) {
        const column: PivotConfigDimension = args[1].column;
        if (column.dimension === 'values') {
          switch (column.aggregator) {
            case 'star':
              return ratingStarRenderer(...args);
            case 'prg':
              return progressLineRenderer(...args);
            case 'prgvalue':
              return progressLineWithValueRenderer(...args);
          }
        }
        return args[1].value;
      },
      aggregators: {
        prg: commonAggregators.avg,
        star: commonAggregators.avg,
        prgvalue: commonAggregators.avg,
      },
    },
    {
      prop: 'Discount Applied',
      sortable: true,
      merge: true,
      filter: ['selection'],
    },
  ],
  rows: ['City', 'Age'],
  columns: ['Time'],
  values: [
    {
      prop: 'Total Spend',
      aggregator: 'sum',
    },
    {
      prop: 'Average Rating',
      aggregator: 'prg',
    },
  ],
  hasConfigurator: true,
  flatHeaders: false,
};

export const ECOMMERCE_SHOWCASE_PIVOT: PivotConfig = {
  ...ECOMMERCE_PIVOT,
  rows: ['City', 'Membership Type'],
  columns: ['Time'],
  values: [
    {
      prop: 'Total Spend',
      aggregator: 'sum',
    },
    {
      prop: 'Average Rating',
      aggregator: 'prgvalue',
    },
  ],
  filters: ['Gender', 'Membership Type'],
  collapsed: true,
  groupAggregations: true,
  columnCollapse: {
    enabled: true,
    collapsed: false,
    aggregator: {
      'Total Spend': 'sum',
      'Average Rating': 'prgvalue',
    },
    placeholder: 'Group Total',
  },
  totals: {
    subtotals: true,
    grandTotal: true,
    subtotalLabel: 'Subtotal',
    grandTotalLabel: 'Grand Total',
  },
};
Datats
[{
  "Customer ID": "101",
  "Gender": "Female",
  "Age": "29",
  "City": "New York",
  "Membership Type": "Gold",
  "Total Spend": "1120.2",
  "Items Purchased": "14",
  "Average Rating": "4.6",
  "Discount Applied": "True",
  "Days Since Last Purchase": "25",
  "Satisfaction Level": "Satisfied",
  "Spend Change (%)": "12.020000000000005",
  "Savings": "112.02000000000001",
  "Items per Purchase": "0.5384615384615384",
  "Lifetime Value": "1680.3000000000002"
}, ...]

Use Pivot when your source rows represent detailed facts such as orders, transactions, tickets, or events, and users need to answer questions like:

  • “Sales by region and quarter”
  • “Count of incidents by team and priority”
  • “Average margin by category and month”
  • “Grand totals and subtotals across multiple hierarchies”

Use a normal grid when users mainly inspect or edit row-level records. Use Pivot when users need grouped analytical summaries.

  • dimension: A field that can be placed on the row axis, column axis, or used as a value source.
  • row field: A dimension listed in rows. It creates the vertical hierarchy.
  • column field: A dimension listed in columns. It creates generated headers.
  • value: A measure listed in values. It is aggregated into Pivot cells.
  • generated row: A synthetic row produced by Pivot, not a raw input row.
  • generated column: A synthetic column or column group produced by Pivot.
  • subtotal: A summary for one intermediate branch of a hierarchy.
  • grand total: A summary across the full filtered result.

Client-side Pivot follows this shape:

source rows
-> group by rows / columns
-> aggregate values
-> generate row hierarchy
-> generate column hierarchy
-> render with RevoGrid

Server-side Pivot keeps the same public config, but the aggregation step moves behind a remote engine boundary:

source rows
-> analytical engine
-> visible analytical window
-> RevoGrid

That means the same concepts stay in place even when the data becomes too large for full client-side materialization.

If you are new to Pivot tables or Business Intelligence (BI) concepts, we recommend starting with these conceptual guides:

  1. Install the Enterprise package.

    Terminal window
    pnpm add @revolist/revogrid-enterprise
  2. Import the plugin and any aggregators you want to expose.

    import {
    PivotPlugin,
    commonAggregators,
    type PivotConfig,
    } from '@revolist/revogrid-enterprise';
  3. Register PivotPlugin on the grid.

    grid.plugins = [PivotPlugin];
  4. Define a PivotConfig.

    const pivot: PivotConfig = {
    dimensions: [
    { prop: 'region', name: 'Region' },
    { prop: 'quarter', name: 'Quarter' },
    {
    prop: 'sales',
    name: 'Sales',
    aggregators: {
    ...commonAggregators,
    },
    },
    ],
    rows: ['region'],
    columns: ['quarter'],
    values: [{ prop: 'sales', aggregator: 'sum' }],
    };
  5. Apply it through the direct pivot binding.

    grid.pivot = pivot;

    additionalData.pivot is still observed for legacy integrations, but new code should bind pivot directly.

const pivot: PivotConfig = {
dimensions: [...],
rows: ['region', 'rep'],
columns: ['year', 'quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
collapsed: true,
};
  • dimensions describes the reusable field metadata.
  • rows defines the row hierarchy from left to right.
  • columns defines the generated header hierarchy from left to right.
  • values defines which measures are aggregated in each visible cell.
  • totals enables grand totals and subtotals.
  • collapsed enables grouped row drill-down when the row hierarchy has multiple levels.
  • groupAggregations makes grouped Pivot rows show aggregate values across generated metric columns.

When Pivot is active, PivotPlugin temporarily owns:

  • the rendered source
  • the rendered columns
  • optional pinnedBottomSource for grand totals
  • grouped-row metadata used for drill-down

When Pivot is cleared, the plugin restores the original source, columns, grouping, and pinned totals.

Current Scope

  • Client-side Pivot rows, columns, values, totals, and drill-down are implemented.
  • Client-side grouped row aggregates are implemented through pivot.groupAggregations.
  • valuesOnRows, flat headers, and the drag-and-drop configurator are implemented.
  • Server-side Pivot exposes a first-class remote engine/store contract and plugin integration.
  • Pivot Charts expose a renderer-neutral linked chart lifecycle, the first-party Pivot Charts renderer, persistence, external containers, and an optional modeless UI.
  • The docs below describe the current implementation in packages/enterprise/plugins/pivot, packages/enterprise/plugins/pivot-charts, and packages/enterprise/plugins/pivot-config.
  1. What Is Pivot?
  2. What is an OLAP Cube?
  3. Data Modeling for Pivot
  4. Dimensions
  5. Aggregations
  6. Configuration Reference
  7. Totals
  8. Values On Rows
  9. Drill-Down
  10. Sorting And Filtering
  11. Pivot Charts
  12. Plugin Lifecycle
  13. Configurator
  14. Field Panel
  15. Server-Side Pivot
  16. Caching And Cache Keys
  17. OLAP Best Practices