Skip to content

Financial Pivot Showcase

This showcase uses 3,600 original synthetic records shaped like a familiar financial sales model. The schema is inspired by Microsoft’s Financial Sample workbook, but the bundled values and product names are generated specifically for this demo.

Source code
TypeScriptts
import { defineCustomElements } from '@revolist/revogrid/loader';
import type { PivotConfig } from '@revolist/pivot';
import './financial-pivot-header/financial-pivot-header.scss';
import { currentTheme, observeCurrentTheme } from './shared/theme';
import {
  persistPivotState,
  restoreActivePreset,
  restorePivotConfig,
} from './financial.analytics';
import {
  FINANCIAL_COLUMNS,
  FINANCIAL_COLUMN_TYPES,
  FINANCIAL_MULTI_ROW_HEADER,
  FINANCIAL_PIVOT_CHARTS,
  FINANCIAL_PIVOT_CHARTS_UI,
  FINANCIAL_SHOWCASE_PLUGINS,
  applyFinancialPivotOptions,
  createFinancialPreset,
  resolveFinancialRows,
  type FinancialPresetId,
} from './financial.pivot';
import {
  FINANCIAL_PIVOT_CONFIGURATOR_EVENT,
  FINANCIAL_PIVOT_EXPANDED_EVENT,
  FINANCIAL_PIVOT_PRESET_EVENT,
  createFinancialPivotHeader,
} from './financial-pivot-header/financial-pivot-header';

defineCustomElements();

const isSmallScreen = () => window.matchMedia('(max-width: 767px)').matches;

export function load(parentSelector: string, rows: any[] | { isDark?: boolean } = []) {
  const parent = document.querySelector(parentSelector);
  if (!parent) return;

  const { isDark } = currentTheme();
  const data = resolveFinancialRows(Array.isArray(rows) ? rows : undefined);
  let activePreset: FinancialPresetId = restoreActivePreset();
  let pivotConfig: PivotConfig = restorePivotConfig(activePreset);
  let configuratorVisible = !isSmallScreen();
  let expanded = false;

  const container = document.createElement('div');
  container.className = 'financial-pivot-showcase grow flex flex-col gap-2 h-full p-2 box-border';

  const header = createFinancialPivotHeader({
    activePreset,
    configuratorVisible,
    expanded,
  });

  const scrollContainer = document.createElement('div');
  scrollContainer.className = 'grow min-h-0 overflow-auto';
  const gridContainer = document.createElement('div');
  gridContainer.className = 'pivot-grid-container h-full overflow-hidden';
  let grid: HTMLRevoGridElement;

  function applyPivotOptions() {
    grid.pivot = applyFinancialPivotOptions(
      pivotConfig,
      data,
      configuratorVisible,
    );
  }

  function refreshLayout() {
    header.state = {
      activePreset,
      configuratorVisible,
      expanded,
    };
    gridContainer.style.minWidth = configuratorVisible ? '920px' : '680px';
    Object.assign(container.style, expanded
      ? { position: 'fixed', inset: '8px', zIndex: '1000', background: 'var(--financial-pivot-expanded-background)' }
      : { position: '', inset: '', zIndex: '', background: '' });
  }

  const onPivotConfigUpdate = (event: CustomEvent<PivotConfig>) => {
    pivotConfig = event.detail || createFinancialPreset();
    persistPivotState(pivotConfig, activePreset);
    refreshLayout();
  };
  function initializeGrid() {
    grid = document.createElement('revo-grid') as any;
    grid.className = 'overflow-hidden skip-style h-full min-h-0 cell-border';
    Object.assign(grid, {
      hideAttribution: true,
      range: true,
      resize: true,
      filter: true,
      multiRowHeader: FINANCIAL_MULTI_ROW_HEADER,
      colSize: 180,
      readonly: true,
      theme: isDark() ? 'darkCompact' : 'compact',
      columns: FINANCIAL_COLUMNS,
      columnTypes: FINANCIAL_COLUMN_TYPES,
      plugins: FINANCIAL_SHOWCASE_PLUGINS,
      pivotCharts: FINANCIAL_PIVOT_CHARTS,
      pivotChartsUi: FINANCIAL_PIVOT_CHARTS_UI,
      pivot: applyFinancialPivotOptions(
        pivotConfig,
        data,
        configuratorVisible,
      ),
    });
    grid.addEventListener('pivot-config-update', onPivotConfigUpdate as EventListener);
    gridContainer.appendChild(grid);
    grid.source = data;
  }

  function replacePivotOptions() {
    grid.pivot = undefined;
    window.setTimeout(applyPivotOptions);
  }

  header.addEventListener(FINANCIAL_PIVOT_PRESET_EVENT, (event) => {
    const id = (event as CustomEvent<FinancialPresetId>).detail;
    pivotConfig = createFinancialPreset(id);
    activePreset = id;
    persistPivotState(pivotConfig, activePreset);
    refreshLayout();
    replacePivotOptions();
  });
  header.addEventListener(FINANCIAL_PIVOT_CONFIGURATOR_EVENT, () => {
    configuratorVisible = !configuratorVisible;
    applyPivotOptions();
    refreshLayout();
  });
  header.addEventListener(FINANCIAL_PIVOT_EXPANDED_EVENT, () => {
    expanded = !expanded;
    refreshLayout();
  });
  scrollContainer.appendChild(gridContainer);
  container.append(header, scrollContainer);
  parent.appendChild(container);
  refreshLayout();
  initializeGrid();
  persistPivotState(pivotConfig, activePreset);
  const disconnectTheme = observeCurrentTheme((darkTheme) => {
    grid.theme = darkTheme ? 'darkCompact' : 'compact';
  });

  return () => {
    disconnectTheme();
    grid.removeEventListener('pivot-config-update', onPivotConfigUpdate as EventListener);
    grid.remove();
    container.remove();
  };
}
Reacttsx
import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type DetailedHTMLProps,
  type HTMLAttributes,
} from 'react';
import { RevoGrid, type DataType } from '@revolist/react-datagrid';
import type { PivotConfig } from '@revolist/pivot';
import './financial-pivot-header/financial-pivot-header.scss';
import { currentTheme, observeCurrentTheme } from './shared/theme';
import {
  persistPivotState,
  restoreActivePreset,
  restorePivotConfig,
} from './financial.analytics';
import {
  FINANCIAL_COLUMNS,
  FINANCIAL_COLUMN_TYPES,
  FINANCIAL_MULTI_ROW_HEADER,
  FINANCIAL_PIVOT_CHARTS,
  FINANCIAL_PIVOT_CHARTS_UI,
  FINANCIAL_SHOWCASE_PLUGINS,
  applyFinancialPivotOptions,
  createFinancialPreset,
  resolveFinancialRows,
  type FinancialPresetId,
} from './financial.pivot';
import {
  FINANCIAL_PIVOT_CONFIGURATOR_EVENT,
  FINANCIAL_PIVOT_EXPANDED_EVENT,
  FINANCIAL_PIVOT_PRESET_EVENT,
  defineFinancialPivotHeaderElement,
  type FinancialPivotHeaderElement,
  type FinancialPivotHeaderState,
} from './financial-pivot-header/financial-pivot-header';

