Skip to content

Drill-Down

Drill-down lets users move between summary and detail inside the Pivot structure. In the current implementation there are two separate drill-down concepts: grouped row expansion and generated column collapse.

Source code
TypeScript ts
// src/components/pivot/PivotDrillDown.ts

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

import NumberColumnType from '@revolist/revogrid-column-numeral';
import { currentTheme } from '../composables/useRandomData';
import type { DataType } from '@revolist/revogrid';
import { PIVOT_DRILLDOWN, PIVOT_DRILLDOWN_PLUGINS, PIVOT_DRILLDOWN_ROWS } from '../sys-data/pivot.drilldown';

export function load(parentSelector: string, rows: any[] | { isDark?: boolean } = PIVOT_DRILLDOWN_ROWS) {
  const { isDark } = currentTheme();
  // Ensure rows is an array with data, otherwise fallback to default data
  const data = Array.isArray(rows) && rows.length > 0 ? rows : PIVOT_DRILLDOWN_ROWS;
  const grid = document.createElement('revo-grid') as any;

  grid.className = 'grow h-full w-full cell-border';
  grid.range = true;
  grid.resize = true;
  grid.filter = true;
  grid.colSize = 180;
  grid.readonly = true;
  grid.hideAttribution = true;
  grid.theme = isDark() ? 'darkCompact' : 'compact';
  grid.columnTypes = {
    currency: new NumberColumnType('$0,0.00'),
  };
  grid.plugins = PIVOT_DRILLDOWN_PLUGINS;
  grid.columns = [];

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

  document.querySelector(parentSelector)?.appendChild(grid);

  // Set source last after DOM attachment and config
  grid.source = data;

  return () => {
    grid.remove();
  };
}
Vue vue
<template>
  <RevoGrid
    class="grow h-full cell-border"
    hide-attribution
    range
    resize
    filter
    :colSize="180"
    :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 RevoGrid from '@revolist/vue3-datagrid';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import { currentThemeVue } from '../composables/useRandomData';
import { PIVOT_DRILLDOWN, PIVOT_DRILLDOWN_PLUGINS, PIVOT_DRILLDOWN_ROWS } from '../sys-data/pivot.drilldown';

const { isDark } = currentThemeVue();

const rows = ref(PIVOT_DRILLDOWN_ROWS);
const plugins = PIVOT_DRILLDOWN_PLUGINS;
const columnTypes = ref({
  currency: new NumberColumnType('$0,0.00'),
});

const pivot = computed(() => ({
  ...PIVOT_DRILLDOWN,
}));
</script>
React tsx
// src/components/pivot/PivotDrillDown.tsx

import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import { currentTheme } from '../composables/useRandomData';
import { PIVOT_DRILLDOWN, PIVOT_DRILLDOWN_PLUGINS, PIVOT_DRILLDOWN_ROWS } from '../sys-data/pivot.drilldown';

interface PivotDrillDownProps {
  rows?: any[];
}

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

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

  const pivot = useMemo(
    () => ({ ...PIVOT_DRILLDOWN }),
    [],
  );

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

export default PivotDrillDown;
Angular ts
import { Component, ViewEncapsulation, NO_ERRORS_SCHEMA } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import { currentTheme } from '../composables/useRandomData';
import { PIVOT_DRILLDOWN, PIVOT_DRILLDOWN_PLUGINS, PIVOT_DRILLDOWN_ROWS } from '../sys-data/pivot.drilldown';

