Field Panel
The Pivot field panel renders the active field layout above the grid. Use it when users need to see or rearrange row, column, data, and filter fields without opening a separate configurator panel.
Source code
TypeScriptts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { filterPivotDimensions } from '@revolist/revogrid-enterprise';
import { currentTheme } from '../composables/useRandomData';
import {
PIVOT_FIELDS_PANEL_CHARTS_UI,
PIVOT_FIELDS_PANEL_DIMENSIONS,
PIVOT_FIELDS_PANEL_PLUGINS,
PIVOT_FIELDS_PANEL_ROLES,
PIVOT_FIELDS_PANEL_ROWS,
createPivotFieldsPanelCharts,
createPivotFieldsPanelColumnTypes,
createPivotFieldsPanelConfig,
openPivotFieldsPanelChart,
} from './pivotFieldsPanel.shared';
defineCustomElements();
function renderFieldList(
target: HTMLElement,
search: string,
showHidden: boolean,
) {
const dimensions = filterPivotDimensions(
PIVOT_FIELDS_PANEL_DIMENSIONS,
search,
{ showHidden },
);
target.replaceChildren(
...dimensions.map((dimension) => {
const item = document.createElement('li');
const heading = document.createElement('div');
const label = document.createElement('strong');
const prop = document.createElement('code');
const role = document.createElement('span');
const description = document.createElement('p');
item.className = 'rounded border border-slate-200 p-3 text-xs';
heading.className = 'flex flex-wrap items-center gap-2';
label.textContent = dimension.name ?? String(dimension.prop);
prop.className = 'rounded bg-slate-100 px-1.5 py-0.5';
prop.textContent = String(dimension.prop);
role.className = 'rounded bg-blue-50 px-1.5 py-0.5 text-blue-700';
role.textContent = PIVOT_FIELDS_PANEL_ROLES[String(dimension.prop)];
description.className = 'm-0 mt-2 leading-5 text-slate-500';
description.textContent = dimension.description ?? '';
heading.append(label, prop, role);
if (dimension.hidden) {
const hiddenBadge = document.createElement('span');
hiddenBadge.className = 'rounded bg-amber-100 px-1.5 py-0.5 text-amber-800';
hiddenBadge.textContent = 'hidden';
heading.append(hiddenBadge);
}
item.append(heading, description);
return item;
}),
);
}
export function load(
parentSelector: string,
rows: any[] = PIVOT_FIELDS_PANEL_ROWS,
) {
const parent = document.querySelector(parentSelector);
if (!parent) {
return;
}
const { isDark } = currentTheme();
const data = Array.isArray(rows) && rows.length > 0
? rows
: PIVOT_FIELDS_PANEL_ROWS;
const wrapper = document.createElement('div');
wrapper.className = 'pivot-fields-panel-demo flex h-full min-h-[620px] w-full flex-col gap-3';
const toolbar = document.createElement('div');
toolbar.className = 'flex flex-wrap items-center justify-between gap-3 rounded border border-slate-200 bg-white p-3 text-slate-900';
toolbar.innerHTML = `
<div>
<h2 class="m-0 text-sm font-semibold">Pivot Fields Panel</h2>
<p class="m-0 mt-1 text-xs text-slate-500">Use the fields panel to arrange Rows, Columns, Values, and Filters with readable labels and formatting.</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button class="open-field-metadata rounded border border-slate-300 bg-white px-3 py-2 text-sm" type="button">Open fields panel</button>
<button class="create-pivot-chart rounded bg-blue-600 px-3 py-2 text-sm text-white" type="button">Create Pivot Chart</button>
</div>
`;
const metadataDialog = document.createElement('dialog');
metadataDialog.className = 'm-auto max-h-[90vh] w-[min(1100px,calc(100%-2rem))] rounded-xl border border-slate-200 bg-white p-0 text-slate-900 shadow-2xl backdrop:bg-slate-950/50';
metadataDialog.setAttribute('aria-labelledby', 'pivot-fields-panel-title');
metadataDialog.innerHTML = `
<div class="flex max-h-[90vh] flex-col">
<header class="flex items-start justify-between gap-4 border-b border-slate-200 px-5 py-4">
<div>
<h2 id="pivot-fields-panel-title" class="m-0 text-lg font-semibold">Pivot Fields Panel</h2>
<p class="m-0 mt-1 max-w-3xl text-sm text-slate-500">Arrange fields across Rows, Columns, Values, and Filters. Field metadata supplies the labels, descriptions, visibility, and formatting shown in the panel.</p>
</div>
<button class="close-field-metadata rounded px-2 py-1 text-xl text-slate-500 hover:bg-slate-100" type="button" aria-label="Close field metadata">×</button>
</header>
<div class="grid min-h-0 grow gap-4 overflow-auto p-5 lg:grid-cols-[minmax(300px,0.8fr)_minmax(520px,1.2fr)]">
<section class="flex min-h-0 flex-col gap-3" aria-labelledby="metadata-definitions-title">
<div>
<h3 id="metadata-definitions-title" class="m-0 text-sm font-semibold">Field details</h3>
<p class="m-0 mt-1 text-xs leading-5 text-slate-500"><code>name</code> and <code>description</code> explain fields, <code>hidden</code> controls discovery, and <code>columnType</code> supplies measure formatting. Hidden fields are not removed from the data.</p>
</div>
<div class="grid gap-2 sm:grid-cols-3 lg:grid-cols-1">
<div class="rounded border border-slate-200 bg-slate-50 p-3"><strong class="text-xs">Display</strong><p class="m-0 mt-1 text-xs text-slate-500">Readable labels, descriptions, and currency/integer formats.</p></div>
<div class="rounded border border-slate-200 bg-slate-50 p-3"><strong class="text-xs">Discovery</strong><p class="m-0 mt-1 text-xs text-slate-500">Internal Segment is hidden by default but still powers a column and filter.</p></div>
<div class="rounded border border-slate-200 bg-slate-50 p-3"><strong class="text-xs">Pivot rules</strong><p class="m-0 mt-1 text-xs text-slate-500">Empty labels, null labels, totals, and selective subtotal rules are configured separately.</p></div>
</div>
<input class="pivot-field-search rounded border border-slate-300 px-3 py-2 text-sm" type="search" placeholder="Search metadata" aria-label="Search field metadata" />
<label class="flex items-center gap-2 text-xs"><input type="checkbox" />Include hidden fields</label>
<ul class="m-0 grid list-none gap-2 overflow-auto p-0"></ul>
</section>
<section class="flex min-h-[520px] flex-col rounded border border-slate-200" aria-labelledby="pivot-panels-title">
<div class="border-b border-slate-200 px-4 py-3">
<h3 id="pivot-panels-title" class="m-0 text-sm font-semibold">Arrange Pivot fields</h3>
<p class="m-0 mt-1 text-xs text-slate-500">Drag fields between Rows, Columns, Values, and Filters. Changes apply directly to the grid behind this dialog.</p>
</div>
<div class="pivot-fields-panel-config min-h-0 grow overflow-auto"></div>
</section>
</div>
<footer class="flex justify-end border-t border-slate-200 px-5 py-3">
<button class="done-field-metadata rounded bg-slate-900 px-4 py-2 text-sm text-white" type="button">Done</button>
</footer>
</div>
`;
const searchInput = metadataDialog.querySelector('input[type="search"]') as HTMLInputElement;
const showHiddenInput = metadataDialog.querySelector('input[type="checkbox"]') as HTMLInputElement;
const fieldList = metadataDialog.querySelector('ul') as HTMLUListElement;
const configMount = metadataDialog.querySelector('.pivot-fields-panel-config') as HTMLElement;
const openMetadataButton = toolbar.querySelector('.open-field-metadata') as HTMLButtonElement;
const createChartButton = toolbar.querySelector('.create-pivot-chart') as HTMLButtonElement;
const closeMetadataButtons = metadataDialog.querySelectorAll<HTMLButtonElement>(
'.close-field-metadata, .done-field-metadata',
);
const refreshList = () => renderFieldList(
fieldList,
searchInput.value,
showHiddenInput.checked,
);
searchInput.addEventListener('input', refreshList);
showHiddenInput.addEventListener('change', refreshList);
refreshList();
const gridHost = document.createElement('div');
gridHost.className = 'flex min-h-0 grow flex-col';
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 = 170;
grid.readonly = true;
grid.hideAttribution = true;
grid.theme = isDark() ? 'darkCompact' : 'compact';
grid.columnTypes = createPivotFieldsPanelColumnTypes();
grid.plugins = PIVOT_FIELDS_PANEL_PLUGINS;
grid.pivot = createPivotFieldsPanelConfig(configMount);
grid.pivotCharts = createPivotFieldsPanelCharts();
grid.pivotChartsUi = PIVOT_FIELDS_PANEL_CHARTS_UI;
openMetadataButton.addEventListener('click', () => {
metadataDialog.showModal();
});
closeMetadataButtons.forEach((button) => {
button.addEventListener('click', () => metadataDialog.close());
});
metadataDialog.addEventListener('click', (event) => {
if (event.target === metadataDialog) {
metadataDialog.close();
}
});
createChartButton.addEventListener('click', () => {
void openPivotFieldsPanelChart(grid);
});
gridHost.append(grid);
wrapper.append(toolbar, gridHost, metadataDialog);
parent.append(wrapper);
grid.source = data;
return () => {
grid.remove();
wrapper.remove();
};
}
Vuevue
<template>
<div ref="demoRoot" class="pivot-fields-panel-demo flex h-full min-h-[620px] w-full flex-col gap-3">
<div class="flex flex-wrap items-center justify-between gap-3 rounded border border-slate-200 bg-white p-3 text-slate-900">
<div>
<h2 class="m-0 text-sm font-semibold">Pivot Fields Panel</h2>
<p class="m-0 mt-1 text-xs text-slate-500">
Use the fields panel to arrange Rows, Columns, Values, and Filters with readable labels and formatting.
</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button
class="rounded border border-slate-300 bg-white px-3 py-2 text-sm"
type="button"
@click="openMetadata"
>
Open fields panel
</button>
<button
class="rounded bg-blue-600 px-3 py-2 text-sm text-white"
type="button"
@click="openChart"
>
Create Pivot Chart
</button>
</div>
</div>
<div class="flex min-h-0 grow flex-col">
<RevoGrid
class="grow h-full cell-border"
hide-attribution
range
resize
filter
:colSize="170"
:source="gridRows"
:pivot.prop="pivot"
:pivot-charts.prop="pivotCharts"
:pivot-charts-ui.prop="pivotChartsUi"
:additional-data="additionalData"
:theme="isDark ? 'darkCompact' : 'compact'"
:plugins="plugins"
:column-types="columnTypes"
readonly
/>
</div>
<dialog
ref="metadataDialog"
aria-labelledby="pivot-fields-panel-title"
class="m-auto max-h-[90vh] w-[min(1100px,calc(100%-2rem))] rounded-xl border border-slate-200 bg-white p-0 text-slate-900 shadow-2xl backdrop:bg-slate-950/50"
@click="closeMetadataOnBackdrop"
>
<div class="flex max-h-[90vh] flex-col">
<header class="flex items-start justify-between gap-4 border-b border-slate-200 px-5 py-4">
<div>
<h2 id="pivot-fields-panel-title" class="m-0 text-lg font-semibold">
Pivot Fields Panel
</h2>
<p class="m-0 mt-1 max-w-3xl text-sm text-slate-500">
Arrange fields across Rows, Columns, Values, and Filters. Field metadata supplies the labels, descriptions, visibility, and formatting shown in the panel.
</p>
</div>
<button
aria-label="Close field metadata"
class="rounded px-2 py-1 text-xl text-slate-500 hover:bg-slate-100"
type="button"
@click="closeMetadata"
>
×
</button>
</header>
<div class="grid min-h-0 grow gap-4 overflow-auto p-5 lg:grid-cols-[minmax(300px,0.8fr)_minmax(520px,1.2fr)]">
<section class="flex min-h-0 flex-col gap-3" aria-labelledby="metadata-definitions-title">
<div>
<h3 id="metadata-definitions-title" class="m-0 text-sm font-semibold">
Field details
</h3>
<p class="m-0 mt-1 text-xs leading-5 text-slate-500">
<code>name</code> and <code>description</code> explain fields,
<code>hidden</code> controls discovery, and <code>columnType</code>
supplies measure formatting. Hidden fields are not removed from the data.
</p>
</div>
<div class="grid gap-2 sm:grid-cols-3 lg:grid-cols-1">
<div class="rounded border border-slate-200 bg-slate-50 p-3">
<strong class="text-xs">Display</strong>
<p class="m-0 mt-1 text-xs text-slate-500">Readable labels, descriptions, and currency/integer formats.</p>
</div>
<div class="rounded border border-slate-200 bg-slate-50 p-3">
<strong class="text-xs">Discovery</strong>
<p class="m-0 mt-1 text-xs text-slate-500">Internal Segment is hidden by default but still powers a column and filter.</p>
</div>
<div class="rounded border border-slate-200 bg-slate-50 p-3">
<strong class="text-xs">Pivot rules</strong>
<p class="m-0 mt-1 text-xs text-slate-500">Empty labels, null labels, totals, and selective subtotal rules are configured separately.</p>
</div>
</div>
<input
v-model="search"
class="pivot-field-search rounded border border-slate-300 px-3 py-2 text-sm"
type="search"
placeholder="Search metadata"
aria-label="Search field metadata"
/>
<label class="flex items-center gap-2 text-xs">
<input v-model="showHidden" type="checkbox" />
Include hidden fields
</label>
<ul class="m-0 grid list-none gap-2 overflow-auto p-0">
<li
v-for="dimension in filteredDimensions"
:key="String(dimension.prop)"
class="rounded border border-slate-200 p-3 text-xs"
>
<div class="flex flex-wrap items-center gap-2">
<strong>{{ dimension.name ?? dimension.prop }}</strong>
<code class="rounded bg-slate-100 px-1.5 py-0.5">{{ dimension.prop }}</code>
<span class="rounded bg-blue-50 px-1.5 py-0.5 text-blue-700">
{{ PIVOT_FIELDS_PANEL_ROLES[String(dimension.prop)] }}
</span>
<span v-if="dimension.hidden" class="rounded bg-amber-100 px-1.5 py-0.5 text-amber-800">
hidden
</span>
</div>
<p class="m-0 mt-2 leading-5 text-slate-500">{{ dimension.description }}</p>
</li>
</ul>
</section>
<section class="flex min-h-[520px] flex-col rounded border border-slate-200" aria-labelledby="pivot-panels-title">
<div class="border-b border-slate-200 px-4 py-3">
<h3 id="pivot-panels-title" class="m-0 text-sm font-semibold">Arrange Pivot fields</h3>
<p class="m-0 mt-1 text-xs text-slate-500">
Drag fields between Rows, Columns, Values, and Filters. Changes apply directly to the grid behind this dialog.
</p>
</div>
<div ref="configMount" class="pivot-fields-panel-config min-h-0 grow overflow-auto"></div>
</section>
</div>
<footer class="flex justify-end border-t border-slate-200 px-5 py-3">
<button class="rounded bg-slate-900 px-4 py-2 text-sm text-white" type="button" @click="closeMetadata">
Done
</button>
</footer>
</div>
</dialog>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import RevoGrid, { type GridPlugin } from '@revolist/vue3-datagrid';
import {
filterPivotDimensions,
} from '@revolist/revogrid-enterprise';
import { currentThemeVue } from '../composables/useRandomData';
import {
PIVOT_FIELDS_PANEL_CHARTS_UI,
PIVOT_FIELDS_PANEL_DIMENSIONS,
PIVOT_FIELDS_PANEL_PLUGINS,
PIVOT_FIELDS_PANEL_ROLES,
PIVOT_FIELDS_PANEL_ROWS,
createPivotFieldsPanelCharts,
createPivotFieldsPanelColumnTypes,
createPivotFieldsPanelConfig,
openPivotFieldsPanelChart,
} from './pivotFieldsPanel.shared';
const props = defineProps<{
rows?: any[];
}>();
const { isDark } = currentThemeVue();
const search = ref('');
const showHidden = ref(false);
const demoRoot = ref<HTMLElement>();
const metadataDialog = ref<HTMLDialogElement>();
const configMount = ref<HTMLElement>();
const gridRows = ref(
Array.isArray(props.rows) && props.rows.length > 0
? props.rows
: PIVOT_FIELDS_PANEL_ROWS,
);
const filteredDimensions = computed(() =>
filterPivotDimensions(PIVOT_FIELDS_PANEL_DIMENSIONS, search.value, { showHidden: showHidden.value }),
);
const columnTypes = ref(createPivotFieldsPanelColumnTypes());
const plugins: GridPlugin[] = PIVOT_FIELDS_PANEL_PLUGINS;
const pivot = computed(() => createPivotFieldsPanelConfig(configMount.value));
const pivotCharts = createPivotFieldsPanelCharts();
const pivotChartsUi = PIVOT_FIELDS_PANEL_CHARTS_UI;
const additionalData = computed(() => ({}));
const openChart = () => openPivotFieldsPanelChart(
demoRoot.value?.querySelector<HTMLRevoGridElement>('revo-grid'),
);
const openMetadata = () => metadataDialog.value?.showModal();
const closeMetadata = () => metadataDialog.value?.close();
const closeMetadataOnBackdrop = (event: MouseEvent) => {
if (event.target === metadataDialog.value) {
closeMetadata();
}
};
</script>
Reacttsx
import { useMemo, useRef, useState } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { filterPivotDimensions } from '@revolist/revogrid-enterprise';
import { currentTheme } from '../composables/useRandomData';
import {
PIVOT_FIELDS_PANEL_CHARTS_UI,
PIVOT_FIELDS_PANEL_DIMENSIONS,
PIVOT_FIELDS_PANEL_PLUGINS,
PIVOT_FIELDS_PANEL_ROLES,
PIVOT_FIELDS_PANEL_ROWS,
createPivotFieldsPanelCharts,
createPivotFieldsPanelColumnTypes,
createPivotFieldsPanelConfig,
openPivotFieldsPanelChart,
} from './pivotFieldsPanel.shared';
interface PivotFieldsPanelProps {
rows?: any[];
}
function PivotFieldsPanel({ rows }: PivotFieldsPanelProps) {
const { isDark } = currentTheme();
const [search, setSearch] = useState('');
const [showHidden, setShowHidden] = useState(false);
const [configMount, setConfigMount] = useState<HTMLElement | null>(null);
const gridRef = useRef<HTMLRevoGridElement>(null);
const metadataDialogRef = useRef<HTMLDialogElement>(null);
const data = useMemo(
() => Array.isArray(rows) && rows.length > 0
? rows
: PIVOT_FIELDS_PANEL_ROWS,
[rows],
);
const filteredDimensions = useMemo(
() => filterPivotDimensions(
PIVOT_FIELDS_PANEL_DIMENSIONS,
search,
{ showHidden },
),
[search, showHidden],
);
const columnTypes = useMemo(createPivotFieldsPanelColumnTypes, []);
const plugins = useMemo(() => PIVOT_FIELDS_PANEL_PLUGINS, []);
const additionalData = useMemo(() => ({}), []);
const pivot = useMemo(
() => createPivotFieldsPanelConfig(configMount ?? undefined),
[configMount],
);
const pivotCharts = useMemo(createPivotFieldsPanelCharts, []);
const pivotChartsUi = useMemo(
() => PIVOT_FIELDS_PANEL_CHARTS_UI,
[],
);
return (
<div className="pivot-fields-panel-demo flex h-full min-h-[620px] w-full flex-col gap-3">
<div className="flex flex-wrap items-center justify-between gap-3 rounded border border-slate-200 bg-white p-3 text-slate-900">
<div>
<h2 className="m-0 text-sm font-semibold">Pivot Fields Panel</h2>
<p className="m-0 mt-1 text-xs text-slate-500">
Metadata gives raw fields readable names, descriptions, visibility,
formatting, and Pivot roles.
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<button
className="rounded border border-slate-300 bg-white px-3 py-2 text-sm"
type="button"
onClick={() => metadataDialogRef.current?.showModal()}
>
Open fields panel
</button>
<button
className="rounded bg-blue-600 px-3 py-2 text-sm text-white"
type="button"
onClick={() => void openPivotFieldsPanelChart(gridRef.current)}
>
Create Pivot Chart
</button>
</div>
</div>
<div className="flex min-h-0 grow flex-col">
<RevoGrid
ref={gridRef}
className="grow h-full cell-border"
hideAttribution
range
resize
filter
colSize={170}
source={data}
pivot={pivot}
pivotCharts={pivotCharts}
pivotChartsUi={pivotChartsUi}
additionalData={additionalData}
theme={isDark() ? 'darkCompact' : 'compact'}
plugins={plugins}
columnTypes={columnTypes}
readonly
/>
</div>
<dialog
ref={metadataDialogRef}
aria-labelledby="pivot-fields-panel-title"
className="m-auto max-h-[90vh] w-[min(1100px,calc(100%-2rem))] rounded-xl border border-slate-200 bg-white p-0 text-slate-900 shadow-2xl backdrop:bg-slate-950/50"
onClick={(event) => {
if (event.target === event.currentTarget) {
event.currentTarget.close();
}
}}
>
<div className="flex max-h-[90vh] flex-col">
<header className="flex items-start justify-between gap-4 border-b border-slate-200 px-5 py-4">
<div>
<h2
id="pivot-fields-panel-title"
className="m-0 text-lg font-semibold"
>
Pivot Fields Panel
</h2>
<p className="m-0 mt-1 max-w-3xl text-sm text-slate-500">
The source rows keep their original property keys. Metadata
adds the labels and behavior that users see when they build a
Pivot.
</p>
</div>
<button
aria-label="Close field metadata"
className="rounded px-2 py-1 text-xl text-slate-500 hover:bg-slate-100"
type="button"
onClick={() => metadataDialogRef.current?.close()}
>
×
</button>
</header>
<div className="grid min-h-0 grow gap-4 overflow-auto p-5 lg:grid-cols-[minmax(300px,0.8fr)_minmax(520px,1.2fr)]">
<section
className="flex min-h-0 flex-col gap-3"
aria-labelledby="metadata-definitions-title"
>
<div>
<h3
id="metadata-definitions-title"
className="m-0 text-sm font-semibold"
>
Field details
</h3>
<p className="m-0 mt-1 text-xs leading-5 text-slate-500">
<code>name</code> and <code>description</code> explain
fields, <code>hidden</code> controls discovery, and{' '}
<code>columnType</code> supplies measure formatting. Hidden
fields are not removed from the data.
</p>
</div>
<div className="grid gap-2 sm:grid-cols-3 lg:grid-cols-1">
<div className="rounded border border-slate-200 bg-slate-50 p-3">
<strong className="text-xs">Display</strong>
<p className="m-0 mt-1 text-xs text-slate-500">
Readable labels, descriptions, and currency/integer formats.
</p>
</div>
<div className="rounded border border-slate-200 bg-slate-50 p-3">
<strong className="text-xs">Discovery</strong>
<p className="m-0 mt-1 text-xs text-slate-500">
Internal Segment is hidden by default but still powers a
column and filter.
</p>
</div>
<div className="rounded border border-slate-200 bg-slate-50 p-3">
<strong className="text-xs">Pivot rules</strong>
<p className="m-0 mt-1 text-xs text-slate-500">
Empty labels, null labels, totals, and selective subtotal
rules are configured separately.
</p>
</div>
</div>
<input
className="pivot-field-search rounded border border-slate-300 px-3 py-2 text-sm"
type="search"
placeholder="Search metadata"
aria-label="Search field metadata"
value={search}
onChange={(event) => setSearch(event.currentTarget.value)}
/>
<label className="flex items-center gap-2 text-xs">
<input
type="checkbox"
checked={showHidden}
onChange={(event) => (
setShowHidden(event.currentTarget.checked)
)}
/>
Include hidden fields
</label>
<ul className="m-0 grid list-none gap-2 overflow-auto p-0">
{filteredDimensions.map((dimension) => (
<li
className="rounded border border-slate-200 p-3 text-xs"
key={String(dimension.prop)}
>
<div className="flex flex-wrap items-center gap-2">
<strong>
{dimension.name ?? String(dimension.prop)}
</strong>
<code className="rounded bg-slate-100 px-1.5 py-0.5">
{String(dimension.prop)}
</code>
<span className="rounded bg-blue-50 px-1.5 py-0.5 text-blue-700">
{PIVOT_FIELDS_PANEL_ROLES[String(dimension.prop)]}
</span>
{dimension.hidden && (
<span className="rounded bg-amber-100 px-1.5 py-0.5 text-amber-800">
hidden
</span>
)}
</div>
<p className="m-0 mt-2 leading-5 text-slate-500">
{dimension.description}
</p>
</li>
))}
</ul>
</section>
<section
className="flex min-h-[520px] flex-col rounded border border-slate-200"
aria-labelledby="pivot-panels-title"
>
<div className="border-b border-slate-200 px-4 py-3">
<h3
id="pivot-panels-title"
className="m-0 text-sm font-semibold"
>
Arrange Pivot fields
</h3>
<p className="m-0 mt-1 text-xs text-slate-500">
Drag fields between Rows, Columns, Values, and Filters.
Changes apply directly to the grid behind this dialog.
</p>
</div>
<div
ref={setConfigMount}
className="pivot-fields-panel-config min-h-0 grow overflow-auto"
/>
</section>
</div>
<footer className="flex justify-end border-t border-slate-200 px-5 py-3">
<button
className="rounded bg-slate-900 px-4 py-2 text-sm text-white"
type="button"
onClick={() => metadataDialogRef.current?.close()}
>
Done
</button>
</footer>
</div>
</dialog>
</div>
);
}
export default PivotFieldsPanel;
Angularts
import { CommonModule } from '@angular/common';
import {
Component,
ElementRef,
Input,
NO_ERRORS_SCHEMA,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { filterPivotDimensions } from '@revolist/revogrid-enterprise';
import { currentTheme } from '../composables/useRandomData';
import {
PIVOT_FIELDS_PANEL_CHARTS_UI,
PIVOT_FIELDS_PANEL_DIMENSIONS,
PIVOT_FIELDS_PANEL_PLUGINS,
PIVOT_FIELDS_PANEL_ROLES,
PIVOT_FIELDS_PANEL_ROWS,
createPivotFieldsPanelCharts,
createPivotFieldsPanelColumnTypes,
createPivotFieldsPanelConfig,
openPivotFieldsPanelChart,
} from './pivotFieldsPanel.shared';
@Component({
selector: 'pivot-fields-panel-grid',
standalone: true,
imports: [CommonModule, RevoGrid],
schemas: [NO_ERRORS_SCHEMA],
template: `
<div class="pivot-fields-panel-demo flex h-full min-h-[620px] w-full flex-col gap-3">
<div class="flex flex-wrap items-center justify-between gap-3 rounded border border-slate-200 bg-white p-3 text-slate-900">
<div>
<h2 class="m-0 text-sm font-semibold">Pivot Fields Panel</h2>
<p class="m-0 mt-1 text-xs text-slate-500">
Use the fields panel to arrange Rows, Columns, Values, and Filters with readable labels and formatting.
</p>
</div>
<div class="flex flex-wrap items-center gap-2">
<button
class="rounded border border-slate-300 bg-white px-3 py-2 text-sm"
type="button"
(click)="openMetadata()"
>
Open fields panel
</button>
<button
class="rounded bg-blue-600 px-3 py-2 text-sm text-white"
type="button"
(click)="openChart()"
>
Create Pivot Chart
</button>
</div>
</div>
<div class="flex min-h-0 grow flex-col">
<revo-grid
#grid
class="grow h-full cell-border"
[hideAttribution]="true"
[range]="true"
[resize]="true"
[filter]="true"
[colSize]="170"
[source]="gridRows"
[pivot]="pivot"
[pivotCharts]="pivotCharts"
[pivotChartsUi]="pivotChartsUi"
[additionalData]="additionalData"
[theme]="theme"
[plugins]="plugins"
[columnTypes]="columnTypes"
[readonly]="true"
></revo-grid>
</div>
<dialog
#metadataDialog
aria-labelledby="pivot-fields-panel-title"
class="m-auto max-h-[90vh] w-[min(1100px,calc(100%-2rem))] rounded-xl border border-slate-200 bg-white p-0 text-slate-900 shadow-2xl backdrop:bg-slate-950/50"
(click)="closeMetadataOnBackdrop($event)"
>
<div class="flex max-h-[90vh] flex-col">
<header class="flex items-start justify-between gap-4 border-b border-slate-200 px-5 py-4">
<div>
<h2 id="pivot-fields-panel-title" class="m-0 text-lg font-semibold">
Pivot Fields Panel
</h2>
<p class="m-0 mt-1 max-w-3xl text-sm text-slate-500">
Arrange fields across Rows, Columns, Values, and Filters. Field metadata supplies the labels, descriptions, visibility, and formatting shown in the panel.
</p>
</div>
<button
aria-label="Close field metadata"
class="rounded px-2 py-1 text-xl text-slate-500 hover:bg-slate-100"
type="button"
(click)="closeMetadata()"
>×</button>
</header>
<div class="grid min-h-0 grow gap-4 overflow-auto p-5 lg:grid-cols-[minmax(300px,0.8fr)_minmax(520px,1.2fr)]">
<section class="flex min-h-0 flex-col gap-3" aria-labelledby="metadata-definitions-title">
<div>
<h3 id="metadata-definitions-title" class="m-0 text-sm font-semibold">
Field details
</h3>
<p class="m-0 mt-1 text-xs leading-5 text-slate-500">
<code>name</code> and <code>description</code> explain fields,
<code>hidden</code> controls discovery, and <code>columnType</code>
supplies measure formatting. Hidden fields are not removed from the data.
</p>
</div>
<div class="grid gap-2 sm:grid-cols-3 lg:grid-cols-1">
<div class="rounded border border-slate-200 bg-slate-50 p-3">
<strong class="text-xs">Display</strong>
<p class="m-0 mt-1 text-xs text-slate-500">Readable labels, descriptions, and currency/integer formats.</p>
</div>
<div class="rounded border border-slate-200 bg-slate-50 p-3">
<strong class="text-xs">Discovery</strong>
<p class="m-0 mt-1 text-xs text-slate-500">Internal Segment is hidden by default but still powers a column and filter.</p>
</div>
<div class="rounded border border-slate-200 bg-slate-50 p-3">
<strong class="text-xs">Pivot rules</strong>
<p class="m-0 mt-1 text-xs text-slate-500">Empty labels, null labels, totals, and selective subtotal rules are configured separately.</p>
</div>
</div>
<input
class="pivot-field-search rounded border border-slate-300 px-3 py-2 text-sm"
type="search"
placeholder="Search metadata"
aria-label="Search field metadata"
[value]="search"
(input)="setSearch($event)"
/>
<label class="flex items-center gap-2 text-xs">
<input
type="checkbox"
[checked]="showHidden"
(change)="setShowHidden($event)"
/>
Include hidden fields
</label>
<ul class="m-0 grid list-none gap-2 overflow-auto p-0">
<li
*ngFor="let dimension of filteredDimensions"
class="rounded border border-slate-200 p-3 text-xs"
>
<div class="flex flex-wrap items-center gap-2">
<strong>{{ dimension.name || dimension.prop }}</strong>
<code class="rounded bg-slate-100 px-1.5 py-0.5">{{ dimension.prop }}</code>
<span class="rounded bg-blue-50 px-1.5 py-0.5 text-blue-700">
{{ fieldRoles[dimension.prop] }}
</span>
<span
*ngIf="dimension.hidden"
class="rounded bg-amber-100 px-1.5 py-0.5 text-amber-800"
>hidden</span>
</div>
<p class="m-0 mt-2 leading-5 text-slate-500">{{ dimension.description }}</p>
</li>
</ul>
</section>
<section class="flex min-h-[520px] flex-col rounded border border-slate-200" aria-labelledby="pivot-panels-title">
<div class="border-b border-slate-200 px-4 py-3">
<h3 id="pivot-panels-title" class="m-0 text-sm font-semibold">Arrange Pivot fields</h3>
<p class="m-0 mt-1 text-xs text-slate-500">
Drag fields between Rows, Columns, Values, and Filters. Changes apply directly to the grid behind this dialog.
</p>
</div>
<div #configMount class="pivot-fields-panel-config min-h-0 grow overflow-auto"></div>
</section>
</div>
<footer class="flex justify-end border-t border-slate-200 px-5 py-3">
<button class="rounded bg-slate-900 px-4 py-2 text-sm text-white" type="button" (click)="closeMetadata()">
Done
</button>
</footer>
</div>
</dialog>
</div>
`,
encapsulation: ViewEncapsulation.None,
})
export class PivotFieldsPanelGridComponent {
@Input() rows?: any[];
@ViewChild('grid', { read: ElementRef })
gridRef?: ElementRef<HTMLRevoGridElement>;
@ViewChild('metadataDialog', { read: ElementRef })
metadataDialogRef?: ElementRef<HTMLDialogElement>;
@ViewChild('configMount', { read: ElementRef })
set configMountRef(ref: ElementRef<HTMLElement> | undefined) {
if (ref) {
this.pivot = createPivotFieldsPanelConfig(ref.nativeElement);
}
}
search = '';
showHidden = false;
fieldRoles = PIVOT_FIELDS_PANEL_ROLES;
theme = currentTheme().isDark() ? 'darkCompact' : 'compact';
columnTypes = createPivotFieldsPanelColumnTypes();
plugins = PIVOT_FIELDS_PANEL_PLUGINS;
pivot = createPivotFieldsPanelConfig();
pivotCharts = createPivotFieldsPanelCharts();
pivotChartsUi = PIVOT_FIELDS_PANEL_CHARTS_UI;
additionalData = {};
get gridRows() {
return Array.isArray(this.rows) && this.rows.length > 0
? this.rows
: PIVOT_FIELDS_PANEL_ROWS;
}
get filteredDimensions() {
return filterPivotDimensions(
PIVOT_FIELDS_PANEL_DIMENSIONS,
this.search,
{ showHidden: this.showHidden },
);
}
setSearch(event: Event) {
this.search = (event.target as HTMLInputElement).value;
}
setShowHidden(event: Event) {
this.showHidden = (event.target as HTMLInputElement).checked;
}
openChart() {
return openPivotFieldsPanelChart(this.gridRef?.nativeElement);
}
openMetadata() {
this.metadataDialogRef?.nativeElement.showModal();
}
closeMetadata() {
this.metadataDialogRef?.nativeElement.close();
}
closeMetadataOnBackdrop(event: MouseEvent) {
if (event.target === this.metadataDialogRef?.nativeElement) {
this.closeMetadata();
}
}
}
Enable It
Section titled “Enable It”const pivot = { dimensions: [...], rows: ['region', 'rep'], columns: ['year', 'quarter'], values: [{ prop: 'sales', aggregator: 'sum' }], filters: ['year'], fieldPanel: { visible: true, showResetLayout: false, allowFieldDragging: true, allowFieldRemoving: false, showDataFields: true, showRowFields: true, showColumnFields: true, showFilterFields: true, texts: { dropFilterFieldsHere: 'Drop Filter Fields Here', }, },};Options
Section titled “Options”fieldPanel.visible: mounts the compact in-grid field panel.fieldPanel.showResetLayout: shows the Reset layout action in Pivot configuration UI. Default isfalse.allowFieldDragging: enables drag-and-drop between panel areas.allowFieldRemoving: enables explicit removal from areas. Default isfalse.showDataFields,showRowFields,showColumnFields,showFilterFields: toggle individual areas.fieldPanel.texts: overrides field panel labels and placeholders.filters: stores the ordered filter fields shown in the filter area.
fieldPanel.visible is separate from hasConfigurator. Use fieldPanel.visible for the compact in-grid field strip, and hasConfigurator for the full side configurator.