defineFinancialPivotHeaderElement();

declare module 'react' {
  namespace JSX {
    interface IntrinsicElements {
      'financial-pivot-header': DetailedHTMLProps<
        HTMLAttributes<FinancialPivotHeaderElement>,
        FinancialPivotHeaderElement
      >;
    }
  }
}

interface PivotProps {
  rows?: DataType[];
}

const isSmallScreen = () => typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches;

function PivotShowcase({ rows }: PivotProps) {
  const [isDark, setIsDark] = useState(() => currentTheme().isDark());
  const initialData = useMemo(() => resolveFinancialRows(rows), [rows]);
  const [data, setData] = useState<DataType[]>(initialData);
  const [activePreset, setActivePreset] = useState<FinancialPresetId>(restoreActivePreset);
  const [pivotConfig, setPivotConfig] = useState<PivotConfig>(() => restorePivotConfig(activePreset));
  const [configuratorVisible, setConfiguratorVisible] = useState(() => !isSmallScreen());
  const [expanded, setExpanded] = useState(false);

  const pivot = useMemo(
    () => applyFinancialPivotOptions(pivotConfig, data, configuratorVisible),
    [pivotConfig, data, configuratorVisible],
  );
  const plugins = useMemo(() => FINANCIAL_SHOWCASE_PLUGINS, []);
  const columns = useMemo(() => FINANCIAL_COLUMNS, []);
  const columnTypes = useMemo(() => FINANCIAL_COLUMN_TYPES, []);
  const gridRef = useRef<HTMLRevoGridElement>(null);
  const headerRef = useRef<FinancialPivotHeaderElement>(null);

  useEffect(() => observeCurrentTheme(setIsDark), []);

  useEffect(() => {
    setData(initialData);
  }, [initialData]);

  useEffect(() => {
    persistPivotState(pivotConfig, activePreset);
  }, [activePreset, pivotConfig]);

  useEffect(() => {
    const grid = gridRef.current;
    if (!grid) return;
    const handler = (event: Event) => {
      const nextConfig = (event as CustomEvent<PivotConfig>).detail || createFinancialPreset();
      setPivotConfig(nextConfig);
    };
    grid.addEventListener('pivot-config-update', handler);
    return () => grid.removeEventListener('pivot-config-update', handler);
  }, []);

  const selectPreset = useCallback((id: FinancialPresetId) => {
    if (gridRef.current) gridRef.current.pivot = undefined;
    window.setTimeout(() => {
      setPivotConfig(createFinancialPreset(id));
      setActivePreset(id);
    });
  }, []);

  const headerState = useMemo<FinancialPivotHeaderState>(() => ({
    activePreset,
    configuratorVisible,
    expanded,
  }), [activePreset, configuratorVisible, expanded]);

  useEffect(() => {
    if (headerRef.current) headerRef.current.state = headerState;
  }, [headerState]);

  useEffect(() => {
    const header = headerRef.current;
    if (!header) return;
    const onPreset = (event: Event) => {
      selectPreset((event as CustomEvent<FinancialPresetId>).detail);
    };
    const onConfigurator = () => setConfiguratorVisible((value) => !value);
    const onExpanded = () => setExpanded((value) => !value);
    header.addEventListener(FINANCIAL_PIVOT_PRESET_EVENT, onPreset);
    header.addEventListener(FINANCIAL_PIVOT_CONFIGURATOR_EVENT, onConfigurator);
    header.addEventListener(FINANCIAL_PIVOT_EXPANDED_EVENT, onExpanded);
    return () => {
      header.removeEventListener(FINANCIAL_PIVOT_PRESET_EVENT, onPreset);
      header.removeEventListener(FINANCIAL_PIVOT_CONFIGURATOR_EVENT, onConfigurator);
      header.removeEventListener(FINANCIAL_PIVOT_EXPANDED_EVENT, onExpanded);
    };
  }, [selectPreset]);

  return (
    <div
      className="financial-pivot-showcase grow flex flex-col gap-2 h-full p-2 box-border"
      style={expanded ? { position: 'fixed', inset: 8, zIndex: 1000, background: 'var(--financial-pivot-expanded-background)' } : undefined}
    >
      <financial-pivot-header ref={headerRef} />

      <div className="grow min-h-0 overflow-auto">
        <div className="pivot-grid-container h-full overflow-hidden" style={{ minWidth: configuratorVisible ? 920 : 680 }}>
          <RevoGrid
            ref={gridRef}
            className="overflow-hidden skip-style h-full min-h-0 cell-border"
            hideAttribution
            range
            resize
            filter
            multiRowHeader={FINANCIAL_MULTI_ROW_HEADER}
            colSize={180}
            source={data}
            columns={columns}
            pivot={pivot}
            pivotCharts={FINANCIAL_PIVOT_CHARTS}
            pivotChartsUi={FINANCIAL_PIVOT_CHARTS_UI}
            theme={isDark ? 'darkCompact' : 'compact'}
            plugins={plugins}
            columnTypes={columnTypes}
            readonly
          />
        </div>
      </div>
    </div>
  );
}