@Component({
  selector: 'pivot-drilldown-grid',
  standalone: true,
  imports: [RevoGrid],
  template: `
    <revo-grid
      class="grow h-full cell-border"
      [hideAttribution]="true"
      [range]="true"
      [resize]="true"
      [filter]="true"
      [colSize]="180"
      [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 PivotDrillDownGridComponent {
  rows = PIVOT_DRILLDOWN_ROWS;
  theme = currentTheme().isDark() ? 'darkCompact' : 'compact';

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

  plugins = PIVOT_DRILLDOWN_PLUGINS;

  pivot = { ...PIVOT_DRILLDOWN };
}
Data & Config ts
import type { GridPlugin } from '@revolist/revogrid';
import { PivotPlugin, type PivotConfig } from '@revolist/revogrid-enterprise';
import { AdvanceFilterPlugin, ColumnCollapsePlugin, RowOddPlugin, commonAggregators } from '@revolist/revogrid-pro';
import { PIVOT_TIME_RANGE_ROWS } from './pivot.shared';

export const PIVOT_DRILLDOWN_ROWS = PIVOT_TIME_RANGE_ROWS;

export const PIVOT_DRILLDOWN_PLUGINS: GridPlugin[] = [
  PivotPlugin,
  ColumnCollapsePlugin,
  AdvanceFilterPlugin,
  RowOddPlugin,
] as any[];

export const PIVOT_DRILLDOWN: PivotConfig = {
  dimensions: [
    { prop: 'region', sortable: true },
    { prop: 'rep', sortable: true },
    { prop: 'year', sortable: true },
    { prop: 'quarter', sortable: true },
    {
      prop: 'sales',
      name: 'Sales',
      columnType: 'currency',
      aggregators: {
        sum: commonAggregators.sum,
      },
    },
  ],
  rows: ['region', 'rep'],
  columns: ['year', 'quarter'],
  values: [{ prop: 'sales', aggregator: 'sum' }],
  collapsed: true,
  groupAggregations: true,
  columnCollapse: {
    collapsed: true,
    aggregator: 'sum',
  },
};

Row drill-down is enabled when:

  • the effective row hierarchy has more than one level
  • collapsed is a boolean or expanded state exists
const pivot: PivotConfig = {
rows: ['region', 'rep'],
values: [{ prop: 'sales', aggregator: 'sum' }],
collapsed: true,
};

Behavior:

  • collapsed: true starts grouped rows collapsed
  • collapsed: false starts grouped rows expanded
  • expanded stores per-group overrides using grouped path keys

PivotPlugin translates the Pivot row structure into RevoGrid grouping options, then emits pivot-config-update when users expand or collapse groups.

Set groupAggregations: true to render aggregate values inside grouped Pivot rows.

const pivot: PivotConfig = {
rows: ['region', 'rep'],
columns: ['year'],
values: [{ prop: 'sales', aggregator: 'sum' }],
collapsed: true,
groupAggregations: true,
};

Behavior:

  • the grouped row still shows the expand/collapse UI and group label
  • generated Pivot value columns show the aggregate for the current grouped branch
  • nested groups aggregate only their own descendants
  • collapsed groups still show their aggregate values

If you hide the grouped row field column (for example with hide-columns="region"), you can move the grouped label into the first visible row cell:

const pivot: PivotConfig = {
rows: ['region', 'product'],
columns: ['quarter'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
collapsed: true,
groupAggregations: true,
groupLabelColumn: 'firstVisible',
};

groupLabelColumn modes:

  • 'grouped' (default): keep label in the grouped field column
  • 'firstVisible': render label in the first currently visible row cell

Current limitation:

  • groupAggregations currently applies to the client-side Pivot path
  • server-side Pivot still renders grouped labels only unless backend-precomputed grouped aggregates are added

Column drill-down is controlled by columnCollapse.

columnCollapse: {
enabled: true,
collapsed: false,
aggregator: 'sum',
}

Behavior:

  • generated Pivot column groups become collapsible
  • collapsed groups render a placeholder header/cell
  • placeholder cells use a collapsed-bucket aggregator, not a naive sum of visible child cells

Persisted state for generated groups lives in collapsedColumns.

  • collapsed: row drill-down default
  • expanded: persisted row group expansion state
  • groupAggregations: render aggregate values inside grouped client-side Pivot rows
  • groupLabelColumn: choose where grouped row labels render ('grouped' or 'firstVisible')
  • columnCollapse: enable and configure generated column collapse
  • collapsedColumns: persisted generated column collapse state

When a column group is collapsed, Pivot can render:

  • a default placeholder header based on the group name
  • a custom placeholder header or cell template
  • a custom collapsed aggregator, globally or per value field

This is useful when a collapsed analytical bucket should still show a meaningful aggregate instead of just hiding all child columns.

PivotPlugin emits pivot-config-update as users interact with grouped rows or collapsible columns. That event is the right place to persist the current Pivot state if you want drill-down state to survive reloads.

  • Expecting row drill-down with only one row field.
  • Expecting grouped rows to show metric values without enabling groupAggregations.
  • Expecting columnCollapse to work without generated column groups.
  • Treating expanded as UI row indexes. It is persisted grouped state, not viewport position.

Continue with Sorting And Filtering or Plugin Lifecycle.