export default PivotShowcase;
Vuevue
<template>
  <div
    class="financial-pivot-showcase grow flex flex-col gap-2 h-full box-border"
    :style="expandedStyle"
  >
    <financial-pivot-header
      :state.prop="headerState"
      @financial-pivot-preset-select="onPresetSelect"
      @financial-pivot-configurator-toggle="configuratorVisible = !configuratorVisible"
      @financial-pivot-expanded-toggle="expanded = !expanded"
    />

    <div class="grow min-h-0 overflow-auto">
      <div
        class="pivot-grid-container h-full overflow-hidden gap-1"
        :style="{ minWidth: configuratorVisible ? '920px' : '680px' }"
      >
        <RevoGrid
          ref="gridElement"
          class="overflow-hidden skip-style h-full min-h-0 cell-border"
          hide-attribution
          range
          resize
          filter
          :multi-row-header.prop="FINANCIAL_MULTI_ROW_HEADER"
          :colSize="180"
          :source="rows"
          :columns="FINANCIAL_COLUMNS"
          :pivot.prop="pivot"
          :pivot-charts.prop="FINANCIAL_PIVOT_CHARTS"
          :pivot-charts-ui.prop="FINANCIAL_PIVOT_CHARTS_UI"
          :theme="isDark ? 'darkCompact' : 'compact'"
          :plugins="plugins"
          :column-types="columnTypes"
          readonly
          @pivot-config-update="configUpdate"
        />
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import type { PivotConfig } from '@revolist/pivot';
import { currentTheme, observeCurrentTheme } from './shared/theme';
import {
  persistPivotState,
  restoreActivePreset,
  restorePivotConfig,
} from './financial.analytics';
import {
  FINANCIAL_COLUMNS,
  FINANCIAL_COLUMN_TYPES,
  FINANCIAL_MULTI_ROW_HEADER,
  FINANCIAL_PIVOT_CHARTS,
  FINANCIAL_PIVOT_CHARTS_UI,
  FINANCIAL_SHOWCASE_PLUGINS,
  applyFinancialPivotOptions,
  createFinancialPreset,
  resolveFinancialRows,
  type FinancialPresetId,
} from './financial.pivot';
import {
  defineFinancialPivotHeaderElement,
  type FinancialPivotHeaderState,
} from './financial-pivot-header/financial-pivot-header';

defineFinancialPivotHeaderElement();

const isDark = ref(currentTheme().isDark());
let disconnectTheme: (() => void) | undefined;
const props = defineProps({ rows: { type: Array<any>, default: () => [] } });
const initialRows = resolveFinancialRows(props.rows);
const rows = shallowRef(initialRows);
const isSmallScreen = () => typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches;

onMounted(() => {
  disconnectTheme = observeCurrentTheme((value) => {
    isDark.value = value;
  });
});
onBeforeUnmount(() => disconnectTheme?.());

const activePreset = ref<FinancialPresetId>(restoreActivePreset());
const pivotConfig = shallowRef<PivotConfig>(restorePivotConfig(activePreset.value));
const configuratorVisible = ref(!isSmallScreen());
const expanded = ref(false);
const gridElement = ref<HTMLRevoGridElement>();
const columnTypes = ref(FINANCIAL_COLUMN_TYPES);
const plugins = FINANCIAL_SHOWCASE_PLUGINS;
const headerState = computed<FinancialPivotHeaderState>(() => ({
  activePreset: activePreset.value,
  configuratorVisible: configuratorVisible.value,
  expanded: expanded.value,
}));
watch(
  [pivotConfig, activePreset],
  ([config, preset]) => persistPivotState(config, preset),
  { immediate: true },
);

const pivot = computed(() =>
  applyFinancialPivotOptions(
    pivotConfig.value,
    rows.value,
    configuratorVisible.value,
  ),
);

const expandedStyle = computed(() => expanded.value
  ? { position: 'fixed', inset: '8px', zIndex: 1000, background: 'var(--financial-pivot-expanded-background)' }
  : undefined,
);

const configUpdate = (event: CustomEvent<PivotConfig>) => {
  pivotConfig.value = event.detail || createFinancialPreset();
};

const selectPreset = (id: FinancialPresetId) => {
  if (gridElement.value) gridElement.value.pivot = undefined;
  window.setTimeout(() => {
    pivotConfig.value = createFinancialPreset(id);
    activePreset.value = id;
  });
};

const onPresetSelect = (event: Event) => {
  selectPreset((event as CustomEvent<FinancialPresetId>).detail);
};
</script>

<style lang="scss">
@use './financial-pivot-header/financial-pivot-header.scss';

revo-grid.cell-border .rgHeaderCell[highlight] {
  box-shadow: 0 -3px 0 0 #00b997 inset, -1px 0 0 0 var(--revo-grid-cell-border) inset;
}
</style>
Angularts
import {
  ChangeDetectionStrategy,
  Component,
  computed,
  ElementRef,
  effect,
  Input,
  NO_ERRORS_SCHEMA,
  OnDestroy,
  signal,
  ViewChild,
  ViewEncapsulation,
} from '@angular/core';
import { RevoGrid, type DataType } from '@revolist/angular-datagrid';
import type { PivotConfig } from '@revolist/pivot';
import { currentTheme, observeCurrentTheme } from './shared/theme';
import {
  persistPivotState,
  restoreActivePreset,
  restorePivotConfig,
} from './financial.analytics';
import {
  FINANCIAL_COLUMNS,
  FINANCIAL_COLUMN_TYPES,
  FINANCIAL_MULTI_ROW_HEADER,
  FINANCIAL_PIVOT_CHARTS,
  FINANCIAL_PIVOT_CHARTS_UI,
  FINANCIAL_SHOWCASE_PLUGINS,
  applyFinancialPivotOptions,
  createFinancialPreset,
  resolveFinancialRows,
  type FinancialPresetId,
} from './financial.pivot';
import {
  defineFinancialPivotHeaderElement,
  type FinancialPivotHeaderState,
} from './financial-pivot-header/financial-pivot-header';

defineFinancialPivotHeaderElement();

const isSmallScreen = () => typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches;

@Component({
  selector: 'pivot-showcase-grid',
  standalone: true,
  imports: [RevoGrid],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  styleUrls: ['./financial-pivot-header/financial-pivot-header.scss'],
  schemas: [NO_ERRORS_SCHEMA],
  template: `
    <div
      class="financial-pivot-showcase grow flex flex-col gap-2 h-full p-2 box-border"
      [style.position]="expanded() ? 'fixed' : null"
      [style.inset]="expanded() ? '8px' : null"
      [style.z-index]="expanded() ? 1000 : null"
      [style.background]="expanded() ? 'var(--financial-pivot-expanded-background)' : null"
    >
      <financial-pivot-header
        [state]="headerState"
        (financial-pivot-preset-select)="selectPreset($any($event).detail)"
        (financial-pivot-configurator-toggle)="configuratorVisible.set(!configuratorVisible())"
        (financial-pivot-expanded-toggle)="expanded.set(!expanded())"
      ></financial-pivot-header>

      <div class="grow min-h-0 overflow-auto">
        <div
          class="pivot-grid-container h-full overflow-hidden"
          [style.min-width]="configuratorVisible() ? '920px' : '680px'"
        >
          <revo-grid
            #gridElement
            class="overflow-hidden skip-style h-full min-h-0 cell-border"
            [hideAttribution]="true"
            [range]="true"
            [resize]="true"
            [filter]="true"
            [multiRowHeader]="multiRowHeader"
            [colSize]="180"
            [source]="sourceRows()"
            [columns]="FINANCIAL_COLUMNS"
            [pivot]="pivot()"
            [pivotCharts]="pivotCharts"
            [pivotChartsUi]="pivotChartsUi"
            [theme]="theme()"
            [plugins]="plugins"
            [columnTypes]="columnTypes"
            [readonly]="true"
            (pivot-config-update)="configUpdate($event)"
          ></revo-grid>
        </div>
      </div>
    </div>
  `,
})
export class PivotShowcaseGridComponent implements OnDestroy {
  private benchmarkSource: DataType[] = resolveFinancialRows();
  readonly sourceRows = signal<DataType[]>(this.benchmarkSource);

  @Input() set rows(value: DataType[]) {
    this.benchmarkSource = resolveFinancialRows(value);
    this.sourceRows.set(this.benchmarkSource);
  }

  @ViewChild('gridElement', { read: ElementRef })
  gridElement?: ElementRef<HTMLRevoGridElement & { pivot?: PivotConfig }>;

  readonly FINANCIAL_COLUMNS = FINANCIAL_COLUMNS;
  readonly columnTypes = FINANCIAL_COLUMN_TYPES;
  readonly multiRowHeader = FINANCIAL_MULTI_ROW_HEADER;
  readonly pivotCharts = FINANCIAL_PIVOT_CHARTS;
  readonly pivotChartsUi = FINANCIAL_PIVOT_CHARTS_UI;
  readonly plugins = FINANCIAL_SHOWCASE_PLUGINS;
  readonly isDark = signal(currentTheme().isDark());
  readonly theme = computed(() => this.isDark() ? 'darkCompact' : 'compact');
  private readonly disconnectTheme = observeCurrentTheme((value) => this.isDark.set(value));

  readonly activePreset = signal<FinancialPresetId>(restoreActivePreset());
  readonly pivotSignal = signal<PivotConfig>(restorePivotConfig(this.activePreset()));
  readonly configuratorVisible = signal(!isSmallScreen());
  readonly expanded = signal(false);

  constructor() {
    effect(() => persistPivotState(this.pivotSignal(), this.activePreset()));
  }

  get headerState(): FinancialPivotHeaderState {
    return {
      activePreset: this.activePreset(),
      configuratorVisible: this.configuratorVisible(),
      expanded: this.expanded(),
    };
  }

  readonly pivot = computed(() => applyFinancialPivotOptions(
    this.pivotSignal(),
    this.sourceRows(),
    this.configuratorVisible(),
  ));

  configUpdate(event: CustomEvent<PivotConfig>) {
    this.pivotSignal.set(event.detail || createFinancialPreset());
  }

  selectPreset(id: FinancialPresetId) {
    this.replacePivot(createFinancialPreset(id), id);
  }

  private replacePivot(config: PivotConfig, preset: FinancialPresetId) {
    if (this.gridElement) this.gridElement.nativeElement.pivot = undefined;
    window.setTimeout(() => {
      this.pivotSignal.set(config);
      this.activePreset.set(preset);
    });
  }

  ngOnDestroy(): void {
    this.disconnectTheme();
  }
}
Pivot Configts
import type { ColumnRegular, DataType, GridPlugin } from '@revolist/revogrid';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import {
  PivotChartsPlugin,
  PivotChartsUiPlugin,
  type PivotConfig,
  type PivotConfigDimension,
  clonePivotFilterSelectionMap,
  createPivotChartsRenderer,
  filterPivotSource,
  PivotPlugin,
  type PivotChartsConfig,
  type PivotChartsUiConfig,
} from '@revolist/pivot';
import {
  AdvanceFilterPlugin,
  ColumnCollapsePlugin,
  ContextMenuPlugin,
  FilterHeaderPlugin,
  MultiRowHeaderPlugin,
  RowOddPlugin,
  RowSelectPlugin,
  SameValueMergePlugin,
  commonAggregators,
  columnTypeRenderer,
} from '@revolist/revogrid-pro';
import { FINANCIAL_DATA, type FinancialRow } from './financial-dataset';
import { createFinancialHeatmapColumnType } from './financial.heatmap';

export const FINANCIAL_COLUMN_TYPES = {
  currency: new NumberColumnType('$0,0.00'),
  number: new NumberColumnType('0,0.00'),
  integer: new NumberColumnType('0,0'),
  salesHeatmap: createFinancialHeatmapColumnType('Sales', 'currency'),
  profitHeatmap: createFinancialHeatmapColumnType('Profit', 'currency'),
  unitsHeatmap: createFinancialHeatmapColumnType('Units Sold', 'number'),
  grossSalesHeatmap: createFinancialHeatmapColumnType('Gross Sales', 'currency'),
  discountsHeatmap: createFinancialHeatmapColumnType('Discounts', 'currency'),
  cogsHeatmap: createFinancialHeatmapColumnType('COGS', 'currency'),
};

const percentile90 = (values: any[]) => {
  if (!values.length) return 0;
  const sorted = values.map(Number).sort((left, right) => left - right);
  return sorted[Math.ceil(sorted.length * 0.9) - 1] ?? 0;
};

const currencyAggregators = {
  sum: commonAggregators.sum,
  avg: commonAggregators.avg,
  min: commonAggregators.min,
  max: commonAggregators.max,
  p90: percentile90,
};

const numberAggregators = {
  sum: commonAggregators.sum,
  avg: commonAggregators.avg,
};

const MONTH_ORDER = [
  'January', 'February', 'March', 'April', 'May', 'June',
  'July', 'August', 'September', 'October', 'November', 'December',
];

const monthCompare: NonNullable<PivotConfigDimension['cellCompare']> = (_prop, a, b) =>
  MONTH_ORDER.indexOf(String(a.Month)) - MONTH_ORDER.indexOf(String(b.Month));

const FINANCIAL_DIMENSION_DEFINITIONS: PivotConfigDimension[] = [
  { prop: 'Country', fieldGroup: 'Dimensions', sortable: true, order: 'asc', merge: true, filter: ['string', 'selection'] },
  { prop: 'Segment', fieldGroup: 'Dimensions', sortable: true, order: 'asc', merge: true, filter: ['string', 'selection'] },
  { prop: 'Year', fieldGroup: ['Dimensions', 'Date'], sortable: true, order: 'asc', columnType: 'integer', filter: ['number', 'selection'] },
  {
    prop: 'Month',
    fieldGroup: ['Dimensions', 'Date'],
    sortable: true,
    order: 'asc',
    cellCompare: monthCompare,
    filter: ['string', 'selection'],
    filterOptions: [...MONTH_ORDER],
  },
  { prop: 'Product', fieldGroup: 'Dimensions', sortable: true, filter: ['string', 'selection'] },
  {
    prop: 'Discount Band',
    fieldGroup: 'Dimensions',
    sortable: true,
    filter: ['string', 'selection'],
    filterOptions: ['None', 'Low', 'Medium', 'High'],
  },
  {
    prop: 'Units Sold',
    fieldGroup: 'Data',
    sortable: true,
    columnType: 'unitsHeatmap',
    filter: ['number'],
    aggregators: numberAggregators,
  },
  {
    prop: 'Sales',
    fieldGroup: 'Data',
    sortable: true,
    columnType: 'salesHeatmap',
    filter: ['number'],
    aggregators: currencyAggregators,
  },
  {
    prop: 'Profit',
    fieldGroup: 'Data',
    sortable: true,
    columnType: 'profitHeatmap',
    filter: ['number'],
    aggregators: currencyAggregators,
  },
  {
    prop: 'Gross Sales',
    fieldGroup: 'Data',
    sortable: true,
    columnType: 'grossSalesHeatmap',
    filter: ['number'],
    aggregators: currencyAggregators,
  },
  {
    prop: 'Discounts',
    fieldGroup: 'Data',
    sortable: true,
    columnType: 'discountsHeatmap',
    filter: ['number'],
    aggregators: currencyAggregators,
  },
  {
    prop: 'COGS',
    fieldGroup: 'Data',
    sortable: true,
    columnType: 'cogsHeatmap',
    filter: ['number'],
    aggregators: currencyAggregators,
  },
  { prop: 'Date', fieldGroup: 'Dimensions', sortable: true, filter: ['string'] },
];

export const FINANCIAL_DIMENSIONS: PivotConfigDimension[] = FINANCIAL_DIMENSION_DEFINITIONS
  .map((dimension) => ({
    columnTemplate: columnTypeRenderer,
    ...dimension,
  }));

export const FINANCIAL_COLUMNS: ColumnRegular[] = FINANCIAL_DIMENSIONS.map((dimension) => ({
  ...dimension,
  size: dimension.size ?? 150,
}));

export const FINANCIAL_SHOWCASE_PLUGINS: GridPlugin[] = [
  RowSelectPlugin,
  SameValueMergePlugin,
  ContextMenuPlugin,
  PivotPlugin,
  PivotChartsPlugin,
  PivotChartsUiPlugin,
  ColumnCollapsePlugin,
  MultiRowHeaderPlugin,
  AdvanceFilterPlugin,
  FilterHeaderPlugin,
  RowOddPlugin,
] as GridPlugin[];

export const FINANCIAL_PIVOT_CHARTS: PivotChartsConfig = {
  renderer: createPivotChartsRenderer(),
  defaultChartType: 'groupedColumn',
  limits: {
    maxSeries: 120,
    maxDataPoints: 2_500,
  },
};

export const FINANCIAL_PIVOT_CHARTS_UI: PivotChartsUiConfig = {
  contextMenu: true,
};

export const FINANCIAL_MULTI_ROW_HEADER = {
  // Pivot row-axis headers contain sorting and filtering controls and should
  // retain their normal leaf-header geometry. Collapsed column groups opt in
  // to downward spanning independently through spanHeaderHeight.
  spanLeafHeaders: false,
} as const;

const SALES_OVERVIEW: PivotConfig = {
  dimensions: FINANCIAL_DIMENSIONS,
  rows: ['Country', 'Segment'],
  columns: ['Year', 'Month'],
  values: [
    { prop: 'Sales', aggregator: 'sum' },
    { prop: 'Profit', aggregator: 'sum' },
    { prop: 'Units Sold', aggregator: 'sum' },
  ],
  filters: ['Product', 'Discount Band'],
  filterSelections: { 'Discount Band': ['High'] },
  hasConfigurator: true,
  flatHeaders: false,
  collapsed: true,
  groupAggregations: true,
  columnCollapse: {
    enabled: true,
    collapsed: true,
    aggregator: {
      Sales: 'sum',
      Profit: 'sum',
      'Units Sold': 'sum',
    },
    placeholder: 'Period Total',
  },
  columnLevels: {
    0: {
      collapsible: true,
      collapsed: true,
      subtotal: true,
      subtotalLabel: 'Year Total',
      subtotalPosition: 'before',
      subtotalValues: ['Sales'],
      filterable: true,
      sortable: true,
    },
    1: {
      collapsible: true,
      collapsed: true,
      subtotal: false,
      filterable: true,
      sortable: false,
    },
  },
  totals: {
    subtotals: true,
    // This preset demonstrates column-level totals without adding row subtotals.
    disabledSubtotals: {
      rows: {
        fields: ['Country', 'Segment'],
      },
    },
    grandTotal: true,
    grandTotalLabel: 'Grand Total',
  },
};

const PROFITABILITY: PivotConfig = {
  ...SALES_OVERVIEW,
  rows: ['Segment', 'Country'],
  columns: ['Year', 'Month'],
  values: [
    { prop: 'Profit', aggregator: 'sum' },
    { prop: 'Sales', aggregator: 'sum' },
    { prop: 'COGS', aggregator: 'sum' },
  ],
  filters: ['Product', 'Discount Band'],
  filterSelections: { 'Discount Band': ['Medium'] },
  columnCollapse: {
    enabled: true,
    collapsed: true,
    aggregator: { Profit: 'sum', Sales: 'sum', COGS: 'sum' },
    placeholder: 'Period Total',
  },
};

const PRODUCT_PERFORMANCE: PivotConfig = {
  ...SALES_OVERVIEW,
  rows: ['Product', 'Segment'],
  columns: ['Year', 'Country'],
  values: [
    { prop: 'Gross Sales', aggregator: 'sum' },
    { prop: 'Units Sold', aggregator: 'sum' },
    { prop: 'Discounts', aggregator: 'sum' },
  ],
  filters: ['Month', 'Discount Band'],
  filterSelections: { Month: ['December'] },
  columnCollapse: {
    enabled: true,
    collapsed: true,
    aggregator: { 'Gross Sales': 'sum', 'Units Sold': 'sum', Discounts: 'sum' },
    placeholder: 'Market Total',
  },
};

export type FinancialPresetId = 'sales' | 'profitability' | 'product';

export interface FinancialPreset {
  id: FinancialPresetId;
  label: string;
  description: string;
}

export const FINANCIAL_PRESETS: FinancialPreset[] = [
  {
    id: 'sales',
    label: 'Sales Overview',
    description: 'Compare monthly revenue and demand across markets and segments.',
  },
  {
    id: 'profitability',
    label: 'Profitability',
    description: 'See which segments convert revenue into profit most effectively.',
  },
  {
    id: 'product',
    label: 'Product Performance',
    description: 'Understand which products drive volume, gross sales, and discounts.',
  },
];

const PRESET_CONFIGS: Record<FinancialPresetId, PivotConfig> = {
  sales: SALES_OVERVIEW,
  profitability: PROFITABILITY,
  product: PRODUCT_PERFORMANCE,
};

export function createFinancialPreset(id: FinancialPresetId = 'sales'): PivotConfig {
  const config = PRESET_CONFIGS[id];
  return {
    ...config,
    dimensions: config.dimensions?.map((dimension) => ({ ...dimension })),
    rows: [...config.rows],
    columns: [...(config.columns || [])],
    values: config.values.map((value) => ({ ...value })),
    filters: [...(config.filters || [])],
    filterSelections: clonePivotFilterSelectionMap(config.filterSelections || {}),
    columnLevels: config.columnLevels
      ? Object.fromEntries(
          Object.entries(config.columnLevels).map(([level, settings]) => [
            level,
            settings ? { ...settings } : settings,
          ]),
        )
      : undefined,
    totals: config.totals ? { ...config.totals } : undefined,
    columnCollapse: typeof config.columnCollapse === 'object'
      ? {
          ...config.columnCollapse,
          aggregator:
            typeof config.columnCollapse.aggregator === 'object'
              ? { ...config.columnCollapse.aggregator }
              : config.columnCollapse.aggregator,
        }
      : config.columnCollapse,
  };
}

export const FINANCIAL_SHOWCASE_PIVOT = createFinancialPreset();

export function resolveFinancialRows(rows?: DataType[]): FinancialRow[] {
  return rows?.length ? rows as FinancialRow[] : FINANCIAL_DATA;
}

export function applyFinancialPivotOptions(
  config: PivotConfig | null,
  data: DataType[],
  configuratorVisible = true,
): PivotConfig | undefined {
  if (!config) return undefined;

  const rows = config.rows || [];
  const nextConfig: PivotConfig = {
    ...config,
    rows,
    hasConfigurator: configuratorVisible,
  };

  if (typeof nextConfig.expanded === 'undefined') {
    nextConfig.expanded = getInitialExpandedGroups(filterPivotSource(data, nextConfig), rows);
  }

  return nextConfig;
}

function getInitialExpandedGroups(data: DataType[], rows: Array<string | number>) {
  const expanded: Record<string, boolean> = {};
  const groupingDepth = Math.max(0, rows.length - 1);

  data.forEach((row) => {
    const path: unknown[] = [];
    for (let index = 0; index < groupingDepth; index += 1) {
      path.push(row[rows[index]] ?? null);
      expanded[path.join(',')] = true;
    }
  });

  return expanded;
}
  • multiple row levels
  • generated column groups
  • sales, profit, and units-sold measures
  • product and discount-band filters
  • totals and subtotals
  • interactive configurator updates
  • pivot-config-update for external state sync
  • instant Sales Overview, Profitability, and Product Performance presets
  • responsive configuration, reset, and expanded-grid controls

Watch how the layout changes as you:

  • move fields between rows, columns, and values
  • change aggregators
  • expand or collapse grouped rows
  • enable or disable totals

The initial layout uses Country → Segment for rows and Year → Month for columns, so you can compare markets and segments while following monthly performance.

The example is useful because it shows that Pivot is not a separate reporting component. It is still RevoGrid, with generated analytical rows and columns layered on top.