# RevoGrid Pro Documentation
> Full normalized Markdown export of the RevoGrid Pro portal documentation.
Source: https://pro.rv-grid.com
---
# Welcome to the RevoGrid Pro documentation!
URL: https://pro.rv-grid.com/guides/
Source: src/content/docs/guides/index.mdx
Description: Learn how to leverage the advanced features of RevoGrid Pro, access detailed documentation, and make the most of our support and resources.
This guide is designed to help you understand and utilize the advanced features of RevoGrid Pro.
We are going to provide you with the tools and knowledge needed to build complex, high-performance grid applications. Our goal is to:
1. **Save Integration Time**: Provide you with comprehensive examples and documentation to streamline the integration of advanced features into your grid applications.
2. **Offer Pro-Level Examples**: Present real-world examples that demonstrate how to implement complex Pro-level features effectively.
3. **Enhance Customization**: Show you how to extend our existing plugins and create your own custom functionalities to suit your specific needs.
4. **Enable Advanced Understanding**: Open up the Grid Core functionality and provide deep insights for those who are interested in customizing and extending the grid at a fundamental level.
## How to Get Started
Most of the information you need is available in our docs portal.
Start with [Installation](/guides/installation), then review [key concepts from the main docs](https://rv-grid.com/guide/).
From there, jump to the available features, plugins, and examples.
To jump start your project, you can use our [RevoGrid Pro boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git), which include examples for all major frameworks.
These product examples provide additional tips and insights to help you manage complex data workflows.
## Use AI with RevoGrid Pro
If you are using Codex, Cursor, Claude Code, or VS Code MCP clients, start with the hosted RevoGrid MCP guide:
- [How to use RevoGrid MCP](/guides/ai-mcp)
- [AI Agent Documentation Sources](/guides/ai-agent-documentation)
### Repository layout
The source repository is a pnpm workspace. Pro and standalone product packages live under `packages/`, the documentation and runnable demo apps live under `apps/`, shared example sources live under `examples/`, and Playwright suites live under `tests/`.
- apps/
- portal/ Documentation website (Astro/Starlight)
- src/
- components/ Components rendered by documentation pages
- content/docs/ MDX documentation files
- demos/ Runnable demo catalog and framework previews
- src/
- catalog/ Demo catalog metadata
- preview/ Framework preview lifecycles
- examples/
- components/ Shared demo components used by docs and demos
- src/components/ Vanilla, React, Vue, and Angular examples
- angular/ Angular preview app and wrapper examples
- react/ React preview app and wrapper examples
- core/ Core example helpers
- packages/
- pro/ RevoGrid Pro package
- plugins/ Pro plugin source code
- src/ Package entrypoints and shared exports
- pivot/ Standalone Pivot package
- charts/ Standalone Charts package
- gantt/ Standalone Gantt package
- scheduler/ Standalone Scheduler package
- kanban/ Standalone Kanban package
- src/ Package entrypoints and shared exports
- revogrid/ Core grid library (Git submodule)
- tests/
- e2e-pro/ Pro browser tests
- e2e-enterprise/ Product integration browser tests
- e2e-demos/ Demo browser tests
- helpers/ Shared E2E helpers
- pnpm-workspace.yaml Workspace configuration
### Installation and Usage
1. Installation Instructions
To get started, follow the installation process in our [Installation Guide](/guides/installation).
This guide includes private npm, GitHub npm, pre-built release, and source-build options for Pro and standalone product packages.
2. Import Pro and Product Packages and CSS
After installation, import the packages and CSS based on the features you need.
Use Pro for advanced grid features, then add Pivot, Gantt, Scheduler, or Kanban from its standalone package.
Here is a typical setup:
```ts
// Pro
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import { RowOddPlugin } from '@revolist/revogrid-pro';
// Standalone Pivot product
import '@revolist/pivot/styles.css';
import { PivotPlugin } from '@revolist/pivot';
```
Make sure package styles are included so features render correctly.
3. Review the Documentation
Begin by exploring our detailed Pro documentation. We advice to start from simpler examples, then move to more advanced scenarios:
- [Data Synchronization](/guides/faq/data-sync)
- [Odd Rows](/guides/rows/row-odd/)
- [Custom Row Header](/guides/rows/row-advanced-header/)
- [Selection & Focus](/guides/selection/)
- [Column Selection](/guides/selection/column-selection/)
- [Multi-Range Selection](/guides/selection/multi-range-selection/)
- [Event Manager](/guides/data-manage/event-manager-explanation)
- [Collaborative Editing](/guides/data-manage/collaborative-editing/)
- [Collaborative Presence](/guides/data-manage/collaborative-presence/)
- [Pagination](/guides/grid-utilities/pagination)
- [Formula](/guides/formula/overview/)
- [Merge Cells](/guides/cells/cell-merge/)
- …
4. Implement Examples
Follow the provided examples to see how advanced features are implemented. Use these examples as a starting point for your own customizations.
5. Utilize Support Resources
If you encounter challenges or have questions, make use of our support options. Reach out for personalized assistance or schedule a call with one of our creators.
## Conclusion
With detailed examples, extensive documentation we aim to provide everything you need to build powerful, customized grid applications efficiently. Dive in, explore the possibilities, and take your grid solutions to the next level with RevoGrid Pro.
For any questions or additional support, don't hesitate to reach out.
---
# AI Agent Documentation Sources
URL: https://pro.rv-grid.com/guides/ai-agent-documentation/
Source: src/content/docs/guides/ai-agent-documentation.mdx
Description: Choose the right RevoGrid Pro documentation source for an AI agent: llms.txt, the full corpus, split skill references, an installable skill, or MCP.
RevoGrid Pro publishes several documentation formats for AI agents. They all describe the same portal documentation, but serve different retrieval workflows. Start with the smallest source that answers the task, then retrieve more context only when needed.
These resources provide guidance and examples. When an agent can inspect the project, local source code and public TypeScript types remain the final authority for implementation details.
## Choose a source
| Source | Best for | What the agent receives |
| --- | --- | --- |
| [`llms.txt`](https://pro.rv-grid.com/llms.txt) | First lookup and URL discovery | A compact Markdown index of high-signal guides, API references, demos, and links to larger resources. |
| [`llms-full.txt`](https://pro.rv-grid.com/llms-full.txt) | Offline context or a one-shot research task | The complete normalized Markdown documentation corpus. |
| [`llms-skill.txt`](https://pro.rv-grid.com/llms-skill.txt) | Skill-aware agents that fetch only relevant references | A `SKILL.md` index that routes the agent to topic-specific reference files. |
| [`llms-skill/`](https://pro.rv-grid.com/llms-skill/) | Selective, URL-based retrieval | The same split skill index and individual Markdown references under `llms-skill/references/`. |
| [`revogrid-pro.skill`](https://pro.rv-grid.com/revogrid-pro.skill) | Installing reusable RevoGrid documentation context in a supported agent | A downloadable skill archive containing `SKILL.md` and its split reference files. |
| [RevoGrid MCP](/guides/ai-mcp) | Task-oriented structured retrieval | Core, Pro, and Enterprise docs, examples, feature resolution, migration notes, and typed plugin context. |
## Start with `llms.txt`
Fetch [`https://pro.rv-grid.com/llms.txt`](https://pro.rv-grid.com/llms.txt) when the agent needs to find the right RevoGrid page before reading it. The index favors high-signal pages, groups the remaining documentation by topic, and points to the full corpus and skill formats.
This is the best default for an agent that can follow links. It avoids placing the entire documentation corpus into context before the agent knows whether the task concerns Pivot, Gantt, filtering, or a core API.
```text
Read https://pro.rv-grid.com/llms.txt. Find the relevant RevoGrid Pro documentation,
then fetch only the pages needed for this task before proposing an implementation.
```
## Use the full corpus for broad context
[`https://pro.rv-grid.com/llms-full.txt`](https://pro.rv-grid.com/llms-full.txt) contains every portal documentation page as normalized Markdown. Each section includes its portal URL and source path, so an agent can trace guidance back to its documentation page.
Use it for a research or offline workflow that cannot follow many links. Avoid loading it by default if the agent has a limited context window; `llms.txt` or the split skill is usually more efficient.
## Use the split skill for selective retrieval
The split skill is designed for agents that understand `SKILL.md` instructions or can fetch Markdown files by URL.
- [`llms-skill.txt`](https://pro.rv-grid.com/llms-skill.txt) returns the root `SKILL.md` as plain text.
- [`llms-skill/`](https://pro.rv-grid.com/llms-skill/) returns the same index and exposes the individual references it links to.
- [`revogrid-pro.skill`](https://pro.rv-grid.com/revogrid-pro.skill) packages that index and all reference files as a downloadable skill archive.
The index groups references by product area. Read the index first, then fetch only the referenced Markdown files that match the task. This gives an agent structured guidance without sending unrelated documentation into its context.
```text
Read https://pro.rv-grid.com/llms-skill.txt first. Follow only the references for
the feature I am implementing, and validate API details against local TypeScript types.
```
If your agent supports skill archives, install [`revogrid-pro.skill`](https://pro.rv-grid.com/revogrid-pro.skill) to make the split documentation available as a reusable local skill. Otherwise, use the public `llms-skill.txt` URL and reference links directly.
## Use MCP for structured retrieval
The public files are documentation exports suited to direct HTTP retrieval. MCP
adds structured tools for agents that need examples, feature resolution, migration
notes, or typed plugin context. Both are token-free; MCP results label Pro and
Enterprise requirements explicitly.
For setup, supported clients, and example prompts, see [How to use RevoGrid MCP](/guides/ai-mcp).
## Recommended agent workflow
1. Fetch [`llms.txt`](https://pro.rv-grid.com/llms.txt) or read the installed split skill index.
2. Retrieve the smallest set of relevant guides and API references.
3. Query MCP for relevant Core or Pro examples, migrations, and typed context.
4. Inspect local source and public TypeScript types before writing or changing code.
5. Keep the implementation aligned with the retrieved documentation and the local project version.
This sequence gives agents quick discovery, focused context, and a final implementation check against the code they are changing.
---
# How to use RevoGrid MCP
URL: https://pro.rv-grid.com/guides/ai-mcp/
Source: src/content/docs/guides/ai-mcp.mdx
Description: Use the hosted RevoGrid MCP in Codex, Cursor, Claude Code, or VS Code to work with Core, Pro, and Enterprise docs, examples, plugins, migration notes, and typed API context.
RevoGrid MCP gives AI coding tools structured access to the unified RevoGrid
knowledge catalog:
```text
https://mcp.rv-grid.com
```
No MCP access token or authorization header is required. The same endpoint
searches Core, Pro, and Enterprise knowledge. Results retain `requiresPro`
metadata so agents can explain package and license requirements accurately.
RevoGrid has a wide API and plugin surface, with advanced features such as pivot,
event manager, history, specialized renderers, and custom editors. MCP helps an
AI tool retrieve the right implementation guidance before it writes code.
## What you can use it for
Use RevoGrid MCP when you want an AI tool to:
- find the best Core or Pro example for a specific framework
- determine whether a feature requires a Pro package before suggesting code
- build around typed plugin APIs instead of guessing event names or option shapes
- gather migration notes before upgrading a RevoGrid implementation
- work from real examples for pivot, export, filters, history, validation, or custom renderers
## Agent-readable documentation files
For public, text-only documentation sources—including the `llms.txt` index, full
corpus, split skill references, and downloadable skill—see
[AI Agent Documentation Sources](/guides/ai-agent-documentation).
Use MCP when your tool supports structured retrieval and typed context. Use the
public agent-readable files when it needs documentation it can fetch directly.
## Recommended setup
1. Add `https://mcp.rv-grid.com` to your AI coding tool.
2. Ask the agent to search RevoGrid MCP before writing code.
3. Ask it to validate the final implementation against RevoGrid types and examples.
4. If your project must remain Core-only, say so explicitly in the prompt.
## Install in AI coding tools
### Claude Code
```bash
claude mcp add --transport http revogrid https://mcp.rv-grid.com
```
### Codex
```bash
codex mcp add revogrid --url https://mcp.rv-grid.com
```
### Cursor
```json
{
"mcpServers": {
"RevoGrid": {
"url": "https://mcp.rv-grid.com",
"type": "http"
}
}
}
```
### VS Code
Install the `RevoGrid DataGrid MCP` extension, or configure the server directly:
```json
{
"servers": {
"RevoGrid MCP": {
"url": "https://mcp.rv-grid.com",
"type": "http"
}
},
"inputs": []
}
```
## How to prompt effectively
The best prompts tell the agent:
1. your framework
2. whether the solution may use Pro features
3. which RevoGrid behavior matters most
### Good prompt examples
- `Build a Vue RevoGrid Pro pivot table. Search MCP first, use the best matching example, and explain the required plugin configuration.`
- `Create a React grid with RevoGrid Pro history and validation. Use MCP docs and types before writing code.`
- `Find the best RevoGrid example for advanced filter headers in Angular and adapt it to server-side filtering.`
- `Check if this feature is available in Core or Pro, then recommend the best matching implementation path.`
- `Find a Core-only fallback for this Pro feature.`
## Best practices
Prompt the agent to inspect RevoGrid MCP before generating code. This reduces
invented APIs and helps it start from real examples.
Ask the agent to verify plugin names, event payloads, config fields, and feature
usage against RevoGrid types.
MCP knowledge is token-free, but using a Pro or Enterprise package still requires
the appropriate product access. Ask for a Core-only fallback when needed.
Ask the agent to use the best matching demo or guide as the starting point, then
adapt it to your app.
## Example workflow
1. Add `https://mcp.rv-grid.com` to your MCP client.
2. Open your RevoGrid project.
3. Ask the agent to find the best matching examples and docs.
4. Ask it to adapt the result to your framework and data model.
5. Ask it to verify the result against RevoGrid types before finalizing.
---
# Audit Trail
URL: https://pro.rv-grid.com/guides/audit-trail/
Source: src/content/docs/guides/audit-trail/index.mdx
Description: Track who changed grid data, review transactions, and recover previous values.
`AuditHistoryPlugin` records accountable data-change transactions for spreadsheet-like business workflows. Use it when the grid edits important records and your application needs to answer who changed what, when it changed, and how to recover it.
`HistoryPlugin` owns local undo and redo. `AuditHistoryPlugin` owns the reviewable change log, user metadata, persistence hooks, restore actions, and audit panel. Audit History automatically reuses an existing `HistoryPlugin` or installs one when needed; use `grid.history` when you need to configure History behavior.
## Live Demo
Try the compact audit feed with sample operational data, then inspect the framework source below the grid.
### What To Try
The demo is wired like a production audit surface: edit cells, select a changed cell, filter the history, export records, and restore previous values from the panel.
Change a cell and watch the panel add a timestamped record with user and before/after value details.
Hover a marked cell to see the latest change summary while keeping the full audit trail in the panel.
Select a cell or row, then choose All activity, This row, or This cell in the panel scope menu. Grid selection updates the available context without switching the chosen scope.
Use restore actions to roll back a cell, row snapshot, or transaction while recording the restore itself.
## Getting Started
Register `AuditHistoryPlugin`, provide the current user and a stable row ID, then mount the optional panel.
1. Import the plugins, panel helper, and config type.
```ts
import {
AuditHistoryPlugin,
CellFlashPlugin,
defineAuditHistoryPanel,
type AuditHistoryConfig,
} from '@revolist/revogrid-pro';
```
2. Provide the audit config.
```ts
const auditHistory: AuditHistoryConfig = {
getCurrentUser: () => ({
id: 'avery-stone',
name: 'Avery Stone',
email: 'avery.stone@company.com',
}),
rowIdProp: 'id',
storage: 'memory',
onAuditRecord(record) {
// Persist to your API, Supabase, Firebase, PostgreSQL, etc.
console.log(record);
},
};
```
3. Register plugins and bind the config directly on the grid.
```ts
grid.plugins = [AuditHistoryPlugin, CellFlashPlugin];
grid.auditHistory = auditHistory;
```
`HistoryPlugin` does not need to be listed for normal Audit History restore behavior. Add it explicitly only when the page also has direct History UI controls or you want to control plugin order yourself.
4. Mount the optional review panel.
```ts
const auditPanel = defineAuditHistoryPanel(panelElement, grid, {
pageSize: 25,
allowExport: true,
placement: 'right',
restoreActions: ['cell', 'row'],
dateFormat: 'eu',
timeFormat: '24h',
events: {
beforeExport: ({ format }) => format !== 'json',
beforeRestore: ({ type }) => type !== 'transaction',
},
});
// Later:
auditPanel.setPlacement('bottom');
auditPanel.refresh();
```
The plugin still observes `additionalData.auditHistory` for older integrations, but new code should use the direct `grid.auditHistory` property.
## Stable Row Identity
Audit records should point to business records, not visual row positions. Row indexes can change after sorting, filtering, grouping, or lazy loading, so every row should expose a stable identity.
```ts
const rows = [
{ id: 'invoice-123', status: 'Draft', amount: 1200 },
{ id: 'invoice-124', status: 'Paid', amount: 800 },
];
const auditHistory: AuditHistoryConfig = {
rowIdProp: 'id',
};
```
If your row identity is derived, use `getRowId` instead:
```ts
const auditHistory: AuditHistoryConfig = {
getRowId: (row) => `${row.tenantId}:${row.invoiceNumber}`,
};
```
## Find Your Guide
| Goal | Guide |
| --- | --- |
| Enable audit history and mount the panel | [Getting started](#getting-started) |
| Try editing, filtering and recovery | [Live demo](#live-demo) |
| Understand transactions and custom events | [Records and events](/guides/audit-trail/records/) |
| Configure the compact feed and scopes | [Panel and filters](/guides/audit-trail/panel/) |
| Compare and restore changes | [Review and restore](/guides/audit-trail/restore/) |
| Persist and load history | [Storage and providers](/guides/audit-trail/storage/) |
| Apply redaction and restore policies | [Policies and privacy](/guides/audit-trail/policies/) |
| Translate labels and format timestamps | [Localization and dates](/guides/audit-trail/localization/) |
| Export filtered records | [Export](/guides/audit-trail/export/) |
For signatures, see [Audit History API](/api/audit-history/) and [Audit History Panel API](/api/audit-history-panel/).
## Why Use It
When users edit operational data, the final cell value is not enough. Audit history keeps the context around each change so business users, admins, and support teams can review the full lifecycle of a value.
Record the current user, timestamp, action type, row identity, changed column, previous value, and new value.
Group paste and range-edit operations into one transaction instead of treating every changed cell as an unrelated record.
Restore a single cell, a row snapshot, or a complete transaction after accidental edits.
Keep records in memory, browser `localStorage`, or a custom backend adapter.
## When To Use It
Audit history is useful when RevoGrid edits real business data, not temporary UI state.
Track changes to orders, invoices, stock levels, delivery statuses, prices, and approvals.
Review manual adjustments to budgets, forecasts, risk values, and reporting tables.
Log edits to permissions, limits, subscriptions, account status, and configuration records.
Explain why a value changed without guessing from the current dataset.
## Change Lifecycle
```txt
User edits a grid cell or range
-> one audit transaction captures the user and before/after values
-> records are retained locally or sent to your backend
-> users filter and compare changes in the panel
-> permitted restores recover values and create their own audit record
```
---
# Export
URL: https://pro.rv-grid.com/guides/audit-trail/export/
Source: src/content/docs/guides/audit-trail/export.mdx
Description: Export filtered audit transactions to CSV or JSON without changing stored records.
The panel exports every matching page in its current search and row/cell scope. Scoped exports preserve transaction identity and include only changes in the selected scope.
## Plugin Exports
Use `exportRecords` for custom export buttons or reporting workflows:
```ts
const csv = auditHistoryPlugin?.exportRecords({
format: 'csv',
filter: { actionType: 'bulk-paste' },
includeMetadata: true,
});
const json = auditHistoryPlugin?.exportRecords({ format: 'json' });
```
Export failures are reported through `auditerror` with phase `export` and return an empty string. That keeps export buttons from crashing the page while still giving your app a single error channel for observability.
The optional second argument to `exportRecords(options, selection?)` exports an explicit record selection through the same serializer and error handling without replacing stored history. Display translations and date/value formatters do not alter raw export values.
[Configure panel export controls](/guides/audit-trail/panel/).
---
# Localization and Dates
URL: https://pro.rv-grid.com/guides/audit-trail/localization/
Source: src/content/docs/guides/audit-trail/localization.mdx
Description: Customize every audit label and format EU, US, ISO, and locale-specific dates and times.
Every panel-owned caption, placeholder, accessible name, button title, action/source name and generated summary can be supplied through `labels`. Filter captions have independent `userFilterLabel`, `columnFilterLabel`, `actionFilterLabel`, `dateFromFilterLabel` and `dateToFilterLabel` overrides, while `scopeAriaLabel` names the activity selector independently of the panel title. Omitted caption overrides preserve existing label/placeholder defaults. Partial overrides retain the defaults; nested `actions`, `actionVerbs` and `sourceLabels` maps merge independently. Use the callback labels for complete phrases and plural rules rather than translating individual words. `recordSummary` can reorder the entire activity sentence.
```ts
import type { AuditHistoryPanelOptions } from '@revolist/revogrid-pro';
const options: AuditHistoryPanelOptions = {
locale: 'es-ES',
dateFormat: 'eu',
timeFormat: '24h',
timeZone: 'Europe/Madrid',
labels: {
title: 'Actividad',
allTab: 'Toda la actividad',
rowTab: 'Esta fila',
cellTab: 'Esta celda',
addFilter: 'Filtros',
closeFilters: 'Listo',
searchPlaceholder: 'Buscar…',
searchAriaLabel: 'Buscar actividad',
recordsSummary: n => `${n} ${n === 1 ? 'registro' : 'registros'}`,
cellsSummary: n => `${n} ${n === 1 ? 'celda' : 'celdas'}`,
rowsSummary: n => `${n} ${n === 1 ? 'fila' : 'filas'}`,
scopedChangesSummary: (visible, total) => `${visible} de ${total}`,
scopeLabel: (name, n) => `${name} (${n})`,
additionalChanges: (target, n) => `${target} y ${n} más`,
countLabel: n => new Intl.NumberFormat('es-ES').format(n),
recordSummary: ({ user, verb, target, detail }) =>
`${user} ${verb} ${target}${detail ? ` · ${detail}` : ''}`,
dateFromChip: date => `Desde ${date}`,
dateToChip: date => `Hasta ${date}`,
actions: { 'cell-change': 'Edición', 'bulk-paste': 'Pegado' },
actionVerbs: { 'cell-change': 'editó', 'bulk-paste': 'pegó' },
sourceLabels: { manual: 'Usuario', api: 'Integración' },
},
tooltips: {
totalRecords: 'Registros cargados',
searchFilter: 'Buscar registros, usuarios y valores',
},
formatValue: value => value == null ? 'Sin valor' : String(value),
};
const panel = defineAuditHistoryPanel(panelElement, grid, options);
```
The example overrides a subset of labels. The [complete label contract](/api/audit-history-panel/#audithistorypanellabels) also includes all filter fields, export commands, restore actions, comparison headings, empty states, pagination, today/yesterday headings and the collapsed panel. `actionLabel(type)` and `actionVerb(type)` optionally connect dynamic action types to your translation service; returning `undefined` falls back to the configured maps. Record `presentation` fields take precedence over generated text, so supply those business descriptions in the application's language. Column names come from the grid's column definitions, with recorded names for historical columns. Users' names and stored values remain application data.
`tooltips` customizes both help text and its accessible name; set an entry or the entire option to `false` to hide it. `formatValue(value, { side, change })` formats before/after values consistently in the feed and comparison, including `null`, booleans, numbers and snapshots. Display formatting does not change audit records, filter matching or exported values.
| `dateFormat` | Example date | Default clock |
| --- | --- | --- |
| `'eu'` | `14/10/2026` | 24-hour |
| `'us'` | `10/14/2026` | 12-hour |
| `'iso'` | `2026-10-14` | 24-hour |
| `'long'` | `14. Oktober 2026` with `de-DE` | Locale |
| `'locale'` (default) | Localized short weekday/month/day | Locale |
`timeFormat: '12h' | '24h' | 'locale'` overrides the clock independently of the date preset. `locale` accepts an Intl locale or fallback list, such as `'en-US'`, `'en-GB'`, `'de-DE'` or `'ja-JP'`. The panel uses the browser locale and timezone when omitted. `timeZone` accepts an IANA zone and controls record timestamps and their today/yesterday grouping. Native date-filter inputs retain the browser's local wall-time semantics and native presentation; their chips use the configured date/clock formats and include both date and time.
For another format, supply `formatDate(value, { mode, record })`; it takes precedence over presets for timestamps (`'time'`), headings (`'day'`) and filter chips (`'filter'`). To apply another language, replace `options.labels` with a new translation object and call `panel.refresh()`. Date/clock options can also be changed before refreshing, without remounting the panel.
---
# Panel and Filters
URL: https://pro.rv-grid.com/guides/audit-trail/panel/
Source: src/content/docs/guides/audit-trail/panel.mdx
Description: Configure the compact audit feed, scope selection, filters, comparison, and docking.
The built-in panel docks to the right of the grid by default and can be pinned with `placement: 'left' | 'right' | 'top' | 'bottom' | 'none'`. It includes a compact feed with timeline day grouping, search, a floating user/column/action/date filter menu, removable active-filter chips, explicit cell and row scope selection, pagination, CSV/JSON export of all matching records in the current scope, hover/focus restore and jump actions, keyboard-accessible inline comparison, optional mini close state, and i18n-friendly labels. Use `restoreActions`, callback-capable `allowCompare`, `formatDate`, and `events` to control panel actions. Pass `tooltips: false` to hide all help icons, or override/hide individual entries with `tooltips: { searchFilter: 'Find a record', clearFilters: false }`. Pass `labels` to rename visible text, placeholders, aria labels, action names, source labels, and dynamic strings such as pagination or selected row/cell summaries.
Listen for `beforeauditrestore` to validate or block a restore, and `auditrestore` after a restore succeeds. New audit records are emitted through `auditrecord`. Storage and hook failures are emitted through `auditerror`; loaded record batches are emitted through `auditrecordsloaded`.
The scope menu stays on the selected mode while the grid selection changes. Filters open above the feed without reducing the records area; press Escape to close the menu and return focus to its trigger. Activate an event with a click or Enter to compare changes inline while keeping the feed and search visible. Panel exports include every matching page and only the changes within the selected row or cell scope. The plugin-level `exportRecords(options, selection?)` API retains its explicit `filter` contract. Its optional second argument exports a supplied record selection through the same serializer and `auditerror` handling without replacing stored history.
## Cell Change Indicators
The Audit History examples also mark edited cells directly in the grid. When a cell has recorded changes, the cell shows a small upper-right corner marker aligned to the cell edge. Hovering the cell uses `TooltipPlugin` to show the change count, latest before/after value, user, and timestamp.
This is built on existing plugin APIs: resolve the `AuditHistoryPlugin` instance, call `getCellHistory(rowId, column)` from cell rendering hooks, add tooltip attributes through `cellProperties`, and refresh visible rows when audit events fire.
```ts
const records = auditPlugin.getCellHistory(row.id, column.prop);
```
The marker is only a visual affordance. The audit panel remains the complete review and restore surface.
## Configure the Panel
```ts
const panel = defineAuditHistoryPanel(panelElement, grid, {
placement: 'right',
pageSize: 25,
allowExport: true,
restoreActions: ['cell', 'row'],
});
panel.setPlacement('bottom');
panel.refresh();
// When the owning view unmounts:
panel.destroy();
```
Column filter choices use grid column names, while their keys remain the original props. The selected grid cell or range remains highlighted while using panel controls. Use [localization and dates](/guides/audit-trail/localization/) to customize every label and timestamp format.
---
# Policies and Privacy
URL: https://pro.rv-grid.com/guides/audit-trail/policies/
Source: src/content/docs/guides/audit-trail/policies.mdx
Description: Redact values, choose tracked actions, and enforce restore and immutability policies.
Production audit logs often need more than raw before/after values. Use `ignoredColumns`, `sanitizeValue`, `getMetadata`, `canRestore`, and `immutable` to align the client behavior with your business rules.
```ts
const auditHistory: AuditHistoryConfig = {
getCurrentUser: () => currentUser,
rowIdProp: 'id',
ignoredColumns: ['internalNotes'],
sanitizeValue(value, context) {
if (context.column === 'amount') {
return '[redacted]';
}
return value;
},
getMetadata() {
return {
workspaceId: currentWorkspace.id,
requestId: crypto.randomUUID(),
};
},
canRestore({ type }) {
return currentUser.role === 'admin' && type !== 'transaction';
},
};
```
Use action-type policy when entire classes of audit records should be included or ignored:
```ts
const auditHistory: AuditHistoryConfig = {
ignoredActionTypes: ['undo', 'redo'],
trackedActionTypes: ['cell-change', 'bulk-paste', 'approval-submitted'],
shouldTrackRecord({ type, changes, metadata }) {
if (type === 'server-validation' && metadata?.severity === 'debug') {
return false;
}
return changes.length > 0 || type === 'approval-submitted';
},
};
```
For compliance-style screens where the client must not mutate local audit records, set `immutable: true`. This disables `clear`, `replaceRecords`, and restore actions in the plugin. Server-side immutability should still be enforced by your backend.
```ts
const auditHistory: AuditHistoryConfig = {
storage: 'custom',
storageAdapter,
immutable: true,
};
```
See [storage and providers](/guides/audit-trail/storage/) for backend persistence and [review and restore](/guides/audit-trail/restore/) for recovery behavior.
---
# Records and Events
URL: https://pro.rv-grid.com/guides/audit-trail/records/
Source: src/content/docs/guides/audit-trail/records.mdx
Description: Understand audit records, grouped transactions, custom actions, and named snapshots.
Audit records retain stable identities, raw before/after values and user metadata. One transaction can contain several cell changes.
## What Gets Recorded
Each top-level audit item is an `AuditRecord`. A record contains one transaction ID and one or more `AuditChange` entries.
```ts
{
id: 'audit-001',
transactionId: 'tx-001',
type: 'cell-change',
changedAt: '2026-05-08T12:00:00Z',
changedBy: {
id: 'avery-stone',
email: 'avery.stone@company.com',
},
changes: [
{
id: 'change-001',
rowId: 'invoice-123',
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Approved',
},
],
}
```
For a paste operation, the same record can contain many changes:
```ts
{
transactionId: 'tx-002',
type: 'bulk-paste',
changedAt: '2026-05-08T12:05:00Z',
changedBy: { id: 'avery-stone' },
changes: [
{
id: 'change-002',
rowId: 'invoice-123',
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Approved',
},
{
id: 'change-003',
rowId: 'invoice-124',
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Rejected',
},
],
}
```
## Application Events and Snapshots
Use `recordEvent` for application-driven records that do not come from direct grid editing:
```ts
auditHistoryPlugin?.recordEvent({
type: 'api-sync',
changedBy: { id: 'webhook', name: 'Webhook' },
presentation: {
source: 'api',
targetLabel: 'F1:F8',
detailLabel: '8 cells recalculated',
},
});
```
Custom action types are plain strings. When the record carries normal cell or row changes, existing restore helpers can use it for revert workflows:
```ts
auditHistoryPlugin?.recordEvent({
type: 'approval-submitted',
transactionId: 'approval-42',
changedBy: currentUser,
changes: [{
rowId: 'invoice-123',
rowIndex: 0,
rowType: 'rgRow',
column: 'status',
oldValue: 'Draft',
newValue: 'Approved',
}],
presentation: {
source: 'api',
verb: 'submitted approval',
targetLabel: 'Invoice 123',
},
});
auditHistoryPlugin?.restoreTransaction('approval-42');
```
Summary-only custom records are ignored unless you opt in with `allowEmpty: true`.
Use snapshots when users need a named restore point:
```ts
const snapshot = auditHistoryPlugin?.createSnapshot('Before Q4 close');
if (snapshot) {
auditHistoryPlugin?.restoreSnapshot(snapshot);
}
```
See [restore workflows](/guides/audit-trail/restore/) for recovery and [localization](/guides/audit-trail/localization/) for record presentation labels.
---
# Review and Restore
URL: https://pro.rv-grid.com/guides/audit-trail/restore/
Source: src/content/docs/guides/audit-trail/restore.mdx
Description: Inspect scoped history and recover a cell, row, transaction, or named snapshot.
The audit panel lets users inspect grid changes without leaving the page. Common review flows include full-grid history, selected-row history, selected-cell history, user filtering, date filtering, action-type filtering, and old/new value comparison.
Switch between full-grid, selected-row, and selected-cell history without changing the underlying audit records.
The panel refreshes after new audit records, provider-loaded batches, and restore events from other UI controls.
`canRestore`, `beforeauditrestore`, and `immutable` let the app decide which restore actions users can run.
Generate filtered CSV or JSON exports for reporting, support workflows, or backend handoff.
Use the plugin instance when you need audit data in custom UI:
```ts
const plugins = await grid.getPlugins();
const auditHistoryPlugin = plugins.find(
(plugin) => plugin instanceof AuditHistoryPlugin,
);
const records = auditHistoryPlugin?.getRecords({ userId: 'avery-stone' });
const cellHistory = auditHistoryPlugin?.getCellHistory('invoice-123', 'status');
const rowHistory = auditHistoryPlugin?.getRowHistory('invoice-123');
const lastStatusChange = auditHistoryPlugin?.getLastChange('invoice-123', 'status');
const stats = auditHistoryPlugin?.getStats();
```
Restore helpers return `true` when a restore was applied:
```ts
auditHistoryPlugin?.restoreCell(change);
auditHistoryPlugin?.restoreRow('invoice-123');
auditHistoryPlugin?.restoreTransaction(transactionId);
```
Cell, row replacement, and transaction restores follow the normal edit pipeline. The restore is recorded as `restore-cell`, `restore-row`, or `restore-transaction` without a duplicate edit record.
Query helpers return defensive copies, so application code cannot accidentally mutate the internal audit log. For named restore points, see [snapshots](/guides/audit-trail/records/#application-events-and-snapshots). Restore permissions are covered in [policies and privacy](/guides/audit-trail/policies/).
---
# Storage and Providers
URL: https://pro.rv-grid.com/guides/audit-trail/storage/
Source: src/content/docs/guides/audit-trail/storage.mdx
Description: Load and persist audit history with browser storage or trusted backend providers.
Use `storage: 'memory'` for demos, temporary sessions, and test fixtures.
Use `storage: 'localStorage'` when browser persistence is enough for the workflow.
Use `storage: 'custom'` with `storageAdapter` when your backend should load, append, save, or clear records.
Use `storageProviders` to list cache or server providers. Provider loads refresh the panel automatically.
Use `maxRecords` to bound retained records. The implementation defaults to `1000`.
For production systems, prefer a trusted server provider. If you include several providers, the default is intentionally isolated: the first provider that returns records owns the loaded audit trail, and later providers with records are ignored with a console warning. Set `mergeStorageProviders: true` only when your app deliberately wants to merge records by id across providers.
```ts
const auditHistory: AuditHistoryConfig = {
getCurrentUser: () => currentUser,
rowIdProp: 'id',
storageProviders: [
{
async load() {
const response = await fetch('/api/audit-history');
return response.json();
},
async append(record) {
await fetch('/api/audit-history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(record),
});
},
async save(records) {
await fetch('/api/audit-history', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(records),
});
},
async clear() {
await fetch('/api/audit-history', { method: 'DELETE' });
},
},
],
};
```
Provider write failures emit `auditerror` but do not block editing. Configuring `storageProviders` together with `storage` or `storageAdapter` shows a console warning because `storageProviders` takes precedence.
When you need a deliberate cache-plus-server merge, opt in explicitly:
```ts
const auditHistory: AuditHistoryConfig = {
rowIdProp: 'id',
mergeStorageProviders: true,
storageProviders: [
createLocalStorageAuditHistoryAdapter('invoices:audit-history'),
serverAuditHistoryAdapter,
],
};
```
### Provider Load Flow
Provider chains are useful when the user should see cached history quickly while a server-backed provider loads the durable records.
1. The panel mounts and reads the current in-memory records.
2. Storage providers load in order. By default, the first provider that returns records owns the loaded audit trail.
3. The plugin emits `auditrecordsloaded` whenever loaded records are applied.
4. The built-in panel refreshes on `auditrecordsloaded`, `auditrecord`, and `auditrestore`, so async loads and external restore controls stay visible without polling.
Client-side records are excellent for review panels and recovery UX. Compliance-grade audit trails should be written to a trusted backend.
## Refresh Existing Records
Use `refreshRecords` when your custom storage adapter can load existing records from a backend. A single `storageAdapter` still supports merge-by-id refreshes by default; pass `{ merge: false }` to replace the local set. Provider chains follow the configured provider policy: isolated by default, merged only with `mergeStorageProviders: true`.
```ts
await auditHistoryPlugin?.refreshRecords();
await auditHistoryPlugin?.replaceRecords(serverRecords, { merge: false });
```
[Apply privacy and policy controls](/guides/audit-trail/policies/) before persisting sensitive changes.
---
# Flashing Cells
URL: https://pro.rv-grid.com/guides/cells/cell-flash/
Source: src/content/docs/guides/cells/cell-flash.mdx
Description: Highlight edited or programmatically updated cells and rows in RevoGrid.
Cell flashing makes recent changes visible in editable grids, live dashboards, and audit workflows. The Pro `CellFlashPlugin` can react to edits from `EventManagerPlugin`, undo/redo changes from `HistoryPlugin`, manual `flashcell` events, or direct plugin method calls.
The demo below updates market-style rows, supports burst/live updates, row flashing, custom duration, manual row flash, and undo/redo.
## Enable Cell Flash
Add `CellFlashPlugin` and mark columns with `flash`. Existing boolean predicates still work, and `EventManagerPlugin` keeps edit events connected to the source update flow.
```ts
import { CellFlashPlugin, EventManagerPlugin } from '@revolist/revogrid-pro';
grid.plugins = [EventManagerPlugin, CellFlashPlugin];
grid.eventManager = { applyEventsToSource: true };
grid.columns = [
{
name: 'Price',
prop: 'price',
flash: () => true,
},
];
```
## Context-Aware Flashing
The `flash` callback receives the new value and a context object with the previous value, row model, row index, column, row type, and edit metadata. Return a boolean for simple behavior or a decision object to customize the flash.
```ts
grid.columns = [
{
name: 'Price',
prop: 'price',
flash: (value, context) => ({
flash: true,
rowFlash: Math.abs(Number(value) - Number(context.previousValue)) > 2,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
duration: 800,
rowDuration: 1200,
className: 'price-flash',
rowClassName: 'large-move-row',
}),
cellTemplate: cellFlashArrowTemplate({
symbols: { up: '+', down: '-', changed: '~' },
className: 'price-flash-arrow',
arrowClassName: 'price-flash-symbol',
directionClassNames: { up: 'positive', down: 'negative', changed: 'neutral' },
}),
},
];
```
## Configure Timing And Queueing
Use `grid.cellFlash` or `additionalData.cellFlash` to tune behavior. Defaults preserve legacy behavior: enabled, cell-only, 1000ms duration, replace active flashes on each flash event, and clear on source reset.
```ts
grid.cellFlash = {
duration: 700,
rowDuration: 1000,
mode: 'cell-and-row',
queue: 'merge',
maxActive: 300,
clearOnSourceChange: true,
respectReducedMotion: true,
aria: {
enabled: true,
live: 'polite',
atomic: true,
liveRegionClassName: 'rv-cell-flash-live-region',
},
labels: {
cellUpdated: state => `Updated ${String(state.prop)} in row ${state.rowIndex + 1}`,
rowUpdated: state => `Updated row ${state.rowIndex + 1}`,
},
className: 'cell-flash-active',
rowClassName: 'row-flash-active',
};
```
Queue modes:
- `replace` clears current flash state before applying the next batch.
- `merge` keeps existing flashes active until their own timers expire.
Announcement priority is: per-change `ariaLabel`, `aria.label`, `labels.cellUpdated` or `labels.rowUpdated`, then the built-in defaults. Use `labels` for normal localization and `aria.label` when one callback should fully own every announcement. The live-region politeness, atomic flag, and live-region class are also configurable through `aria`.
## Manual Flash API
Manual methods are available from the plugin instance. This is useful when updates come from a websocket, polling loop, or external calculation.
```ts
const plugins = await grid.getPlugins();
const cellFlash = plugins.find(plugin => plugin.constructor.name === 'CellFlashPlugin');
cellFlash.flashCell({
rowIndex: 0,
prop: 'price',
previousValue: 100,
value: 104,
direction: 'up',
});
cellFlash.flashRows({
rowIndex: 0,
rowClassName: 'manual-row-flash',
});
cellFlash.clearFlash({ rowIndex: 0, prop: 'price' });
```
The existing event API is still supported:
```ts
grid.dispatchEvent(new CustomEvent('flashcell', {
detail: {
data: { 0: { price: 104 } },
previousData: { 0: { price: 100 } },
type: 'rgRow',
},
}));
```
## Styling
The plugin sets `flash`, `flash-direction`, `data-flash-direction`, and duration CSS variables on active cells. Row flashes receive `flash-row` and the configured row class.
```css
revo-grid {
--rv-change-highlight: rgba(255, 238, 0, 0.6);
--rv-row-change-highlight: rgba(255, 238, 0, 0.22);
--rv-cell-flash-up-color: #15803d;
--rv-cell-flash-down-color: #dc2626;
--rv-cell-flash-arrow-transition-duration: 0.3s;
--rv-cell-flash-arrow-transition-timing: ease-in-out;
}
revo-grid[theme^='dark'] {
--rv-change-highlight: rgba(250, 204, 21, 0.34);
--rv-row-change-highlight: rgba(250, 204, 21, 0.16);
--rv-cell-flash-up-color: #4ade80;
--rv-cell-flash-down-color: #f87171;
}
revo-grid .rgCell[flash][flash-direction="up"] {
color: var(--rv-cell-flash-up-color);
}
revo-grid .rgRow[flash-row].large-move-row {
font-weight: 500;
}
```
When `respectReducedMotion` is enabled, users with reduced-motion preferences keep the flash attributes and final highlight state without the animation.
---
# Merge Cells
URL: https://pro.rv-grid.com/guides/cells/cell-merge/
Source: src/content/docs/guides/cells/cell-merge.mdx
Description: Combine adjacent cells into a single cell.
The Cell Merge feature allows you to combine adjacent body or pinned data cells into a single, larger cell, much like Excel's merge functionality. Use it to visually group related data within your grid.
For spreadsheet-style headers that span multiple rows or columns, use the [Multi Row Headers](/guides/columns/multi-row-header/) plugin instead.
### Why Use Cell Merge?
Cell Merge is a powerful tool that can help you:
- **Group Data Visually**: Highlight or group related data by merging cells, which improves the readability and interpretability of your grid.
### Example Use Cases
- **Data Grouping**: Use cell merging to visually segment related data or to emphasize specific sections of your grid.
### How It Works
The Cell Merge feature enables you to specify which cells should be merged into a larger cell. This larger cell will span across multiple columns and/or rows. The content of the merged cell will be centrally displayed, enhancing the layout of your grid.
Since this is a Pro feature, ensure that you have access to the Pro version of RevoGrid to utilize this functionality.
```javascript
// fixing render for multiframework
```
---
# Highlight Cells and Styling
URL: https://pro.rv-grid.com/guides/cells/cell-style/
Source: src/content/docs/guides/cells/cell-style.mdx
Description: Learn how to style cells dynamically based on their values and implement custom event handling.
Highlighting and styling cells dynamically, allows you to create visually appealing and informative data grids.
By utilizing cell properties and custom templates, you can style cells based on their values, manage interactions, and enhance user experience.
## Dynamic Cell Styling
One of the easiest ways to provide visual feedback to users is through dynamic cell styling.
You can apply styles based on the content of the cells, such as changing the background color depending on the value.
You can also manage various events directly within the cell using the `cellProperties`.
This allows you to add interactivity to your cells, such as handling clicks or double clicks.
### Interactive Example
Below is a live example demonstrating dynamic cell styling and event handling:
This example shows:
- Dynamic background colors based on cell values
- Click event handling
- Custom cell templates
In this example, the **Price** column's background color changes based on its value:
- **Grey** for the first column,
- **Red** for prices below 10,
- **Orange** for prices between 10 and 20,
- **Green** for prices above 20,
- Clicking on the **Price** cell will log the value to the console, while double-clicking will display an alert with the price.
## Using Cell Templates for Advanced Styling
If you need more control over the rendering of cells, you can utilize cell templates. This allows for complex HTML structures within cells while maintaining the ability to style dynamically.
In this case, the **Price** cell uses a custom template to render a `div` element, allowing for more advanced styling and content manipulation.
## Conclusion
Capabilities for highlighting and styling cells enhance the user experience by providing meaningful visual feedback and interactivity. By utilizing dynamic styling, event handling, and custom templates, you can create a rich and engaging data grid that meets your specific requirements.
---
# Chart and Visualization
URL: https://pro.rv-grid.com/guides/cells/charts/
Source: src/content/docs/guides/cells/charts.mdx
Description: Discover the wide range of charts and visual renderers available in RevoGrid.
RevoGrid supports a variety of visual renderers and chart types, enabling users to present data in visually engaging and intuitive formats. Below is a description of each available feature:
## Chart Configuration API
Each chart type supports the following common configuration options:
```typescript
interface ChartConfig {
name: string; // Column name
prop: string; // Data property name
minValue?: number; // Minimum value for normalization
maxValue?: number; // Maximum value for normalization
thresholds?: { // Visual thresholds for value ranges
value: number | string | boolean; // Threshold value
className: string; // CSS class for styling
}[];
progress?: (config: { model: any; data: any; prop: string }) => number; // Custom progress calculation
}
```
## Progress Line Renderer
Displays a horizontal progress bar within a cell to visually represent a value's progression relative to a maximum limit. Useful for showing percentages or task completion statuses.
### Configuration
```typescript
{
name: 'Linear',
prop: 'num',
minValue: 0,
maxValue: 100,
thresholds: [
{ value: 90, className: 'high' },
{ value: 50, className: 'medium' },
{ value: 0, className: 'low' }
],
cellTemplate: progressLineRenderer,
// Optional: Custom progress calculation
progress: (config) => {
// Calculate progress based on multiple fields
const completed = config.model.completedTasks || 0;
const total = config.model.totalTasks || 1;
return (completed / total) * 100;
}
}
```
## Progress Line with Value Renderer
Combines the progress line visualization with a numerical value label, offering both a visual and textual representation of the data.
### Configuration
```typescript
{
name: 'Linear with Value',
prop: 'num',
minValue: 0,
maxValue: 40,
thresholds: [
{ value: 90, className: 'high' },
{ value: 30, className: 'medium' },
{ value: 0, className: 'low' }
],
cellTemplate: progressLineWithValueRenderer,
// Optional: Custom progress calculation
progress: (config) => {
// Calculate progress based on multiple fields
const completed = config.model.completedTasks || 0;
const total = config.model.totalTasks || 1;
return (completed / total) * 100;
}
}
```
## Circular Progress Renderer
Displays a circular progress bar within a cell to visually represent a value's progression relative to a maximum limit. Useful for showing percentages or task completion statuses.
### Configuration
```typescript
{
name: 'Circular Progress',
prop: 'num',
cellTemplate: circularProgressRenderer
}
```
## Sparkline Renderer
Renders mini inline charts directly within cells to display trends or patterns in data over time, ideal for quick overviews of performance metrics.
### Configuration
```typescript
{
name: 'Sparkline',
prop: 'trend',
minValue: 0,
maxValue: 100,
cellTemplate: sparklineRenderer
}
```
## Bar Chart Renderer
Generates horizontal or vertical bar charts inside cells to compare values across categories, making it perfect for categorical or segmented data.
### Configuration
```typescript
{
name: 'Bars',
prop: 'trend',
minValue: 0,
maxValue: 100,
cellTemplate: barChartRenderer
}
```
## Timeline Renderer
Visualizes timelines within cells, enabling users to see task durations or event periods in an intuitive linear format.
### Configuration
```typescript
{
name: 'Timeline',
prop: 'timeline',
timelineRange: { start: 0, end: 100 },
cellTemplate: timelineRenderer
}
```
## Rating Star Renderer
Renders star icons to represent ratings, making it ideal for reviews or score-based data.
### Configuration
```typescript
{
name: 'Rating',
prop: 'rating',
maxStars: 5,
cellTemplate: ratingStarRenderer
}
```
## Badge Renderer
Displays badges with customizable fill and optional text colors. Data Grid Formatting can override the text color.
### Configuration
```typescript
{
name: 'Badge',
prop: 'status',
badgeStyles: {
Active: { backgroundColor: '#008620', color: '#fff' },
Pending: { backgroundColor: '#ff9800', color: '#fff' },
Completed: { backgroundColor: '#0068ba', color: '#fff' },
Failed: { backgroundColor: '#f44336', color: '#fff' }
},
cellTemplate: badgeRenderer
}
```
## Change Renderer
Highlights changes in data with visual cues such as arrows or color-coded indicators, helping users track trends or differences over time.
### Configuration
```typescript
{
name: 'Change',
prop: 'num',
cellTemplate: changeRenderer
}
```
## Thumbs Renderer
Displays thumbs-up or thumbs-down icons to indicate approvals, votes, or boolean-like states in a simple and user-friendly format.
### Configuration
```typescript
{
name: 'Thumbs',
prop: 'thumbs',
cellTemplate: thumbsRenderer
}
```
## Pie Chart Renderer
Renders pie charts inside cells to display proportional data distribution, ideal for visualizing parts of a whole within a dataset.
### Configuration
```typescript
{
name: 'Pie',
prop: 'trend',
cellTemplate: pieChartRenderer
}
```
---
# Heatmap/Coldmap Feature
URL: https://pro.rv-grid.com/guides/cells/heatmap/
Source: src/content/docs/guides/cells/heatmap.mdx
Description: Enhance your RevoGrid with dynamic heatmap and coldmap visualizations.
The **Heatmap Feature** provides dynamic color-based visualizations for grid cells, making it easy to interpret numerical data at a glance. By mapping cell values to color gradients, this feature enhances data visibility and helps users quickly identify patterns.
---
### Key Highlights
- **Heatmap and Coldmap Modes**:
- **Heatmap**: Red-to-green gradient for highlighting data intensity.
- **Coldmap**: Shades of blue for cooler, more subtle visualizations.
- **Customizable Value Ranges**: Automatically normalizes values between a defined `minValue` and `maxValue`.
- **WCAG-Compliant Font Colors**: Ensures text remains readable by dynamically adjusting font color (black/white) based on the background's luminance.
---
### Example Configuration
To enable the heatmap feature, set a custom cell template on the desired column:
```javascript
import { heatmapRenderer } from './heatmap';
grid.columns = [
{
name: 'Score',
prop: 'score',
minValue: 0, // Minimum value for normalization
maxValue: 100, // Maximum value for normalization
colorMap: 'heatmap', // Choose 'heatmap' or 'coldmap'
cellTemplate: heatmapRenderer, // Apply the heatmap renderer
},
];
```
---
# Multi Renderer
URL: https://pro.rv-grid.com/guides/cells/multi-renderer/
Source: src/content/docs/guides/cells/multi-renderer.mdx
Description: Display different types of data in the same column using multiple renderers.
The Multi Renderer feature allows you to display different types of data in the same column using different renderers based on the data type. This creates a more flexible and dynamic way to present your data.
### Why Use Multi Renderer?
Multi Renderer is particularly useful when you want to:
- **Handle Mixed Data Types**: Display different types of data (numbers, dates, sliders) in the same column
- **Create Dynamic Presentations**: Automatically choose the best way to display each value
- **Improve Data Visualization**: Use appropriate visualizations for different data types
### Example Use Cases
- **Mixed Data Columns**: When a column contains different types of data (e.g., numbers, dates, percentages)
- **Dynamic Formatting**: Automatically format values based on their type or content
- **Conditional Rendering**: Show different visualizations based on data conditions
### How It Works
The Multi Renderer works by:
1. Defining a set of renderers with conditions that determine when to use them
2. For each cell value, checking the conditions to determine which renderer to use
3. Applying the appropriate renderer to display the data
4. If no condition matches, using a default renderer
### Configuration
To use Multi Renderer, configure your column with the appropriate renderers and conditions:
```typescript
import { MultiColumn } from '@revolist/revogrid-pro';
import { editorSlider, editorCheckbox } from '@revolist/revogrid-pro';
import NumberColumnType from '@revolist/revogrid-column-numeral';
import DateColumnType from '@revolist/revogrid-column-date';
const numberType = new NumberColumnType('0.00');
const dateType = new DateColumnType();
const grid = document.createElement('revo-grid');
grid.columnTypes = {
multi: MultiColumn,
};
grid.columns = [
{
prop: 'data',
name: 'Mixed Data',
columnType: 'multi',
multiColumn: {
defaultRenderer: numberType.cellTemplate,
defaultEditor: numberType.editor,
renderers: [
{
// Use dateRenderer for date strings
condition: (value) => value instanceof Date ||
(typeof value === 'string' && !isNaN(Date.parse(value))),
renderer: dateType.cellTemplate,
editor: dateType.editor,
},
{
// Use sliderRenderer for numbers between 0 and 100
condition: (value) => typeof value === 'number' && value >= 0 && value <= 100,
renderer: editorSlider,
editor: editorSlider,
},
{
// Use checkboxRenderer for boolean values
condition: (value) => typeof value === 'boolean',
renderer: editorCheckbox,
editor: editorCheckbox,
},
],
},
},
];
```
### Configuration Options
The MultiColumn configuration accepts the following options:
```typescript
interface MultiColumnConfig {
/**
* Default renderer to use if no condition matches
*/
defaultRenderer: ColumnRegular['cellTemplate'];
/**
* Default editor to use if no condition matches
*/
defaultEditor?: EditorCtr;
/**
* Array of renderers with conditions
*/
renderers: Array<{
/**
* Condition function that returns true if the renderer should be used
*/
condition: (value: any) => boolean;
/**
* Renderer to use if the condition is true
*/
renderer: ColumnRegular['cellTemplate'];
/**
* Optional editor to use if the condition is true
*/
editor?: EditorCtr;
}>;
}
```
### Example Data
```typescript
const data = [
{ data: 42 }, // Will use numberRenderer
{ data: new Date() }, // Will use dateRenderer
{ data: 75 }, // Will use sliderRenderer
{ data: '2024-03-15' }, // Will use dateRenderer
{ data: true }, // Will use checkboxRenderer
];
```
### Best Practices
1. **Order Matters**: Place more specific conditions before general ones
2. **Default Renderer**: Always provide a default renderer for unmatched cases
3. **Type Checking**: Use proper type checking in conditions
4. **Editor Matching**: Match editors with their corresponding renderers
5. **Performance**: Keep condition functions simple and efficient
### Common Patterns
#### Date Detection
```typescript
condition: (value) => value instanceof Date ||
(typeof value === 'string' && !isNaN(Date.parse(value)))
```
#### Numeric Range
```typescript
condition: (value) => typeof value === 'number' && value >= 0 && value <= 100
```
#### Boolean Values
```typescript
condition: (value) => typeof value === 'boolean'
```
#### String Pattern
```typescript
condition: (value) => typeof value === 'string' && value.startsWith('http')
```
---
# Same Value Merge
URL: https://pro.rv-grid.com/guides/cells/same-value-merge/
Source: src/content/docs/guides/cells/same-value-merge.mdx
Description: Automatically merge cells with identical values in a column.
The Same Value Merge feature automatically combines cells in a column when they contain identical values. This creates a cleaner, more organized view of your data by visually grouping repeated values.
### Why Use Same Value Merge?
Same Value Merge is particularly useful when you want to:
- **Reduce Visual Clutter**: Hide repeated values in a column, making the data easier to read
- **Highlight Data Patterns**: Quickly identify groups of identical values
- **Improve Data Presentation**: Create a cleaner, more professional look for your grids
### Example Use Cases
- **Category Grouping**: When displaying items grouped by category, merge cells with the same category name
- **Status Display**: Merge cells with the same status to make state changes more apparent
- **Hierarchical Data**: Visually group related items in hierarchical data structures
### How It Works
The Same Value Merge plugin works by:
1. Checking each cell against the nearest comparable visible rows above and below it in columns marked with `merge: true`
2. If the values are identical, repeated values are hidden and internal borders are removed
3. This creates a visual effect of merged cells while maintaining the original data structure
When row grouping or Pivot row drill-down inserts group rows, those rows participate in the comparison only when they carry the merged column's value. Deeper group rows without that column value are skipped.
Same Value Merge is a visual merge. It does not create real merged grid ranges and does not change selection, focus, copy, or edit behavior. Use the Cell Merge plugin when you need explicit row or column spans.
### Configuration
To enable Same Value Merge for specific columns, set the `merge` property to `true` in your column configuration:
```javascript
const columns = [
{ prop: 'id', name: 'ID' },
{ prop: 'category', name: 'Category', merge: true }, // Enable merging for this column
{ prop: 'name', name: 'Name' }
];
```
### Sticky Merge Start Cells
Use `merge: { sticky: true }` when the first visible cell in a same-value run should stay visible while users scroll. This marks only the merge start cell as sticky; it does not pin every cell in that row.
```ts
import {
SameValueMergePlugin,
StickyCellsPlugin,
} from '@revolist/revogrid-pro';
const columns = [
{ prop: 'id', name: 'ID' },
{
prop: 'region',
name: 'Region',
merge: { sticky: true },
},
{ prop: 'category', name: 'Category' },
];
grid.plugins = [SameValueMergePlugin, StickyCellsPlugin];
grid.columns = columns;
```
`SameValueMergePlugin` can register Sticky Cells automatically for `merge.sticky`, but adding `StickyCellsPlugin` explicitly keeps the dependency visible and matches the standalone Sticky Cells setup.
### Grouped Merge Boundaries
Some datasets need repeated values to merge only inside another column's run. For example, a `quantity` column may contain the same value across many rows, but it should stop merging when the controlling `workcenter` or operation changes.
Use a merge group with one leader column:
```javascript
const columns = [
{ prop: 'batch', name: 'Batch', merge: true },
{ prop: 'material', name: 'Material' },
{ prop: 'operationNo', name: 'Operation' },
{
prop: 'workcenter',
name: 'Work Center',
merge: {
mergeGroup: 'operation',
mergeLeader: true,
mergeDependsOn: ['material', 'operationNo'],
},
},
{
prop: 'quantity',
name: 'Quantity',
merge: { mergeGroup: 'operation' },
},
{
prop: 'tool',
name: 'Tool',
merge: { mergeGroup: 'operation' },
},
];
```
`workcenter` is the leader for the `operation` group. It merges only while its own value, `material`, and `operationNo` all match. `quantity` and `tool` are followers: they still require their own values to match, but they cannot merge beyond the leader's boundary.
### Merge Configuration
```ts
type SameValueMergeConfig = {
mergeGroup?: string;
mergeLeader?: boolean;
mergeDependsOn?: ColumnProp[];
sticky?: boolean;
};
```
- `mergeGroup` connects columns that should share a leader boundary.
- `mergeLeader` marks the column that defines the boundary for a group.
- `mergeDependsOn` adds row fields that must match before the configured column can merge.
- `sticky` marks merge start cells for Sticky Cells. Only cells rendered as the start of a merge run become sticky.
If a column uses `merge: { mergeGroup: '...' }` and no matching leader is found, it falls back to ordinary same-value merge behavior.
---
# Sticky Cells
URL: https://pro.rv-grid.com/guides/cells/sticky-cells/
Source: src/content/docs/guides/cells/sticky-cells.mdx
Description: Pin selected row cells near the top after their source row starts scrolling out of view.
Sticky Cells keep important checkpoint cells visible while users scroll through a virtualized grid. Mark cells with `stickyCell`; when the row starts crossing the top visibility edge, the marked row renders through the pinned top row area and is trimmed from the main viewport until the next sticky row replaces it.
## Usage
Add `StickyCellsPlugin` to the grid and define a column `stickyCell` predicate.
```ts
import { StickyCellsPlugin } from '@revolist/revogrid-pro';
const stickyCell = ({ model }) => model.isCheckpoint === true;
const columns = [
{
name: 'Account',
prop: 'account',
stickyCell,
},
{
name: 'Status',
prop: 'status',
stickyCell,
},
];
grid.plugins = [StickyCellsPlugin];
```
If any cell in a row is marked as sticky, that row can become the active sticky row.
## With Other Header Plugins
Sticky Cells render through the header layer, so the plugin can be combined with grouped headers and filter headers. When using the filter header plugin, register filtering before sticky cells so Sticky Cells wraps the final header content:
```ts
import {
AdvanceFilterPlugin,
FilterHeaderPlugin,
StickyCellsPlugin,
ColumnStretchPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
AdvanceFilterPlugin,
FilterHeaderPlugin,
StickyCellsPlugin,
ColumnStretchPlugin,
];
grid.filter = {};
grid.additionalData = {
stretch: 'all',
};
```
## Behavior
- One sticky row is active by default. Set `stickyCells.maxRows` to keep multiple recent sticky rows pinned.
- A marked row becomes sticky once 10% of that source row has scrolled above the viewport top.
- Sticky cells work across `colPinStart`, `rgCol`, and `colPinEnd`.
- Custom `cellTemplate` output is reused in the pinned sticky row.
- Existing `pinnedTopSource` rows remain before plugin-managed sticky rows.
- Active sticky source rows are trimmed out of the main row viewport.
- Plugin-managed sticky pinned rows receive the `sticky-cells-pinned-row` class and attribute. They also expose `data-sticky-cells-pinned-row="true"`, `data-sticky-cells-source-index`, and `data-sticky-cells-mode`.
```css
revo-grid .sticky-cells-pinned-row {
font-weight: 500;
}
```
`data-sticky-cells-mode` is `"row"` for ordinary Sticky Cells rows and `"cell"` for integrations that pin only selected cells, such as `merge: { sticky: true }` in Same Value Merge.
Sticky Cells are a display layer. Selection, editing, and focus behavior still belong to the underlying grid cells.
---
# Charts
URL: https://pro.rv-grid.com/guides/charts/
Source: src/content/docs/guides/charts/index.mdx
Description: Explore every built-in standalone chart type, try live examples, and choose the right chart for your data.
`@revolist/charts` renders interactive charts in an ordinary HTML container.
It includes {catalogItems.length} built-in chart types, themes, selection, navigation and exports.
Use it with JavaScript or your preferred framework, independently of Pivot,
RevoGrid and Pro. No grid stylesheet or custom element registration is required.
## Start here
| Guide | What you will learn |
| --- | --- |
| [Installation](/guides/charts/installation/) | Package access, installation, imports and your first chart. |
| [Framework components](/guides/charts/framework-components/) | React, Vue, Angular and Svelte components with reactive configuration. |
| [Analysis](/guides/charts/analysis/) | Moving averages, regression, period comparisons, forecasts, computed references and comparison charts. |
| [Data & Configuration](/guides/charts/data-and-configuration/) | Row mapping, analytical datasets, bindings, styling, updates, selection, exports and cleanup. |
| [Server-side rendering (SSR)](/guides/charts/server-rendering/) | Render SVG on the server and optionally hydrate it in the browser. |
## A simple chart, live
Switch between line and column charts, load forecast data, select points and
download CSV or SVG. These two chart types share one controller and your data.
## Explore the live catalog
Choose any of the {catalogItems.length} built-in chart types or the three optional
geographic examples. Each example uses a valid dataset for its requirements.
For native charts, open **View sample dataset and bindings** to inspect the input,
including specialized channels or distributions. Geographic examples link to
their complete provider and rendering code. The catalog includes all
renderers; your application can import just one type instead.
## Common chart tools
Explore the [shared chart tools](/guides/charts/tools/): drawing and annotations,
navigation, interaction, linking, exports and configuration. Each tool has its
own guide and a live example. These tools belong to Charts; Pivot adds its
query, grid filtering and editor integration.
Select a continuous interval on the plot with [Axis range selection](/guides/charts/tools/range-selection/), or use [Data selection](/guides/charts/tools/selection/) to select individual observations.
## Available chart types
The catalog below is generated from the package's chart definitions. Each row
links to a dedicated guide with a live interactive example, interpretation notes,
data requirements, supported options and a complete example. It also lists the
independent import subpath. For example,
`/line` means `@revolist/charts/line`, which exports `lineChart` and
`createLineChart`.
## Choose the right data shape
- Use columns, bars and lollipops for category comparisons; stacked variants
show totals, while 100% stacked variants show proportions.
- Use lines and areas for ordered trends; combination charts compare series
with different mark styles.
- Use scatter, bubble and density charts for numeric relationships. Map the
required X, Y and optional size channels explicitly.
- Use hierarchy metadata for treemap, sunburst, icicle and circle packing.
- Use observations or distribution summaries for box plots and violins;
use explicit bounds for ranges and intervals.
- Use financial channels for OHLC and volume-price charts, and actual/target
bindings for KPI and progress charts.
The library preserves explicit numeric values and nulls. It does not silently
aggregate repeated category keys or infer missing observations. See
[Data & Configuration](/guides/charts/data-and-configuration/) for conversion rules.
## Optional geographic charts
Three geographic types are available through `@revolist/charts/geographic`.
They are capability packs with application-supplied providers and assets, and
are separate from the native catalog above:
| Type | Purpose | Required application input |
| --- | --- | --- |
| [Choropleth](/guides/charts/types/choropleth/) | Compare values through filled geographic regions. | Region geometry and values. |
| [Proportional symbol map](/guides/charts/types/proportional-symbol-map/) | Compare geographic values through sized symbols. | Coordinates, values and symbol sizes. |
| [Geographic heatmap](/guides/charts/types/geographic-heatmap/) | Show spatial concentration through a heat layer. | Geographic heat points and values. |
ECharts and vector PDF are optional integrations rather than additional chart
families. See [Installation](/guides/charts/installation/#optional-integrations).
## When to use Pivot Charts
Standalone Charts owns visualization and local interaction. For grouped grid
results, queries, linked refreshes, drill-down or filter synchronization, use
[Pivot Charts](/guides/pivot-charts/), which consumes the same chart engine.
---
# Analysis
URL: https://pro.rv-grid.com/guides/charts/analysis/
Source: src/content/docs/guides/charts/analysis.mdx
Description: Add moving averages, regression, calendar comparisons, forecasts, calculated references, and source-record investigation to standalone and Pivot charts.
Charts can show a trend, explain how it was calculated, and help a reader inspect
its source. Analytical settings belong to the shared `@revolist/charts` engine:
standalone charts and the native Pivot renderer use the same calculations.
Start with [Installation](/guides/charts/installation/) and
[Data & Configuration](/guides/charts/data-and-configuration/) for mounting a
chart and mapping rows. This guide explains the analytical methods, their
configuration, and what their results mean.
## Choose an analytical workflow
| Question | Feature | Read next |
| -------------------------------------------------- | ------------------------------------------ | -------------------------------------------------------------- |
| Are missing months being connected? | UTC calendar ticks and explicit gap policy | [Calendar axes](#calendar-axes-and-missing-observations) |
| What is the recent average? | Trailing moving average | [Moving averages](#moving-averages) |
| Is there a linear relationship? | Regression and mean confidence interval | [Regression](#regression-and-confidence-intervals) |
| How does this compare with a previous period? | Exact calendar comparison | [Period comparisons](#calendar-period-comparisons) |
| What would continuation of a linear trend imply? | Forecast and prediction interval | [Forecasts](#linear-forecasts-and-prediction-intervals) |
| What is the mean, median, percentile, or spread? | Calculated references | [References](#calculated-reference-lines-and-bands) |
| How do groups compare on the same scale? | Dimension-driven small multiples | [Facets](#compare-groups-with-small-multiples) |
| How does actual performance compare with a target? | Bullet ranges and variance | [Bullet charts](#compare-actuals-with-targets) |
| How do distributions differ? | Grouped box plots | [Distributions](#compare-distributions-with-grouped-box-plots) |
| Which categories account for most of the total? | Pareto preset | [Pareto](#pareto-analysis) |
| Which source records contributed to this point? | Application/Pivot source-record provider | [Underlying records](#inspect-underlying-source-records) |
## Try the analytical views
Use the selector to compare moving averages, regression, calendar comparisons,
forecasts, calculated references, and Pareto analysis. The example uses
synthetic data to explain the settings and methods.
## A first analytical chart
Run this function after your container is mounted. Give the container a height,
for example `height: 440px`. The example generates monthly demonstration values;
replace them with your own observations in an application.
```ts
import { createLineChart } from '@revolist/charts/line';
export async function mountRevenueAnalysis(container: HTMLElement) {
const data = Array.from({ length: 24 }, (_, index) => ({
month: new Date(Date.UTC(2025, index, 1)).toISOString().slice(0, 10),
revenue: 100 + index * 5 + [0, 12, -8][index % 3],
}));
const chart = await createLineChart({
container,
data,
mapping: {
category: { field: 'month' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
axes: {
x: { scale: 'time', interval: 'month', missingPeriods: 'gap' },
},
presentation: {
title: 'Monthly revenue',
yAxisTitle: 'Revenue (€ thousands)',
legendPosition: 'none',
},
analysis: {
movingAverages: [
{ id: 'rolling', seriesId: 'revenue', window: 3, minPeriods: 3 },
],
regressions: [
{
id: 'trend',
seriesId: 'revenue',
showConfidence: true,
confidence: 0.95,
},
],
},
});
return chart;
}
```
The source line remains the observed data. Moving averages and fitted lines are
separate derived overlays. They can be inspected with the keyboard but cannot
activate a source-data drill or acquire an invented record identity.
In the remaining standalone examples, `chart` is the controller returned above.
Mapping a numeric field creates its initial series ID from that field. For an
existing dataset, check `chart.getDataset().series` rather than guessing IDs.
## Calendar axes and missing observations
```ts
await chart.update({
axes: {
...chart.model.axes,
x: { scale: 'time', interval: 'month', missingPeriods: 'gap' },
},
});
```
Time-axis ticks align to real UTC calendar boundaries. Labels adapt to the
available space: a year, quarter, month, date, or intraday timestamp.
`interval` sets the minimum tick cadence and, when gaps are requested, the
calendar bucket used to identify an absent period.
| Input or policy | Meaning |
| ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| Numeric `0` | An observed zero; it remains a real point. |
| Explicit `null` | A missing observation; it breaks the source path. |
| An absent March category with `interval: 'month', missingPeriods: 'gap'` | February and April remain separate finite runs. No March value is created. |
| Omitted `missingPeriods`, or `'connect'` | Connect existing finite observations across absent calendar buckets. Explicit nulls still break the path. |
Supported intervals are `day`, `week`, `month`, `quarter`, and `year`. A gap
policy requires a time axis and an explicit interval. Comparisons use UTC
calendar buckets, so adjacent month ends such as February 29 and March 31 are
not incorrectly treated as a missing month.
The policy does not resample or aggregate transactions. Supply your intended
observation grain before charting. Customize labels through
`localeText.renderer.formatTimeAxisTick(epoch, dateOnly, defaultText)`; the
formatter receives the actual tick instant. See also
[Axes](/guides/charts/tools/axes/) and
[Date ranges](/guides/charts/tools/date-ranges/).
## Moving averages
```ts
await chart.update({
analysis: {
...chart.model.analysis,
movingAverages: [
{
id: 'rolling',
seriesId: 'revenue',
window: 3,
minPeriods: 2,
label: 'Trailing 3 observations · at least 2 available',
},
],
},
});
```
A moving average uses the trailing `window` ordered category positions. Null
positions occupy space in that window but do not enter the sum or finite count.
`minPeriods` defaults to `window`; until enough finite observations are present,
the result is null.
For `[10, null, 30]`, a three-position window with `minPeriods: 2` produces `20`
at the third position. With `minPeriods: 3`, it produces null. An absent category
is not a position: this is an observation-window average, not automatically a
three-calendar-month average. To express that distinction, provide explicit
missing positions and choose the intended grain before calculating.
`window` accepts 2–1,000 positions; `minPeriods` accepts 1 through `window`.
The resolver reports `insufficient-data` when no point can be calculated.
## Regression and confidence intervals
```ts
await chart.update({
analysis: {
...chart.model.analysis,
regressions: [
{
id: 'trend',
seriesId: 'revenue',
label: 'Linear trend · 95% mean confidence interval',
showConfidence: true,
confidence: 0.95,
},
],
},
});
```
Regression fits ordinary least squares to finite source observations. The X
coordinate is elapsed UTC days on a time axis, a category position on a category
axis, or the bound numeric X value on scatter, bubble, and quadrant charts.
A categorical trend therefore depends on category ordering; it does not infer
elapsed time from labels.
Inspection exposes sample count, slope, intercept, R², RMSE, and residual
standard error. The diagnostics identify the X unit and `xOrigin`. For a time
fit, the intercept is the fitted value at that origin, not at epoch zero.
R² is null when the observed Y values have no variation.
With `showConfidence: true`, the band estimates uncertainty in the **mean
response**. Its margin uses Student-t with `n − 2` degrees of freedom:
`margin = t × s × sqrt(1/n + (x − meanX)² / Sxx)`
Here `s` is the residual standard error and `Sxx` is the sum of squared centered
X values. Accepted confidence levels are `0.9`, `0.95`, and `0.99`; the default
is `0.95`. A two-observation fit can produce a line but cannot estimate a
residual interval: its result explicitly reports
`intervalUnavailable: 'insufficient-degrees-of-freedom'`.
At least two finite observations and variation in X are needed for a fit. On a
relationship chart, the selected Y series must be bound to the Y channel. If the
same Y measure belongs to several X groups, provide `xSeriesId` to identify
which bound X measure to use. See [Scatter](/guides/charts/types/scatter/) for
numeric bindings.
## Calendar period comparisons
```ts
await chart.update({
analysis: {
...chart.model.analysis,
periodComparisons: [
{
id: 'prior-year',
seriesId: 'revenue',
unit: 'year',
offset: 1,
partialPeriod: 'exclude',
asOf: Date.UTC(2026, 9, 15),
label: 'Previous-year value · completed periods',
},
],
},
});
```
A comparison matches each observation to the exact UTC timestamp shifted by
`offset` weeks, months, quarters, or years. The default offset is one; month-end
shifts clamp to the last valid day of the destination month. Timestamp matches
must be unique. Missing matches or null values produce missing results.
The overlay shows the matched **previous value**, aligned with the current
observation. The resolver also returns `referenceCategoryIndex`, `delta`,
`percentChange`, and `partial` for application-owned summaries. Percentage
change uses `100 × (current − previous) / abs(previous)`; it is null when the
previous value is zero. Unrepresentable arithmetic is not exposed as Infinity.
`asOf` is your explicit coverage cutoff in epoch milliseconds. It defaults to the latest
source instant, rather than today's date. A month labeled by its first day does
not establish that the month's data is complete; supply the actual coverage
cutoff when bucket labels differ from reporting completeness. The period containing an observation
is considered partial until its UTC end has passed that cutoff.
- `partialPeriod: 'include'` is the default and retains eligible matches with
explicit partial-period metadata.
- `partialPeriod: 'exclude'` omits unfinished periods. Observations after `asOf`
are excluded under either policy.
For `unit: 'year'` with an October 15 cutoff, the current calendar year is
unfinished. Choose `unit: 'month'` to compare completed months instead of
excluding that entire year. The library does not infer which records are
complete or aggregate daily transactions into monthly totals.
## Linear forecasts and prediction intervals
```ts
await chart.update({
analysis: {
...chart.model.analysis,
forecasts: [
{
id: 'forecast',
seriesId: 'revenue',
unit: 'month',
horizon: 6,
confidence: 0.95,
label: 'Linear extrapolation · 95% prediction interval',
},
],
},
});
```
A forecast extends the linear OLS fit at the explicitly chosen UTC calendar
cadence. `horizon` is the number of future predictions and accepts 1–120.
Forecasting requires at least three finite observations and variation in their
time coordinates. Its confidence levels match regression.
A **prediction interval** estimates uncertainty for a future individual
observation. It includes residual variability in addition to uncertainty in the
mean, so its margin has an extra `1` under the square root:
`margin = t × s × sqrt(1 + 1/n + (x − meanX)² / Sxx)`
This method assumes a linear relationship with independent, constant-variance
residuals. It does not estimate seasonality, holidays, structural breaks, or
causal effects. Assess those assumptions before interpreting the band as useful
forecast uncertainty. A close historical fit does not establish forecasting
accuracy.
Future points are separate from historical observations and have no invented
source category. Automatic display domains include predictions and intervals;
authored bounds and viewports retain precedence. Source CSV exports retain the
canonical dataset, rather than manufacturing source rows for the forecast.
## Read calculated results without SVG
```ts
import { resolveChartAnalysis } from '@revolist/charts/core';
const result = resolveChartAnalysis(
chart.getDataset(),
chart.model.analysis,
chart.model.axes,
{ chartType: chart.model.chartType, bindings: chart.model.bindings },
);
for (const overlay of result.overlays) {
console.log(overlay.id, overlay.status, overlay.diagnostics);
if (overlay.kind === 'periodComparison') {
console.table(
overlay.points.map(({ x, value, delta, percentChange, partial }) => ({
instant: new Date(x).toISOString(),
previousValue: value,
delta,
percentChange,
partial,
})),
);
}
}
```
The resolver operates on the supplied canonical dataset before display sampling.
It does not query raw transactions or read rendered SVG. Pass the same bindings
and chart type when resolving relationship regressions so its numeric X/Y groups
match the rendered chart.
## Calculated reference lines and bands
```ts
await chart.update({
annotations: {
...chart.model.annotations,
calculated: [
{ id: 'average', seriesId: 'revenue', statistic: 'mean', scope: 'all' },
{ id: 'middle', seriesId: 'revenue', statistic: 'median', scope: 'all' },
{
id: 'p90',
seriesId: 'revenue',
statistic: 'percentile',
percentile: 90,
scope: 'visible',
},
{
id: 'spread',
seriesId: 'revenue',
statistic: 'standardDeviation',
deviations: 1,
scope: 'all',
},
],
},
});
```
Mean and median produce lines. The mean is an **unweighted mean of the accepted
chart-cell values**. Those values may already be Pivot aggregates: averaging
regional revenue averages does not compute a record-weighted average across
all transactions. Compute the appropriate measure upstream when that is your
question. Percentiles use linear interpolation between
ordered finite values. A standard-deviation band is `mean ± deviations × σ`,
where `σ` is the **population** standard deviation, dividing by `n`. The default
multiplier is one; positive multipliers up to ten are accepted.
| Scope | Observations used |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `all` (default) | The chart's canonical dataset after upstream queries, filters, and ranking. It does not mean every record in a remote database. |
| `visible` | Categories in the chart's authored viewport. Display sampling does not redefine the population. |
| `selected` | Observations matching the chart's semantic selection. An empty selection produces no reference. |
Nulls, synthetic Others, subtotals, and grand totals are excluded. Empty scopes
omit the reference; references recalculate after relevant updates. Unrepresentable
calculated coordinates reject rather than silently rendering an invalid band.
Reference IDs must be unique across calculated references, explicit numeric
references, notes, and shapes. A reference follows its series' axis assignment
unless `axis` is explicitly supplied. A secondary reference requires a plotted
secondary series. See [Reference lines](/guides/charts/tools/reference-lines/)
and [Reference bands](/guides/charts/tools/reference-bands/) for authored numeric
annotations that do not depend on a statistic.
## Use the same tools in Pivot
Register [Pivot Charts](/guides/pivot-charts/) on an active Pivot grid first. In
this example, the Pivot is configured with a date row dimension and a revenue
measure. The function finds the public plugin, reads the projected dataset, and
uses its actual measure ID.
```ts
import { PivotChartsPlugin } from '@revolist/pivot';
export async function mountPivotRevenueAnalysis(
grid: HTMLRevoGridElement,
container: HTMLElement,
) {
const charts = (await grid.getPlugins()).find(
(plugin) => plugin instanceof PivotChartsPlugin,
);
if (!charts)
throw new Error('Register PivotChartsPlugin before creating a chart.');
const dataset = await charts.getChartData({
chartType: 'line',
totals: 'none',
});
const revenue = dataset.series.find(
(series) => series.measure?.prop === 'revenue',
);
if (!revenue)
throw new Error('Configure a revenue measure on the Pivot first.');
const chart = await charts.createChart({
container,
chartType: 'line',
totals: 'none',
seriesIds: [revenue.id],
axes: { x: { scale: 'time', interval: 'month', missingPeriods: 'gap' } },
analysis: {
movingAverages: [{ id: 'rolling', seriesId: revenue.id, window: 3 }],
regressions: [
{ id: 'trend', seriesId: revenue.id, showConfidence: true },
],
},
annotations: {
calculated: [{ id: 'average', seriesId: revenue.id, statistic: 'mean' }],
},
underlyingRecords: true,
});
return chart;
}
```
Pivot supplies data acquisition, query/filter context, and linked refreshes.
Analytical calculations still run in shared Charts. Read Pivot datasets through
`charts.getChartData(...)`; `getDataset()` belongs to the standalone controller.
For a source-record action using Pivot's built-in drillthrough, retain a current
linked chart without an independent chart query. Details follow below.
## Compare groups with small multiples
Use a real dataset dimension instead of copying a chart manually for each group.
Inspect available dimensions first; the IDs belong to analytical-frame fields,
not arbitrary display labels.
```ts
import { createChart, getChartFacetDimensions } from '@revolist/charts';
import type { ChartDataset } from '@revolist/charts';
export async function mountRegionalPanels(
container: HTMLElement,
dataset: ChartDataset,
) {
const [dimension] = getChartFacetDimensions(dataset);
if (!dimension) throw new Error('Supply a dataset with a usable dimension.');
return createChart({
container,
dataset,
chartType: 'smallMultiples',
facets: {
dimension: dimension.id,
type: 'column',
scaleMode: 'shared',
pageSize: 6,
page: 0,
},
});
}
```
Line, bar, column, and area panels support shared or independent value scales.
Shared scales include panels on other pages. Page capacity also depends on the
host's dimensions, and previous/next controls keep later groups reachable.
Without `dimension`, the existing one-panel-per-series mode remains available.
See [Small multiples](/guides/charts/types/small-multiples/) for dataset examples.
## Compare actuals with targets
Bullet charts combine actual/target bindings with authored qualitative ranges.
The range boundaries are strictly increasing upper limits; they do not change
source values. Optional variance can be absolute or percentage, and percentage
variance is undefined for a zero target.
```ts
import { createChart, createChartDataset } from '@revolist/charts';
export async function mountTargetComparison(container: HTMLElement) {
const dataset = createChartDataset(
[{ team: 'North', actual: 88, target: 100 }],
{
category: { field: 'team' },
series: [{ field: 'actual' }, { field: 'target' }],
},
);
return createChart({
container,
dataset,
chartType: 'bullet',
bindings: { actual: ['actual'], target: ['target'] },
bullet: {
minimum: 0,
maximum: 120,
ranges: [
{ to: 50, label: 'Below target', color: '#dedee5' },
{ to: 90, label: 'On track', color: '#b6a9ff' },
{ to: 120, label: 'Strong', color: '#8971f5' },
],
showVariance: true,
varianceFormat: 'percent',
},
});
}
```
Read [Bullet charts](/guides/charts/types/bullet/) for range interpretation and
binding requirements.
## Compare distributions with grouped box plots
A grouped box plot places genuine distributions for several measures side by
side. Series colors remain stable; inspection exposes sample size, summary
statistics, and outlier information. Supply observations or valid distribution
summaries, rather than converting an aggregate total into invented samples.
[Box plots](/guides/charts/types/box-plot/) documents the standalone distribution
schema. Pivot keeps its automatic single eligible measure default; select
multiple eligible `seriesIds` or `bindings.value` explicitly to compare several
distributions, subject to source-sample availability and budgets.
## Pareto analysis
Pareto analysis sorts nonnegative category contributions descending and overlays
the cumulative share. Use the preset to construct an explicit derived dataset
and a combination chart with an independent percentage axis.
```ts
import {
createChart,
createChartDataset,
createParetoPreset,
} from '@revolist/charts';
export async function mountContributionAnalysis(container: HTMLElement) {
const dataset = createChartDataset(
[
{ cause: 'Delivery', cost: 60 },
{ cause: 'Returns', cost: 30 },
{ cause: 'Billing', cost: 10 },
],
{ category: { field: 'cause' }, series: [{ field: 'cost' }] },
);
return createChart({
container,
...createParetoPreset(dataset, {
seriesId: 'cost',
threshold: 80,
limit: 2,
}),
});
}
```
The example retains Delivery and Returns, then groups Billing into Others.
Cumulative percentages are `60%`, `90%`, and `100%`: the denominator includes
**all** eligible contributions, including those grouped into Others. `limit`
limits the individually ranked categories; the disclosed Others group is
additional. Equal contributions retain their input order.
Nulls, totals, and existing synthetic categories are excluded. Negative values
and zero-total populations reject. Original category IDs and semantic paths
remain attached to retained categories, while cumulative and Others marks are
inspection-only. The resulting dataset is a flat analytical snapshot, not a
live hierarchy.
On an active Pivot chart plugin, use the corresponding snapshot factory:
```ts
const dataset = await charts.getChartData({
chartType: 'groupedColumn',
totals: 'none',
});
const [measure] = dataset.series;
if (!measure) throw new Error('Configure a Pivot measure first.');
const paretoChart = await charts.createParetoChart({
container,
pareto: { seriesId: measure.id, threshold: 80, limit: 10 },
});
```
Here `charts` is the public `PivotChartsPlugin` instance obtained in the earlier
Pivot example. `createParetoChart` creates an **unlinked** saved snapshot; create
another snapshot when you want to rerun the ranking against refreshed source
results.
## Inspect underlying source records
**View underlying records** is distinct from a table of chart values. Chart
values can be aggregates; underlying records must come from a source provider.
The action becomes available after focusing or selecting a real observation.
It supports keyboard entry, paginated rows, cancellation, and focus return.
For standalone charts, supply application-owned access. This example explicitly
aggregates raw orders into daily chart values, then queries those same raw orders
for the inspected date. It does not infer transactions from an aggregate.
```ts
import { createLineChart } from '@revolist/charts/line';
export async function mountOrders(container: HTMLElement) {
const orders = [
{ id: 'A1', date: '2026-01-01', revenue: 30 },
{ id: 'A2', date: '2026-01-01', revenue: 70 },
{ id: 'A3', date: '2026-01-02', revenue: 80 },
];
const data = [...new Set(orders.map((order) => order.date))].map((date) => ({
date,
revenue: orders
.filter((order) => order.date === date)
.reduce((total, order) => total + order.revenue, 0),
}));
return createLineChart({
container,
data,
mapping: {
category: { field: 'date' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
axes: { x: { scale: 'time' } },
underlyingRecords: async ({ target, offset, limit, signal }) => {
if (signal.aborted) throw new DOMException('Aborted', 'AbortError');
const matching = orders.filter(
(order) => order.date === target.category.path[0],
);
return {
records: matching.slice(offset, offset + limit),
totalCount: matching.length,
columns: [
{ key: 'id', label: 'Order' },
{ key: 'date', label: 'Date' },
{ key: 'revenue', label: 'Revenue' },
],
};
},
});
}
```
For a remote provider, forward `signal` to your request, preserve `offset` and
`limit`, and enforce your source-access and column policies on the server.
The shared view requests 50 rows per page. Returned columns must have unique
nonempty keys and labels; invalid pages or contradictory counts show an error.
Closing, updating, or destroying the chart aborts pending work and late
responses cannot reopen the view.
Pivot creation and restore accept `underlyingRecords: true` to reuse
`PivotPlugin.loadPivotDrilldown` with the observation's semantic cell and current
committed query scope. Built-in access requires a current **linked** chart
without an independent chart query. Independent queries and unlinked snapshots
need an explicit application provider. Synthetic groups, aggregates, and future
predictions cannot request fabricated source rows.
Providers are runtime callbacks and are **not saved** in a model. Reattach the
appropriate provider when restoring a chart.
## Supported configurations, persistence, and limits
- Analytical overlays support ordered, unstacked Cartesian charts. Scatter,
bubble, and quadrant charts support numeric regression. Stacked/normalized
overlays and arbitrary categorical forecasting reject.
- Calendar comparisons and forecasts require a time X axis. Comparisons require
unique category timestamps. Calculated references also depend on the chart's
annotation and axis capabilities.
- The shared analysis resolver accepts at most 50,000 ordered categories and 32
overlays across all analysis arrays. The chart/query acquisition limits can
be lower; calculations describe the supplied dataset, not records that were
never acquired.
- `chart.update({ analysis: {} })` clears analytical overlays. Settings objects
are replaced as a whole: spread the previous object when retaining its other
fields, as the examples above do.
- Analysis, calculated references, facets, bullet settings, and explicit axes
persist in saved models. Derived points are recalculated; raw source-record
providers must be supplied again. See [Saved state](/guides/charts/tools/saved-state/).
- Await updates before reading state or exporting. Call `chart.destroy()` when
removing the host so pending work and event handlers are released.
The settings express an analytical method; they do not establish whether the
input is representative, complete, or suitable for that method. Keep units,
aggregation grain, query scope, and incomplete periods visible to readers.
## Analysis on named X axes
Each overlay inherits `seriesOverrides[seriesId].xAxis` and `yAxis`. Moving averages follow that X axis's ordered observations; numeric regressions fit raw X values regardless of log/symlog/sqrt display transforms or reversal. Calendar comparisons and forecasts require the target series' X axis to use `scale: 'time'`. A numeric primary axis can coexist with a named time axis:
```ts
axes: {
x: { scale: 'linear', level: 0 },
xAxes: { calendar: { scale: 'time', level: 1 } },
},
seriesOverrides: { revenue: { xAxis: 'calendar' } },
analysis: {
forecasts: [{ id: 'future', seriesId: 'revenue', unit: 'month', horizon: 3 }],
},
```
Forecasts extend only their assigned X domain. Independent forecast horizons remain independent; hidden series do not extend the visible chart. Authored axes and viewports, source data, CSV and semantic activation identities remain unchanged. Explicit Y bounds still clip derived values and prediction intervals. Model validation checks series assignments together with analysis, so changing a forecast series to a numeric/category X axis requires clearing or replacing its calendar analysis in the same update.
Applications using the resolver directly must provide the same assignment map:
```ts
const result = resolveChartAnalysis(dataset, analysis, axes, {
chartType: 'line',
seriesOverrides,
});
```
Derived overlays expose `xAxisId` for named assignments. `forecastDomains` contains named X extents; `forecastDomain` retains the primary X extent. A forecast overlay also reports its own `forecastDomain`, allowing visible-series domain policies without combining unrelated horizons. Chart renderers and Pivot adapters pass assignments automatically.
Combination charts with `chartOptions.normalizeStacks` compute overlays and calculated references from normalized component values for grouped columns, preserving raw source cells and raw noncolumn layers. See the [normalization contract](/guides/charts/tools/axes/#normalized-combination-column-stacks).
---
# Chart Customization
URL: https://pro.rv-grid.com/guides/charts/customization/
Source: src/content/docs/guides/charts/customization.mdx
Description: Customize every native chart with colors, label formatters, visual styles and SVG render hooks.
Every built-in native chart uses the same renderer customization contract. This applies to all 68 native types, whether created through a per-type factory, the full catalog, or Pivot. Use chart state for saved settings and renderer options for application callbacks and branding.
## Choose the right control
| Detail | API |
| --- | --- |
| Default SVG line/grouped-bar transitions | `rendererOptions.transitions` (runtime preference) |
| Default series/category colors | `rendererOptions.palette` |
| Stable semantic colors, member/measure colors, gradients, conditional colors | `colors` |
| Series color, visibility, axis, mark type, stacking, errors | `seriesOverrides[seriesId]` (supported fields depend on the chart family) |
| Chart background, foreground, muted text, grid | `rendererOptions.theme` (tokens or a callback) |
| Titles, description, legend placement, data labels | `presentation` |
| Every SVG label's content, visibility, typography, class, accessible name | `rendererOptions.labels.styles` and `labels.formatter` |
| Individual marks, fills, outlines, dashes, opacity and typography | `rendererOptions.elements.styles` and `elements.formatter` |
| Tooltips and their content | `rendererOptions.tooltip`, including `formatter`, `className` and timing |
| Domains, scales, tick formats, units, titles | `axes` |
| Reference lines, ranges, text and drawing shapes | `annotations` |
| Family-specific geometry | `chartOptions`; inspect the selected catalog definition's schema |
| Arbitrary SVG geometry, custom layers and remaining visual details | `rendererOptions.onRender` |
| UI strings and number/date formatting | `localeText` |
Model properties remain subject to the selected chart's capabilities and validation. For example, axis-free charts have no numeric axis to configure. Renderer style callbacks provide visual overrides across all native types; they do not add new analytical capabilities.
## Per-chart factories
```ts
import { createLineChart } from '@revolist/charts/line';
const chart = await createLineChart({
container,
data: [{ month: 'Jan', revenue: 120 }, { month: 'Feb', revenue: 180 }],
mapping: {
category: { field: 'month' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
presentation: { title: 'Monthly revenue', dataLabels: true },
rendererOptions: {
palette: ['#6647f0', '#fd7e14'],
fontFamily: 'Inter, system-ui, sans-serif',
theme: { background: '#ffffff', foreground: '#172033', grid: '#e2e8f0' },
labels: {
styles: {
title: { fontSize: 20, fontWeight: 700 },
data: { fill: '#172033', fontSize: 12 },
},
formatter(context) {
if (context.kind === 'data' && context.value != null) {
return { text: `€${context.value}`, ariaLabel: `${context.value} euros` };
}
},
},
elements: {
styles: { mark: { strokeWidth: 2, strokeLinejoin: 'round' } },
formatter(context) {
if (context.kind === 'mark' && context.value != null && context.value > 150) {
return { fill: '#0f766e', stroke: '#115e59' };
}
},
},
onRender({ svg, bounds }) {
const note = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', 'text');
note.setAttribute('x', String(bounds.left + bounds.width));
note.setAttribute('y', '16');
note.setAttribute('text-anchor', 'end');
note.setAttribute('fill', '#64748b');
note.setAttribute('font-size', '10');
note.textContent = 'Internal reporting';
svg.append(note);
},
},
});
```
`rendererOptions` configures the factory's native renderer. Supply either it or an explicit `renderer`; combining both rejects before rendering. A custom renderer is responsible for its own customization contract. A palette colors the family-defined series or category channel; use `colors` when identities must remain stable across ordering changes.
For a small custom native renderer bundle, the equivalent is `createNativeChartsRenderer([lineChart], options)` from `@revolist/charts/core`. For Pivot, pass the same options to `createPivotChartsRenderer(options)` from `@revolist/pivot/charts` and install that renderer in the chart plugin configuration.
## Semantic labels
`labels.styles` keys are `title`, `subtitle`, `axisTitle`, `axisTick`, `category`, `legend`, `data`, `annotation`, `summary` and `specialized`. `labels.formatter` receives `chartId`, `chartType`, `renderMode`, `kind`, `defaultText`, and available axis/category/series/value/annotation metadata. Return a string to replace text, a `ChartLabelResult` to set text/style/classes/accessibility, `null` or `{ hidden: true }` to suppress a label, or `undefined` to keep it. Use this API for typography so native heading and label layout can measure the authored text.
## Visual elements and precedence
`elements.styles` keys are:
- `background`: the chart's background rectangle.
- `mark`: datum, inspection or mark-group graphics, including grouped decorative children.
- `label`: all SVG text. `labelKind` identifies its semantic label role.
- `legend`: non-text graphics within legend items or overflow controls.
- `annotation`: non-text graphics with an annotation identity.
- `decoration`: remaining visible SVG graphics, such as grids, axes, paths and navigation graphics.
`elements.formatter` receives the chart ID/type, render mode, role, SVG `tagName`, original `attributes`, and available canonical `categoryIndex`, `seriesIndex`, `category`, `series` and `value`. Series identities remain canonical when hidden/helper series are omitted from geometry. Aggregate or decorative visuals may have no individual datum. Use `attributes` to target a chart-specific graphic that shares a role, such as a line path or a gauge track. The original attributes describe the built-in result before element styling, including label customization.
Styles support fill, stroke, fill/stroke opacity, stroke width/dashes/offset/caps/joins, fill rule, vector effect, paint order, overall opacity and the `ChartLabelStyle` typography properties. Shared kind styles apply first; the element formatter wins for properties it supplies. Element styles override built-in presentation, chart options and label styles; `onRender` runs last. Gradients and patterns authored through the model remain intact unless their fill is explicitly overridden.
SVG definitions, clipping paths, masks, patterns, markers the source keyboard focus ring and the plot-bounds measurement rectangle are excluded from element styling. Groups are not styled a second time, avoiding multiplied inherited opacity. To alter definitions or geometry, use `onRender`.
## Lifecycle, updates and exports
Both style callbacks and `onRender` run synchronously on each complete SVG paint: creation, model/data updates, resize, category scrolling and viewport navigation. Callbacks also run in previews; inspect `renderMode` when needed. Lightweight peer-hover and status-only updates do not repaint the whole chart.
`onRender` receives `{ svg, input, bounds, theme }`, with the canonical input including hidden series. Mutate only the supplied chart SVG. Custom SVG children are removed on the next complete paint, so append them again on every invocation. Keep callbacks deterministic, synchronous and free of chart-update calls; treat the input, theme and bounds as read-only. The chart owns its interaction targets and accessibility, so preserve their event handlers, identity attributes and focus semantics when changing geometry.
SVG, PNG and PDF exports use the customized SVG. SVG additions must be self-contained for portable export. Image/PDF encoder limitations still apply. Runtime callbacks and renderer options are not serialized in `chart.model`: reapply them when restoring a model. To change dynamic branding, close over application settings and trigger an ordinary `chart.update({})`; callbacks use the latest settings on that repaint. To change renderer-wide configuration objects, create a new renderer/chart.
## Discover each chart's geometry controls
```ts
import { CHART_DEFINITIONS } from '@revolist/charts/catalog';
const definition = CHART_DEFINITIONS.find(({ type }) => type === chart.model.chartType)!;
console.table(definition.options.fields);
// Each field includes its key, control, default and numeric range or select values.
// definition.options.defaults contains the complete defaults for this type.
```
Use those exact keys in `chartOptions`. The schema is authoritative for that chart's built-in controls. Common family controls include bar corner radius and padding, line stroke width, area fill opacity, radial angles/radii, histogram bins, financial price styles, gauge domains/segments and facet scale mode. See [chart catalog](/guides/charts/#available-chart-types) for the family-specific contracts.
## Geographic charts
Geographic charts have a separate provider lifecycle. All three types (`choropleth`, `proportionalSymbolMap`, `geographicHeatmap`) accept `rendererOptions` on the `pack.render(input)`/`instance.update(input)` input:
```ts
const instance = pack.render({
container, request, localeText,
rendererOptions: {
theme: { background: '#f8fafc', foreground: '#172033' },
elements: {
styles: { mark: { fill: '#0f766e', stroke: '#ffffff', strokeWidth: 1 } },
formatter: ({ kind, value }) => kind === 'mark' && value != null && value < 0
? { fill: '#b91c1c' } : undefined,
},
onRender({ svg, data }) {
svg.setAttribute('aria-label', `Map: ${data.projectionId}`);
},
},
});
await instance.ready;
```
Geographic renderer options include `className`, `fontFamily`, theme tokens, the shared element styles/formatter and `onRender({ svg, input, data, theme })`. Marks expose category/series IDs when supplied by the provider. Locale functions format geographic values and accessible labels; `onRender` can replace visible attribution text or add data labels. The hook runs after a successful provider paint, including updates and provider-owned overlays. Projection, paths and positions remain provider-owned.
The optional ECharts Canvas adapter has a separate supported-feature contract. Native SVG element callbacks do not apply to Canvas; it does not support the full native appearance/annotation/axis contract.
## Grouping, flow callbacks and axis identity
`rendererOptions.categoryAxis.groups` accepts runtime `getValue(category, categoryIndex)` callbacks with the same size/tick/color options as saved `axes.x.groups`. Return a finite scalar or `{ value, label }`; callbacks override saved group rows and are reattached on restore. See [grouped categories](/guides/charts/tools/axes/#grouped-category-ticks-and-labels).
`rendererOptions.sankey` supplies sort comparators, node/link colors, value formatters and node/link/highlight callbacks; see the [complete Sankey contract](/guides/charts/types/sankey/#runtime-callbacks). `labels.formatter` now receives stable `axisId` for primary, secondary and named axes. Horizontal bar value labels report `axis: 'x'`; vertical secondary labels preserve `axis: 'ySecondary'`. Supported Cartesian renderers measure styled/formatted labels once per semantic identity per frame, reserve same-side gutters and retain single-pass custom renderers. See [measured layout](/guides/charts/tools/axes/#measured-cartesian-gutters).
Scatter, bubble and quadrant charts also accept `axes.ySecondary`, `axes.yAxes[id]` and `seriesOverrides[boundYId].yAxis`. The Pivot inspector limits assignment controls to bound Y measures. Scale/domain/reversal/ticks, error bars, value references, semantic notes and regression overlays follow that Y axis. Y navigation and synchronization remain primary-only; named/secondary hover shares X only. See [all relationship axis options and limits](/guides/charts/tools/axes/#multiple-value-axes-on-relationship-charts).
Relationship groups also support `axes.xAxes[id]`, `seriesOverrides[boundYId].xAxis`, `viewport.xAxes[id]`, `navigation.xAxis` and `rangeSelection.xAxis`. The bound Y measure owns both assignments; `bindings.x` supplies numeric X values. Named X forms default to linear and omit category mapping controls. Named X scales, errors, annotations, brushes, navigator, synchronization and async full-domain metadata use that projection. See [the full relationship X contract](/guides/charts/tools/axes/#independent-numeric-x-axes-on-relationship-charts).
Candlestick and OHLC charts expose the shared primary price-axis form: `axes.y` supports linear/log/symlog/sqrt, bounds, reversal, vertical placement, title/unit/colors, numeric ticks and tick formatting. Price marks and annotations use the same clipped projection, and styled labels use measured gutters. See [all financial price options and limits](/guides/charts/tools/axes/#candlestick-and-ohlc-price-axes). Volume-price uses fixed price/volume controls described below; additional financial axis collections remain unsupported.
`volumePrice` shares category/time/numeric X navigation across aligned panes. `axes.y` configures prices and `axes.ySecondary` configures volume, including independent transforms, bounds, reversal, placement, units, formatting and ticks. Fixed roles preserve canonical data and reject swaps. Pivot exposes both forms. Notes/references/drawings use their pane's scale; full-source/synchronized Y metadata remains price-only. See [all volume-price options and limits](/guides/charts/tools/axes/#volume-price-pane-axes).
Bound range columns/bars/areas, confidence bands and error intervals also support primary/secondary/named value axes and named X projections. The bound high measure owns each range group; low and actual fields follow it. Pivot exposes assignment controls on high measures. Scales, bounds, reversal, formatting, ticks, measured labels, clipping, annotations, navigator and brushes use the shared projections. See [all range-axis options and limits](/guides/charts/tools/axes/#range-group-axes-and-navigation).
Box plots, violins and statistical intervals also expose primary `axes.y`, secondary `axes.ySecondary` and named `axes.yAxes` numeric controls. Actual distributions own box/violin projections, and bound matrix observations own statistical intervals. Clipping, measured labels and annotations share those projections. Horizontal boxes retain physical value/category forms in Pivot. See [all statistical-axis options and limits](/guides/charts/tools/axes/#statistical-primary-value-axes).
Statistical charts also support category windows, reversal/grouped labels, navigator/brush controls and primary-domain/category synchronization. Box/violin overviews and shared hover use displayed medians; canonical CSV cells remain unchanged. See [statistical navigation options and boundaries](/guides/charts/tools/axes/#statistical-categorical-navigation).
Statistical primary X also supports time and all four numeric scales, including horizontal boxes. Raw key spacing, continuous brushes, annotations, navigator, source snapshots and remote detail/overview requests share that projection. See [all statistical continuous-X options and physical-axis roles](/guides/charts/tools/axes/#statistical-time-and-numeric-x).
Histograms also expose primary aggregate-Y controls and optional numeric bin-boundary X scales. X comes from bound sample values; Y configures counts, sums or means. Bounds clip bins without rebucketing, and canonical CSV/observation identities remain intact. See [all histogram-axis options and boundaries](/guides/charts/tools/axes/#histogram-bin-and-metric-axes).
Histograms support local raw-sample X windows, zoom/pan, a bin-metric navigator, independent numeric brushes and compatible domain/window synchronization. Bins remain stable while the window clips their rectangles. See [all histogram navigation options and boundaries](/guides/charts/tools/axes/#histogram-sample-navigation). Histograms also support [shared/crosshair/peer bin readouts and typed formatter metadata](/guides/charts/tools/axes/#histogram-shared-hover-and-crosshair). Remote histogram loading uses [complete-bin source summaries](/guides/charts/tools/axes/#complete-histogram-source-frames); typed metadata keeps global metrics separate from loaded raw rows.
## Source marker filtering
Use `rendererOptions.markers.isVisible(context)` to select source glyphs before bounded marker sampling. It preserves raw values and refines saved boolean/start/end marker modes. The runtime callback is shared by Charts and Pivot and must be supplied again after restore/SSR/hydration. See [context, diagnostics and examples](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Named statistical X axes
Box plots, violins and statistical intervals accept named `axes.xAxes` and per-series `seriesOverrides[id].xAxis`, plus primary/named `data` and `level` X mappings. Each X frame has its own category/time/numeric scale and `viewport.xAxes[id]`; `navigation.xAxis` and `rangeSelection.xAxis` select interaction targets. Statistical intervals partition observations by the X/Y assignment pair, while each Y domain includes its full assigned source statistics across X frames. Named X decorations use top/bottom/none vertically, left/right/none for horizontal boxes. Pivot shares the controls and reconciles positions on orientation changes. References/shapes/notes, save/SSR, activation and canonical data use the assigned projections. See the axes guide for fields, defaults and limits.
### Financial numeric X
Candlestick, OHLC and volume-price charts accept primary `axes.x.scale: 'linear' | 'log' | 'symlog' | 'sqrt'` plus `data`/`level` X mappings, numeric bounds, reversal, units, tick formatting/policy and physical styles/placement. Both volume-price panes share raw X coordinates and navigation; glyph widths follow the nearest distinct visible numeric gap. Source activation, annotations, model/SSR and canonical CSV retain their existing contracts. Pivot exposes primary mapping/numeric controls, with named X on all financial collections and named Y on candlestick/OHLC. See the axes guide for defaults, field limits and remaining collection/pane capabilities.
### Candlestick/OHLC collections
Candlestick and OHLC accept parallel open/high/low/close binding arrays. Close fields uniquely own groups and their `seriesOverrides[id].xAxis`/`yAxis` assignments; open/high/low helpers can broadcast and inherit their owners. Use `axes.xAxes`, `axes.ySecondary` and `axes.yAxes` for independent scale/style/window controls. Groups keep canonical source indexes, stable slots and independent value domains; missing rows omit only the incomplete group. CSV windows include every owned OHLC field, and primary runtime Y hints exclude secondary/named price groups. Pivot preserves explicit grouped bindings and exposes axis selectors on close owners. See the axes guide for channel rules, limits, annotations and source contracts. Volume-price paired collections retain one shared price pane and one shared volume pane, as described below.
### Paired volume-price collections
`volumePrice` supports parallel OHLC/volume binding arrays, distinct paired close/volume owners and named X axes. Each close owns X for its prices and volume; Y remains primary for prices and secondary for all volumes. Both panes share aligned group slots, windows and raw source identity. Missing volume preserves complete prices; incomplete OHLC observations omit both sides of that pair. `chartOptions.volumePaneRatio` accepts `0.1`–`0.8` of post-gap height; unset/reset retains automatic 24% sizing and its small-pane policy. Pivot exposes close-owner X selectors, fixed Y forms and the ratio control. See the axes guide for pairing, mappings, helper transitions, hover, annotations, exports, source/SSR and remaining additional-pane/Y limits.
## Keyboard inspection of omitted source glyphs
Native line/area charts and combination line/area layers retain full eligible
source keyboard traversal when marker modes, predicates or sampling omit glyphs.
There is one inspection Tab stop and at most one temporary focus target per SVG;
arrow keys wrap, Home/End choose the first/last item, and Enter/Space activate the
raw canonical source datum. Visual highlight suppression does not hide the focus
ring. No marker callback reruns and no hidden DOM is created per observation.
See [eligibility, ordering, lifecycle and export behavior](/guides/charts/tools/axes/#source-point-keyboard-traversal).
## Pie arc geometry
`pie`, `donut` and `semiDonut` share these saved `chartOptions` in standalone
Charts and Pivot. The native renderer keeps existing geometry when the new
options are omitted. Resetting appearance removes authored options.
| Field | Omitted behavior / editor default | Accepted values and units |
| --- | --- | --- |
| `outerRadius` | Full available radius / `1` | `0.05`–`1`, step `0.05`; fraction of the fitted radius, after existing plot/legend layout. |
| `paddingAngle` | No angular gap / `0` | `0`–`45` degrees, step `0.5`. |
| `cornerRadius` | Sharp corners / `0` | `0`–`100` SVG pixels, step `1`; bounded by sector angle and ring thickness during rendering. |
| `arcLabelRadius` | Existing adaptive per-ring label placement / editor preset `0.7` | `0`–`1`, step `0.05`; distance from center as a fraction of the individual slice's actual outer radius. |
| `arcLabelMinAngle` | No additional angular filter / `0` | `0`–`360` degrees, step `1`; minimum raw source sector angle for an on-arc label. |
```ts
const chart = await createChart({
container,
chartType: 'donut',
dataset,
presentation: { dataLabels: true },
chartOptions: {
innerRadius: 0.5,
outerRadius: 0.85,
paddingAngle: 3,
cornerRadius: 8,
arcLabelRadius: 0.8,
arcLabelMinAngle: 15,
},
});
```
`innerRadius` remains a fraction of the effective fitted pie envelope;
`outerRadius` scales that envelope before hole and concentric-ring layout.
`startAngle` and `sweepAngle` retain their existing degree convention. Donuts
can use multiple angular series as concentric rings; the same saved geometry
settings apply to every ring. Radius-bound pie sectors retain the existing
square-root radius mapping, and authored label radii and outside-label leaders
follow each sector's actual outer radius. Small rings keep their inner radius
within their own outer radius.
Spacing and rounding affect the rendered shape, not the raw share, source value,
category/series identity, order, tooltip, selection, keyboard activation, data
view or CSV. Padding is bounded by the individual source sector span; very
narrow sectors can collapse visually and remain inspectable through their
canonical keyboard target. A full-circle singleton remains a complete circle
or ring: padding and corner rounding do not create an artificial seam. Corners
shrink as necessary on narrow sectors and thin rings.
`presentation.dataLabels: true` is still required for on-arc labels. The angle
threshold uses the sector's raw source share of `sweepAngle`, before padding;
existing label-density and layout rules can hide additional labels. Exactly
matching the threshold remains eligible. `arcLabelMinAngle` does not filter
outside callout labels, geometry or records. `calloutLabels` remains independent.
An explicit `arcLabelRadius` can put labels inside a donut hole; label placement
is an authored visual choice. Omission restores the renderer's previous
per-ring placement, rather than applying the editor's `0.7` preset.
In Pivot, use **Customize → Appearance** for these shared range controls. Reset
individual fields or reset appearance to remove authored values. Localize them
through `localeText.chartOptions.labels` and `.descriptions` using each field's
name. `cornerRadius` also exists for columns, whose default remains `3` pixels;
pie's default is `0`.
Options survive model save/restore, SVG server rendering, hydration and SVG/PNG/
PDF exports. Invalid fields, out-of-range values and off-step values reject
before replacing the existing chart frame. Updating `chartOptions` replaces the
whole authored option object, so retain other desired options in that patch.
These controls do not add arbitrary pie-center offsets, per-ring geometry
objects, highlight/fade radius animation, item sorting, runtime arc-label content
callbacks or animated transitions. Existing shared label customization remains
available for text and typography.
## Stack baseline policies
`chartOptions.stackOffset` selects how a stack uses its baseline. The default is
`'diverging'`, preserving existing Charts and Pivot rendering. A stack can override
the chart policy through `seriesOverrides[id].stackOffset` on any one member.
| Policy | Coordinates |
| --- | --- |
| `'diverging'` | Positive values accumulate above zero; negative values accumulate below zero independently. |
| `'none'` | Each series starts at the previous series' end in resolved stack order. Negative values move the stack back toward/below zero, so segments can overlap. |
| `'expand'` | Divide every finite value by its stack's algebraic total, then accumulate as `'none'`. A nonzero-total stack ends at `1`. |
```ts
chartOptions: { stackOffset: 'diverging', stackOrder: 'none' },
seriesOverrides: {
revenue: { stackGroup: 'income', stackOffset: 'expand' },
costs: { stackGroup: 'income' }, // inherits the income partition's policy
other: { stackGroup: 'other', stackOffset: 'none' },
}
```
Supported native types are grouped/stacked/normalized columns and bars,
all seven native line/area types, plus explicit stacked column/line/area layers
in `columnLine` and `areaLine`. On grouped columns/bars, an explicit `stackGroup`
is still required to create stacks. On combinations, `stackGroup` still creates
the stack; choosing an offset alone does not stack an independent layer. Range
and scatter layers reject a per-series offset. Unsupported chart types reject
both chart-wide and series policies.
Policies resolve independently for each X-axis, Y-axis and stack group.
Combination column stacks and line/area stacks remain separate families even
when their group text matches. Native paths also partition by explicit groups;
regular paths without a group remain independent, while dedicated stacked areas
retain their implicit default stack. One member may
supply the policy, or several may supply the same policy. Conflicting authored
policies within one actual stack reject atomically. Hidden members do not affect
an active stack. `stackOrder` changes accumulation order independently of the
baseline policy; canonical datasets and series IDs retain their original order.
Expansion uses the algebraic total, including negative values. For `[6, -4, 2]`,
the expanded values are `[1.5, -1, 0.5]`, and cumulative ends are `[1.5, 0.5, 1]`.
They can extend above `1` or below `0`; automatic domains include the actual
bases and ends. All-negative values divide by a negative total and therefore
produce positive shares. A zero-total row retains its original values rather
than inventing a ratio. Nulls remain missing. Large finite totals use scaled
arithmetic; nonfinite expanded coordinates reject before replacing the frame.
`expand` replaces built-in normalized-type or combination `normalizeStacks`
percentage preprocessing for its own partition. Other partitions retain their
existing `normalizedTo` behavior. Axes mixing expanded and percentage/raw groups
use numeric labels; pure legacy normalized axes retain percentage labels. Saved
`normalizedTo` does not change the unit-total expansion target. Logarithmic axes
retain their existing positive-source requirements; an offset does not make a
nonpositive raw observation valid on a log axis.
Y-domain synchronization uses the same offset coordinates and distinguishes
unit-expanded axes from raw/legacy-normalized axes. Connecting incompatible
Y-domain peers rejects through the existing sync compatibility check; changing
an already connected chart to expansion stops sharing domains with raw peers.
Expanded charts with matching declared units and pure/mixed expansion policies
can share domains. Data-source providers supplying an explicit full Y domain
must return bounds in the configured plot policy's units, including after an
offset change. Generic row-source requests do not carry stack policies: the
application coordinates provider configuration/revision and reloads with such
changes, or omits full Y bounds to use loaded-frame inference. The client does
not infer global aggregate bounds from partial rows.
The renderer uses offset coordinates for geometry, domains and source-point
projection. Tooltips, hover identity, source activation, selection, underlying
records, data views and CSV retain canonical raw values. Sampling, named axes,
axis reversal and viewport updates retain the selected policy. Navigator plots
continue their existing raw-series overview behavior; these policies do not turn
the overview into a stacked chart.
In Pivot, **Customize → Appearance → Stack baseline** authors the chart policy.
**Customize → Axes and scales → [series] Stack baseline** authors a partition
policy; Automatic removes that member override. Reset appearance removes authored
policies. Resetting only the chart-wide option retains member overrides.
UI-policy keys are `chartOptions.stackOffset` and
`axes.seriesStackOffset`. Localize `chartOptions.labels.stackOffset`,
`.descriptions.stackOffset`, and `.selectValues.diverging` / `.expand` / `.none`.
The public types are `ChartStackOffset` and `PivotChartStackOffset`.
Policies persist through models, server rendering, hydration and exports. They
do not add streamgraph silhouette/wiggle offsets, new stack families, animated
transitions or cross-axis accumulation.
## Native line and area stack groups
All seven native path types accept `seriesOverrides[id].stackGroup`: `line`,
`smoothLine`, `steppedLine`, `area`, `steppedArea`, `stackedArea` and
`normalizedArea`. This uses the shared Charts renderer in standalone charts and
Pivot. It does not change the chart type reported to callbacks or require a
combination chart.
```ts
const chart = await createChart({
container,
chartType: 'line',
dataset,
chartOptions: { stackOrder: 'none', stackOffset: 'diverging' },
seriesOverrides: {
revenue: { stackGroup: 'income' },
services: { stackGroup: 'income' },
online: { stackGroup: 'channels' },
offline: { stackGroup: 'channels' },
target: {}, // independent raw line
},
});
```
Regular line and area types leave series without a group independent. Choosing
`stackOrder` or `stackOffset` does not create a stack on its own. Dedicated
`stackedArea` and `normalizedArea` retain their existing implicit `default`
stack for unassigned series. An explicit `stackGroup: 'default'` joins that
implicit stack; other group names create separate stacks. Names must be nonempty
strings. Groups are independent within each assigned X/Y-axis pair; matching
names on different axes do not add together. Distinct groups overlay in the same
plot rather than accumulating into each other.
`normalizedArea` normalizes every group independently using its existing signed
percentage convention. A single member of a normalized group can therefore
occupy a complete 100% band. Other native path types use raw values unless an
actual group chooses `stackOffset: 'expand'`. Expansion uses algebraic unit totals
and replaces percentage preprocessing for that group. Axes mixing expanded and
percentage/raw groups use numeric labels. The [stack offset guide](/guides/charts/customization/#stack-baseline-policies)
explains signed and zero-total behavior, units and sync compatibility.
`chartOptions.stackOrder` and `.stackOffset` now also appear on regular native
path types. Defaults are `'none'` for order and `'diverging'` for baseline policy,
preserving existing behavior. A group's member can override either policy;
other members inherit it. Multiple authored policies in one active group must
agree. Independent paths' policy overrides remain dormant until they join a
group. Hidden members do not affect totals, domain inference or policy agreement.
Unstacked native areas retain numeric/min/max fill baselines. A fixed baseline
and a stack group cannot apply to the same series; direct updates reject that
combination before replacing the frame. Pivot clears the fixed baseline when a
series enters a group. Existing error-bar restrictions still apply: a chart with
explicit groups cannot also author error bars.
Missing observations stay missing and do not fabricate a zero-valued source
point. Each series keeps its original categories, IDs, labels and values for
accessible descriptions, tooltips, hover, marker predicates, selection, source
activation, underlying records, data views and CSV. The renderer uses cumulative
endpoints for geometry, notes and domains, including named axes and reversed or
numeric/time X. Values in a stack can be signed; the selected offset controls
whether signs diverge or accumulate algebraically.
Explicit M4/minmax/LTTB path sampling aligns cumulative edges within each group.
Unrelated groups and raw paths retain independent selections and budgets. Null
and calendar gaps keep their existing behavior. Hover and keyboard inspection
still use accepted unsampled source geometry. Large values share finite geometry
units when a real group needs them; unrelated large scalars are not added merely
to detect overflow. Source datasets remain unchanged.
In Pivot, use **Customize → Axes and scales → [series] stack group**. An empty
value removes the explicit group; the chart family's defaults then apply.
**Stack order** and **Stack baseline** appear in Appearance and per-series axis
controls. Supported gallery switches retain group/policy settings; unsupported
shapes clear them. The existing group control uses UI-policy key
`axes.seriesLayer` and locale function `chartOptions.stackGroup`; order/baseline
use `axes.seriesStackOrder` / `axes.seriesStackOffset` and their existing label
and description keys.
Groups persist through models, SVG server rendering, hydration and exports.
Navigator paths keep their raw-series overview policy: main group/order/offset
overrides are not inherited. An ordinary path navigator can explicitly author
its own `navigation.navigator.seriesOverrides` groups and per-member policies;
these apply to raw overview series. Histogram, matrix and numeric-XY navigators
retain their dedicated contracts. Y-domain sync uses the
same partitioned coordinates as rendering; provider-supplied full Y bounds remain
an application contract in the configured plot units. This does not add mixed
native line/area layer types, streamgraph offsets, cross-axis accumulation or
animated stack transitions.
## Compact sparklines
Use `chartOptions.sparkline` for saved compact layout in Charts and Pivot, or `createSparklineChart` for a number-array source. See [sparklines](/guides/charts/tools/sparklines/) for every option, defaults, sizing, inspection and restore/export behavior.
## Pie highlighting and fading
Shared `pieHighlight` and `pieFade` appearance controls select item/ring scope and peer fading. Saved `highlightedArc`/`fadedArc` styles and `highlightedPieItem` controlled identity configure geometry and paint. See [all fields, units, defaults, callbacks and snapshot behavior](/guides/charts/tools/pie-highlighting/).
## SVG data transitions
Native SVG `line`, `groupedColumn` and `groupedBar` data/series updates animate
by default for **180 ms** with **ease-out**. Initial rendering stays immediate.
Keep source IDs stable and update data to see the default motion:
```ts
import { createChart } from '@revolist/charts';
const chart = await createChart({
container: document.querySelector('#chart')!,
chartType: 'groupedColumn',
data: [{ id: 'north', sales: 20 }, { id: 'south', sales: 40 }],
mapping: { category: { field: 'id' }, series: [{ field: 'sales' }] },
});
await chart.update({ data: [{ id: 'south', sales: 25 }, { id: 'north', sales: 55 }] });
```
To opt out, pass `rendererOptions: { transitions: false }` to `createChart`.
Override motion with `rendererOptions: { transitions: { duration: 300, easing: 'linear' } }`.
Standalone Charts and Pivot share these defaults through the native Charts owner.
`createChartsRenderer()` and `createPivotChartsRenderer()` need no transition
options. Both accept `{ transitions: false }` to opt out or
`{ transitions: { duration: 300, easing: 'linear' } }` to override. Pass the Pivot
renderer to the existing standalone Pivot or grid chart configuration. Renderer
options belong to the native renderer; a custom renderer owns its own behavior.
| Option | Contract |
| --- | --- |
| `transitions` | Omitted or `{}`: 180 ms ease-out for supported SVG updates. `false` opts out. `true`, null, arrays, functions and unknown policy fields reject before mounting. |
| `duration` | Finite number from **0 through 2000 milliseconds**; default **180**. Zero bypasses animation. |
| `easing` | `'linear'` or `'ease-out'`; default `'ease-out'`, a cubic ease out. |
The renderer owns a snapshot of this policy. Mutating the original options does
not change a mounted renderer. This runtime preference is not saved in
`chart.model`; reattach it when creating a renderer after JSON restore or SVG
hydration. Initial creation, SSR and hydration paints are immediate. Update
promises resolve when the target state commits; they do not wait for animation.
### Support and fallback
| Configuration | Behavior |
| --- | --- |
| `line` with linear interpolation, ordinary SVG | Source-point path and marker positions interpolate; runs/series without retained source points fade in. |
| `groupedColumn`, `groupedBar`, ordinary SVG, no stack groups | Matched source rects move/resize; new rects grow from the clipped zero baseline and fade in. |
| Removed/null observations or removed/hidden series | Removed marks disappear immediately. Remaining source IDs can move to their new positions. No exit ghost datums are created. |
| Other types, including area/smooth/stepped lines, scatter, heatmap, polar and combinations | Immediate rendering. No chart-type morphing. |
| Batched SVG policy | Immediate rendering, including when another customization causes that policy to fall back to individual SVG. |
| WebGL or third-party renderer | No transition support is promised by this native SVG option. |
| Stacked groups, non-linear line interpolation, any `analysis` configuration | Immediate rendering. |
| Segment encoding, runtime `datumStyle`, `elements` or `onRender` customization | Immediate rendering; custom geometry is not interpolated. |
| Preview/sparkline, unavailable animation frames, reduced motion | Immediate rendering. |
Paint, authored styles, marker sizes, axes, labels, tooltips and controls commit
immediately. Gradients/patterns and ordinary per-datum bar/marker paint remain
authored styles, not animated color channels. CSS animations/transitions and
external mutation of renderer-owned geometry are outside this contract.
### Identity and interaction
Canonical **series IDs and category IDs** own interpolation, including reordered
rows/series, named axes and numeric/time X. Labels and array positions do not own
identity. Keep IDs stable for the same source observation; changed IDs enter as
new sources. If a category field is itself the identity, changing it creates a
new source identity. Line topology and null runs are those of the committed
frame: retained points move from their previous displayed coordinates, newly
appearing observations start at their target coordinates, and removed
observations disappear. Curves and cross-family topology are not morphed.
The dataset, model, hit-testing projections, accessible inspection and activation
always use the latest committed raw values/IDs. A visible moving mark activates
its committed source datum, never a fabricated intermediate value. No animation
frame invokes formatting/style callbacks or emits synthetic data events. Motion
changes presentation coordinates, not canonical data or domains.
### Cancellation and export
A newer data/series update captures the displayed coordinates by source ID and
replaces the pending animation. Rapid updates settle at the latest successful
commit. Resize, viewport/navigation/domain paints and layout changes cancel
motion and render the committed target immediately. Invalid/rejected updates
retain or restore the last accepted target according to the existing update
transaction; no rejected frame becomes saved data.
`prefers-reduced-motion: reduce` bypasses animation. If that preference becomes
active during motion, the current target settles immediately. If paints were bypassed while reduced
motion was active, the first subsequent paint establishes an immediate baseline.
Destroy cancels
the pending animation frame, unregisters the motion listener and drops geometry
state; stale callbacks cannot repaint a destroyed chart.
**SVG, PNG and PDF export settle the live animation synchronously before taking
the export snapshot.** The snapshot contains the latest committed frame at
capture time, including authored styles, and never an intermediate animation
frame. Raster/vector encoders share this policy. CSV and raw data views continue
to use canonical committed data. Await a queued update before exporting if that
update must be included. Export cancellation after capture does not restart
motion. Exporting another size scales the committed snapshot through the
existing export sizing policy.
This milestone does not add flash-on-update, exit ghosts, chart-type morphs or
animation support across every family/backend.
## Host actions and dynamic submenus
Native renderers accept runtime-only `hostActions`. The existing controls strip
adds a button, and the chart surface opens its menu with right-click,
Shift+F10/Menu key or stationary touch long-press. Enter/Space and touch tap also
open the button. Up/Down and Home/End inspect items; Right/Left enter/leave a
submenu. Escape restores focus to the invoker. Outside input and Tab close.
```ts
import { createChart, type ChartHostActionsOptions } from '@revolist/charts';
const hostActions: ChartHostActionsOptions = {
label: 'Report actions',
items: [
{ id: 'report', label: 'Open report', run(context) {
openReport(context.chartId, context.selection);
} },
{ id: 'selected', label: 'Selected reports', scope: 'selection',
children: async context => {
const reports = await loadReports({ signal: context.signal });
return reports.map(report => ({ id: report.id, label: report.name,
run: action => openReport(report.id, action.selection) }));
} },
],
onError(error, context) { showError(error, context.chartId); },
};
const chart = await createChart({ container, chartType: 'line', data, mapping,
rendererOptions: { hostActions } });
// Pivot uses createPivotChartsRenderer({ hostActions }).
```
| Option | Behavior |
| --- | --- |
| `hostActions.label?` | Accessible button/menu label; default `Chart actions`. |
| `hostActions.items` | Action array or sync/async `(context) => actions`, resolved on each opening. |
| `hostActions.onError?` | `(error, context) => void`; stale aborts are silent and observer errors are isolated. |
| Action `id`, `label` | Nonempty strings; IDs must be unique among siblings. |
| Action `disabled?` | Boolean; unavailable items cannot activate. |
| Action `scope?` | `chart` or `selection`; selection-only items are omitted without selection. |
| Action `run(context)` | Sync/async command; mutually exclusive with `children`. Menu closes before invocation. |
| Action `children` | Action array or sync/async resolver; resolves on entry, opens a submenu with Back. Back resolves the parent again. |
`ChartHostContext` contains `chartId`, `chartType`, `host` (`standalone`/`pivot`),
`scope` (`chart`/`selection` according to the captured selection), canonical
`selection`, `bindings`, `viewport`, full committed `dataset`, optional Pivot
`drillScope`, and `signal`. Each invocation gets a fresh independent snapshot.
Selection-scoped actions do not imply raw-record access or change selection mode.
The Pivot plugin supplies its active row/column drill scope; standalone Pivot
compatibility controllers have `host: 'standalone'`.
Any repaint, navigation, resize, new opening or destroy cancels pending work.
Late results cannot install a menu or invoke a command. Application callbacks
must honor the signal before their own async side effects. Touch long-press uses
550 ms and cancels after 8 px movement; its consumed click does not activate a
raw datum. `actions.host` follows the shared enabled/disabled/hidden control
policy, including inherited `actions`. Hidden disables all opening paths.
Preview has no menu. Controls stay outside SVG and are excluded from exports.
Reattach actions after restore/hydration; they are not saved in models. These
surface actions preserve Pivot's native record/drill-through behavior and existing
management controls. They do not add a general component or per-datum menu API.
See [export lifecycle hooks](/guides/charts/tools/exports/#export-lifecycle-hooks)
for using the same canonical context around exports.
## Renderer diagnostics and shared runtime options
`onDiagnostic` receives `{ code, message, chartId, chartType }` when a formatting
callback returns an invalid customization value. Codes are
`invalid-label-result`, `invalid-tooltip-result`, `invalid-element-style`,
`invalid-marker-result` and `invalid-datum-style`. The renderer falls back locally
and reports each code once per SVG lifecycle. Without an observer it uses
`console.warn`; observer exceptions are isolated. Exceptions thrown *by* a
formatter retain the normal render/error/rollback behavior, so validate external
inputs before formatting. Diagnostics are not a general performance or GPU
context-loss event stream.
```ts
const chart = await createChart({ container, chartType: 'line', data, mapping,
rendererOptions: {
className: 'revenue-chart', categorySeparator: ' / ',
onDiagnostic: diagnostic => reportChartDiagnostic(diagnostic),
},
});
```
`className` adds application classes to the SVG; `categorySeparator` controls
joined category labels. `fontFamily`, `palette`, `theme`, labels and element
styles remain runtime renderer configuration. Keep portable SVG styles
self-contained for exports. Additional scoped runtime options are documented at
their owners:
- `sankey`: [graph sorting, colors, value formatting and callbacks](/guides/charts/types/sankey/#general-graphs-configuration-and-runtime-controls).
- `pie`: [controlled highlight notification](/guides/charts/tools/pie-highlighting/).
- `markers.isVisible`: [canonical source-marker predicates](/guides/charts/tools/axes/#runtime-source-marker-predicates).
- `polarAxis`: [tick predicates and bounded placement](/guides/charts/tools/polar-axis-layout/).
- `categoryAxis.groups`, `matrixAxes.rows/columns.groups`: [category grouping](/guides/charts/tools/axes/#grouped-category-ticks-and-labels) and [heatmap runtime groups](/guides/charts/tools/axes/#runtime-heatmap-grouping-callbacks).
- `datumStyle`: [raw datum/segment style callbacks](/guides/charts/tools/datum-styling/).
- `hydrate`: [native SVG adoption](/guides/charts/server-rendering/); normally use `hydrateChart` or a framework wrapper's server result.
- `imageEncoder`, `pdfEncoder`: [image/vector export policy](/guides/charts/tools/exports/#export-options-reference).
- `hostActions`: [scoped menu lifetimes](/guides/charts/customization/#host-actions-and-dynamic-submenus).
Reattach runtime configuration after restore. Accelerated renderers can reject
or fall back for individual-DOM/CSS customizations; check [batched SVG](/guides/charts/tools/batched-svg/)
and [WebGL](/guides/charts/tools/webgl/) before combining those policies with
callbacks. Their capability matrix takes precedence over the general SVG
customization examples.
---
# Data & Configuration
URL: https://pro.rv-grid.com/guides/charts/data-and-configuration/
Source: src/content/docs/guides/charts/data-and-configuration.mdx
Description: Map rows, bind analytical datasets, style standalone charts, manage selection, update data and export results.
Start with [Installation](/guides/charts/installation/) if Charts is not yet installed.
This guide follows a chart from its first rows through configuration, updates,
interaction, exports and cleanup.
For dedicated interactive examples of drawing, navigation, linking, selection,
exports and other shared features, see [Chart tools](/guides/charts/tools/).
For calculated overlays, calendar-aware comparisons, forecasts, faceting and
performance comparisons, see the separate [Analysis guide](/guides/charts/analysis/).
These tools preserve source observations and use explicit statistical policies.
## Try configuration changes
Switch chart type, update rows, turn value labels or grid lines on and off, and
export the result. The example uses only the two registered chart definitions.
## Create a chart
Give the host an explicit height, then call the factory after the host is mounted.
No stylesheet or custom element registration is required.
```html
```
```ts
import { createLineChart } from '@revolist/charts/line';
const container = document.getElementById('revenue-chart')!;
const chart = await createLineChart({
container,
data: [
{ month: 'Jan', revenue: 120 },
{ month: 'Feb', revenue: 180 },
{ month: 'Mar', revenue: null },
],
mapping: {
category: { field: 'month' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
presentation: { title: 'Monthly revenue' },
});
// Later, when your application removes the host:
// chart.destroy();
```
Each row becomes one category. Values must be finite numbers or explicit `null`;
missing values and numeric strings are rejected. Category keys must be unique
by type and value. The converter preserves identities and does not aggregate
rows. Use `category.label` to map a separate string display-label field.
## Configure presentation and chart options
Use `presentation` for titles, legend placement, axis titles and value labels.
Use `chartOptions` for the selected type's options. Supported options and their
ranges are declared by that type's definition; an unsupported option is rejected.
```ts
await chart.update({
presentation: {
title: 'Monthly revenue',
subtitle: 'Actual revenue · € thousands',
legendPosition: 'bottom',
yAxisTitle: '€ thousands',
dataLabels: true,
},
chartOptions: { showGridLines: false },
theme: 'dark',
});
```
Presentation is replaced as a whole, so include titles you want to retain.
To edit an existing chart while keeping other presentation settings:
```ts
await chart.update({
presentation: { ...chart.model.presentation, dataLabels: true },
});
```
## Use explicit datasets and channel bindings
Rows are convenient for simple categorical charts. Use `createChartDataset` to
reuse a conversion, or supply a complete `ChartDataset` from your own analytical
pipeline. Channel bindings reference series IDs, not display labels:
```ts
import { createScatterChart } from '@revolist/charts/scatter';
const scatter = await createScatterChart({
container,
data: [
{ product: 'A', cost: 20, revenue: 60 },
{ product: 'B', cost: 35, revenue: 90 },
],
mapping: {
category: { field: 'product' },
series: [{ field: 'cost' }, { field: 'revenue' }],
},
bindings: { x: ['cost'], y: ['revenue'] },
});
scatter.destroy();
```
Hierarchy charts require hierarchy metadata. Box plots and violins require
observations or valid distribution summaries; aggregate totals cannot recover
a distribution. Financial charts need explicit OHLC channels. The
[overview's live catalog](/guides/charts/#explore-the-live-catalog) exposes a valid
dataset and bindings for every built-in type.
## Update, select and export
Updates reuse the last successful field mapping. Await an update before reading
a snapshot or exporting. Invalid updates preserve the previous chart.
```ts
await chart.update({
data: [
{ month: 'Apr', revenue: 240 },
{ month: 'May', revenue: 210 },
],
});
await chart.update({ interaction: { mode: 'select' } });
// Clicking a point now commits local selection.
await chart.clearSelection();
const csv = chart.getCSV();
await chart.download({ fileFormat: 'svg', fileName: 'monthly-revenue' });
// PNG, CSV and raster PDF downloads are also supported by the native renderer.
```
Presentation updates replace that settings object. Include titles and other
presentation settings you want to keep. `undefined` preserves a setting owner;
`{}` resets it and arrays replace it.
## Choose one type or compose several
Per-type imports such as `/line`, `/grouped-column`, `/pie` and `/box-plot` expose
a definition and a bound factory, for example `lineChart` and `createLineChart`.
A bound factory keeps its chart type fixed.
For an application that switches types, register only the definitions it needs
with `createChartFactory` from `/core`. Here is the actual setup used by the live
example above. Save it as `revenue-chart.ts`:
The returned controller supports both registered types:
```ts
import { mountRevenueChart } from './revenue-chart';
const container = document.getElementById('revenue-chart')!;
const chart = await mountRevenueChart(container);
await chart.update({ chartType: 'groupedColumn' });
await chart.update({ chartType: 'line' });
chart.destroy();
```
The package-root `createChart` includes the full built-in catalog when arbitrary
type switching is needed. Specialized charts retain their data requirements:
for example, scatter charts need channel bindings and box plots need source
observations or valid distribution summaries. Supply an explicit `ChartDataset`
for analytical data, hierarchy metadata, bindings or distributions.
`createChartDataset` from `/core` also converts rows separately for reuse.
## Customize renderer colors and locale
Every per-type factory also accepts `rendererOptions` directly. See [Chart Customization](/guides/charts/customization/) for all color, label, element-style and advanced SVG controls, including Pivot and geographic usage.
Pass a renderer to a bound factory when your application supplies theme tokens,
a palette, or label formatting. This example still loads only the line renderer:
```ts
import { createNativeChartsRenderer } from '@revolist/charts/core';
import { createLineChart, lineChart } from '@revolist/charts/line';
const chart = await createLineChart({
container,
data: [{ month: 'Jan', revenue: 120 }],
mapping: { category: { field: 'month' }, series: [{ field: 'revenue' }] },
renderer: createNativeChartsRenderer([lineChart], {
palette: ['#6647f0', '#fd7e14'],
theme: { foreground: '#172033', grid: '#e2e8f0' },
}),
localeText: { dataTable: { categoryHeader: 'Month' } },
});
chart.destroy();
```
## Themes, saved state and cleanup
Set `theme: 'light'` or `'dark'` to follow your application's theme, or pass
renderer theme tokens through a custom renderer. `theme: 'auto'` follows the nearest host `data-theme` attribute, falling back to
the system color preference. Configure labels and titles with `presentation`, per-type
settings with `chartOptions`, and translations with `localeText`.
```ts
const saved = JSON.stringify(chart.model);
chart.destroy();
const restored = await createLineChart({
container,
model: JSON.parse(saved),
});
// Dispose when your component unmounts or route changes.
restored.destroy();
```
Chart models are version 1 and contain chart state. Pass either rows with a
mapping, an explicit dataset, or a saved model. A supplied `AbortSignal` cancels
pending creation and disposes the chart when aborted. Always destroy a chart
when its host unmounts; `destroy()` is idempotent.
## Optional integrations and Pivot
- `@revolist/charts/echarts` accepts an application-supplied ECharts runtime.
- `@revolist/charts/vector-pdf` accepts a vector PDF encoder runtime.
- `@revolist/charts/geographic` provides opt-in geographic capability packs
with application-supplied providers and assets.
For linked charts built from grouped grid results, queries, drill-down or filter
synchronization, use [Pivot Charts](/guides/pivot-charts/). Pivot consumes the same
chart engine and owns those grid-specific operations.
## Advanced axes and graph data
[Axes & number formatting](/guides/charts/tools/axes/) documents numeric X (`linear`, `log`, `symlog`, `sqrt`), `level`/category-ID `data` mappings, placement/reversal/ticks, `xAxes`/`yAxes`, per-series assignments, grouped categories, measured gutters and Pivot authoring. Settings remain subject to each catalog definition's capabilities.
Use [Sankey graph data](/guides/charts/types/sankey/#general-graphs-configuration-and-runtime-controls) for general DAG inputs, sort/layout controls, colors, labels, callbacks and controlled highlights. [Heatmap navigation](/guides/charts/types/heatmap/#two-dimensional-navigation) covers independent row/column windows and navigator tracks.
For dense native line/area and combination paths,
[`chartOptions.sampling`](/guides/charts/tools/axes/#path-sampling) selects
`'auto'`, `'none'`, `'m4'`, `'minmax'`, or `'lttb'` without modifying raw data or
domains. The option persists in the model and is shared with Pivot.
For native line/area and combination categorical X axes, choose
[`axes.x.categoryPlacement`](/guides/charts/tools/axes/#category-point-and-band-placement)
(or `axes.xAxes[id].categoryPlacement`) as `'point'` or `'band'`. Omit it to keep
the chart family default; it does not change raw category IDs or values.
Candlestick and OHLC role bindings can contain parallel arrays for multiple groups. Open/high/low helpers can broadcast, while each close field uniquely owns its group and X/Y assignments. See [candlestick and OHLC collections](/guides/charts/tools/axes/#candlestick-and-ohlc-collections). Volume-price adds one distinct paired volume measure per close owner and keeps shared price/volume Y roles.
## Controller methods and creation callbacks
A `ChartRef` returned by the root, registered, or per-type factory exposes the
same lifecycle. A per-type factory restricts `chartType` updates to its type.
| Member | Purpose and lifetime |
| --- | --- |
| `model`, `getDataset()` | Independent snapshots of committed state/canonical loaded data. Mutating a snapshot does not update the chart. |
| `capabilities` | Active renderer contract, including supported types, interaction modes, image data URLs and download formats; also check the catalog type's feature support. |
| `update(patch)` | Validated asynchronous data/settings update. Nested setting owners replace rather than deep merge. Runtime callbacks and `rendererOptions` are creation options, not update patches. |
| `applyTransaction({ add?, update?, remove?, addIndex? })` | Atomic row changes by category-field identity; see [live data](/guides/charts/tools/live-data/). |
| `setSelection(targets)`, `clearSelection()` | Commit semantic source selection; see [selection](/guides/charts/tools/selection/). |
| `setRangeSelection(rangeOrNull)`, `clearRangeSelection()` | Enable/set or clear the authored axis interval without dropping retained styling; see [range selection](/guides/charts/tools/range-selection/). |
| `setDataSource(source?)`, `reloadData()` | Attach, detach or refresh the asynchronous data owner; see [async data](/guides/charts/tools/async-data/). |
| `subscribe(listener)` | Observe committed model snapshots; returns an idempotent unsubscribe. It does not synthesize a first event or report animation frames. |
| `getCSV(options?)`, `download(options?)`, `getImageDataURL(options?)` | Synchronous data serialization or asynchronous file/PNG capture; see [all export options](/guides/charts/tools/exports/#export-options-reference). |
| `focus()` | Focus the native chart's accessible inspection surface where supported. |
| `id`, `getSyncSnapshot()`, `subscribeEvents()`, `applyViewport()`, `applySyncState()` | Provider-facing linking contract. Prefer `createChartSyncGroup` to hand-written propagation; see [chart linking](/guides/charts/tools/chart-linking/). |
| `destroy()` | Idempotent cleanup of renderer, listeners, data requests and subscriptions; later operations reject. |
Creation options also include `chartId` for stable chart identity, `signal` for
creation/lifetime cancellation and `localeText` for UI/formatting. Do not pass
these through `update`. Reattach runtime values when restoring a saved model.
| Creation callback | Receives / when it runs |
| --- | --- |
| `onNodeClick(detail)` | Canonical category, series and raw value; relationship points also expose bound `channelValues`/`channelSeries`. Native user activation can include `originalEvent`. Aggregate inspection does not invent source records. |
| `onChange(model)` | A committed model snapshot after an accepted change. Notification failures do not undo a committed model or block other subscribers. |
| `onViewportChange(viewport, origin)` | Committed public windows and `'pointer'`, `'keyboard'`, `'api'` or `'calendar'` origin. |
| `onHoverChange(hover)` | Transient source-coordinate hover used by linking; separate from saved selection. |
| `onSelectionRequest(selection)` | Semantic targets requested by `interaction.mode: 'event'`; the host decides whether to commit them. |
| `onRangeSelectionStart`, `onRangeSelectionChange`, `onRangeSelectionEnd` | Axis-range gesture lifecycle with public range/origin context; see the range-selection guide. |
| `underlyingRecords` | Runtime source-record provider, not model data; see [underlying records](/guides/charts/tools/underlying-records/). |
| `onReloadData` | Inherited low-level host callback. Public factories route Retry to their own `reloadData()` source runtime and do not invoke a caller-supplied replacement. Use `dataSource.load` / `reloadData` for application refresh. |
| `onError(error)` | Runtime/notification failures. Also catch rejected creation/update/export promises; validation may throw ordinary `TypeError`/`RangeError` as well as `ChartError`. |
| `exportHooks`, `onBeforeDownload`, `onDownloaded` | Export operation lifecycle and synchronous legacy veto/notification; see [export hooks](/guides/charts/tools/exports/#export-lifecycle-hooks). |
`parseChartModel` and `cloneChartModel` from `/core` parse/own saved model values;
`assertChartDataset` validates a canonical dataset. These utilities do not mount
a chart or establish backend support: creation still validates the complete
selected type/settings/renderer combination. `chartDimensionFieldId(axis, field)`
provides the stable field ID for generated analytical-frame dimensions.
## Input budgets
`limits` persists declared input budgets, independently of rendering policy and
path sampling. Standalone creation resolves defaults to **10,000 categories,
250 series and 50,000 category × series cells**. `DEFAULT_CHART_LIMITS` exported
from `/core` is the shared base (**1,000**, **250**, **50,000**); hosts such as
Pivot can apply their own category policy. Supply all overrides explicitly when
comparing hosts or reproducing dense-renderer benchmarks.
```ts
const chart = await createChart({ container, chartType: 'scatter', dataset, bindings,
limits: { maxCategories: 100_000, maxSeries: 4, maxDataPoints: 400_000 } });
```
Only `maxCategories`, `maxSeries` and `maxDataPoints` are accepted. Values must be
finite, at least one and normalize to a safe integer; fractional values are
floored. Cell limits include null matrix cells, not just painted observations.
Distribution/histogram metadata also obeys the source validation budgets.
Exceeding a budget rejects with `ChartError.code === 'LIMIT_EXCEEDED'` and count/
limit details. Invalid budgets reject before replacing the accepted frame.
Increasing a limit does not promise a frame rate, GPU availability, one mark per
source observation or more browser memory; use the renderer performance guides
for measured host-specific limits.
## Mixed Cartesian sample layers
`columnLine` and `areaLine` also support explicitly bound bubble and histogram
layers through the existing combination owner. Sample-owned X differs from the
chart's category-owned line/column X; complete histogram summaries do not invent
loaded records. See [Cartesian bubble and histogram layers](/guides/charts/tools/cartesian-sample-layers/)
for every channel, named-axis assignment, layer order, shared tooltip, async
source, inspector and export rule. This does not add arbitrary type composition.
## Row mapping reference
`mapping` and `createChartDataset(rows, mapping)` share the same contract. One
row becomes one category; conversion does not aggregate, coerce numeric strings
or group duplicate keys.
| Mapping field | Meaning |
| --- | --- |
| `category.field` | Required source field for a unique finite string/number/boolean/null identity. Numeric `-0` and `0` share identity. Duplicate keys reject; changing a label does not change identity. |
| `category.label` | Optional **field name** whose row value is the display string, not a literal label. Omission uses the final path value. |
| `category.path` | Optional nonempty list of unique hierarchy field names, at most 128. Path values must be finite typed scalars/null. It cannot be combined with `parent`. |
| `category.parent` | Optional parent-ID field for organization hierarchy. Every nonnull referenced parent must exist; cycles and chains exceeding 128 levels reject. |
| `category.subtitle`, `category.image` | Optional source field names for organization card metadata. Nonmissing values must be strings; image loading/export follows the organization family's policy. |
| `series[].field` | Required numeric value source field. Missing/undefined/null produces a null observation; all other values must be finite numbers. |
| `series[].id` | Stable series ID; defaults to its field name. Use these IDs in bindings and overrides. |
| `series[].label` | Literal display label; defaults to its field name. |
Category IDs are generated from typed source keys, rather than display labels or
array positions. A new mapping must accompany new `data` in an update. An invalid
replacement does not replace the last accepted mapping or row set.
---
# Framework components
URL: https://pro.rv-grid.com/guides/charts/framework-components/
Source: src/content/docs/guides/charts/framework-components.mdx
Description: Render standalone charts as React, Vue, Angular or Svelte components.
Use one generic `RevoChart` component from the package for your framework:
| Framework | Package | Ready notification |
| --- | --- | --- |
| React 19 | `@revolist/react-charts` | `onReady(chart)` |
| Vue 3 | `@revolist/vue-charts` | `@ready="handler"` |
| Angular 22 | `@revolist/angular-charts` | `(ready)="handler($event)"` |
| Svelte 5 | `@revolist/svelte-charts` | `onReady(chart)` |
## Installation
The four wrapper packages are configured for public npm publishing. `@revolist/charts` remains a
private commercial package on GitHub Packages; you need licensed access to
install and use it. The wrapper imports the engine and does not bundle it.
### Before publication: local development
The wrapper packages are not published yet. Public npm package names and tarball
URLs will fail until the first release. Inside this monorepo, use workspace links
instead. From the repository root, replace `your-app` with your consumer workspace
name and choose your wrapper:
```sh
wrapper=react-charts # or vue-charts, angular-charts, svelte-charts
pnpm --filter your-app add "@revolist/$wrapper@workspace:*" "@revolist/charts@workspace:*"
pnpm build
```
The wrappers live in `packages/charts/packages/` and still import as
`@revolist/react-charts`, `@revolist/vue-charts`, `@revolist/angular-charts` or
`@revolist/svelte-charts`. Root `pnpm build` compiles them automatically.
### After publication: install from the registries
Configure your project's `.npmrc` for the engine, keeping credentials in your
environment:
```ini
@revolist:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}
```
Choose your wrapper and release version, then install both packages:
```sh
wrapper=react-charts # or vue-charts, angular-charts, svelte-charts
version=3.0.0
pnpm add "@revolist/charts@$version" \
"https://registry.npmjs.org/@revolist/$wrapper/-/$wrapper-$version.tgz"
```
The wrapper URL selects public npm while `.npmrc` routes the private engine to
GitHub Packages. Both packages use the same scope, so a plain package-name install
cannot select the two registries separately. Keep their versions matched and
commit your lockfile. CI installs also need `GITHUB_PACKAGES_TOKEN`.
## React
```ts
const options = {
chartType: 'line',
data: [{ month: 'Jan', revenue: 120 }, { month: 'Feb', revenue: 180 }],
mapping: {
category: { field: 'month' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
} satisfies ChartComponentOptions;
```
```tsx
import { RevoChart, type ChartComponentOptions } from '@revolist/react-charts';
// Use the options above in your component:
console.log(chart.model)}
style={{ height: 400 }} className="sales-chart" />
```
## Vue
```vue
console.log(chart.model)"
style="height:400px" class="sales-chart" />
```
## Angular
```ts
import { Component } from '@angular/core';
import { RevoChart, type ChartComponentOptions, type ChartRef } from '@revolist/angular-charts';
@Component({
selector: 'sales-chart',
standalone: true,
imports: [RevoChart],
template: ' ',
})
export class SalesChart {
options: ChartComponentOptions = {
chartType: 'line',
data: [{ month: 'Jan', revenue: 120 }, { month: 'Feb', revenue: 180 }],
mapping: { category: { field: 'month' }, series: [{ field: 'revenue' }] },
};
onReady(chart: ChartRef) { console.log(chart.model); }
}
```
## Svelte
```svelte
console.log(chart.model)}
style="height:400px" class="sales-chart" />
```
## Updates and lifecycle
`options` has type `Omit`. It accepts the same rows and
mapping, dataset, saved model, renderer, data source and chart settings as
`@revolist/charts`. Existing callbacks such as `onNodeClick`, `onChange` and
`onError` belong inside `options`. Callback replacements take effect without
recreating a chart.
Replace `options` and changed nested values to update the chart; do not mutate
nested objects in place. In Vue, replace the ref value; in Svelte, prefer
`$state.raw` for chart configuration. Ordinary data/settings changes update the
current chart. Source replacements use `setDataSource`. Initialization settings,
input-mode changes and removed settings recreate it to restore engine defaults.
The host defaults to width `100%` and height `300px`. Override its dimensions with
framework styles. The engine observes resizing. No stylesheet, element
registration or manual mount call is needed.
The readiness callback/event receives `ChartRef` for `download`, `getCSV`,
`getImageDataURL`, selection and transactions. It fires again after recreation;
replace any stored ref on each readiness event. The component owns destruction,
so do not destroy its ref yourself.
Creation and updates are asynchronous. Pending work is cancelled on unmount or
superseding configuration, and stale results are destroyed. Failures call
`options.onError`, or `console.error` when no error handler is supplied. Server
rendering produces an empty host by default. React, Vue and Svelte also accept
an optional `serverRender` result from `renderChartToSVG` to emit initial SVG
and hydrate it after mounting. The React entry declares `'use client'` for
Next.js App Router. See [Server rendering & hydration](/guides/charts/server-rendering/)
for the live SSR example and server setup.
## Builds and distribution
Components compile with your application; no generator or separate chart build
step is required. Releases publish the wrappers as public npm packages, with the private chart
engine as an external dependency. Public wrapper access does not grant access
to the engine. There are no separate wrapper trial packages.
## Shared adapter support
`@revolist/charts/framework` exports `ChartComponentOptions`,
`ChartComponentController` and `createChartComponentController(container, onReady)`.
The adapters use `setOptions(options)` and `destroy()` on this shared controller.
`setOptions` resolves after processing the configuration; rendering failures are
reported through `onError`. The optional `chart` getter exposes the current ref.
Most applications should use their framework component directly.
---
# Installation
URL: https://pro.rv-grid.com/guides/charts/installation/
Source: src/content/docs/guides/charts/installation.mdx
Description: Install the standalone Charts package, choose lightweight imports, and mount your first chart in a browser container.
Install `@revolist/charts` to use charts independently of Pivot, RevoGrid and Pro.
Charts is a commercial package distributed through GitHub Packages.
## Configure package access
Use your existing licensed repository and package access. If your project is not
configured yet, follow the [GitHub Packages installation guide](/guides/installation-github/).
Configure the `@revolist` scope in your project's `.npmrc`:
```ini
@revolist:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_PACKAGES_TOKEN}
```
Set `GITHUB_PACKAGES_TOKEN` in your local environment or CI secret store with
package-read access. Keep the actual token out of application code and source control.
## Install Charts
```sh
pnpm add @revolist/charts
```
The package manager installs Charts' runtime dependency automatically. You do
not need a Pivot package, grid component, framework wrapper or grid stylesheet.
Use the ESM imports below with your application's build tool.
## Choose an import
| Import | Use it for |
| --- | --- |
| `@revolist/charts/line` | A line definition and a factory fixed to that type. Every built-in type has an equivalent subpath. |
| `@revolist/charts/core` | Lightweight composition, row conversion, neutral contracts and lifecycle utilities. Register the definitions you need. |
| `@revolist/charts` | The full built-in catalog with a `createChart` factory that can switch types. |
| `@revolist/charts/catalog` | Catalog metadata and preview datasets for a gallery or chart picker. |
Start with one type or `/core` plus a small set of definitions. Import the full
catalog when your application needs all built-in renderers. The
[overview](/guides/charts/#available-chart-types) lists all per-type subpaths.
## Mount your first chart
Create a host with an explicit height:
```html
```
Run this code after the element is mounted:
```ts
import { createLineChart } from '@revolist/charts/line';
const chart = await createLineChart({
container: document.getElementById('chart')!,
data: [
{ month: 'Jan', revenue: 120 },
{ month: 'Feb', revenue: 180 },
{ month: 'Mar', revenue: 160 },
],
mapping: {
category: { field: 'month' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
presentation: { title: 'Monthly revenue' },
});
// Call when your application removes the host.
function unmount() {
chart.destroy();
}
```
In Vue or React, create the chart inside the mounted/effect lifecycle and destroy
it in cleanup. In Angular, create it after the view initializes and destroy it
with the component. Modules can be imported during server rendering, but chart
creation with `createChart` requires a DOM host. For initial server SVG, use
`renderChartToSVG`, then `hydrateChart` in the browser. See
[Server rendering & hydration](/guides/charts/server-rendering/) for a running server-rendered chart example.
## Check the result
A working integration renders a chart inside the host and adapts when the host
resizes. This live example uses the same public package APIs:
If the chart does not appear:
- Confirm the host exists, is visible, and has a non-zero width and height.
- Await the factory and handle rejected promises. Numeric strings, missing
values, duplicate category keys or unsupported bindings can reject input.
- Check the package registry and access token if installation returns an access error.
- Match the subpath's kebab-case name, such as `/grouped-column` or `/box-plot`.
## Optional integrations
These subpaths keep optional runtimes and assets out of ordinary chart imports:
| Import | What your application supplies |
| --- | --- |
| `@revolist/charts/echarts` | An initialized ECharts runtime for the adapter's supported chart types. |
| `@revolist/charts/vector-pdf` | A vector PDF encoder runtime. Native raster PDF export needs no additional PDF backend. |
| `@revolist/charts/geographic` | A geographic provider, projection and map assets for its supported types. |
Install optional backend libraries only when your application uses that
integration. Inspect the adapter's capabilities before enabling a chart type
or export format in your UI.
Next: [Data & Configuration](/guides/charts/data-and-configuration/) explains
rows, advanced datasets, styling, updates and cleanup. Or explore the
[complete live catalog](/guides/charts/#explore-the-live-catalog).
## Framework components
For declarative rendering and automatic lifecycle management, use the framework wrappers
`@revolist/react-charts`, `@revolist/vue-charts`, `@revolist/angular-charts`
or `@revolist/svelte-charts`. See [framework components](/guides/charts/framework-components/)
for examples, reactive configuration and local workspace setup before publication.
After the first release, that guide covers installing the public wrappers alongside
the private engine. These packages share a scope across two registries, so follow
the explicit tarball installation steps in that guide. No wrapper trial packages
are required.
---
# Server-side rendering (SSR)
URL: https://pro.rv-grid.com/guides/charts/server-rendering/
Source: src/content/docs/guides/charts/server-rendering.mdx
Description: Generate chart SVG on a Node.js server for HTTP responses, server-rendered pages, static sites and reports.
Render Charts to SVG directly on a **Node.js server** with
`renderChartToSVG` from `@revolist/charts/server`. Use the output in an HTTP
response, a server-rendered page, a generated report or a saved SVG file.
The server produces the chart's marks, labels and accessible description before
any browser JavaScript runs.
Server rendering is complete without a browser or UI framework. The SVG remains
readable with JavaScript disabled. Start with the server example below; optional
[browser hydration](#hydrate-in-the-browser-when-needed) is covered later for
charts that also need interactions.
## Server requirements
Follow [Installation](/guides/charts/installation/) for licensed access to
`@revolist/charts`. The server renderer's `happy-dom` dependency is installed
automatically with Charts; no additional package installation is needed.
The server entry is ESM and requires Node.js supported by Happy DOM 20 (Node.js
20 or later). Every render uses an isolated, non-executing DOM; you do not need
to install global `window` or `document` objects. Browser imports do not load
Happy DOM. This entry does not support Edge runtimes.
## Render an SVG on the server
This complete Node.js example creates a chart and saves it as `revenue.svg`:
```js
// render-chart.mjs
import { writeFile } from 'node:fs/promises';
import { renderChartToSVG } from '@revolist/charts/server';
const result = await renderChartToSVG({
chartId: 'monthly-revenue',
chartType: 'line',
width: 800,
height: 400,
theme: 'light',
data: [
{ month: 'Jan', revenue: 120 },
{ month: 'Feb', revenue: 180 },
{ month: 'Mar', revenue: 160 },
],
mapping: {
category: { field: 'month' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
presentation: { title: 'Monthly revenue' },
});
await writeFile('revenue.svg', result.svg, 'utf8');
```
Run it with `node render-chart.mjs`. The result contains:
| Property | Contents |
| --- | --- |
| `svg` | The serialized SVG, ready to save or insert into HTML. |
| `model` | The accepted chart model and dataset, suitable for transfer to browser hydration. |
| `width`, `height` | The actual SVG layout dimensions. |
You can also supply a `dataset` or a saved `model` instead of rows and mappings.
Fetch and authorize data before calling the renderer; server fetch functions and
credentials stay in your application.
## Return SVG or include it in HTML
To serve the chart directly from a route using the standard `Response` API:
```js
// In your server route, after obtaining result from renderChartToSVG:
return new Response(result.svg, {
headers: { 'Content-Type': 'image/svg+xml; charset=utf-8' },
});
```
For a server-rendered page, insert `result.svg` inside a sized host:
```js
// Server: include this markup in your HTML response.
const chartHTML = `
${result.svg}
`;
```
This is a complete server-rendered chart. No model transfer or browser script
is required. Insert only trusted renderer output as HTML, and include only data
the visitor may read.
## Build-time rendering and request-time SSR
The rendering API works in either lifecycle:
- **Build time:** render known data while generating a static site or report,
and save the SVG or include it in generated HTML.
- **Request time:** load the authorized dataset in an HTTP handler or framework
server loader, render the chart, and include its output in that response.
Both produce usable SVG before browser JavaScript runs. Hydration adds
interactions to either output; it is independent of when the SVG was generated.
In an SSR bundler, keep the Node-only `happy-dom` dependency external to the server
bundle. Next.js configuration is described in the framework section below.
## Live server-rendered example
This chart follows the same server-render → HTML → optional hydration flow.
Disable JavaScript and reload to inspect the static SVG. With JavaScript enabled,
focus a point or choose **Show forecast** to update it with illustrative values.
The status identifies when browser hydration finishes; **Show forecast** stays
disabled until then. View the document's network response or page source to find
`` before browser scripts run. Hydration consumes that
marker. The example uses the same server-rendering and browser hydration APIs
shown above.
## API and rendering contracts
| API | Purpose |
| --- | --- |
| `renderChartToSVG(options)` from `@revolist/charts/server` | Render rows and mappings, a dataset, or a saved model to a serializable SVG result. |
| `hydrateChart(options)` from `@revolist/charts` | Adopt the initial SVG in `options.container` and return the normal `ChartRef`. |
| `chart.update(patch)` | Update the hydrated chart. Rendering the server snapshot again is unnecessary. |
| `chart.destroy()` | Remove owned SVG, controls and listeners when the application removes the host. |
- Supply finite positive `width` and `height`, at most 32768. The native layout
keeps its existing minimum of 320 × 240 and rounds to pixels; returned
dimensions describe the actual SVG. Keep the host sized to avoid layout shifts.
- Set a unique `chartId`, or a unique `idPrefix` for repeated copies of a saved
model. Generated IDs and fragment references are deterministic and scoped.
- Use the same initial model, locale, renderer customization, theme and dimensions
in the browser to retain corresponding datum nodes. Later resize or font
measurement can refine layout. `theme: 'auto'` resolves light on the server;
choose an explicit theme for predictable initial output.
- Leave the hydration markers intact. A host must contain one matching server
SVG. Call hydration once, then use the returned ref; normal `createChart` calls
continue to mount fresh SVG by default.
- Fetch data before server rendering. Runtime `dataSource`, custom renderers,
containers and abort signals are not accepted by the server API. Hydration
accepts browser callbacks and an abort signal, as shown above.
- This API covers the 68 native chart types. Optional provider-backed geographic
charts, ECharts Canvas, and browser image/raster export are outside its scope.
- Insert only trusted renderer output as HTML. Transfer
plain JSON through an escaped HTML attribute or your framework's serializer,
rather than inserting unescaped JSON into an executable script. Do not pass arbitrary visitor-authored SVG as output.
## Hydrate in the browser when needed
Add hydration only when the server-rendered chart needs browser interactions.
Transfer `result.model` alongside the SVG using your framework's serializer or
an escaped HTML attribute. For a plain server HTML template:
```js
// Server: escape JSON for a double-quoted HTML attribute.
const modelAttribute = JSON.stringify(result.model)
.replaceAll('&', '&')
.replaceAll('"', '"')
.replaceAll('<', '<')
.replaceAll('>', '>');
const chartHTML = `
${result.svg}
`;
```
The transferred model is visible to the visitor. Keep credentials and server
fetch functions on the server. Then run this code in your application's browser
bundle after the host exists:
```ts
// Browser entry, bundled by your application.
import { hydrateChart } from '@revolist/charts';
const container = document.querySelector('#revenue-chart')!;
const controller = new AbortController();
const chart = await hydrateChart({
container,
model: JSON.parse(container.dataset.chartModel!),
signal: controller.signal,
onNodeClick(detail) { console.log(detail); },
});
// Later, update the existing chart through its normal API.
await chart.update({ presentation: { title: 'Updated revenue' } });
// Call from your application's unmount/navigation cleanup.
export function disposeChart() {
controller.abort();
chart.destroy();
}
```
Hydration adopts the existing SVG and reuses matching nodes while rebuilding
chart state, controls and listeners. Keep the hydration markers in the SVG and
use the same initial model, theme, locale and renderer customization. Call
hydration once per host, then use the returned `ChartRef` for updates and cleanup.
## Framework wrappers and Next.js
React, Vue and Svelte wrappers accept the optional `serverRender` result along
with `options={{ model: result.model }}` (or the framework's equivalent binding).
They include initial SVG and hydrate it after mounting. Without `serverRender`,
their existing empty-host server output remains unchanged. The Angular wrapper
retains its existing browser-mount lifecycle.
The React package declares `'use client'`, so a Next.js Server Component can
---
# Chart tools
URL: https://pro.rv-grid.com/guides/charts/tools/
Source: src/content/docs/guides/charts/tools/index.mdx
Description: Explore every shared chart tool with dedicated documentation and live examples.
The standalone Charts package owns rendering, drawing, navigation, interaction,
exports and the shared runtime APIs. Every tool below has its own guide with a
live example, public configuration, support boundaries and cleanup rules.
## Analysis tools
The dedicated [Analysis guide](/guides/charts/analysis/) explains moving averages,
regression and confidence bands, forecasts and prediction intervals, calendar
comparisons, calculated references, Pareto, facets, bullet bands and grouped
distributions. It includes shared APIs and Pivot integration. For source facts
behind a mark, see [Underlying records](/guides/charts/tools/underlying-records/).
## Explore the tools
Tool Area
{chartTools.map(([slug, label, group]) => {label} {group} )}
## Shared Charts tools and Pivot integration
The native drawing toolbar, navigation controls, range selector, legend and
hover tools work directly in Charts. Example buttons for downloads, styling and
linking call public controller APIs; applications own those buttons.
Pivot adds the chart gallery/editor, query configuration, drill-down,
grid-filter synchronization, data-view integration and discovery of open Pivot
charts in its **Link charts** dropdown. The [shared linking API](./chart-linking/)
works independently of that Pivot UI. See [Pivot Charts](/guides/pivot-charts/)
for the grid-specific workflow.
## Check support before enabling a tool
Tools are shared across the library, but support varies by chart type and
renderer. Use `getChartDefinition(type)` from `@revolist/charts/catalog` for
native type capabilities, including `navigation`, `annotations`, `appearance`,
`interactionModes` and `xScales`. Read `chart.capabilities` for backend-level
support and download formats. A Cartesian tool is not automatically available
on polar, hierarchy or geographic charts.
Start with [Installation](/guides/charts/installation/) and
[Data & Configuration](/guides/charts/data-and-configuration/) for mounting,
mapping data, updates and lifecycle. Browse the
[chart type guides](/guides/charts/#available-chart-types) for type-specific data
contracts and options.
For stable source marker/paint rules and runtime predicates, see [Datum styling and encoding](/guides/charts/tools/datum-styling/), including renderer support, precedence and export behavior.
Financial charts can opt into [Financial sessions and the trading toolbar](/guides/charts/tools/financial-sessions/) with supplied calendars, explicit ordinal anchors and timestamp-preserving source windows.
## Renderer policies and measured limits
Omission keeps SVG. Dense rendering is explicit and subject to each backend’s documented styling, inspection, fallback and export matrix.
{chartRendererReferences.map(([slug, label]) => {label} )}
---
# Line tool
URL: https://pro.rv-grid.com/guides/charts/tools/annotation-lines/
Source: src/content/docs/guides/charts/tools/annotation-lines.mdx
Description: Draw two-point chart annotations without arrowheads.
Draw two-point chart annotations without arrowheads. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Line**, drag from the first coordinate to the second, then **Save**. Select the saved line to move it or drag either endpoint. The form supports solid, dashed and dotted lines.
A two-point line can slope. Use a [reference line](../reference-lines/) for a constant axis threshold.
## API and configuration
```ts
await chart.update({ annotations: { editable: true, shapes: [{
id: 'trend-guide', kind: 'line',
from: { x: { scale: 'time', value: Date.UTC(2026, 2, 1) }, y: 100 },
to: { x: { scale: 'time', value: Date.UTC(2026, 6, 1) }, y: 135 },
lineDash: 'dash', color: '#6647f0',
}] } });
```
## Support and behavior
Requires native Cartesian annotation geometry. A drawn line is an authored visual guide; it does not calculate a regression or forecast. Its coordinates persist across zoom, with off-plot portions clipped.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts. Committed annotations appear in SVG/PNG/PDF output; CSV remains data-only. Shape IDs must be unique across shapes, references and datum notes. `{ annotations: {} }` clears annotations and editing opt-in.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Arrow tool
URL: https://pro.rv-grid.com/guides/charts/tools/arrows/
Source: src/content/docs/guides/charts/tools/arrows.mdx
Description: Point to an event or observation with a directional annotation.
Point to an event or observation with a directional annotation. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Arrow**, drag toward the location you want to emphasize, then **Save**. The arrowhead points toward the end coordinate. Select the saved arrow and move its body or either endpoint; use **Coordinates** for exact positions.
Use a [note](../notes/) when you also need an explanation.
## API and configuration
```ts
await chart.update({ annotations: { editable: true, shapes: [{
id: 'growth-arrow', kind: 'arrow',
from: { x: { scale: 'time', value: Date.UTC(2026, 2, 1) }, y: 100 },
to: { x: { scale: 'time', value: Date.UTC(2026, 6, 1) }, y: 135 },
color: '#6647f0',
}] } });
```
## Support and behavior
Requires native Cartesian annotation geometry. Arrow direction follows the authored endpoints. It does not modify source observations or create dependencies between them.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts. Committed annotations appear in SVG/PNG/PDF output; CSV remains data-only. Shape IDs must be unique across shapes, references and datum notes. `{ annotations: {} }` clears annotations and editing opt-in.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Async data & retry
URL: https://pro.rv-grid.com/guides/charts/tools/async-data/
Source: src/content/docs/guides/charts/tools/async-data.mdx
Description: Load viewport-aware data with native loading and retry feedback.
Load viewport-aware data with native loading and retry feedback. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Reload data** to simulate a refreshed source. Choose **Simulate failed request** to show native error/retry feedback, then use **Retry** to recover. The last accepted plot remains visible during loading and failure.
This example uses an in-memory asynchronous provider with a short delay. No backend or network service is required. Navigation supplies the requested viewport and pixel width.
## API and configuration
```ts
import { createChartDataset, type ChartDataSource } from '@revolist/charts/core';
// rows and mapping can come from the complete sample setup above.
const dataSource: ChartDataSource = {
revision: 1,
async load(request, { signal }) {
if (signal.aborted) throw new DOMException('Aborted', 'AbortError');
// Replace this conversion with an abortable fetch in your application.
return {
dataset: createChartDataset(rows, mapping),
fullDomain: { x: { scale: 'time', min: rows[0].date, max: rows.at(-1)!.date } },
revision: 1,
};
},
};
await chart.setDataSource(dataSource);
await chart.reloadData();
```
## Support and behavior
A response includes a valid dataset, stable whole-source `fullDomain` and a revision matching the attached source. Detail and overview requests are separate. Superseded requests are aborted and stale responses cannot overwrite newer accepted data. Source mode rejects row transactions and direct data replacement. Functions, source handles, loading state and overview stay outside saved models; reattach a source after restoration.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Named X domain metadata
Source requests retain independent `viewport.xAxes` windows. Return matching continuous bounds in `fullDomain.xAxes[id]` for each requested named axis; missing or mismatched bounds reject without committing the response. `fullDomain.y` describes the primary value axis; `fullDomain.yAxes[id]` supplies secondary/named numeric Y bounds. Requested named Y windows require matching full-domain metadata. Without provider bounds, authored/local bounds govern supported local navigation. Runtime async source acquisition requires a numeric or time X domain; categorical bar/column acquisition is not added by their value-axis support. See [named-axis contracts](/guides/charts/tools/axes/#named-x-axes).
## Histogram sources
Histograms require a complete global summary in `dataset.analyticalFrame.derived.histogram` for each detail response. The raw matrix may contain only loaded rows. Histogram requests include bin-count, aggregation and numeric-X hints; the shared runtime derives the navigator from complete statistics without a second overview request. See [every histogram summary/request field, source invariant and CSV limitation](/guides/charts/tools/axes/#complete-histogram-source-frames). `createStandalonePivotChart` also accepts `dataSource`; its returned ref exposes `setDataSource` and `reloadData` through the shared runtime.
Windowed providers can supply `fullDomain.yAxes` for secondary/named numeric Y bounds and receive the corresponding `viewport.yAxes`. Requested named windows require matching domains; see [value navigation](/guides/charts/tools/value-navigation/).
## Provider request and ownership reference
| Field | Contract |
| --- | --- |
| Source `revision` | Nonempty string or finite number identifying the attached source generation; distinct from numeric `dataset.revision`. Responses must match it. |
| Request `purpose` | `'detail'` for committed data, `'overview'` for a separate full-domain navigator. Overview data is transient and does not replace exports' detail dataset. |
| Request `viewport` | Captured primary/named X and Y windows in public coordinates. Epoch instants remain the time identity. |
| Request `bindings` | Captured public semantic channel bindings. |
| Request `pixelWidth` | Positive chart width, for provider-owned resolution decisions. |
| Request `timePolicies` | Financial session policies by X-axis identity when authored; preserve actual timestamps in returned data. |
| Request `bubbleLayers` | Sample-owned combination bubble channel/axis hints; see [sample layers](/guides/charts/tools/cartesian-sample-layers/). |
| Request `histogram`, `histograms` | Single/collection complete-bin requirements; see [histogram source frames](/guides/charts/tools/axes/#complete-histogram-source-frames). |
| Load context `signal` | Cancels superseded requests and requests belonging to a detached/destroyed chart. Pass it through to `fetch` and check it before committing application side effects. |
| Response `dataset`, `fullDomain`, `revision` | Valid accepted detail/overview frame, stable whole-source bounds and matching generation token. A loaded window is not the full domain. |
`createChart({ dataSource, ...settings })` can bootstrap without a dataset/model;
initial acquisition must succeed before creation resolves. An optional supplied
dataset/model seeds the chart while the runtime attaches the source. Do not
combine a source with row `data`/`mapping`. While attached, direct `data`,
`mapping` and `dataset` patches reject. Use `setDataSource(nextSource)` to replace
the owner, `reloadData()` to request it again, or `setDataSource(undefined)` to
detach it. Detaching retains the last accepted dataset; row transactions still
require a subsequently established row/mapping owner. Await replacements and
reloads and handle rejections. Failed/stale requests retain the last accepted
frame; transient loading, errors and overview are not saved in `chart.model`.
---
# Axes & number formatting
URL: https://pro.rv-grid.com/guides/charts/tools/axes/
Source: src/content/docs/guides/charts/tools/axes.mdx
Description: Set axis bounds, titles, scales and numeric tick formatting.
Set axis bounds, titles, scales and numeric tick formatting. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Change settings** to expand the Y range; restore it to compare the same data at the original scale. The source observations stay unchanged.
Numeric axes support linear, logarithmic, symlog and signed square-root scales. Named axes, numeric X, grouped category labels and independent bar value axes are described below; support depends on the chart family.
## API and configuration
Standalone Charts and Pivot use the same axis configuration. Existing `x`, `y` and `ySecondary` aliases remain supported.
```ts
axes: {
x: {
scale: 'log',
position: 'top',
reverse: true,
tickValues: [1, 10, 100],
title: 'Elapsed time',
unit: 'ms',
},
y: {
min: 0,
max: 100,
position: 'right',
tickCount: 5,
tickSpacing: 48,
title: 'Utilization',
unit: '%',
},
}
```
Physical horizontal axes support `top`, `bottom` and `none`; vertical axes support `left`, `right` and `none`. This includes additional logical Y value axes on horizontal bars. `none` hides axis lines, ticks, category labels and titles while retaining the plot and grid. Cartesian axis positions apply to category/time axes too. Matrix and polar positions are not supported yet. Two combination Y axes on the same side receive separate gutters.
Numeric X axes and supported value axes accept `reverse`. This mirrors both projection and inverse projection: navigation, brushes and annotations use the same raw values. Saved viewport and range `min`/`max` remain increasing. Category/time reversal is supported by ordered chart families advertising `categoryAxisReverse` in their catalog definition: navigable line/area, column/bar, combination, financial, statistical and small-multiples charts. On horizontal bars the category axis is still configured as `axes.x` and projects along screen Y. Other category families reject reversal until their geometry supports it.
Ordinary line and area families support primary Y bounds, units, formatting and numeric scales. Log axes require positive observations. Areas use the positive domain edge as their log baseline. Line/area paths are clipped to authored domains; out-of-domain marks are omitted. Bounds on normalized areas use percentage units.
Numeric tick controls:
- `tickValues`: an explicit list of up to 1000 unique finite numbers. Values outside the domain are omitted; an empty list hides numeric tick labels. This overrides automatic density and spacing controls.
- `tickCount`: desired maximum count from 2 to 1000, while retaining readable intervals. An explicit maximum step can request more ticks.
- `tickMinStep` / `tickMaxStep`: positive lower/upper limits for automatic intervals, in raw data units. Minimum step, pixel spacing and the 1000-tick safety bound take precedence when limits conflict. Log scales otherwise use decade ticks.
- `tickSpacing`: minimum pixel spacing between automatic ticks. A shorter axis shows one midpoint tick.
These controls change ticks without changing the scale domain. Compact navigator previews retain their own reduced tick density. Pivot exposes numeric position, reversal and tick controls in its axis inspector. Axis options are included in model save/restore and server-rendered SVG.
Line/area and combination charts also accept named X axes; all six grouped/stacked/normalized bar/column types accept named categorical axes. Other families retain their catalog-specific scale capabilities.
## Signed numeric scales
Numeric X on line/area and combination charts, and supported numeric value axes (line/area, combinations, grouped/stacked/normalized bars and columns, scatter/bubble/quadrant), also accept `symlog` and `sqrt`:
```ts
axes: {
x: { scale: 'symlog', constant: 10 },
y: { scale: 'sqrt', min: -100, max: 100 },
}
```
Symlog maps `sign(value) * log1p(abs(value) / constant)`. Its positive finite `constant` defaults to 1 and controls the transition near zero. A constant is valid only on a symlog axis. Square root uses the signed transformation `sign(value) * sqrt(abs(value))`, allowing negative values and zero. These match the numeric scale families described by [MUI](https://mui.com/x/react-charts/axis/) and the signed [D3 power-scale convention](https://d3js.org/d3-scale/pow).
Zoom windows, brushes, annotations, CSV and saved models retain raw data values. Symlog windows inherit the constant from their axis; the viewport does not repeat it. Reverse, numeric tick policies, full-source domains and SSR use the same projection/inverse pair. Automatic ticks follow transformed spacing on wide domains and readable raw intervals on narrow domains. Charts sync groups require matching symlog constants as well as scale types and units.
Pivot exposes both scale types and a positive symlog constant field; switching away removes the incompatible constant. Other Cartesian families still follow their declared axis capabilities.
## Named Y axes and series assignment
Ordinary line/area families, combination charts, all six grouped/stacked/normalized bar/column types and scatter/bubble/quadrant charts accept additional value axes in `axes.yAxes`. Define stable IDs, then assign series with `seriesOverrides[seriesId].yAxis`:
```ts
axes: {
y: { title: 'Revenue', unit: '$' },
ySecondary: { title: 'Units', position: 'right' },
yAxes: {
temperature: {
title: 'Temperature',
unit: '°C',
min: -40,
max: 60,
position: 'left',
},
utilization: {
title: 'Utilization',
unit: '%',
min: 0,
max: 100,
position: 'right',
},
},
},
seriesOverrides: {
units: { yAxis: 'secondary' },
temperature: { yAxis: 'temperature' },
utilization: { yAxis: 'utilization' },
},
```
Omitted assignments use `primary` (`axes.y`). `secondary` remains the alias for `axes.ySecondary`. Named vertical value axes default to the right; horizontal bar value axes default to the top. Axes on the same side receive separate gutters, in record order. Their scale, bounds, title, colors, tick policy and reversal are independent. Declared named axes remain present when a series is hidden. Use `position: 'none'` to hide decorations without removing the assignment.
Names must be nonblank strings up to 128 characters. A collection supports up to 32 named axes. The aliases `primary`, `secondary`, `x`, `y`, `ySecondary`, and prototype-related names are reserved. Unknown assignments are rejected, including assignments on hidden series; removing an axis requires clearing or reassigning its series in the same update.
Line/area stacks and normalized percentages are calculated independently for each assigned axis. Combination column stack groups are also scoped by axis ID. Canonical cell/series IDs, colors, click events and CSV columns are preserved. Shared tooltip units, error bars and analytical overlays use the assigned axis. Model save/restore and SSR retain the collection. The global Y synchronization and full-source Y metadata continue to describe the primary alias; author explicit bounds when other axes need a fixed domain across remote detail windows.
Value references can target a named axis with `axisId`; their legacy `axis` discriminator remains `primary` or `secondary`:
```ts
annotations: {
references: [
{
id: 'freezing', kind: 'line',
axis: 'primary', axisId: 'temperature',
value: 0, label: 'Freezing',
},
],
shapes: [
{
id: 'temperature-note', kind: 'note', yAxis: 'temperature',
text: 'Target',
at: { x: { scale: 'category', path: [{ field: 'month', value: 'Jan' }] }, y: 20 },
},
],
}
```
References/shapes retain their chart type's annotation capabilities. Pivot's inspector exposes configured named axes and per-series assignment for these families; definitions can be supplied through the model/API. Chart-type changes remove unsupported definitions and assignments together.
Specialized annotated time series, matrix and other families retain their existing value-axis contracts. Primary financial price options are described in [Candlestick and OHLC price axes](#candlestick-and-ohlc-price-axes). Relationship binding and navigation rules are covered in [Multiple value axes on relationship charts](#multiple-value-axes-on-relationship-charts). Horizontal bar value axes use the orientation rules in [Multiple value axes on bars and columns](#multiple-value-axes-on-bars-and-columns). Multiple X axes also apply to grouped/stacked/normalized bars and columns with categorical scales.
## Named X axes
Ordinary line/area families, combination charts and all six grouped/stacked/normalized bar/column types accept `axes.xAxes` and per-series `xAxis` assignments. The existing `axes.x` is named `primary`. Bars/columns support categorical X assignments; line/area/combinations also support their declared time/numeric scales. Named horizontal category axes default to the top; named categories on horizontal bars are vertical and default to the right. Axes sharing a side receive separate measured lanes.
```ts
axes: {
x: { scale: 'log', level: 0, title: 'Frequency' },
xAxes: {
elapsed: { scale: 'linear', level: 1, reverse: true, title: 'Elapsed' },
},
},
seriesOverrides: {
response: { xAxis: 'elapsed' },
},
navigation: { enabled: true, xAxis: 'elapsed', navigator: true },
viewport: {
x: { scale: 'log', min: 1, max: 100 },
xAxes: { elapsed: { scale: 'linear', min: 10, max: 50 } },
},
```
Each X definition selects a zero-based category `path` level. Omit `level` to use the last path member. Alternatively, `data` maps stable category IDs to raw scalar X values; missing mapping keys fall back to the selected path member. Numeric axes require finite numbers (positive for logarithmic axes), and time axes require valid UTC instants. Category/series identities and source row order remain unchanged.
X definitions independently control scale, bounds, tick policy, direction, title and placement. Series sharing a Y axis share its domain across X assignments. Area normalization and combination column stacks are scoped to their X/Y pair; a reused stack-group name does not combine values across X axes. Range-area lower helpers use their owner layer's X projection. Layer ordering and explicit `zIndex` apply across X-axis groups.
`navigation.xAxis` selects the axis for gestures, toolbar actions, navigator and calendar controls. Reset clears only that X window. `rangeSelection.xAxis` independently selects a brush axis. Hover reports the assigned raw numeric/time X value; shared tooltip rows stay within that axis group. Visible calculated references and semantic datum notes use their series' assigned window. Current-scope CSV includes the union of visible canonical rows, leaving a series cell blank outside its own window; all-scope export retains the full canonical table.
Up to 32 named definitions are accepted. Unknown assignments and reserved IDs (`primary`, `x`, and prototype-related names) are rejected. The same API is accepted by the standalone Pivot adapter. [Analytical overlays](/guides/charts/analysis/#analysis-on-named-x-axes) inherit their series' X/Y assignment. Sync groups opt into named identities using `xAxes: ['elapsed']`, preserving unselected windows; see [synchronization](/guides/charts/tools/chart-linking/). Pivot exposes named-X definitions, series assignments and navigation/brush targets in Customize; see [Pivot authoring](#pivot-x-axis-authoring).
### Annotations on named X axes
Shapes and category references select an X projection using `xAxis`. Omit it to use `primary`. The reference discriminator `axis: 'category'` means the chart's logical X axis, including numeric and time scales; value-axis references continue to use `axis`/`axisId` for Y.
```ts
annotations: {
editable: true,
shapes: [{
id: 'elapsed-note', kind: 'note', xAxis: 'elapsed',
at: { x: { scale: 'linear', value: 25 }, y: 20 },
text: 'Elapsed target',
}],
references: [{
id: 'elapsed-band', kind: 'band', axis: 'category', xAxis: 'elapsed',
from: { scale: 'linear', value: 10 },
to: { scale: 'linear', value: 30 },
}],
}
```
Raw coordinates must match the assigned X scale. Logarithmic values must be positive, time values must be valid UTC instants, and band endpoints must increase in raw units even when reversed. Category references and shapes retain semantic paths on category axes. A shape can select a Y axis separately through its existing `yAxis` property; series-anchored notes infer both axes from their target series.
The drawing editor offers an X-axis selector when multiple projections exist. Reassignment converts through the old projection and the new inverse projection, preserving the draft's visible placement. Pointer and keyboard edits retain the assignment; Save commits it and Cancel leaves the authored model unchanged. New drawings start on the configured navigation X axis. Unknown assignments and incompatible coordinate-scale updates are rejected before replacing the current frame. Model save/restore and SVG server rendering preserve the assignment. Controls and editor behavior are shared by standalone Charts and Pivot.
## Reversed category and time axes
Use `axes.x: { scale: 'category', reverse: true }` or `{ scale: 'time', reverse: true }`. Canonical dataset order, category IDs, chronological path order and exported values stay unchanged. Projection, nearest-point lookup, labels, references, navigation and range brushes mirror together. Saved category endpoint IDs remain in canonical increasing order; saved time bounds remain increasing epoch milliseconds. Navigator handle start/end mean physical left/right edges (top/bottom for vertical category brushes). For a reversed time axis, the start handle therefore carries the later timestamp. ARIA numeric limits remain increasing, and navigator formatter callbacks receive the same raw value as `aria-valuenow`. Category handle values are visual boundary ordinals, with the end boundary exclusive.
## Grouped category ticks and labels
Ordered category charts expose `categoryAxisGroups` in the chart catalog. Use `axes.x.groups` to replace flattened category labels with inner-to-outer bands. The same logical category axis projects vertically for horizontal bars; its physical placement/color/title still use `axes.y`.
```ts
axes: {
x: {
scale: 'category',
groups: [
{ level: 2, size: 32, tickSize: 6 }, // month, nearest the plot
{ level: 1, labelColor: '#334155' }, // quarter
{ level: 0, lineColor: '#64748b' }, // year, outermost
],
},
}
```
`level` is a zero-based category path index (`0..127`); omitted uses the final path member. Labels come from the matching `category.labels` entry, with the raw value as fallback. A missing path level has no text and stays available if later data supplies it. `values` can map stable category IDs to finite scalar group values (string, number, boolean or null); unmatched IDs fall back to the path rule. Equal labels with distinct typed values stay distinct. Only consecutive visible categories coalesce; parent-band identities also split child runs, preventing the same quarter in two years from merging.
Up to eight bands are supported. `size` is band thickness (`16..160` SVG pixels; defaults to 32 horizontally or 80 vertically), and `tickSize` is `0..24` (default 6). Colors use the existing hexadecimal contract. Band sizes shrink proportionally to fit short/narrow hosts and facet panels. Text that cannot fit is omitted by the shared label layout; semantic data and marks remain intact. Titles and navigation reserve the grouped gutter. Hidden axes hide the bands, and compact navigator previews omit them. Named-Y render groups draw each category axis once; small multiples get independent bands per panel.
For computed grouping, use runtime callbacks:
```ts
rendererOptions: {
categoryAxis: {
groups: [
{ getValue: (category) => category.path.at(-1) ?? null },
{ getValue: (category) => ({ value: category.path[0], label: `Year ${category.path[0]}` }) },
],
},
}
```
Runtime rows accept the same size/tick/color options and override the model's band list. Callback values must be finite scalars or `{ value, label }` pairs. Each row/category is evaluated once per axis per render, even when multiple plots share that axis. Callbacks stay outside saved models; supply them again when restoring. Serializable path/value groups are retained by save/restore and SSR. Grouping applies only to category scales; explicit groups on time/numeric or unsupported chart families are rejected. Runtime group callbacks are inactive on those families/scales. Time/numeric axis grouping remains separate from this categorical feature.
Reversal and viewport windows use the ordinary projection and canonical IDs. A partial group labels only its visible span. Group labels are decorative and never become fabricated data-click targets. Pivot exposes a grouping checkbox that starts from the current hierarchy, with editable path levels and add/remove controls. Scale/type transitions clear incompatible groups atomically.
This covers grouped band/point label rows described by [MUI grouped axes](https://mui.com/x/react-charts/axis/#grouped-axes). Supported Cartesian families use the measured gutters described below. Named X-axis assignment is supported on the line/area, combination and categorical bar/column families described above.
## Pivot X-axis authoring
For supported line/area, combination and bar/column charts, Customize → Axes exposes additional X axes with stable IDs. Add an axis, choose its scale and category path level, then assign series to it. Existing category-ID value mappings are preserved and identified in the form; “Use category path values” switches back to path projection. Scale, grouping, placement, reversal, title/unit and numeric bounds/ticks reuse the shared axis forms. Blank path levels use the automatic final path member.
Customize → Zoom and navigation selects the navigation X axis and brush X axis independently. The date-range option follows the selected navigation axis; resetting the view clears all authored windows. Removing an axis reassigns its series to primary, removes its window and incompatible shapes/references, clears its brush range, and returns its navigation target to primary. Calendar analysis that no longer targets a time axis is removed. Unrelated windows, annotations, overlays and series styles are retained. Scale/type changes and Reset appearance apply the same cleanup atomically. Direct API updates remain strict and reject incompatible definitions; this cleanup is an inspector policy. Invalid bounds/ticks stay out of the host model and can be corrected in the form.
## Measured Cartesian gutters
Grouped/stacked/normalized bar and column, line, smooth/stepped line, ordinary/stepped/stacked/normalized area, column-line, area-line, scatter, bubble and quadrant charts measure their axis text before painting marks. Browser SVG metrics include the current font, label styles, formatter text and classes. Each visible axis gets its own gutter; axes placed on the same side use cumulative measured offsets. Numeric X labels clear their baseline at large font sizes. Hidden axes reclaim space. Titles, legend and navigation keep separate reservations, and gutters in crowded/narrow hosts share at most 65% of the available frame in each dimension; the existing label-fitting policy handles text that cannot fit.
Built-in renderers explicitly opt into measurement; custom native renderers retain their single-pass behavior. The measurement pass paints axes only and is removed before interaction, exports and the final render callback. Axis/category label formatter results are cached by semantic identity within that render, so measurement and painting reuse the same result. Callback context includes `axisId` (`primary`, `secondary`, or a declared named ID), alongside the physical `axis`; named vertical value axes use `axis: 'y'` with their own `axisId`; horizontal bar value axes use `axis: 'x'`. The existing vertical secondary alias remains `axis: 'ySecondary'`. Plain result/style records are copied so a formatter can reuse a mutable result object safely. Resizing or a subsequent update measures the current frame again. Fonts should be loaded before creation, or trigger an update after changing font resources.
SSR uses deterministic font-size/text-length fallback measurements when SVG glyph metrics are unavailable. Browser and server gutters can differ with platform fonts; authored data, axes and domains remain unchanged. Preview thumbnails retain compact layout. Distribution, financial, range, matrix and facet renderers still use their existing layout policy; measured sizing across those families remains separate work.
## Bar and column value axes
The six grouped/stacked/normalized bar and column types configure their logical primary value scale through `axes.y`. It accepts linear, positive logarithmic, signed symlog and signed square-root scales, min/max, reversal, tick values/count/spacing, formatting and units. Stacks retain independent positive/negative accumulations per stack group. Normalized charts use their normalized target units for bounds, ticks and clipping; raw dataset/callback/CSV values stay unchanged.
Horizontal bars preserve their existing physical appearance contract: `axes.x` controls the visible X line/colors/title/position, while `axes.y` controls the visible Y line/colors/title/position. `axes.x.reverse` still reverses categories; `axes.y.reverse` independently reverses numeric values. Numeric bounds/scale/ticks and the primary unit come from `axes.y` in both orientations. A legacy horizontal `axes.x.unit` remains a fallback when no Y unit is authored.
```ts
// groupedBar: horizontal values, vertical categories
axes: {
x: { reverse: true, position: 'top', title: 'Revenue' },
y: { scale: 'symlog', constant: 2, min: -100, max: 100,
reverse: true, unit: 'USD', position: 'right', title: 'Region' },
}
```
Rectangular segments clip to the authored numeric domain. Partially visible bars retain their raw identities and tooltip values; fully disjoint segments produce no mark. Clipped endpoints do not produce point annotations. Logarithmic bars require positive observations and a positive domain; their zero baseline clips to the positive lower bound. Without an authored lower bound, the renderer chooses a positive floor at or below one, below the smallest positive endpoint where finite representation permits.
Grouped and stacked types expose primary/category references, point notes and drawing shapes. Stacked notes follow the actual accumulated endpoint, and raw references use the same multiplier-safe projection as marks. Normalized types retain their previous annotation capability policy. Error overlays remain grouped-only. Value limits stay separate from category viewport navigation. All six types use measured gutters with orientation-aware grouped category bands. In Pivot, horizontal bars expose **Primary value axis** and **Category axis** forms that route settings through this shared contract; category order retains its existing reversal/group controls.
### Multiple value axes on bars and columns
All six grouped/stacked/normalized bar and column types accept `axes.ySecondary`, named `axes.yAxes`, and `seriesOverrides[id].yAxis`. Each series uses its selected numeric scale, bounds, unit, reversal and ticks. Stacks and normalization stay independent for each value-axis ID and stack group. A large overflowing primary stack cannot rescale a tiny secondary stack. Public data, callbacks and exports retain their original values.
```ts
axes: {
y: { min: 0, max: 100, unit: 'kg' },
ySecondary: { min: 0, max: 5000, unit: 'USD', position: 'top' },
yAxes: { temperature: { scale: 'symlog', unit: 'C', position: 'top' } },
},
seriesOverrides: { revenue: { yAxis: 'secondary' }, heat: { yAxis: 'temperature' } },
```
This example uses horizontal bars. Additional horizontal value axes accept `top`, `bottom` or `none`, defaulting to `top`; columns accept `left`, `right` or `none`, defaulting to `right`. The primary horizontal appearance aliases described above remain unchanged. All value axes share measured orientation-aware gutters and one canonical category projection. Grouped/stacked references, shapes and notes project through the selected value axis; grouped error bounds contribute to their own assigned scale. Normalized annotation and grouped-only error policies remain unchanged.
Pivot exposes secondary and declared named value-axis forms and series assignments. Gallery changes between horizontal bars and vertical charts clear incompatible additional-axis positions while preserving scales, numeric settings, units and valid assignments. Direct API updates validate positions strictly and reject an incompatible transition atomically. Full-source and synchronized Y domain metadata continues to describe the primary value axis; additional value axes use authored or local data bounds. Named categorical assignments follow the separate category-axis contract below.
Runtime full-domain metadata is a renderer contract. The async `ChartDataSource` acquisition API currently requires numeric/time X bounds; these categorical bar/column types do not acquire remote detail windows through that API.
### Multiple category axes on bars and columns
```ts
axes: {
x: { level: 0 },
xAxes: { labels: { scale: 'category', level: 1, reverse: true, position: 'right' } },
y: { unit: 'kg' },
},
seriesOverrides: { actual: { xAxis: 'primary' }, budget: { xAxis: 'labels' } },
viewport: { xAxes: { labels: { scale: 'category', startId: dataset.categories[1].id, endId: dataset.categories[3].id } } },
navigation: { enabled: true, navigator: true, xAxis: 'labels' },
rangeSelection: { enabled: true, xAxis: 'labels' },
```
This example uses a horizontal bar chart. Named categorical axes support `left`, `right` or `none` on bars, defaulting to `right`. Columns use `top`, `bottom` or `none`, defaulting to `top`. The legacy primary horizontal appearance aliases remain unchanged: physical X settings come from `axes.x`, and physical Y appearance comes from `axes.y`. Primary logical category `level`, `data`, `groups` and `reverse` still come from `axes.x`. Additional category appearance belongs to its `axes.xAxes[id]` definition.
Each category axis uses the canonical category IDs with independent inclusive windows, reversal, labels and grouped bands. `level` selects a path member; category-ID `data` mappings override its labels. Category definitions reject time/numeric scales. Series keep canonical adjacent slots and stable callback identities; stacking and normalization are partitioned by X ID, Y ID and stack group. Value-axis domains include all their assigned X groups without summing unrelated stacks.
`navigation.xAxis` chooses gestures, toolbar, navigator and native auto-scroll; an authored target window disables auto-scroll. Reset preserves other windows. `rangeSelection.xAxis` independently targets the semantic brush. Notes, references, shapes, hover, compatible sync groups, CSV windows, saved models and SSR use the assigned projection; global value references paint once. Extra category label callbacks report their stable `axisId` and physical `axis` (`y` on horizontal bars, `x` on columns).
Pivot reuses the named-X definition, series assignment and target controls. Its horizontal-bar forms offer vertical category positions. Gallery transitions preserve compatible category mappings, groups and assignments while clearing incompatible positions or continuous-axis definitions. Direct updates validate strictly and reject before replacing the frame.
## Calendar boundaries and missing periods
Time axes choose real UTC calendar tick boundaries and responsive year, quarter,
month, date or intraday labels. Daily/monthly views omit timestamp noise. Custom
`localeText.renderer.formatTimeAxisTick` formatters receive the actual tick epoch,
the date-only flag and the adaptive default text.
```ts
await chart.update({ axes: {
x: { scale: 'time', interval: 'month', missingPeriods: 'gap' },
} });
```
`interval` sets the minimum tick cadence. With `missingPeriods: 'gap'`, it also
defines which missing periods break a line or area path. For example, February
followed by April has a gap for March. Without the policy, existing finite
observations remain connected. Explicit nulls break paths under either policy;
zero stays a real observation. No missing source rows are invented.
See [Calendar comparisons](/guides/charts/analysis/) for previous-period overlays;
a date-range preset changes the viewport rather than calculating a comparison.
## Multiple value axes on relationship charts
Scatter, bubble and quadrant charts assign each bound Y measure through `seriesOverrides[id].yAxis`. A single X or size binding still broadcasts across groups; parallel bindings retain their canonical group identity.
```ts
bindings: { x: ['cost'], y: ['revenue', 'margin'], size: ['employees'] },
axes: {
x: { scale: 'linear', title: 'Cost' },
y: { min: 0, max: 100000, unit: 'USD' },
yAxes: {
margin: { scale: 'symlog', constant: 1, min: -100, max: 100,
unit: '%', position: 'right', reverse: true, title: 'Margin' },
},
},
seriesOverrides: { margin: { yAxis: 'margin' } },
```
Map all referenced fields as numeric series. `size` applies to bubble charts; omit it for scatter and quadrant charts. `primary` selects `axes.y`, `secondary` selects `axes.ySecondary`, and other IDs select `axes.yAxes[id]`. Only fields in `bindings.y` accept non-primary X/Y assignments; assigning an X-only, size-only or unused helper field is rejected. Explicit primary helper assignments remain accepted.
Each Y axis independently supports `linear`, `log`, `symlog` and signed `sqrt`, `constant` for symlog, authored `min`/`max`, `reverse`, `left`/`right`/`none` placement, title, units, colors, notation, precision and numeric tick controls. Automatic domains use only that axis's bound observations and attached Y error bounds. Logarithmic validation follows the assigned bound measures. Declared axes paint once with measured same-side gutters, even when no group uses them.
Marks, canonical click payloads, shared tooltip rows and crosshairs, errors, regression overlays, semantic notes, numeric-X shapes and value references use the bound Y projection. References select named values with `axisId`; shapes use `yAxis`. Relationship charts support value references and notes; numeric X references use the legacy `category` discriminator as described below. Quadrant backgrounds and `pivotY` use the primary Y scale; each mark's color follows the region where it is displayed, including reversed axes.
`viewport.y`, Y gestures, full-source Y bounds and synchronized Y domains still control the primary axis. Secondary/named Y axes retain their authored or local domains. Hover on their marks synchronizes X only, so another chart never interprets their values in primary Y units. Groups may independently select numeric X axes as described below. Current-scope CSV retains a canonical row visible in any bound group and preserves its columns. Save/restore and server-rendered SVG retain Y definitions and assignments.
Pivot exposes primary, secondary and declared named Y forms. Its per-series Y selector includes only bound Y measures. Gallery transitions retain compatible bound Y assignments and clear non-primary assignments on fields becoming X/size helpers. API updates reject unknown IDs, incompatible definitions and invalid log data before replacing the current chart.
### Independent numeric X axes on relationship charts
Relationship groups now accept `axes.xAxes[id]` and `seriesOverrides[boundYId].xAxis`. Both X and Y axis assignments belong to the group's bound Y measure. The corresponding `bindings.x` measure supplies raw X observations; a single binding still broadcasts. X-only and size-only helpers cannot select non-primary axes.
```ts
bindings: { x: ['cost', 'elapsed'], y: ['revenue', 'temperature'] },
axes: {
x: { scale: 'linear', min: 0, max: 10000, unit: 'USD' },
xAxes: {
elapsed: { scale: 'log', min: 1, max: 1000, reverse: true,
position: 'top', unit: 'ms', title: 'Elapsed time' },
},
yAxes: { temperature: { min: -40, max: 60, unit: 'C' } },
},
seriesOverrides: { temperature: { xAxis: 'elapsed', yAxis: 'temperature' } },
viewport: { xAxes: { elapsed: { scale: 'log', min: 10, max: 100 } } },
navigation: { enabled: true, xAxis: 'elapsed', navigator: true },
rangeSelection: { enabled: true, xAxis: 'elapsed' },
```
Relationship X definitions default to `linear`; `log`, `symlog` (with positive `constant`) and signed `sqrt` are supported. Each X axis independently owns bounds, unit/notation/precision, reversal, numeric ticks, `top`/`bottom`/`none` placement, colors and title. Named axes default to the top and share measured gutters. Category/time scales, category `level`/`data` mappings and grouped category bands are rejected: use numeric `bindings.x`. Stable IDs, reserved-name rules and the 32-axis limit are the same as other named X collections.
Automatic domains include their assigned groups and attached X error bounds; authored bounds take precedence. Partial authored bounds resolve a finite opposite endpoint even when observations lie outside the authored edge. Navigator previews preserve independent Y scales while omitting additional axis decorations. Log validation checks their actual bound X observations and errors. `viewport.x` controls primary X; `viewport.xAxes[id]` controls that named X only. `navigation.xAxis` selects gestures, toolbar and navigator. Reset clears the selected X window and the common primary Y window while preserving other X windows. `rangeSelection.xAxis` independently targets a raw numeric brush. Declared unused X axes retain empty navigation geometry without inventing observations.
Numeric X references use `axis: 'category'`, `xAxis` and raw scaled coordinates; this legacy discriminator names the logical X axis. Shapes select `xAxis` separately from `yAxis`. Semantic notes, error bars, crosshairs, regression overlays, saved models and server-rendered SVG use the assigned projection. Regression retains its bound `xSeriesId` requirement when several X bindings broadcast to one Y field. Shared tooltip rows stay within the activated X axis. Current-scope CSV includes the union of rows visible in any group and keeps their canonical columns; it does not blank individual cells. All-scope export retains the complete canonical table.
Sync groups opt into named identities through `xAxes: ['elapsed']`, preserving unselected windows. Hover carries `xAxis` with the raw X value. Compatible scale, units and symlog constants are required. Async source requests carry named windows; `fullDomain.xAxes[id]` fixes the named source domain across detail frames, and runtime overviews use the selected group's bound X/Y fields. Primary full-domain and synchronization metadata remain independent.
Pivot adds numeric named X definitions and exposes X/Y assignment selectors only for bound Y measures. These forms omit category path controls. Gallery transitions strip category projections and helper assignments while retaining compatible numeric settings; direct API updates reject incompatible fields atomically.
## Candlestick and OHLC price axes
Candlestick and OHLC charts support a primary numeric price axis through `axes.y`:
```ts
axes: {
x: { scale: 'time', reverse: true },
y: {
scale: 'log', min: 1, max: 1000, position: 'right',
title: 'Price', unit: 'USD', reverse: false,
tickValues: [1, 10, 100, 1000],
tickFormat: { notation: 'standard', maximumFractionDigits: 2 },
},
},
navigation: { enabled: true, crosshair: true },
```
Bind the four distinct `open`, `high`, `low` and `close` measures. The existing four-series limit remains. Each complete observation normalizes its extrema across all four values. Incomplete rows remain gaps and do not contribute a price domain. Default and explicit linear scales retain padded observed extrema, without forcing a zero baseline. Category/time X navigation changes the visible observations without recomputing the full price domain.
Price axes accept `linear`, `log`, `symlog` and signed `sqrt`; symlog accepts a positive `constant` (default 1). Complete OHLC observations must be positive on log axes. `min`/`max`, `reverse`, `left`/`right`/`none` placement, `title`, `unit`, `lineColor`, `labelColor`, `titleColor`, `tickFormat.notation`/`minimumFractionDigits`/`maximumFractionDigits`, `tickValues`, `tickCount`, `tickMinStep`, `tickMaxStep` and `tickSpacing` use the shared numeric contracts above. Partial bounds resolve a finite opposite endpoint even when all observations lie outside the authored edge. Runtime full-source/synchronized primary Y bounds use the same price projection.
Candles, OHLC/HLC/high-low ticks, and line/step-line price modes clip to the price plot. Fully outside candles and out-of-domain close markers are omitted; a visible wick can remain when its body is outside the domain. Rising/falling colors still compare the original close/open values under reversal. Clipping intersections are decorative geometry, and callback payloads, canonical data, CSV values and saved observations retain raw prices. Notes and references use the same registered scale; notes anchored to out-of-domain values are not painted.
These two renderers measure styled/formatted category, tick and title text using the shared gutter owner, including resize and deterministic server-rendered fallback. Pivot exposes the primary price form automatically. Axis definitions persist in saved models and server-rendered SVG. Candlestick/OHLC price and X collections are described below. Volume-price has fixed price and volume controls described in [Volume-price pane axes](#volume-price-pane-axes). Financial X supports category/time and the numeric scales described below; navigation, brush and annotation policies share their raw projection.
## Volume-price pane axes
`volumePrice` uses two fixed roles: `axes.y` controls OHLC prices in the upper pane, and `axes.ySecondary` controls volume in the lower pane. Both panes share each paired group’s category/time/numeric X projection, including irregular dates, reversal, viewport windows and grouped category labels.
```ts
bindings: {
open: ['open'], high: ['high'], low: ['low'], close: ['close'],
volume: ['volume'],
},
axes: {
x: { scale: 'time', reverse: true, title: 'Trading session' },
y: { scale: 'log', min: 1, max: 1000, title: 'Price', unit: 'USD' },
ySecondary: {
scale: 'symlog', constant: 5, min: -5000, max: 5000,
reverse: true, position: 'left', title: 'Volume', unit: 'shares',
},
},
navigation: { enabled: true, crosshair: true, navigator: true },
```
Both value axes support `linear`, `log`, `symlog` and signed `sqrt`; `constant` for symlog; `min`/`max`; `reverse`; `left`/`right`/`none` placement; `title`, `unit`, `lineColor`, `labelColor` and `titleColor`; `tickFormat` notation/fraction digits; and `tickValues`, `tickCount`, `tickMinStep`, `tickMaxStep` and `tickSpacing`. Price axes retain padded observed-price defaults. Volume axes use a zero baseline for signed linear/symlog/sqrt columns, clipped to authored bounds; logarithmic columns use the positive domain edge. Zero volume remains zero-height, and negative volume remains signed rather than becoming a fabricated positive bar. Logarithmic price/volume axes require positive complete observations in their own pane.
The five bound roles stay distinct within each pair, and volume fields remain distinct from all price fields. Fixed roles do not require `seriesOverrides`: volume defaults to `secondary`, and OHLC fields to `primary`. Explicit assignments matching those Y roles are accepted; Y swaps and named Y axes are rejected. Price rows can remain visible when volume is missing. Pane domains use full accepted data before X-window clipping. Full-source/synchronized Y metadata continues to describe prices only; author volume bounds to keep that scale fixed across remote detail frames.
Measured gutters take the larger label width when disjoint panes use the same side. Baselines remain aligned with each pane; labels within one pane still use the shared layout policies. Long rotated titles fit their own pane and retain the full accessible tooltip. The existing pane split is bounded for short hosts; authored pane ratios and arbitrary pane composition are not provided.
X navigation, calendar controls, the semantic brush, hover and reset apply to both panes. The navigator defaults to close prices; explicit generic `navigator.series` selection remains available. Shared tooltips use the price/volume units and keep all canonical measures. CSV windows, saved models and server-rendered SVG preserve source identities and raw values.
Value references/shapes select `primary` for prices or `secondary` for volume. Notes anchored to volume use its actual endpoint. Calculated references infer the bound volume role when `axis` is omitted. X references paint one segment in each pane. New drawings in the volume pane select `secondary` automatically; the editor can switch a shape between compatible pane axes. Clipped intersections remain decorative geometry and never become source observations.
Pivot exposes primary price and secondary volume forms, plus close-owner X selectors for paired collections. Gallery transitions clear incompatible fixed-role assignments; direct API updates reject them atomically. Pivot's advanced `resolvePivotChartSeriesPresentation` and `hasPivotChartSecondaryAxisData` helpers accept optional bindings as their fourth argument so custom volume field IDs resolve correctly. Named X and paired collections with pane sizing are described below; additional Y/pane axes and secondary Y synchronization remain outside this contract.
## Range-group axes and navigation
`rangeColumn`, `rangeBar`, `rangeArea`, `confidenceBand` and `errorInterval` use the same bound-group axis contract in standalone Charts and Pivot. Parallel `bindings.low` / `bindings.high` arrays define range groups, with the existing single-channel broadcast policy. Error intervals additionally bind `actual` for their centers and retain their existing single-group channel limits. `rangeInterval` computes statistical sample intervals and is a different renderer; this section does not apply to it or `timelineRange`.
The bound **high measure selects each group's axes**. Its low and actual fields follow those projections; a helper may explicitly select the same axes, but independently moving a bound or center is rejected. Unbound helper fields cannot select non-primary axes. For example, with all four fields mapped as numeric series:
```ts
bindings: {
low: ['forecastLow', 'volumeLow'],
high: ['forecastHigh', 'volumeHigh'],
},
axes: {
y: { scale: 'log', min: 1, max: 100, title: 'Price', unit: 'USD' },
yAxes: {
volume: { scale: 'sqrt', min: 0, max: 10000,
position: 'right', title: 'Volume', unit: 'shares' },
},
},
seriesOverrides: {
volumeHigh: { yAxis: 'volume' },
},
navigation: { enabled: true, navigator: true, crosshair: true },
```
Primary `axes.y`, secondary `axes.ySecondary` and named `axes.yAxes[id]` support `linear`, `log`, `symlog` and signed `sqrt`; positive symlog `constant`; authored `min`/`max`; `reverse`; placement; `title`, `unit`, `lineColor`, `labelColor`, `titleColor`; `tickFormat.notation`, `minimumFractionDigits`, `maximumFractionDigits`; and `tickValues`, `tickCount`, `tickMinStep`, `tickMaxStep`, `tickSpacing`. Axis IDs and definition limits follow the named-axis rules above. Empty declared value axes remain visible and provide references/shapes on the primary X frame. Automatic domains include zero for non-log scales and use only complete bound observations assigned to that axis, including error centers. Log scales require all complete bound values on that projection to be positive; an incomplete range stays a gap and its unrendered helper values do not invalidate the scale.
On horizontal `rangeBar`, logical value settings still belong to `axes.y`. Its primary physical X line/title/colors/position use `axes.x`, and category appearance uses `axes.y`, matching the horizontal bar contract above. Additional value axes use physical `top`/`bottom`/`none` placement; vertical range charts use `left`/`right`/`none`. Reversal changes the numeric direction without producing negative bar dimensions. Styled category, tick and title labels use measured gutters, including deterministic server rendering.
Range groups also accept `axes.xAxes[id]` and `seriesOverrides[highId].xAxis`. Vertical range types support `category`, `time`, `linear`, `log`, `symlog` and `sqrt` X; `rangeBar` supports categorical X. Category axes support path/data mapping, grouping and reversal. Time axes share UTC interval/missing-period policies; numeric X supports bounds, transforms, units, reversal, formatting and ticks. Named X navigation and brushes select `navigation.xAxis` and `rangeSelection.xAxis` independently; windows persist in `viewport.x` / `viewport.xAxes[id]`. The navigator, toolbar, gestures, hover and reset use the selected projection. Navigation and synchronization continue to control X and primary Y metadata; independently navigable secondary/named Y windows are not provided.
Ranges normalize inverted low/high bounds for geometry. Bars and bands clip to authored domains; incomplete rows split bands. A visible interval stem may remain when a bound or center is outside the domain, but a clipped intersection does not acquire a cap or endpoint marker. Area endpoint markers follow the raw bound high measure, including inverted ranges. Activations, tooltips, CSV and saved data retain the original values and field identities. Current-scope CSV cells follow each group's X window; a broadcast helper exports the union of its group windows. Explicit navigator helper selections and synchronized peer tooltips use those same group identities; the compact overview has one shared value scale. Notes use the actual low/high/center endpoint and disappear when that endpoint is outside the domain or its range is incomplete. A broadcast helper can appear in several groups; its semantic note follows each drawn endpoint, while inferred helper formatting and calculated-reference axes use the first bound group. References and editable shapes use the selected axis; calculated references infer low/center axis assignments from their high owner when no axis is specified.
Pivot exposes the shared value/X forms and axis selectors on bound high measures. Gallery changes clear incompatible helper assignments; direct invalid updates remain atomic. Models and server-rendered SVG retain all supported axis options. Remote full-domain/sync Y metadata describes complete primary range groups only; author secondary/named bounds to keep their scales fixed across remote detail frames. Inverted-range styling, additional error-interval group channels, broader value-axis navigation remain separate capabilities.
## Statistical primary value axes
`boxPlot`, `violin` and statistical `rangeInterval` support `axes.y` as their primary numeric value axis in standalone Charts and Pivot. This statistical interval type summarizes its bound matrix observations; it is distinct from the low/high-bound `errorInterval` and other range-group charts above.
```ts
axes: {
y: {
scale: 'log', min: 1, max: 100,
reverse: true, position: 'right', title: 'Sample duration', unit: 'ms',
tickValues: [1, 10, 100],
tickFormat: { notation: 'standard', maximumFractionDigits: 2 },
},
}
```
The primary value axis accepts `linear`, `log`, `symlog` and signed `sqrt`; positive symlog `constant`; `min`/`max`; `reverse`; `left`/`right`/`none` placement; `title`, `unit`, `lineColor`, `labelColor`, `titleColor`; `tickFormat.notation`, `minimumFractionDigits`, `maximumFractionDigits`; and `tickValues`, `tickCount`, `tickMinStep`, `tickMaxStep`, `tickSpacing`. Defaults retain the existing zero-inclusive linear domains. Category labels, numeric ticks and titles now use measured gutters, with deterministic server-rendered fallback.
Box and violin domains/positivity come from selected `analyticalFrame.derived.distributions`: actual samples or valid supplied summaries. Aggregate matrix cells are not substitutes for those source distributions. Summaries retain their quartile, whisker, outlier and optional known-mean semantics; violin summaries still require density. Statistical interval domains use the selected `bindings.value` matrix observations. Hidden box/violin series do not participate in the domain or log validation. Log axes require positive source observations/extrema; a KDE's zero/negative kernel edge can be clipped to the positive display domain without invalidating otherwise positive samples.
Box plots retain `chartOptions.orientation: 'horizontal'`. Their numeric scale/bounds/reversal/ticks/unit stay in logical `axes.y`; primary physical value-axis line/title/colors/`top`/`bottom`/`none` placement use `axes.x`, while category line/title/colors/`left`/`right`/`none` placement use `axes.y`. Pivot exposes physical value/category forms in this orientation. Violin and statistical intervals remain vertical.
Bodies, whiskers, interval stems and density silhouettes clip to the authored value domain. Outside quartile boxes are omitted while visible whiskers/outliers can remain. A clipped stem intersection does not acquire a cap, median, mean or outlier marker. Visible statistical marks keep their original median/sample/summary inspection values; decorative intersections never become observations. Aggregate intervals remain read-only inspection targets rather than fabricated source activations, including a visible stem whose mean lies outside the domain. Density shapes interpolate their supplied/sample support at clipped edges without changing the source distribution.
Value/category references and editable shapes use registered statistical projections. Calculated references on box/violin series summarize their displayed per-category medians, not aggregate cells or a pooled sample population; interval calculated references retain their canonical source-field observations. Semantic notes on box/violin measures anchor to their displayed medians, including grouped box offsets, rather than to the aggregate matrix cell. Statistical interval notes use the selected source field's actual observation along the interval. Out-of-domain endpoints are not annotated. A valid derived view with no statistics in the bound selection can retain authored empty axes; the existing requirement for a valid distribution view is unchanged.
Model save/restore and server-rendered SVG preserve these options. CSV retains the canonical matrix cells and is not rewritten into statistical summaries. Pivot uses the same parser and renderer and exposes the primary, secondary and named value-axis controls and per-series assignment. Histogram collections use their own bound-measure contracts; statistical analytical overlays remain unsupported. Categorical windows, grouping/reversal, brushes, navigator and synchronization are described in [Statistical categorical navigation](#statistical-categorical-navigation). Primary runtime Y hints can fix the value scale through the shared renderer.
### Statistical value-axis collections
Box plots, violins and statistical intervals also accept `axes.ySecondary`, named `axes.yAxes`, and `seriesOverrides[id].yAxis`. All value axes accept the numeric controls listed above and compute independent domains from their assigned source data. An undeclared `secondary` assignment creates the default secondary axis; other IDs must be declared in `yAxes`. Primary remains the default assignment.
```ts
axes: {
y: { title: 'Duration', unit: 'ms' },
ySecondary: { scale: 'log', min: 1, max: 100, title: 'Rate' },
yAxes: { cost: { position: 'right', title: 'Cost', unit: '$' } },
},
seriesOverrides: {
rate: { yAxis: 'secondary' },
cost: { yAxis: 'cost' },
}
```
Box/violin distributions occupy separate slots per selected series. Violin supports multiple selected series, series colors and visibility; a single uncolored series keeps its category palette. Statistical intervals accept two or more selected measures with no three-measure maximum. They aggregate observations **within each assigned value axis**, producing one interval per category and active value-axis group. Measures on the same axis share that interval slot. A one-observation group retains its source activation; multi-observation intervals remain aggregate inspection targets. Hiding a measure removes its observations before aggregation.
For horizontal box plots, additional value axes use `top`/`bottom`/`none` positions directly on `ySecondary` and each `yAxes` entry. The primary physical aliases described above remain in effect. Vertical statistical charts use `left`/`right`/`none`. Pivot clears incompatible additional-axis positions when orientation or chart type changes, while retaining scales, bounds and assignments. Direct incompatible configuration updates are rejected atomically.
References can target `axis: 'secondary'` or a named `axisId`; editable shapes use `yAxis` and notes retain the assigned series projection. Save/restore and SVG export retain all axis collections. Runtime full-domain/synchronization Y hints apply only to primary; author secondary/named bounds when those scales must stay fixed across source frames.
## Statistical categorical navigation
Box plots, violins and statistical intervals (`rangeInterval`) support the shared categorical viewport, navigator, semantic brush, hover and synchronization contracts. This also applies to horizontal box plots, whose logical X categories run along screen Y.
```ts
axes: { x: { scale: 'category', reverse: true,
groups: [{ level: 1 }, { level: 0 }] } },
viewport: { x: { scale: 'category', startId, endId } },
navigation: { enabled: true, navigator: true,
crosshair: true, tooltipMode: 'shared' },
rangeSelection: { enabled: true },
```
`startId` / `endId` are increasing canonical category IDs in dataset order, even when `axes.x.reverse` mirrors the screen. Clear `viewport.x` to restore all categories. Toolbar, wheel/pan/pinch gestures, action policies, navigator selection and reset reuse the general navigation controls. The semantic brush keeps its own persisted `rangeSelection.range` and callbacks; it does not replace the viewport or source data. Horizontal boxes use vertical plot gestures/brush geometry while the overview remains horizontal.
`axes.x.groups` uses the existing category-band `level`, size, tick and label styling contracts. Visible category labels, group spans, statistical bodies, note anchors and inverse coordinates share one ordered projection. Box group offsets and widths retain the full selected series layout when some series have no distributions in the current window. Source extrema and value scales remain full-frame while only the visible category statistics are painted. Visible calculated-reference scope uses that same window; out-of-window semantic notes are hidden. Current-scope CSV exports visible canonical rows with their original matrix values; all-scope CSV remains unchanged.
Box/violin navigator curves and shared/peer tooltip rows use the displayed per-category medians, not aggregate matrix placeholders. `navigator.series` selects bound distribution fields (or `all`); missing statistics remain gaps. Statistical interval overviews use actual bound observation fields, with the first eligible field selected by default. Compact overview values have a shared scale and never replace canonical datasets, callbacks or exports. An explicitly supplied separate overview with no distribution records retains its authored overview values; an overview with distributions projects their medians.
`createChartSyncGroup` can synchronize category X windows/hover and primary Y domains using the existing `viewport`, `domain` and `hover` policies. Category identities and numeric units/scales must be compatible. Primary statistical snapshots use selected sample/summary extrema, including violin support for its display domain, and authored Y bounds. They never infer a statistical domain from placeholder aggregate cells. Peer statistical readouts use the receiving chart's own medians. Disconnect restores the local primary value scale through the shared sync lifecycle; navigation reset clears the current X window.
Categorical windows do not enable the viewport data-source API, which requires time or numeric X. Statistical charts now support those continuous scales and remote-detail requests as described in [Statistical time and numeric X](#statistical-time-and-numeric-x). Source distributions, counts and raw field identities retain their existing contracts.
## Statistical time and numeric X
Box plots, violins and statistical intervals also accept primary `axes.x.scale: 'time' | 'linear' | 'log' | 'symlog' | 'sqrt'`. Their default remains `category`. The mapped category key supplies the actual date or numeric X coordinate; labels and series positions are not substituted for that key. Numeric keys must be finite numbers, and log X keys must be positive. Time keys use the shared dates package's ISO date, zoned instant and epoch-millisecond contracts.
```ts
axes: {
x: { scale: 'log', min: 1, max: 100, reverse: true,
unit: 's', tickValues: [1, 10, 100] },
y: { min: 0, max: 20, unit: 'ms' },
},
viewport: { x: { scale: 'log', min: 2, max: 80 } },
navigation: { enabled: true, navigator: true, crosshair: true },
rangeSelection: { enabled: true },
```
Numeric X supports positive symlog `constant`, authored `min`/`max`, `reverse`, `unit`, `tickFormat.notation`/`minimumFractionDigits`/`maximumFractionDigits`, `tickValues`, `tickCount`, `tickMinStep`, `tickMaxStep` and `tickSpacing`. Automatic X domains use actual full-frame X keys. Time X supports `reverse` and UTC `interval: 'day' | 'week' | 'month' | 'quarter' | 'year'`, with the shared calendar/range-selector policies. Statistical marks are independent silhouettes/stems; `missingPeriods` does not add connecting segments between them. Missing dates retain their real elapsed spacing. Category group bands apply only to categorical X.
Marks, references, shape editing, hover, brushes, navigator, synchronization and CSV windows share the selected raw X projection. Authored `viewport.x` and `rangeSelection.range` use increasing raw `min`/`max`, including under reversal. Different X gaps retain different screen distances. Mark widths use the nearest distinct visible coordinate spacing so nearby distributions do not become broad ordinal slots. Value domains and statistical identities still use full accepted distributions/observations. Window clipping does not aggregate or rewrite samples, matrix cells or callback values.
Horizontal boxes support the same continuous X contracts along screen Y. Logical X scale/bounds/reversal/ticks/numeric formatting/units belong to `axes.x`, while logical Y values remain in `axes.y`. Physical appearance retains the established horizontal-box roles: `axes.y` owns the category line/title/colors and `left`/`right`/`none` placement; `axes.x` owns the primary value line/title/colors and `top`/`bottom`/`none` placement. For continuous categories, `axes.x.unit` describes numeric X and is not a primary-Y unit fallback. Author `axes.y.unit` for values. Legacy categorical fallback behavior remains unchanged. Pivot's numeric X form edits the category's logical numeric settings and physical Y appearance separately from the primary value form.
Time/numeric statistical charts can use the viewport data-source API. Detail responses must provide valid source distributions for box/violin views, plus matching raw `fullDomain.x` and optional primary `fullDomain.y` metadata. Separate overview requests retain their existing contract: distributions project to median curves, while explicitly authored overview values without distributions remain overview-only. Detail data, overview curves and source metadata stay separate. Calendar labels use `@revolist/dates` in browser and server rendering.
Save/restore and server-rendered SVG preserve continuous scale settings. Histogram collections use their own bound-measure contracts; statistical analytical overlays remain outside this contract.
## Histogram bin and metric axes
Histograms retain equal-width categorical bins by default. Set `axes.x.scale` to `linear`, `log`, `symlog` or `sqrt` to place bin boundaries on a real numeric sample axis. X comes from the bound `value` measure (or `x` fallback), not category labels/keys or the optional aggregation measure. Bin membership and count/sum/mean metrics still use equal intervals in raw sample units; a nonlinear X transform changes their displayed widths and does not apply density or area normalization.
Numeric X accepts `min`, `max`, `reverse`, `constant` (symlog only), `tickCount`, `tickValues`, `tickMinStep`, `tickMaxStep`, `tickSpacing`, `tickFormat` (`notation`, `minimumFractionDigits`, `maximumFractionDigits`), `unit`, `title`, `position: 'top' | 'bottom' | 'none'`, `lineColor`, `labelColor` and `titleColor`. Authored bounds clip existing bin rectangles without rebucketing or dropping source observations. Log X requires positive source samples; constant positive samples receive positive relative bin padding instead of crossing zero. At floating-point precision limits, indistinguishable bin boundaries are merged so every retained bin has a nonzero numeric interval and all source observations remain counted. Categorical X keeps its existing appearance controls; category reversal/grouping are not enabled for histogram bins.
Primary `axes.y` configures the aggregate metric: count, sum or mean, as selected by `chartOptions.histogramAggregation`. It accepts `scale: 'linear' | 'log' | 'symlog' | 'sqrt'`, numeric bounds, `reverse`, symlog `constant`, the same numeric tick/format/unit controls and `title`, `position: 'left' | 'right' | 'none'`, `lineColor`, `labelColor` and `titleColor`. Log Y validates nonempty bin metrics: positive counts remain valid over signed samples; nonpositive sums/means are rejected. Empty bins do not introduce zero-valued log observations. Metric baselines and endpoints clip to the authored Y domain. Large overflowing sums retain shared normalized geometry and raw-unit tick labels.
```ts
const axes = {
x: { scale: 'log', min: 1, max: 100, unit: 'ms', reverse: true },
y: { scale: 'sqrt', min: 0, max: 20, title: 'Bin count', tickValues: [0, 5, 20] },
};
```
Axes use measured gutters and preserve physical placement, save/restore and server-rendered SVG behavior. Pivot exposes the same X and primary-Y controls. CSV remains the canonical raw matrix; singleton bins retain their real observation identity and multi-observation bins remain aggregate inspections. Secondary/named histogram axes and explicit sample collections are supported; generic histogram analytical overlays remain separate.
## Histogram sample navigation
Histograms now support local X zoom/pan, a navigator and persistent numeric range selection. Bins are computed from the full accepted source matrix before window clipping, so zoom does not rebucket observations or change bin counts/sums/means. Numeric X uses its configured transform/reversal; legacy `axes.x.scale: 'category'` (including the default) keeps its presentation and navigates in linear sample units. Histogram windows and brushes always use raw numeric sample coordinates, never source category IDs or generated bin IDs.
```ts
navigation: {
enabled: true,
navigator: { height: 64, chartType: 'steppedArea', color: '#6366f1' },
gestures: { wheel: true, pan: true, pinch: true },
actions: { zoomIn: 'enabled', zoomOut: 'enabled', reset: 'enabled' },
},
viewport: { x: { scale: 'linear', min: 20, max: 100 } },
rangeSelection: { enabled: true, range: { scale: 'linear', min: 30, max: 60 } },
```
Use `navigation.enabled`, `navigator` (boolean or appearance object), X action policies (`zoomIn`, `zoomOut`, `panLeft`, `panRight`, `reset`) and wheel/pan/pinch policies as on other ordered charts. The navigator uses bin centers in the active X transform and the selected count/sum/mean metric. Its chart type, color/monochrome, height and selection style customize that derived preview; series selection chooses among the explicitly bound samples. Overflowing sum previews use finite normalized geometric units. Navigator sample selection, per-sample overrides and existing overview color settings are supported; they style derived metric paths rather than replacing source measures.
`viewport.x` accepts the effective numeric scale and raw `min`/`max`; `viewport: {}` resets it. `rangeSelection.enabled`, `range`, `style` and range callbacks operate independently of the zoom window; brushes retain raw values under reversal. Missing/null sample rows have no active sample coordinate. Current-scope CSV filters raw sample rows by an explicit viewport, while `dataScope: 'all'` retains the canonical matrix, including missing sample rows. With no viewport, CSV keeps its existing raw-source behavior even when authored axis bounds clip the plot. A partially clipped bin still reports its full accepted-source metric; its current-scope CSV can contain fewer observations than that bin total. Navigation status counts source sample rows, not bins.
Synchronization shares raw sample X windows/domains and compatible primary aggregate-Y domains; default category presentation publishes linear X. The Y domain uses bin metrics rather than raw samples or helper weights. Overflowed sums whose Y domain is not representable do not publish a finite Y-domain claim. Aggregate-aware peer hover, shared tooltips and X crosshairs are described in [Histogram shared hover and crosshair](#histogram-shared-hover-and-crosshair). Pivot exposes zoom, navigator, brush and readout controls. Save/restore and SVG export preserve X windows and selected ranges.
Independent Y windows/gestures, rectangle zoom, categorical-ID windows and generic histogram analytical overlays remain unsupported. Named histogram axes are supported by the collection contract. Remote histogram loading is supported through [complete source frames](#complete-histogram-source-frames); raw samples and complete bins are available through the [histogram data view](#histogram-sample-and-bin-data-views).
## Histogram shared hover and crosshair
Set `navigation.tooltipMode: 'shared'` or `navigation.crosshair: true` for aggregate-aware histogram readouts on pointer motion and keyboard focus. The containing nonempty bin supplies its full accepted-source count, sample mean and configured count/sum/mean metric, including when the visible window clips that bin. Bins include their lower boundary and exclude their upper boundary; the final bin includes its upper edge. Empty intervals and samples outside the visible X domain clear the readout. A mean uses nonmissing values from the aggregation measure; a bin with no such values has no mean mark/readout and remains a gap in its navigator. Its X samples remain in the canonical data. These rules apply to legacy category presentation, all four numeric transforms, reversal and clipped windows.
The X crosshair follows the raw sample coordinate under the pointer. Keyboard inspection anchors it at the center of the bin's visible projected interval. Singleton click/Enter activation still identifies its genuine source sample; repeated bins remain inspection-only. With both options off, ordinary datum/aggregate inspection keeps its existing behavior. `rendererOptions.tooltip: false` suppresses tooltip content while retaining an enabled crosshair and semantic X hover.
Local tooltip formatters receive optional `context.aggregate` (`ChartHistogramTooltip`, or `PivotChartHistogramTooltip` through the Pivot facade):
| Field | Meaning |
| --- | --- |
| `kind` | Always `'histogram'`. |
| `binIndex` | Index in the full bin partition, before viewport clipping. |
| `start`, `end` | Raw sample boundaries. |
| `count` | Number of accepted source sample rows in the bin. |
| `sampleMean` | Mean of the source X samples, independently of the aggregation measure. |
| `aggregation` | `'count'`, `'sum'` or `'mean'`. |
| `scaledValue`, `multiplier` | The computed metric is conceptually their product; both fields remain finite for overflowed sums. |
`context.value` is the raw aggregate metric when representable, otherwise `null`. `context.displayValue` uses primary Y tick formatting and units, and formats an unrepresentable sum as a finite scaled value times a scientific multiplier. `category` is a genuine representative source member from the bin; `series` is the owning aggregation measure (or the sample measure for count). They provide provenance rather than describing the aggregate as one source cell. `drillable` is `false` and `sharedItems` is omitted. Mutating aggregate metadata or the supplied default rows does not change subsequent inspections.
```ts
rendererOptions: {
tooltip: {
formatter(context, defaults) {
if (!context.aggregate) return defaults;
return {
...defaults,
footer: `${context.aggregate.count} source samples`,
};
},
},
},
navigation: { tooltipMode: 'shared', crosshair: true },
```
The existing tooltip class, delay, follow-pointer, result validation and suppression policies apply. Returning `null` hides the local tooltip and crosshair. Aggregate summaries and metric names use the existing locale hooks (`renderer.aggregateSummary` and `chartOptions.selectValues.count/sum/mean`); textual content is escaped.
Compatible synchronized charts share raw X only for histogram hover. A histogram peer looks up its own bin and computes its own metric at that sample coordinate, even with different bin counts or after replacement. Incoming peer Y values do not replace histogram metrics. Peer readouts use the peer's default localized summary, rather than invoking its local tooltip formatter. Clearing hover or destroying/replacing a chart clears the corresponding resources. Pivot exposes the existing shared-tooltip and crosshair controls. Shared hover does not change CSV, saved source cells or source activation payloads. Remote loading uses [complete histogram source frames](#complete-histogram-source-frames).
## Complete histogram source frames
A singleton primary-axis histogram `dataSource` returns complete bin statistics in `dataset.analyticalFrame.derived.histogram`, alongside its loaded raw matrix rows. Explicit collections and nonprimary assignments use `derived.histograms` and plural source hints as described in the collection guide. Raw detail rows can cover one window or be empty; they are never used to estimate the missing bins. This typed `ChartHistogramSummary` is also accepted as precomputed client data and survives save/restore and server-rendered SVG. Pivot exports the same contract as `PivotChartHistogramSummary`.
The summary owns its partition and all three aggregation statistics. Changing `chartOptions.histogramAggregation` selects count/sum/mean from those statistics immediately. `chartOptions.binCount` is a source query hint for precomputed data, not a client rebucketing command. While a source request is pending or fails, the last accepted complete partition stays visible. Without an attached source, changing the hint does not alter a supplied summary's boundaries.
| Summary field | Contract |
| --- | --- |
| `kind`, `scope` | Exactly `'histogram'` and `'all'`. |
| `sampleSeriesId`, `valueSeriesId` | Existing dataset series IDs matching bound `value` (or `x` fallback) and `y` (or sample fallback). |
| `requestedBinCount` | The source's applied `binCount` hint, integer `0..256`; `0` delegates automatic choice to the source. |
| `aggregation` | Applied request mode: `'count'`, `'sum'` or `'mean'`. All three statistics are still supplied. |
| `boundaries` | Two to 257 finite, strictly increasing raw sample edges, giving at most 256 contiguous bins. Equal and irregular precomputed intervals are supported. |
| `multiplier` | Optional common finite sum multiplier, at least `1`; omitted means `1`. |
| `bins` | One entry per boundary interval, in the same order. A nonzero hint cannot produce more bins than requested; precision coalescing can produce fewer. |
Each bin contains:
| Bin field | Contract |
| --- | --- |
| `count` | Nonnegative safe integer X sample count. The sum of all counts must also be safe. |
| `sampleMean` | Finite mean of accepted X samples within the interval; `null` for an empty bin. |
| `valueCount` | Number of nonmissing aggregation values among those X samples, from `0` through `count`. |
| `sum` | Finite scaled sum: the conceptual raw sum is `sum * multiplier`. With no contributors, it must be `0`. |
| `mean` | Finite raw aggregation mean, or `null` when `valueCount` is zero. A missing mean stays a gap, rather than becoming zero. |
| `representative` | Genuine source `ChartCategory`, required for nonempty bins and omitted for empty bins. It may describe a source member outside the loaded matrix. Representatives must have distinct IDs and satisfy normal category/hierarchy validation. |
```ts
// `dataset` contains only loaded raw rows, with sample/weight series headers.
// These category descriptors come from genuine members of the complete source.
dataset.analyticalFrame.derived = {
histogram: {
kind: 'histogram', scope: 'all',
sampleSeriesId: 'sample', valueSeriesId: 'weight',
requestedBinCount: 2, aggregation: 'sum',
boundaries: [1, 50.5, 100], multiplier: 1,
bins: [
{ count: 5, sampleMean: 16.6, valueCount: 5, sum: 15, mean: 3,
representative: firstSourceCategory },
{ count: 1, sampleMean: 100, valueCount: 1, sum: 6, mean: 6,
representative: lastSourceCategory },
],
},
};
```
Loaded X samples must fit the partition and cannot outnumber its global counts. Known loaded missing/nonmissing values constrain `valueCount`. A globally singleton bin activates a source datum only when its genuine sole sample is loaded; otherwise it remains aggregate inspection. A representative does not manufacture a raw row or an activation target. Metadata is bounded by `maxDataPoints`; global counts themselves do not allocate rows. Series subsetting drops a summary when either measure dependency is removed.
For histograms only, `ChartDataSourceRequest.histogram` adds a `ChartHistogramSourceRequest` (`PivotChartHistogramSourceRequest` in Pivot):
| Request field | Meaning |
| --- | --- |
| `binCount` | Effective numeric hint, `0` for automatic; explicit boundaries set this to their interval count. |
| `boundaries` | Optional raw sample edges from `chartOptions.histogramBinBoundaries`; responses must match exactly. |
| `aggregation` | Current requested count/sum/mean mode. |
| `scale` | Effective numeric X scale. Legacy category presentation requests `'linear'`. |
| `constant` | Effective symlog constant; included only for symlog. |
The existing `purpose`, `viewport`, `bindings` and `pixelWidth` fields remain unchanged. A custom `createChartDataSourceController` supplies the model's histogram `chartOptions` through `ChartDataSourceContext.chartOptions`; the chart runtimes do this automatically. Histogram responses must match captured field IDs, bin-count and aggregation hints, and return `fullDomain.x` in the requested scale with endpoints exactly matching the outer boundaries. Log X requires positive global boundaries. Binding/transform changes that invalidate a stored summary need an atomic matching dataset update; the client does not reinterpret another measure's statistics.
Within a source revision, global statistics, representatives and partition remain stable for the same fields, bin-count and X-transform request, across detail windows, reloads, pixel widths and aggregation modes. Advance `dataSource.revision` when that source truth changes. The controller checks retained signatures, validates before commit, and discards canceled late responses even when the provider ignores `AbortSignal`. Errors retain the last accepted frame. Cache/signature storage stays bounded.
`fullDomain.y` is optional. It applies only to the summary's applied aggregation; another selected mode derives its Y domain from its own global statistics. Authored bounds still win over source hints, while transient synchronization retains its usual precedence. The complete view already supplies navigator statistics, so chart runtimes issue detail requests without a separate histogram overview load. Direct controller overview responses must satisfy the same complete-view contract.
CSV and data tables retain **loaded raw rows only**, including `dataScope: 'all'`; that option does not fetch or reconstruct unloaded source records from totals. Saved models retain loaded rows plus the complete summary. The navigation status distinguishes visible/total bin intervals, visible loaded samples and global source count; customize it with `localeText.navigation.histogramWindowSummary(visibleBins, totalBins, loadedSamples, totalSamples)`.
Both standalone Charts and `createStandalonePivotChart` accept `dataSource`. The standalone Pivot adapter delegates source work to the shared Charts runtime and adds no grid or Pivot query. Their refs expose `setDataSource(source | undefined)` and `reloadData()`; source handles/functions are not serialized. Detaching a source keeps its last accepted summary as ordinary precomputed data. Mounted Pivot chart refs use the same runtime source lifecycle.
Irregular intervals keep raw count/sum/mean heights by default; enable [area encoding](#histogram-area-encoding) to divide heights by their raw widths.
## Explicit histogram intervals
Set `chartOptions.histogramBinBoundaries` to an array of **2..257 finite, strictly increasing raw sample edges**. This defines at most 256 contiguous bins, including irregular widths, and overrides `binCount`. Omit the option or use `[]` to restore automatic/count-based binning. Duplicate, descending, nonfinite or single-edge lists are rejected. Explicit edges must contain every finite bound X sample; out-of-range samples cause an atomic update error rather than disappearing from the totals. Logarithmic X also requires positive edges.
```ts
chartOptions: {
histogramBinBoundaries: [0, 2, 10, 100],
histogramAggregation: 'sum',
},
```
Intervals include their lower edge and exclude their upper edge, except the final interval includes the final endpoint. Counts, sums and means retain their normal raw metric units; unequal-width area/density normalization is not implied. Empty intervals retain their place in the partition. Missing X samples stay in the raw dataset but contribute to no bin. With no loaded samples, explicit edges still define an empty partition. Preview and navigator projections preserve the supplied partition. Numeric axes paint the actual interval widths; legacy category presentation retains its existing bin labels and sample-coordinate navigation.
Bounds, zoom and brushes clip this stable partition without rebucketing. CSV continues to export canonical raw rows, with current-scope filtering by the raw sample window. Save/restore and SVG rendering preserve the edges. Standalone Pivot accepts the same option. Its **Bin boundaries** appearance field edits a comma-separated list, validates before committing, and supports empty/reset for automatic binning. Labels and help text are localized by `localeText.chartOptions.labels.histogramBinBoundaries` and `localeText.chartOptions.descriptions.histogramBinBoundaries`. Schema consumers can recognize its `ChartNumberListOptionDefinition` with `control: 'numberList'`, array `defaultValue`, and the existing `key` field; its empty default means automatic binning.
For a histogram `dataSource`, the request includes optional `histogram.boundaries` when explicit edges are configured. Its effective `histogram.binCount` is then `boundaries.length - 1`, regardless of the authored automatic `binCount`. Responses must return exactly those edges and that effective requested count. They remain part of cache/cancellation and stable-source identity. While a new request is pending or fails, the last accepted complete summary remains visible. Precomputed summaries continue to own their own partition; without an attached source, this option does not reinterpret or rebucket their statistics. See [complete source frames](#complete-histogram-source-frames) for every summary/request field.
## Histogram area encoding
Set `chartOptions.histogramArea: true` to divide each bin's count, sum or mean by its **full raw sample interval width**. The default is `false`, preserving ordinary metric heights. This works with automatic bins, explicit `histogramBinBoundaries`, and complete precomputed/source summaries. It does not change the partition or raw statistics.
```ts
chartOptions: {
histogramBinBoundaries: [0, 2, 10, 100],
histogramAggregation: 'sum',
histogramArea: true,
},
axes: { x: { scale: 'linear', unit: 's' }, y: { unit: 'kg' } },
```
A sum of `12 kg` over a `4 s` interval has height `3 kg / s`. On linear X/Y axes with the full bins visible, rectangle areas are proportional to their aggregates. Area mode removes bar gaps and corner rounding to preserve that relationship. Nonlinear transforms and viewport clipping change visible screen areas; density always divides by the original **raw width**, never a transformed or clipped width. This is metric density, not probability normalization: signed sums/means remain signed, counts keep their original totals, and a missing mean remains a gap.
Y bounds/ticks/transforms/reversal and navigator values use the density heights. `axes.y.unit` remains the raw aggregate unit; displayed density units are derived as `Y / X`, with `1` for an unspecified numerator and symbolic `x` for an unspecified sample unit. In the example, Y ticks show `kg / s`. The sync snapshot uses that derived unit, keeping density domains distinct from raw aggregate domains. Authored Y bounds are interpreted in density units. Source `fullDomain.y` describes the raw aggregation and is ignored in area mode; density domains derive from all complete bin statistics, with authored bounds and compatible transient sync retaining precedence. Toggling area encoding is client-side and does not require a different source query or source revision.
Tooltips retain the original aggregate value, `scaledValue` and `multiplier`, including existing overflow metadata. Area mode adds optional **`ChartHistogramTooltip.density: number`** (also `PivotChartHistogramTooltip.density`) with the finite plotted height. `ChartTooltipContext.value` and `displayValue` continue to describe the raw aggregate. Default bin tooltips add a metric-per-sample row, even without shared/crosshair mode; customize its label with **`localeText.renderer.histogramDensity`**. Genuine singleton click/Enter activation still carries the original observation; aggregate bins remain inspectable without invented rows.
Pivot exposes the same boolean as **Encode aggregate by bin area**. Localize it through `localeText.chartOptions.labels.histogramArea` and `localeText.chartOptions.descriptions.histogramArea`. Save/restore and SVG/image exports preserve area encoding; raw datasets and CSV remain unchanged. Ratio arithmetic handles overflowing finite interval spans and normalized sums when the resulting density is representable. If a nonzero density overflows or underflows the finite JavaScript number range, the option/data update fails atomically instead of painting a false finite height. Raw totals can still use the existing scaled-sum metadata when area mode is off.
## Histogram sample and bin data views
Use `createChartHistogramDataView` from `@revolist/charts` or `@revolist/charts/core` to inspect the same partition and aggregates as the renderer, without reading SVG or fabricating raw rows. It accepts the histogram model's `chartType`, `dataset`, `bindings`, `chartOptions`, `axes` and `viewport`; `chartType` must be `'histogram'`. Normal dataset, option, binding, axis and viewport validation applies. The returned snapshot owns its arrays and category descriptors, so changing it does not change the chart.
```ts
import { createChartHistogramDataView } from '@revolist/charts';
const current = createChartHistogramDataView(chart.model);
const loaded = createChartHistogramDataView(chart.model, { dataScope: 'all' });
console.table(current.samples);
console.table(loaded.bins);
```
The optional second argument is `ChartHistogramDataViewOptions`: **`dataScope: 'current' | 'all'`**, default `'current'`. Current scope filters samples by the committed raw X viewport and includes bins intersecting that window, with their full statistics. Raw samples at a window endpoint are included; the containing bin is retained even if its rectangle has no visible width at that endpoint. Without an explicit viewport, all loaded rows/bins remain in the view; authored axis bounds alone clip painting. All scope includes every loaded matrix row and every accepted bin. Neither scope loads or reconstructs missing source records. These sample-window rules are shared with raw CSV, including explicit partitions extending beyond loaded sample extrema.
`ChartHistogramDataViewInput` is the model-field input type. `ChartHistogramDataView` contains:
| Field | Meaning |
| --- | --- |
| `dataScope` | Effective current/all scope. |
| `sampleSeriesId`, `valueSeriesId` | Bound sample and aggregation series IDs; absent when there are no corresponding series headers. |
| `aggregation`, `area` | Effective count/sum/mean and area-encoding flag. |
| `loadedRowCount` | All loaded canonical rows, including missing X samples, before scope filtering. |
| `loadedSampleCount` | All loaded finite bound X samples, before scope filtering. |
| `sourceSampleCount` | Complete accepted sample count from bins, including unloaded source members. |
| `samples` | Scope-selected `ChartHistogramSampleRow` entries in canonical matrix order. |
| `bins` | Scope-selected `ChartHistogramBinRow` entries in accepted partition order. |
Each `ChartHistogramSampleRow` has **`category`** (genuine source descriptor), **`categoryIndex`** (original matrix row index, never renumbered), **`sample`** (raw X or `null`), **`value`** (raw bound aggregation value or `null`) and **`binIndex`** (zero-based accepted bin index, or `null` for missing X). A row with missing X remains inspectable in the no-window/all view, but contributes to no bin. Row values are not replaced with counts or densities.
Each `ChartHistogramBinRow` has:
| Field | Meaning |
| --- | --- |
| `index`, `start`, `end` | Stable zero-based bin index and full raw edges. |
| `endInclusive` | True only for the final accepted bin. Every bin includes its start; other ends are exclusive. |
| `count`, `loadedCount` | Complete source sample count and number of loaded finite samples in the full bin, before window filtering. |
| `sampleMean` | Complete X mean, or `null` for an empty bin. |
| `valueCount` | Complete nonmissing aggregation contributor count, independent of the selected aggregation mode. |
| `scaledValue`, `multiplier` | Raw aggregate represented as their product, including overflowing sums. |
| `value` | Finite raw aggregate; `null` for an unrepresentable sum or a mean with no contributors. Use aggregation/contributor/scaled metadata to distinguish those cases. |
| `density` | Optional finite plotted height with area encoding; absent when area is off or the mean is missing. |
Complete source summaries therefore expose global bin counts/metrics beside the loaded rows, including when the loaded matrix is empty. They do not manufacture unloaded samples or activation targets. Density, signed/missing values and scaled sums retain the [area encoding](#histogram-area-encoding) and [complete source frame](#complete-histogram-source-frames) contracts.
Pivot exports the same helper as **`createPivotChartHistogramDataView`**, with aliases **`PivotChartHistogramDataViewInput`**, **`PivotChartHistogramDataViewOptions`**, **`PivotChartHistogramDataView`**, **`PivotChartHistogramSampleRow`** and **`PivotChartHistogramBinRow`**, through both `@revolist/pivot` and `@revolist/pivot/charts`. The adapter uses the shared Charts snapshot directly.
In Pivot's histogram **Data** tab, **Samples** retains the native read-only grid, existing sorting/filtering/selection configuration and canonical measure columns. It adds numeric **Bin**, **Bin start** and **Bin end** columns; displayed bin numbers are one-based. **Bins** shows a bounded, scrollable table of complete intervals, source/loaded counts, means, contributors, raw/scaled aggregate and optional density. Switching views does not change the chart model, dataset or CSV. Bins are an inspection table, not additional source rows. The existing `dataView` UI policy hides/disables this view and its controls; sample-grid settings still use `dataGrid`.
Localize the view through **`localeText.histogramData`**. Its string fields are **`viewLabel`**, **`samples`**, **`bins`**, **`bin`**, **`start`**, **`end`**, **`count`**, **`loadedCount`**, **`sampleMean`**, **`valueCount`**, **`metric`**, **`density`** and **`interval`**. Its **`summary(loadedRows, loadedSamples, sourceSamples)`** callback reports the full loaded-frame/global totals independently of the current scope. Existing `dataTable` captions, empty-value/sort labels and renderer number/axis formatters still apply. UI view choice, sorting and filtering are local presentation state; save/restore retains the chart settings and data rather than table state.
## Heatmap matrix axis directions
Heatmap X denotes series/columns and Y denotes categories/rows. Their categorical direction settings live in **`axes.matrix`**, independently of numeric value-axis definitions:
```ts
axes: {
matrix: {
columns: { reverse: true },
rows: { reverse: true },
},
},
```
`ChartMatrixAxes` has optional **`columns`** and **`rows`** objects. Each is a `ChartMatrixAxis` with optional boolean **`reverse`**, default `false`. Columns normally run left to right and rows top to bottom; each `true` mirrors that dimension's projection. The settings apply to cells, category/series labels, aggregate bucket extents, hover/crosshair points, pointer gestures and the full-matrix navigator. Dense/partial buckets keep their original memberships, counts, means and source provenance.
Viewport `x`/`y` still uses inclusive canonical series/category IDs with `scale: 'category'`. Reversal never reorders matrix cells, renumbers IDs, changes CSV/data-table order or reverses saved endpoints. Missing endpoints remain dormant and reactivate when their IDs return. Sync groups retain canonical identities, so compatible peers may use different directions. Navigator start/end handles retain their semantic endpoint roles as physical positions mirror; their keyboard increments follow canonical row/column order. Pointer dragging uses the reversed projection to resolve those same IDs.
Standalone Charts and Pivot share the parser and renderer. Save/restore, SVG/image export and navigator previews preserve directions. Pivot's **Customize → Axes and scales** exposes **Reverse heatmap columns** and **Reverse heatmap rows**. Locale strings are `localeText.chartOptions.matrixColumnsReverse` and `localeText.chartOptions.matrixRowsReverse`; UI policy keys are `axes.matrix.columns.reverse` and `axes.matrix.rows.reverse`. Gallery changes to another chart family clear matrix-only settings. Pivot exports `PivotChartMatrixAxes` and `PivotChartMatrixAxis` aliases through its root and charts facade.
This matrix configuration is currently heatmap-only. It also supports physical placement, titles, colors and measured gutters as described below. Numeric row/column scales/bounds, additional matrix axes remain separate axis work. The cell-value color scale remains independent of row/column direction. Calendar heatmaps retain their calendar layout. General semantic XY brushing also remains separate from viewport navigation.
## Heatmap axis appearance and sizing
Configure physical heatmap axes through `axes.matrix.columns` and `axes.matrix.rows`. Each `ChartMatrixAxis` supports these fields, also exposed through the equivalent Pivot types:
| Field | Meaning and default |
| --- | --- |
| `reverse` | Independent categorical direction; default `false`. |
| `groups` | Optional inner-to-outer categorical group bands; omitted/empty retains leaf captions. See [grouped heatmap axes](#grouped-heatmap-axes). |
| `position` | Columns: `'top'` (default), `'bottom'` or `'none'`. Rows: `'left'` (default), `'right'` or `'none'`. Physical placement is independent of reversal. |
| `title` | Optional string, at most 4096 characters. Overrides the corresponding legacy `presentation.xAxisTitle`/`presentation.yAxisTitle` fallback. An explicit empty string suppresses that fallback. |
| `lineColor` | Shared authored hex-color vocabulary; defaults to the theme's muted axis color. |
| `labelColor` | Row/column label color in the same hex vocabulary; defaults to theme muted text. |
| `titleColor` | Title color in the same hex vocabulary; defaults to theme foreground text. |
```ts
axes: {
matrix: {
columns: { position: 'bottom', title: 'Sample columns', lineColor: '#445566' },
rows: { position: 'right', title: 'Source categories', labelColor: '#334455' },
},
},
```
`position: 'none'` removes that axis's line, labels, title and measured gutter. Matrix cell geometry, source identity, reversal, hover, windows and navigator remain active. Titles and colors affect decorations, independently of the numeric cell-value color mapping. Source data and CSV order stay canonical.
Heatmaps use the shared measured-axis layout. It measures final label/title text, renderer font styles and formatter results before laying out the cells; hidden axes release their space. Row/column label regions fit their allocated gutters/slots, preserving full accessible captions when text is truncated. Gutters are bounded to keep a usable plot on narrow hosts. Browser measurements use SVG text metrics; server-rendered SVG uses deterministic font-metric fallback and retains full captions in accessible labels/titles. Navigator and pointer geometry uses the resulting measured plot bounds. Overview previews retain categorical direction with decorations omitted.
Existing renderer label/element overrides retain precedence over axis colors. Matrix row labels have `kind: 'category'`, `axis: 'y'`, their genuine `category` and original `categoryIndex`. Column labels have `kind: 'category'`, `axis: 'x'`, genuine `series` and `seriesIndex`. Titles use `kind: 'axisTitle'`. Formatter results are reused between measurement and painting per semantic source identity, including when several source labels have identical text. No synthetic numeric row/column values enter formatter provenance.
Pivot reuses its physical axis forms for **Heatmap columns** and **Heatmap rows**, exposing position and title alongside the existing reversal controls. Numeric scale, bound, tick and unit controls are omitted for these categorical axes. Configure colors through the API or existing renderer styles. Localize the group captions with **`localeText.chartOptions.matrixColumnsAxis`** and **`localeText.chartOptions.matrixRowsAxis`**; common `axisPosition`, `axisTop`, `axisBottom`, `axisLeft`, `axisRight`, `axisHidden`, `axisTitle` and `axisField` locale hooks still apply. UI policy keys are **`axes.matrix.columns.position`**, **`axes.matrix.columns.title`**, **`axes.matrix.rows.position`** and **`axes.matrix.rows.title`**. Their `axes.matrix.columns`/`axes.matrix.rows` parents and the existing `axes` policy also apply.
Save/restore and SVG/image exports retain these settings. Queued Pivot updates own matrix appearance before validating the resulting chart type; gallery transitions to another family clear matrix-only settings. Numeric matrix row/column scales/bounds, additional matrix axes remain separate work. Calendar heatmaps continue to use their calendar layout.
## Grouped heatmap axes
**`axes.matrix.columns.groups`** and **`axes.matrix.rows.groups`** accept up to eight `ChartCategoryAxisGroup` objects, ordered from the innermost band next to the cells to the outermost band. A nonempty list replaces that dimension's ordinary leaf captions; include the leaf path level as the first group when both leaf and parent labels are needed. An omitted or empty list retains ordinary captions.
```ts
axes: {
matrix: {
columns: {
position: 'bottom',
groups: [
{ values: { revenue: 'Revenue', costs: 'Costs' }, size: 32 },
{ values: { revenue: 'Financial', costs: 'Financial' }, size: 32 },
],
},
rows: { position: 'right', groups: [{ level: 1 }, { level: 0 }] },
},
},
```
The lookup keys in the example are stable **series IDs**, not labels or row indices. Row groups use stable **category IDs**. Each group supports:
| Field | Behavior |
| --- | --- |
| `level` | Zero-based path index, an integer from 0 through 127. Defaults to the final member of the row category's `path` or column series' `columnPath`. Corresponding `labels` supply display text. |
| `values` | Optional lookup of stable IDs to string, finite number, boolean or `null`, at most 100,000 entries. Missing IDs fall back to the chosen path level. Authored values supply their own text; `null` uses the localized missing-value caption. |
| `size` | Band thickness, 16 through 160 SVG pixels. Defaults to 32 for columns and 80 for rows. Shared measured layout bounds total gutters to retain a usable plot; bands can shrink and captions can truncate or be omitted on small hosts. |
| `tickSize` | Boundary tick length, 0 through 24 pixels; default 6, capped by the fitted band thickness. |
| `lineColor`, `labelColor` | Existing authored hex-color vocabulary. Each band overrides the matrix axis color; absent overrides use the axis color and then the renderer theme. Renderer label styles/formatters retain their existing final precedence. |
Groups merge only adjacent members with the same **typed value and outer-group identities**. Numeric `1` and string `'1'` remain separate even when their captions match. Repeated nonadjacent groups remain separate. Windowing clips groups to visible canonical members; reversal mirrors their physical projection without changing IDs, cell values or CSV. Group boundaries use source row/column positions, independently of dense cell bucket aggregation. Navigator previews omit decorations and preserve the full-matrix overview.
Both physical sides, titles, hidden axes, save/restore, server SVG and image export use the shared axis pipeline. `position: 'none'` hides all bands and releases their gutter. Grouping does not aggregate, reorder or add data. Persisted group definitions contain data, without callback functions. Runtime matrix grouping callbacks are described below; additional matrix axes remain separate work.
Pivot **Customize → Axes and scales** reuses the group editor under **Heatmap columns** and **Heatmap rows**. Enable **Group category labels** to initialize up to eight levels from that dimension's path depth, then edit levels, add or remove bands. When columns contain measures without a column dimension path, enabling grouping initializes an explicit stable series-ID-to-label lookup so each measure retains its caption. Path-only API groups on an empty column path have a missing group value and no invented path caption. Explicit `values` lookups are API-configured and disable the corresponding path-level editor. These controls use **`axes.matrix.columns.groups`** and **`axes.matrix.rows.groups`** UI policy keys plus existing parents. Existing `chartOptions.categoryGroups`, `categoryGroupLevel`, `categoryGroupAdd`, `categoryGroupRemove` and `axisField` locale hooks apply; accessible field labels include the heatmap dimension name. No new locale strings are required.
## Runtime heatmap grouping callbacks
Use **`ChartsRendererOptions.matrixAxes.rows.groups`** and **`matrixAxes.columns.groups`** to derive group values or captions from source metadata. Each dimension independently overrides its persisted `axes.matrix` group list. An explicit empty array disables grouping for that dimension and restores ordinary leaf captions; an omitted `groups` keeps the persisted list. Runtime definitions replace the whole list, including its sizes/styles, without merging by index.
```ts
import { createChartsRenderer } from '@revolist/charts';
const renderer = createChartsRenderer({
matrixAxes: {
rows: {
groups: [
{ getValue: category => ({ value: category.id, label: category.label }) },
{ getValue: category => category.path[0] ?? null },
],
},
columns: {
groups: [
{
size: 70,
getValue: (series, seriesIndex) => ({
value: series.measure?.id ?? series.id,
label: `${seriesIndex + 1}: ${series.measure?.label ?? series.label}`,
}),
},
],
},
},
});
```
**`ChartCategoryAxisRuntimeGroup`** is the row type: **`getValue(category, categoryIndex)`** receives a read-only `ChartCategory` and its original index in the canonical accepted dataset. **`ChartSeriesAxisRuntimeGroup`** is the column type: **`getValue(series, seriesIndex)`** receives a read-only `ChartSeries` with `columnPath`, `labels` and optional measure metadata, plus its original index in the canonical accepted dataset. Columns are genuine series, without fabricated category paths. Windows and physical reversal preserve these indexes and IDs. For remote sources, indexes refer to the accepted frame, without inventing positions or members for unloaded records.
Each runtime group accepts all persisted group fields (`level`, `values`, `size`, `tickSize`, `lineColor`, `labelColor`) plus optional **`getValue`**. Without a callback, that group's own stable-ID lookup/path configuration supplies values and labels. A callback returns a string, finite number, boolean, `null`, or **`{ value, label }`** with a scalar `value` and string `label`. Scalar results display their string form; scalar `null` uses `localeText.renderer.missingValue`. A value/label pair separates typed grouping identity from its caption. Adjacent values merge only when both their type and all outer group identities match. `undefined`, nonfinite numbers and malformed pairs are invalid; invalid configuration/results throw `TypeError`, and callback exceptions propagate. The eight-band and existing size/tick/color limits still apply.
The renderer caches each visible member's result by stable ID **per group, per render**. Measurement and painting share those results; it does not call a row callback once for every matrix cell. A new render after data, viewport, resize or chart updates starts a fresh cache, including when IDs remain the same. Keep callback input read-only. Hidden axes and navigator previews do not evaluate callbacks. Runtime matrix options are used only by heatmaps; other chart families ignore them. Hidden-axis group configuration is still validated.
All band projection, measured gutters, physical sides, reversal, caption fitting and renderer style/label precedence remain shared with persisted grouping. Callbacks do not aggregate/reorder source cells or affect CSV. Runtime functions and derived captions are not serialized into the model; configure the same renderer when restoring a chart. Standalone Charts accepts the options through `rendererOptions` or `createChartsRenderer`. Pivot uses **`createPivotChartsRenderer({ matrixAxes: ... })`** and its public **`renderer`** option. Pivot exports **`PivotChartCategoryAxisRuntimeGroup`** and **`PivotChartSeriesAxisRuntimeGroup`** through both its root and charts facade. The existing Pivot group forms edit persisted groups; renderer runtime overrides keep their normal precedence.
Server rendering accepts the same configuration through **`renderChartToSVG({ model, width, height, rendererOptions: { matrixAxes: ... } })`**. It evaluates callbacks using the server model's real metadata and deterministic text fitting. SVG/image output includes the resulting labels; saved models still contain only persisted settings. Reapply matching renderer options for client hydration. No additional locale or UI policy keys are introduced by these renderer callbacks.
## Normalized combination column stacks
Both **`columnLine`** and **`areaLine`** support:
| Option | Default | Behavior |
| --- | --- | --- |
| `chartOptions.normalizeStacks` | `false` | Normalize explicitly grouped **column, area and line** layers independently for each category, X axis, Y axis and `stackGroup`. Ungrouped scalar layers, scatter and range layers keep raw values; column/path stack families are independent. |
| `chartOptions.normalizedTo` | `100` | Finite target from 1 through 1,000,000. Used by normalized groups only; changing it alone does not enable normalization. |
```ts
chartOptions: { normalizeStacks: true, normalizedTo: 100 },
seriesOverrides: {
revenue: { type: 'column', stackGroup: 'financial', yAxis: 'primary' },
costs: { type: 'column', stackGroup: 'financial', yAxis: 'primary' },
volume: { type: 'line', yAxis: 'secondary' },
},
axes: { y: { title: 'Share' }, ySecondary: { title: 'Raw volume' } },
```
Positive and negative contributors have independent totals, so each nonempty sign reaches `+normalizedTo` or `-normalizedTo`. Nulls do not contribute; zero creates a zero-height segment at the current positive stack endpoint. Hidden series are excluded from denominators, and different named X/Y axes or group IDs never share a denominator. Calculations rescale overflowing raw totals to keep representable proportions finite. Final nonzero-sign endpoints are pinned to the target to avoid rounding outside the axis; underflowing shares retain their original sign and may have zero visible height. Source values, category/series IDs, clicks, tooltips, data labels and CSV remain raw; normalization changes plot-space endpoints and axis domains.
An axis containing only normalized grouped columns uses the corresponding sign domain (`0..target`, `-target..0`, or `-target..target`). An empty/all-zero pure stack axis retains `0..target`. Its ticks display percentages when the target is 100, unless an authored unit/tick format supplies formatting. Other targets use numeric ticks. If raw and normalized layers share a value axis, its numeric domain includes both; use separate Y axes to compare their units clearly. This decision considers all layers, including layers on other named X axes. Existing authored bounds, scales, reversal and clipping still apply, and bounds must form a valid domain. Existing logarithmic-column and error-bar/stack compatibility restrictions remain.
Analytical overlays and calculated references use each normalized column component in axis units; raw layers retain raw values. Derived overlays may expand automatic bounds as usual, while explicit bounds remain authoritative. Public `resolveChartAnalysis` callers supply `chartType`, `seriesOverrides` and `chartOptions` in its final options argument to use matching normalized semantics. Peer points use normalized stack endpoints. Primary-axis synchronization uses a normalization-specific unit descriptor that includes the target, authored unit and whether the axis mixes raw units, preventing accidental Y synchronization with raw axes or a different target. Raw remote `fullDomain.y` hints are ignored for a primary axis containing normalized columns, because their units differ; explicit authored Y bounds remain effective. Denominators are calculated from each accepted category's visible group values. Providers must return complete contributing group values for those categories; omitted remote contributors cannot be reconstructed from a raw domain hint.
Navigator previews show each selected column's normalized **component**, using complete visible chart-group denominators before preview series selection. Selecting one preview series does not rescale that series to 100. Navigator-only hiding/selection does not change the main chart's denominator. Other preview layers retain their normal raw values; preview style selection does not turn the overview into a full stacked composition.
Save/restore, server SVG and image export preserve normalization settings. When updating `chartOptions`, supply the complete desired object (for example `{ ...chart.model.chartOptions, normalizedTo: 10 }`), since it is a replacement. Disabling `normalizeStacks` restores raw stack endpoints without changing the source matrix.
Pivot uses the shared schema in **Customize → Chart appearance**: **Normalize stacks** and **Normalize to**, with existing option reset behavior. Localize them through `localeText.chartOptions.labels.normalizeStacks`, `descriptions.normalizeStacks`, and existing `labels.normalizedTo`/`descriptions.normalizedTo`; `formatValue`, `resetOption` and reset-all hooks also apply. UI policy keys are `chartOptions.normalizeStacks` and `chartOptions.normalizedTo` plus existing parent policies. No separate Pivot math or options contract is required. Bubble/histogram/polar combinations remain separate composition work.
## Grouped combination line and area layers
In `columnLine` and `areaLine`, **`seriesOverrides[id].stackGroup`** now supports **`type: 'area'`** and **`type: 'line'`** as well as columns. Area and line layers in the same group share cumulative endpoints; canonical series order is the default, configurable through stack ordering below. An area fills between its own cumulative baseline and endpoint; a line traces its cumulative endpoint. Each positive and negative sign has its own accumulator. Layers without an explicit group retain their existing raw geometry.
```ts
seriesOverrides: {
baseline: { type: 'area', stackGroup: 'traffic' },
extra: { type: 'area', stackGroup: 'traffic' },
forecast: { type: 'line', stackGroup: 'traffic' },
revenue: { type: 'column', stackGroup: 'traffic', yAxis: 'secondary' },
},
chartOptions: { normalizeStacks: true, normalizedTo: 100 },
```
**Column stacks and path stacks are independent families**, even if they reuse the same group string and axes. Line and area layers may share a path stack. X-axis ID, Y-axis ID and group string further partition each family. Column slots are unaffected by path groups. `zIndex` changes paint order and overlap visibility, while stack accumulation follows the configured stack order independently. Hidden layers do not contribute; missing contributions do not reset other layers' accumulators at that category.
The existing **`chartOptions.normalizeStacks`** toggle applies to every explicit supported stack family. Positive and negative path contributions normalize independently to `±normalizedTo`, just like columns. `normalizedTo` still defaults to 100 and accepts finite values from 1 through 1,000,000. Raw source tooltips, clicks, data labels, CSV and model matrices retain original values; peer points and path geometry use cumulative endpoints. Analytical overlays, calculated references and navigator previews use normalized component values when normalization is enabled. Selecting a single preview series does not change its full visible-group denominator. Reversal, category/time/numeric X, named axes, authored bounds, SVG/SSR and save/restore use the common pipeline.
With default null handling, stacked area fills and their top strokes break at null/calendar gaps and at changes between positive and negative contributions, so they do not bridge unrelated sign baselines. Stacked line traces connect finite cumulative endpoints across sign changes, while nulls (unless `connectNulls` is enabled) and configured calendar gaps still break the trace. One-category runs retain their source marker even when the area has no visible width. Default linear area fills follow the retained envelope; authored per-series curves interpolate both top and baseline. Sign changes do not reorder or synthesize source observations.
Dense combination stacks use per-layer envelope sampling under `sampling: 'auto'`. Explicit `m4`, `minmax`, or `lttb` coordinates one bounded source selection across all cumulative edges in each axis/group partition. M4/minmax retain extrema in their selected buckets; LTTB approximates shape. Finite/calendar runs stay separate and category indexes remain canonical. `sampling: 'none'` retains all accepted path observations. Source rows and CSV are never sampled. Scalar scatter and bound range-area layers cannot join these stack groups; existing error-bar/stack restrictions remain. Positive stacked **lines** may use logarithmic value axes; zero/negative source observations remain invalid there, and normalized log domains use a positive lower limit. Existing logarithmic column/combination-area restrictions remain. Nonlinear scales change screen proportions as usual.
Pivot reuses the existing **series layer** and **stack group** controls under **Customize → Axes and scales**. Group fields appear for column, area and line layers. Switching among those types retains a group; switching to scatter, range-area or a helper clears it. The shared **Normalize stacks** and **Normalize to** controls remain in **Chart appearance**. Existing `localeText.chartOptions.stackGroup`, `seriesLayer` and `labels.normalizeStacks`/`descriptions.normalizeStacks` locale hooks apply, and existing `axes.seriesLayer`, `chartOptions.normalizeStacks`, `chartOptions.normalizedTo` UI policies still apply. No extra saved option or Pivot-specific calculation is introduced.
## Configurable stack order
Stack baselines are selected separately with `chartOptions.stackOffset` and
`seriesOverrides[id].stackOffset`: `diverging` preserves current defaults, `none`
accumulates algebraically, and `expand` uses algebraic unit totals. Expansion
replaces percentage preprocessing for its partition. See [baseline policies,
normalization precedence and raw source behavior](/guides/charts/customization/#stack-baseline-policies).
**`chartOptions.stackOrder`**, default **`'none'`**, controls accumulation within each stack. It is available on `groupedColumn`, `stackedColumn`, `normalizedColumn`, `groupedBar`, `stackedBar`, `normalizedBar`, `line`, `smoothLine`, `steppedLine`, `area`, `steppedArea`, `stackedArea`, `normalizedArea`, `columnLine` and `areaLine`. On grouped bars/columns it takes effect when stack groups are active; unstacked geometry is unchanged.
| Policy | Accumulation order |
| --- | --- |
| `'none'` | Canonical series order. |
| `'reverse'` | Reverse canonical series order. |
| `'ascending'` | Increasing **signed sum of raw values** over the accepted dataset; equal totals retain source order. |
| `'descending'` | The exact reverse of ascending order, including ties. |
| `'appearance'` | Earliest canonical category index at the series' maximum finite raw value. Equal peak indexes retain source order; all-missing series follow observed ones. |
Null/missing values do not contribute to sums or peaks; a series without contributions has total zero. Total comparisons preserve finite Number values across overflowing sums, subnormals and cancellation. Ordering uses raw values before normalization or overflow scaling. Appearance means source category index, independently of physical X position, sorted numeric/time keys or axis reversal.
```ts
chartOptions: { stackOrder: 'ascending' },
seriesOverrides: {
revenue: { type: 'column', stackGroup: 'financial', stackOrder: 'reverse' },
costs: { type: 'column', stackGroup: 'financial' },
count: { type: 'line', yAxis: 'secondary' },
},
```
Optional **`seriesOverrides[id].stackOrder`** selects a stack's policy instead of the chart-wide default. One member can configure the stack; other members may omit it. Multiple authored policies on active members of the same stack must agree, or the update rejects before commit. An explicit `'none'` overrides a global automatic policy; removing the property restores the global fallback. Hidden members do not contribute values or conflicting policies.
Each X-axis/Y-axis/group partition orders independently. Native paths partition by their assigned axes and explicit stack groups. Native bars/columns also respect explicit/default stack groups. Combination column and area/line families remain separate even when their group strings match. Scatter/range/helper layers do not form ordered stacks. An ungrouped combination layer remains unstacked. Regular native line/area paths retain dormant policies while ungrouped; other unsupported types reject the option.
Ordering changes cumulative baselines/endpoints, including source marker locations, without reordering canonical data, colors, legend items, IDs, click payloads or CSV. Positive and negative accumulation remains independent. Column group slots retain their original positions. Combination `zIndex` still controls paint order separately. Native overflow detection follows the actual order, retaining finite geometry even when reordering exposes floating-point overflow.
Policies use all categories in the accepted frame, before viewport clipping or path sampling. Zooming/reversing the same frame does not recalculate from just the visible window. A new data frame, visibility change or member policy can change automatic order. Remote partial frames provide only their accepted observations: `fullDomain` is not a source of global series totals/peaks. Providers needing stable global automatic ordering must supply the relevant full observations, or provide a stable series order and use `'none'`/`'reverse'`.
Normalization, source-safe tooltips/labels/CSV, analytical component values, SVG/image output, SSR and save/restore retain their existing contracts. Ordering does not change normalization denominators. For stack-preserving stable IDs, charts and Pivot use the same domain helper. Public policy types are **`ChartStackOrder`** (Charts root/core) and **`PivotChartStackOrder`** (Pivot root/charts facade).
Pivot **Customize → Chart appearance** exposes **Stack order** from the shared option schema. **Axes and scales → series → Stack order** sets a member policy; **Automatic** removes that override. Supported combination column/area/line switches retain a policy, while unsupported layer switches clear it. Native bar and line/area series also expose the policy. Localize with `localeText.chartOptions.labels.stackOrder`, `descriptions.stackOrder`, `selectValues.none`, `reverse`, `ascending`, `descending`, `appearance`, and existing `automatic`/`axisField`/reset hooks. UI policy keys are **`chartOptions.stackOrder`** and **`axes.seriesStackOrder`**, plus existing parents. Resetting only the global option does not remove member overrides.
## Per-series line and area interpolation
Set **`seriesOverrides[id].interpolation`** to choose the shape between retained observations. It supports native `line`, `smoothLine`, `steppedLine`, `area`, `steppedArea`, `stackedArea` and `normalizedArea`, plus explicitly assigned `line` and `area` layers in `columnLine`/`areaLine`. Other families and combination column/scatter/range/helper layers reject it. There is no chart-wide `chartOptions.interpolation` alias.
| Value | Shape |
| --- | --- |
| `'linear'` | Straight connections. |
| `'smooth'` | Existing monotone-X smoothing algorithm, retained for compatibility. |
| `'step'` | Transition at the midpoint between adjacent X coordinates. |
| `'stepBefore'` | Vertical transition at the previous observation, then horizontal connection. |
| `'stepAfter'` | Horizontal connection to the next observation, then vertical transition. |
| `'monotoneX'` | D3 monotone cubic interpolation along screen X. |
| `'monotoneY'` | D3 monotone cubic interpolation along screen Y. |
| `'natural'` | Natural cubic spline. |
| `'catmullRom'` | Centripetal Catmull–Rom spline (alpha 0.5). |
| `'bumpX'` | Cubic connections with horizontal tangents. |
| `'bumpY'` | Cubic connections with vertical tangents. |
```ts
seriesOverrides: {
revenue: { interpolation: 'natural' },
target: { interpolation: 'stepAfter' },
},
```
Omitting the property keeps the chart-type default: `smoothLine` uses `'smooth'`, `steppedLine`/`steppedArea` use `'step'`, and other supported native types use `'linear'`. Combination paths default to straight connections. Removing an override restores that default. `ChartInterpolation` is exported from Charts root/core; `PivotChartInterpolation` is exported from Pivot root/charts.
Curves operate on projected screen coordinates after axis transforms, ordering, stacking and sampling. For numeric/time X, observations follow the existing ordered-X contract. X/Y names describe physical coordinates; axis reversal does not rename the curve. Use monotone X with monotonic screen X, and monotone Y with monotonic screen Y. Natural and Catmull–Rom curves can overshoot observations or cross neighboring stack boundaries; interpolation changes visual geometry, not source values, normalization totals or analytical component values. Curved areas interpolate both top and baseline; directional steps reverse their return-edge policy so both boundaries transition at the same physical X. Stacked sign changes and null/configured calendar gaps retain their existing run boundaries; curves leave missing observations disconnected by default; the per-series `connectNulls` policy can join them. Single-observation runs keep their source marker. No additional measured points are created.
Existing viewport clipping, dense path budgets and envelope sampling still apply. Curves connect retained observations; sampling is not a guarantee of reproducing every bend in the unsampled spline. Canonical IDs, source mark activation, tooltip values, legend, CSV, saved models and SVG/SSR retain their existing contracts. Navigator previews use the same native path owner and inherited per-series policies where that series is projected as a line/area.
Pivot **Customize → Axes and scales → series → Interpolation** selects the same policy. **Automatic** removes the override. Combination line/area switches retain it; column/scatter/range/helper switches clear it, and gallery transitions to unsupported chart types remove it. UI-policy key: **`axes.seriesInterpolation`**. Localize with `localeText.chartOptions.interpolation`, `axisField`, `automatic` and `selectValues` entries for every curve above.
The named curve choices follow the [MUI line interpolation reference](https://mui.com/x/react-charts/lines/#interpolation) and [D3 curve definitions](https://d3js.org/d3-shape/curve). Legacy `'smooth'` retains its original algorithm rather than aliasing it to a new curve.
## Connecting paths across null values
**`seriesOverrides[id].connectNulls`** is an optional boolean, default **`false`**. Set it to `true` to connect finite observations across intervening null/missing values. It supports native `line`, `smoothLine`, `steppedLine`, `area`, `steppedArea`, `stackedArea` and `normalizedArea`, and explicitly assigned combination `line`/`area` layers in `columnLine`/`areaLine`. Other families and column/scatter/range/helper layers reject the option. There is no `chartOptions.connectNulls` alias.
```ts
seriesOverrides: {
revenue: { interpolation: 'natural', connectNulls: true },
target: { connectNulls: false },
},
```
The selected curve joins existing finite endpoints; it does not fill the data matrix, create values or source marks, or extend before the first finite observation or after the last one. Tooltips, hover/selection targets, analytical values, CSV and raw activation payloads still refer only to source observations. Leading/trailing/all-null runs remain empty. Invalid nonfinite values are not treated as joinable nulls; the normal data boundary still rejects them.
For time axes, **`axes.x.missingPeriods: 'gap'` remains authoritative**. A present category with a null value is joinable, but an absent configured calendar period still splits the path. To join both, explicitly set the time axis to `missingPeriods: 'connect'` as well. This applies to the assigned named X axis. The calendar policy uses the shared dates package.
Zooming into a long null run retains the nearest finite bracketing observations as clipped decorative geometry, including when no source mark is visible in the window. It does not extrapolate outside the accepted dataset or fetch unavailable remote observations. Connection occurs before path sampling; existing budgets and extrema/envelope policies still apply. Stacking/normalization uses the original finite endpoint values and baselines. Combination stacked areas still split at changes between positive and negative contributions; `connectNulls` does not join unrelated sign baselines. Navigator previews inherit the per-series policy through the same path owner; `navigation.navigator.seriesOverrides[id].connectNulls` can explicitly override it for a supported line/area overview type. The option survives saved models and SVG/SSR rendering through `ChartSeriesOverrides`/`PivotChartSeriesOverrides`.
Pivot **Customize → Axes and scales → series → Connect missing values** offers **Automatic** (remove the property and use false), **Connect** (`true`) and **Leave gaps** (`false`). Explicit false is retained in the saved model. Switching between supported combination line/area layers retains the setting; switching to an unsupported layer or chart type clears it. UI-policy key: **`axes.seriesConnectNulls`**. Localize via `localeText.chartOptions.connectNulls`, `axisField`, `automatic`, and `selectValues.connected`/`gapped`.
See the [MUI missing-point connection reference](https://mui.com/x/react-charts/lines/#connect-missing-points) for the upstream series option. Explicit calendar-gap precedence follows the existing Charts time-axis contract.
## Area fill baselines
**`seriesOverrides[id].baseline`** accepts a finite number, **`'min'`** or **`'max'`**. It is available on **ungrouped** native `area`/`steppedArea` series and explicitly assigned, **unstacked** combination `area` layers in `columnLine`/`areaLine`. Omitting it keeps the zero baseline, clipped to the current domain when needed. There is no chart-wide `chartOptions.baseline` alias.
```ts
seriesOverrides: {
revenue: { baseline: 'max' },
costs: { baseline: -25 },
},
```
A numeric baseline contributes to the automatic Y domain for its visible source series and assigned Y axis. Hidden series and unknown/dormant IDs do not contribute. Existing automatic scale rules, including zero-inclusive linear domains, remain in effect. `'min'`/`'max'` resolve the **final numerical axis domain**, including authored bounds, analytical extensions, runtime domains and shared bounds across X-axis partitions; they do not mean the series' observed minimum/maximum or a fixed physical screen edge. Reversing Y changes their physical position. Numeric baselines remain fixed raw values. Authored/runtime bounds clip the fill rather than changing that authored value or forcing the domain to expand.
The setting changes the fill boundary, not source line endpoints or values. It does not offset the data, create observations, or change raw tooltips, mark activation, CSV, normalization denominators or analytical component values. Curves interpolate the existing upper and baseline edges; null-connection, calendar-gap, sampling and viewport policies still apply. An all-null series has no fill even when an authored numeric baseline participates in its axis domain. SVG/SSR and saved models retain the setting. Public policy types are **`ChartAreaBaseline`** (Charts root/core) and **`PivotChartAreaBaseline`** (Pivot root/charts).
Authored baselines are **rejected on stacked/normalized native areas, native area/stepped-area series with a `stackGroup`, grouped combination areas, line/column/scatter/range/helper layers and unrelated chart types**, even when the authored number is zero. Stacks keep their shared cumulative baselines. Native logarithmic areas accept positive numeric baselines and `'min'`/`'max'`; zero/negative numeric values reject atomically. The omitted legacy baseline still uses the logarithmic domain floor. Combination-area logarithmic restrictions remain in effect.
Area/stepped-area navigator previews inherit the setting. Override with `navigation.navigator.seriesOverrides[id].baseline` for those overview types. A line-only preview ignores an inherited area baseline; explicitly authoring a baseline for a line overview rejects as unsupported.
Pivot **Customize → Axes and scales → series → Area baseline** offers **Automatic**, **Axis minimum**, **Axis maximum** and **Fixed value**. Fixed value exposes **Baseline value**. Automatic removes the override; clearing the numeric input does the same. An explicit zero is retained on linear axes. Switching to another unstacked area retains the setting; switching to an unsupported layer/type clears it. Entering a stack group clears the baseline atomically and hides its controls. UI-policy key **`axes.seriesBaseline`** controls both fields. Localize with `localeText.chartOptions.baseline`, `baselineValue`, `axisField`, `automatic` and `selectValues.baselineMin`/`baselineMax`/`baselineNumber`.
See [MUI area baselines](https://mui.com/x/react-charts/lines/#baseline) for the upstream fill policies and stacked-area restriction.
## Path marker visibility and shapes
**`seriesOverrides[id].markers`** accepts **`true`**, **`false`**, **`'start'`** or **`'end'`** for native `line`, `smoothLine`, `steppedLine`, `area`, `steppedArea`, `stackedArea` and `normalizedArea`, and explicitly assigned combination `line`/`area` layers. Omitted main-chart settings keep all retained point glyphs; `true` does the same and `false` hides them. Endpoint strings select one persistent source glyph per series; enabled transient inspection can visually substitute it until leave, blur or Escape. There is no `'all'` string or `chartOptions.markers` alias. Unsupported families/layers reject endpoint modes.
```ts
seriesOverrides: {
revenue: { markers: 'end', marker: 'star', markerSize: 6 },
target: { markers: false },
},
```
Start/end mean the first/last **finite observation in the assigned X ordering of the accepted frame**, before viewport clipping, physical axis reversal and path sampling. Numeric/time X uses its ordered coordinates; categories use canonical category order. Nulls are skipped without fabricating values. A series with no finite observations has no endpoint. These modes do not mean the first/last currently visible point, and axis reversal does not exchange them. A source endpoint outside the visible X/Y domain has no persistent glyph; zooming does not substitute an interior point. Remote partial frames can select only their accepted observations.
Dense paths keep their current sampling budgets. An eligible endpoint omitted by path sampling can be retained as one extra source glyph when the series has a nonzero path budget; it does not create another path segment or synthetic data. Existing path/series omission limits remain. Data labels are independent of marker visibility: `presentation.dataLabels` can still label retained observations with hidden or endpoint-only glyphs, subject to the existing label-density rules. `markerSize` retains its pixel-radius setting and now also applies to supported combination path glyphs. Raw activation values, CSV, analytical values, stacks, IDs, save/restore and SVG/SSR remain source based.
**`seriesOverrides[id].marker`** now supports **`'circle' | 'square' | 'diamond' | 'triangle' | 'cross' | 'star' | 'wye'`** wherever the chart's marker-shape capability is available. The four existing shapes retain their geometry. Cross/star/wye use shared unit SVG symbols scaled around the source position, with nominal area equal to a circle of the supplied radius. Shape changes do not change mark identity or click/keyboard payloads. Source observations retain keyboard activation even when glyphs are not drawn; the shared source traversal uses at most one temporary focus target instead of hidden per-datum DOM. Existing chart navigation/crosshair controls remain independent. Persistent endpoint selection remains separate from the runtime predicate below and does not implement transient pointer-highlight substitution.
Public types **`ChartMarkerMode`** and **`ChartMarkerShape`** are exported from Charts root/core, with **`PivotChartMarkerMode`** and **`PivotChartMarkerShape`** on Pivot root/charts. Native navigator previews hide default glyphs as before; explicit/inherited per-series modes and shapes apply through the same path owner, and `navigation.navigator.seriesOverrides[id]` can override them for supported overview types. The accepted overview frame determines its own endpoint.
Pivot **Customize → Axes and scales → series → Markers** offers **Automatic**, **All retained points**, **Hidden**, **First observation** and **Last observation**. Automatic removes the override; all/hidden save boolean true/false. The shared UI authors the effective combination path type when needed. Supported line/area switches retain modes; switching to an unsupported layer/type clears them. UI-policy key **`axes.seriesMarkers`** controls this selector. Localize with `localeText.chartOptions.markersMode`, `axisField`, `automatic` and `selectValues.markersAll`/`markersNone`/`markersStart`/`markersEnd`.
Marker shape remains in the existing **Customize → Colors → series → marker** control and its paginated series editor, using **`colors.marker`**. Localize new captions with `localeText.colors.cross`, `star` and `wye`; existing shape captions and `colors.marker` label function remain. Changing marker visibility, shape, interpolation, baseline or null connection preserves sibling path settings.
See [MUI line marks](https://mui.com/x/react-charts/lines/#marks) for the upstream shape and endpoint choices. The source-frame endpoint and sampling contracts above describe Charts/Pivot behavior.
## Runtime source marker predicates
**`rendererOptions.markers.isVisible(context)`** is an optional synchronous **boolean** predicate for native line/area glyphs and explicit combination line/area layers. Use it to select source observations independently of the sampled line vertices. It refines the persisted marker mode: `markers: false` remains hidden; start/end can only keep or suppress their accepted-frame endpoint. It does not filter paths, data, analytical components or CSV, and does not create values for nulls or decorative boundary points.
```ts
const chart = await createChart({
container,
chartType: 'line',
dataset,
seriesOverrides: { revenue: { markers: true } },
rendererOptions: {
markers: {
isVisible: context =>
context.renderMode === 'preview' ||
(context.series.id === 'revenue' && context.categoryIndex % 100 === 0),
},
},
});
```
**`ChartMarkerContext`** contains `chartId`, the current rendered `chartType`, `renderMode: 'chart' | 'preview'`, canonical accepted-frame `categoryIndex`/`seriesIndex`, readonly `category`/`series` descriptor snapshots, finite `value`, assigned `xAxisId`/`yAxisId`, and SVG plot coordinates `point: { x, y }`. Main-chart `value` remains the raw accepted-frame value through hidden-series projection, named axes, stacking, normalization and overflow scaling; it is not the cumulative or percentage endpoint. Indices refer to the canonical frame before series projection, not the glyph's DOM index or X-sort position. Descriptor/path/label metadata and the context are frozen snapshots; treat them as readonly.
Preview values, descriptors and indices belong to the accepted **preview projection**, which can contain normalized or derived metrics. Preview `chartType` describes that native overview. Inspect `renderMode` rather than assuming a preview value equals the original main-frame value. Previews keep their normal default hidden glyphs; explicitly inherited or navigator-overridden marker modes use the same predicate. Branch on `renderMode` to apply a different preview policy.
The callback examines eligible visible finite source candidates for **regular glyphs before bounded marker sampling**. Transient source highlights bypass this predicate without invoking it again. It can select a source observation absent from the line's sampled vertices, such as every 100th point. Matching glyphs obey the existing per-series/chart marker budgets unless `chartOptions.sampling: 'none'` disables that reduction; when matches exceed the budget, bounded source-order extrema sampling applies. Glyph/focus order follows source X order. Hidden series, nulls, out-of-domain points, zero-budget series and `markers: false` do not invoke it. Native line/area families and explicit combination line/area layers support it; it does not customize scatter, column, range, flow or matrix glyphs. Existing path labels are not suppressed by the predicate; newly selected source glyphs can also participate in existing data-label density rules.
Keep the predicate pure and synchronous. It can run again for data/style changes, resize, navigation, exports, SSR and hydration; invocation counts are not a business-event contract. A nonboolean result or invalid predicate type reports **`invalid-marker-result`** through `rendererOptions.onDiagnostic` and retains the built-in visibility locally. Reports are bounded to once per root SVG lifecycle, including preview/repaint calls. Observer errors are isolated, and missing observers use the existing console diagnostic behavior. Predicate exceptions propagate like other renderer formatter exceptions.
These callbacks are **runtime-only**. Saved models contain no predicate. Re-supply it after restore, to `renderChartToSVG`, and to `hydrateChart` for matching output and future repaints. Hydration can reconcile a different client predicate while retaining the root SVG. Public runtime types are **`ChartMarkerContext`**, **`ChartMarkerPredicate`** and **`ChartMarkersRendererOptions`**, exported from Charts root/core. `ChartsRendererOptions.markers` carries the options.
For Pivot, configure the same options with **`createPivotChartsRenderer({ markers: { isVisible } })`** and install that renderer in the chart plugin/standalone mount configuration. Public aliases **`PivotChartMarkerContext`**, **`PivotChartMarkerPredicate`** and **`PivotChartMarkersRendererOptions`** are available from Pivot root/charts. Pivot's persisted Markers selector still narrows the host predicate; Automatic/all does not disable application filtering. No function editor or serialized callback field is added to the inspector.
This covers source predicates corresponding to [MUI callback-based mark selection](https://mui.com/x/react-charts/lines/#optimization). Transient source highlighting is implemented independently of the regular marker predicate; see the transient source-point highlights section.
## Path sampling
Use `chartOptions.sampling` on `line`, `smoothLine`, `steppedLine`, `area`,
`steppedArea`, `stackedArea`, `normalizedArea`, `columnLine`, and `areaLine`.
Pivot exposes the same **Path sampling** selector under **Customize → Appearance**.
```ts
await chart.update({ chartOptions: { sampling: 'lttb' } });
```
| Value | Behavior |
| --- | --- |
| `'auto'` (default) | Preserves existing defaults: M4 reduction for ordinary paths and combination envelopes; native stacked and normalized areas retain all accepted observations. |
| `'none'` | Retains every accepted path observation and eligible marker, without path or marker downsampling. Normal dataset limits still apply. Large accepted datasets can cost more time and DOM nodes. |
| `'m4'` | Keeps first, minimum, maximum, and last observations in each X bucket, in source order. |
| `'minmax'` | Keeps overall endpoints and each interior X bucket's minimum and maximum observations, in source order. |
| `'lttb'` | Largest triangle three buckets: keeps endpoints and chooses observations that approximate the path's shape. It does not guarantee retention of every peak. |
Explicit algorithms use the existing width-based path budget (up to 1,200
observations per series, with a 4,800 observation chart allocation). Separate
finite runs share that budget; very small budgets can omit entire runs. Runs are
split before reduction, so omitted runs never create a connecting segment.
Viewport boundary neighbors can add decorative vertices outside the visible
window. Sampling operates within the current viewport; retained observations
can change after zooming or resizing. Preview mode may already project a reduced
dataset before the renderer runs, including when sampling is `'none'`.
Explicit sampling of stacked line/area paths uses one bounded set of source
indices across cumulative edges in each X axis, Y axis, and stack group
partition. Every member applies that selection to its original finite/sign
runs. Range bands reduce their lower and upper edges together. These are display
approximations; LTTB favors shape, while M4 and min/max retain extrema in their
chosen buckets. Null handling, `connectNulls`, calendar gaps, stack order, and
normalization still apply. Column and scatter layers in combination charts keep
their existing rendering policy; this option does not enable bar envelopes or
scatter sampling choices. Specialized chart types do not accept this option.
Raw datasets, automatic domains, CSV values, source indices, and interaction
values retain their original meaning. A runtime marker predicate is evaluated on
eligible source observations before its independent M4 marker budget; its result
need not match the path's selected vertices. `'none'` also disables that marker
reduction. Boolean and endpoint marker modes remain authoritative.
The choice is serializable in `chart.model`, survives save/restore, and applies
to SVG server rendering and hydration. `ChartSampling` and `PivotChartSampling`
name the public five-value union. Resetting the appearance options restores
`'auto'`. For a narrow update, preserve other authored `chartOptions` fields if
needed by your application's update flow.
## Step endpoint policy
`seriesOverrides[id].strictStepCurve` accepts a boolean (default **false**) on
native `line`, `smoothLine`, `steppedLine`, `area`, `steppedArea`, `stackedArea`,
and `normalizedArea`, and explicitly assigned combination `line`/`area` layers.
It takes effect with `interpolation: 'step' | 'stepBefore' | 'stepAfter'` when
the assigned X layout has categorical bands.
```ts
seriesOverrides: {
forecast: { type: 'line', interpolation: 'stepAfter', strictStepCurve: true },
}
```
| Value | Stepped path edges on categorical bands |
| --- | --- |
| Omitted or `false` | Expand each finite run horizontally by half one category cell at each edge. |
| `true` | Start and finish at the exact observation positions. |
Set `true` to retain the earlier exact-endpoint shape on categorical combinations.
Combination charts default to band-centered categorical X positions. Native
line/area charts default to points; `axes.x.categoryPlacement` can choose bands
or points explicitly. Continuous numeric/time axes have no category band width.
`strictStepCurve` itself does not change X spacing. Other interpolation
methods ignore the flag. Unsupported families and column/scatter/range layers
reject it; nonboolean values reject before a chart update replaces the frame.
Expansion uses the category cell width, including after viewport changes and
reversal. It never infers cell width from the distance between sampled points.
Each null/sign-delimited retained run expands independently; a singleton can
span its own cell. `connectNulls: true` can join null-separated observations
before interpolation, while explicit calendar gaps retain their existing rule.
Sampling can omit source observations/runs; expansion only decorates the retained
run edges and does not restore omitted data. Area top and baseline edges expand
together and return with the matching directional step. Plot clipping still
applies to authored X/Y domains, including positive logarithmic line axes.
Expanded endpoints are decorative geometry. They do not add data, glyphs,
predicate calls, focus targets, labels, domain values, CSV rows, or activation
values. The boolean persists in models, SVG/SSR and hydration through
`ChartSeriesOverrides`/`PivotChartSeriesOverrides`. Navigator overrides can carry
it for supported overview line/area types. Point overviews retain exact geometry;
explicit band placement is inherited by their selected X axis.
Pivot exposes **Customize → Axes → [series] Step endpoints** with **Automatic**,
**Expanded bands**, and **Strict points**. Automatic removes the authored flag
and restores the default. Editing an implicit combination path authors its
effective line/area type; switching that layer to a nonpath type clears the flag.
Reset appearance clears it with other series overrides. The controls use shared
localized captions (`strictStepCurve`, `expandedSteps`, `strictSteps`).
## Category point and band placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept
`'point'` or `'band'` on native `line`, `smoothLine`, `steppedLine`, `area`,
`steppedArea`, `stackedArea`, `normalizedArea`, `columnLine`, and `areaLine`.
`ChartCategoryPlacement` and `PivotChartCategoryPlacement` name the public union.
The catalog advertises support through `categoryAxisPlacement`.
```ts
axes: {
x: { scale: 'category', categoryPlacement: 'band' },
xAxes: { detail: { categoryPlacement: 'point', reverse: true } },
}
```
| Setting | Observation placement |
| --- | --- |
| Omitted / Automatic | Native line/area charts use points; combinations use bands, preserving existing defaults. |
| `'point'` | The first and last observations occupy the ends of the projected X range; a singleton sits in the center. |
| `'band'` | Observations sit in the centers of equal category cells, with half a cell at each outer edge. A singleton owns the full cell. |
This is a placement policy for `scale: 'category'`, including when that scale is
implicit. Other chart families and time/numeric X axes reject the field. There is
no `'auto'` string: remove the field to restore the chart family default. Direct
API updates reject invalid settings before replacing a frame.
Band placement enables expanded native stepped lines/areas. With `step`,
`stepBefore`, or `stepAfter`, run edges extend to cell edges by default;
`seriesOverrides[id].strictStepCurve: true` keeps exact observation endpoints.
Point placement does not expand steps. Nonstep interpolation still uses the
chosen observation positions. Sampling uses the same positions; it does not
create larger cells from the distance between retained observations. Stacks keep
their current Y geometry and source-index alignment.
Point combinations with columns reserve half a column-group width at the outer
edges so columns remain inside the plot; their observation range is inset by that
margin. Band combinations keep the existing widths and centers. Labels and
category-group bands follow the registered projection, including reversal and
independent named axes. Placement does not reorder categories or change raw
values, domain values, CSV, focus targets, tooltip identities, or activation
values. The observed X coordinates and decorative geometry change.
Category viewport/brush ranges continue to use stable category IDs. Changing
placement retains the semantic selection and projects it onto the new cells;
zoom, nearest-category lookup, references, shapes, crosshairs and compatible peers
use the same axis projection. Compact navigators inherit their selected X axis's
placement. Models, SVG/SSR and hydration retain explicit settings.
Pivot exposes **Customize → Axes → [axis] Category placement** with **Automatic**,
**Point positions**, and **Centered bands**, separately for the primary and named
X axes. Numeric/time scale changes clear the category-only setting; gallery
changes clear it when unsupported. Other axis styles, category mappings/groups
and compatible semantic windows are preserved. Reset appearance clears authored
axes, restoring family defaults. Localized captions are `categoryPlacement`,
`categoryPoint`, and `categoryBand` under `chartOptions`.
## Transient source-point highlights
Native line/area charts and explicitly assigned combination `line`/`area` layers
show a transient source-point glyph during pointer or existing datum-focus
inspection. This visual is independent of regular `markers` and
`rendererOptions.markers.isVisible`: hidden, predicate-rejected and sampled-out
source observations can still be highlighted without rerunning that predicate.
| Option | Default | Effect |
| --- | --- | --- |
| `chartOptions.disableLineItemHighlight` | `false` | Suppresses transient line/area glyphs chart-wide. |
| `seriesOverrides[id].disableHighlight` | Omitted / `false` | Suppresses transient glyphs for that series when `true`. |
Both are persisted booleans supported by `line`, `smoothLine`, `steppedLine`,
`area`, `steppedArea`, `stackedArea`, `normalizedArea`, `columnLine`, and
`areaLine`. Per-series flags require explicit `type: 'line'` or `'area'` in
combinations; unsupported families/layers and nonboolean values reject before
replacing a frame. The global flag wins over a per-series `false`. Disabling this
visual leaves ordinary markers, source tooltips, crosshairs, selection and peer
highlighting enabled under their respective settings.
```ts
chartOptions: { disableLineItemHighlight: false },
seriesOverrides: {
revenue: { markers: 'end', disableHighlight: false },
}
```
Hovering an existing source glyph inspects its exact series/category. Hovering a
line/area path chooses the nearest ordered X observation in that source series;
plot-background inspection uses `navigation.xAxis` (primary by default) and its
path series. Shared tooltip mode can highlight the same category across the
assigned X axis. Numeric/time order, category bands, reversal, named axes and
stacked/normalized Y projections use the actual registered geometry. A null or
clipped observation has no glyph; no interpolated point or unloaded remote row
is invented. This is nearest-source inspection, not continuous curve-distance
hit detection.
Transient glyphs use the shared marker shape and raw-value color, with radius
`max(4, markerSize/defaultRadius) + 2`. Runtime element styles/formatters apply as
`kind: 'mark'` with canonical raw values and source indices. The decorative SVG
nodes have `data-pivot-chart-transient-marker`, `aria-hidden="true"` and
`pointer-events="none"`; they are not extra datum/roving/activation targets.
They add no data labels, source rows, predicate calls, path vertices, or CSV rows.
For saved `'start'`/`'end'` marker modes, an enabled active glyph visually replaces
the persistent endpoint until inspection leaves, blurs, is cancelled or Escape
is pressed. Endpoint paint is suppressed while its existing pointer/focus target remains active;
original styles are restored. Retained datum focus can keep its focus glyph after
the pointer leaves until blur or Escape. Repaints discard pointer state and recompute any restored datum-focus highlight
from the new frame. Destruction discards the state; drag/brush pointer movement suppresses
it. Source keyboard traversal can inspect hidden/sampled-out eligible observations
using the separate focus target described below. Decorative path highlights do not
convert pointer clicks into datum activation.
Glyph feedback is immediate, including with `rendererOptions.tooltip: false`.
Source-point tooltip inspection uses existing delays and canonical raw values,
even when the visual is disabled. Preview charts do not show transient glyphs.
Models retain only the authored boolean policies; SSR starts without transient
state. Image/SVG/PDF snapshots omit transient glyphs and restore persistent
endpoints in the exported clone without changing live hover/focus state.
Pivot exposes **Customize → Chart appearance → Disable point highlights** and
**Customize → Axes → [series] Point highlights** (**Automatic**, **Enabled**,
**Disabled**). Automatic removes the per-series flag. Layer/gallery changes clear
it when unsupported; appearance reset restores default policies. Shared public
`ChartSeriesOverrides`/`PivotChartSeriesOverrides` carry `disableHighlight`, and
`ChartOptionKey` includes `disableLineItemHighlight`. Localized captions are the
schema label/description plus `disableHighlight`, `highlightEnabled` and
`highlightDisabled` under `chartOptions`.
## Named statistical X axes
`boxPlot`, `violin` and statistical `rangeInterval` support `axes.xAxes` and `seriesOverrides[id].xAxis` in standalone Charts and Pivot. Primary X remains the default. Each declared X axis owns its category/time/numeric projection, reversal, labels, grouping and window; unassigned declared axes can still display their captions. The existing limit is 32 named axes. Unknown assignment IDs are rejected before the model changes.
```ts
axes: {
x: { scale: 'category', level: 0 },
xAxes: {
elapsed: {
scale: 'log', level: 1, reverse: true,
position: 'top', title: 'Elapsed', unit: 's',
tickValues: [1, 10, 100],
},
},
ySecondary: { title: 'Rate' },
},
seriesOverrides: { rate: { xAxis: 'elapsed', yAxis: 'secondary' } },
viewport: { xAxes: { elapsed: { scale: 'log', min: 1, max: 100 } } },
navigation: { enabled: true, xAxis: 'elapsed', navigator: true },
```
Primary and named X axes accept `level` (a category-path index, 0–127) or `data` (a map from canonical category ID to an X value). An explicit `data` entry takes precedence over `level`; missing entries fall back to the selected path level, or the last path element by default. These mappings change projection/captions without rewriting category identities, matrix cells or distribution records. Time values follow the shared dates package; numeric values must be finite and logarithmic keys positive. `data` accepts at most 100,000 entries. Models copy and persist these definitions; runtime providers and callbacks remain separate.
All named X axes accept `category`, `time`, `linear`, `log`, `symlog` and signed `sqrt`. They reuse the primary X controls described above: numeric bounds, units/formatting/tick controls, positive symlog constant, reversal, time calendar intervals/missing-period policy, and categorical group bands. Default categories retain band spacing. Explicit `categoryPlacement` remains limited to supported line/area/combination families. Vertical statistical charts place named X axes at `top`/`bottom`/`none` (default top); horizontal box plots place them at `left`/`right`/`none` (default right). Their primary physical aliases remain unchanged. Measured gutters reserve space per displayed axis, including titles and grouped bands. Pivot offers named-axis creation/removal, path-level selection, scale/format controls and per-series assignments; orientation/type changes clear incompatible positions while retaining valid settings.
Each X frame contains only its assigned distributions. Box/violin slots are grouped per series within that frame. Statistical intervals aggregate only measures sharing **both** X and Y assignments, so observations with different X units never form one interval. Value-axis domains still include all accepted observations assigned to that Y axis across X frames, independently of visible X windows. Hidden/unbound series remain excluded. Generic value references render once; category references use `xAxis`, shapes use `xAxis` and `yAxis`, and semantic notes follow the source measure's actual X/Y projection.
Use `viewport.xAxes[id]` for an independent window, `navigation.xAxis` for navigator/zoom/pan targets and `rangeSelection.xAxis` for a brush target. Synchronization, peer hover and continuous remote-detail `fullDomain.xAxes` metadata use the same stable IDs and raw mapped coordinates. Runtime/source/sync Y hints continue to describe primary Y only and exclude secondary/named-Y observations. Secondary/named Y bounds can be authored separately. These X windows do not add independent value-axis navigation. Save/restore, server SVG, source activation and raw CSV retain the existing source-data contracts; histogram collections have their own bound-measure contracts; statistical analytical overlays remain separate.
## Financial numeric X axes
`candlestick`, `ohlc` and `volumePrice` accept primary `axes.x.scale: 'linear' | 'log' | 'symlog' | 'sqrt'` in standalone Charts and Pivot. The default remains `category`, and existing `time` axes remain available. Numeric X uses the mapped category's actual numeric key, not the row index or an OHLC/volume measure. Values must be finite; log keys must be positive. Signed symlog/sqrt keys remain supported.
```ts
axes: {
x: {
scale: 'log', level: 0, min: 1, max: 1000,
reverse: true, position: 'bottom', title: 'Observation distance', unit: 's',
tickValues: [1, 10, 1000],
tickFormat: { notation: 'standard', maximumFractionDigits: 2 },
},
},
viewport: { x: { scale: 'log', min: 10, max: 1000 } },
navigation: { enabled: true, navigator: true },
```
Primary financial X also accepts `level` (category-path index 0–127) and `data` (up to 100,000 canonical-category-ID/value entries). Explicit map entries take precedence over the selected path level; missing entries fall back to that level or the last path element. Category maps affect captions; numeric/time maps provide coordinates. Canonical category IDs, financial fields and source/export values remain unchanged. Date/time values keep the shared dates-package contract.
Numeric X supports positive symlog `constant` (default 1), `min`/`max`, `reverse`, `unit`, `tickFormat.notation`/`minimumFractionDigits`/`maximumFractionDigits`, `tickValues`, `tickCount`, `tickMinStep`, `tickMaxStep`, `tickSpacing`, `top`/`bottom`/`none` placement, `title`, `lineColor`, `labelColor` and `titleColor`. Nonlinear ticks and pointer inversion use the same raw coordinate transform. Explicit category point/band placement remains limited to the chart families advertising that capability.
Candle/OHLC centers and line/step-line price paths follow real numeric spacing. Glyph widths are bounded by the nearest distinct visible numeric X gap; numeric volume bars can have fractional widths below 0.1 pixels instead of overlapping close observations. Price and volume panes share X centers, reversal, bounds and viewport windows. Their Y scales, OHLC close activation and volume activation retain existing ownership. Numeric windows are increasing raw `min`/`max` values even when reversed. Numeric X has no calendar missing-period interpolation policy.
Navigation, navigator, brushes, category references, shapes, notes, synchronization and continuous data-source `fullDomain.x` use those same raw coordinates. Remote full-domain Y remains price-only; fixed volume bounds are authored in `axes.ySecondary`. Save/restore and server SVG preserve the configuration, and CSV retains canonical observations with the existing current/all export policy. Pivot exposes numeric scale/format/tick controls and primary path-level selection; edits and gallery transitions preserve valid primary mappings. Candlestick/OHLC collections are described below. Paired volume-price collections, named X and pane sizing are described below; additional pane/Y axes and volume Y synchronization remain separate capabilities. Invalid direct axis updates are rejected atomically.
## Candlestick and OHLC collections
`candlestick` and `ohlc` support multiple bound price groups in standalone Charts and Pivot. Parallel `bindings.open`, `high`, `low` and `close` arrays identify each group by stable source-series IDs. Each group needs four distinct fields. A helper channel (`open`, `high`, `low`) can contain one shared field or the common group count; `close` must contain one distinct owner per group. Missing fields, inconsistent counts and duplicate owners are rejected. Numeric values are never inferred from column position. The catalog no longer caps these two types at four selected source fields; normal chart limits still apply.
```ts
bindings: {
open: ['openA', 'openB'], high: ['highA', 'highB'],
low: ['lowA', 'lowB'], close: ['closeA', 'closeB'],
},
axes: {
x: { scale: 'time', level: 0 },
xAxes: { elapsed: { scale: 'linear', level: 1, title: 'Elapsed', unit: 's' } },
y: { title: 'Market A' },
yAxes: { marketB: { scale: 'log', title: 'Market B', position: 'right' } },
},
seriesOverrides: { closeB: { xAxis: 'elapsed', yAxis: 'marketB' } },
navigation: { enabled: true, xAxis: 'elapsed', navigator: true },
```
The **close field owns its group's axes**. `seriesOverrides[closeId].xAxis` selects primary or a declared `axes.xAxes` entry. `yAxis` selects primary (`axes.y`), secondary (`axes.ySecondary`, explicit or default), or a declared `axes.yAxes` entry. Unset assignments default to primary. Open/high/low helpers inherit the owner assignment; an explicitly conflicting helper assignment is rejected. A broadcast helper can participate in several projections, so an explicit assignment must agree with every owning group. Unbound measures cannot acquire non-primary financial axes.
Every X collection supports the existing category/time/four numeric scales, `data` maps, path `level`, bounds/formatting/ticks appropriate to its scale, reversal, grouped category captions, styles and `top`/`bottom`/`none` placement. Value collections support all four numeric scales, bounds/reversal, units/formatting/ticks, styles and `left`/`right`/`none` placement. Named axes retain the existing maximum of 32 entries per collection. Unknown IDs and invalid scale/domain changes fail before the live model changes. Declared unused axes can retain decorations and references without inventing source marks.
Groups assigned to the same X frame occupy stable neighboring slots, including when they use different Y axes. Slots are based on bound groups before window clipping or missing-row omission. Continuous collections reserve an edge margin, and widths/offsets remain bounded by the nearest distinct continuous-observation gap, including irregular times. One-group geometry keeps its existing defaults. Price paths and all six `priceStyle` values share these assignments; `candleFill` and the existing rising/falling palette retain their behavior. This does not enable arbitrary per-group colors, visibility or a new financial legend.
Each Y domain uses complete OHLC observations assigned to it across all X frames. A missing price field omits only that group's observation at that category. Existing high/low normalization and raw close activation remain unchanged. A log Y axis validates only its assigned complete groups. Runtime/source/synchronized Y metadata describes **primary price groups only**; secondary/named prices compute from their own groups or authored bounds. Secondary/named Y synchronization remains separate.
`viewport.xAxes[id]`, `navigation.xAxis` and `rangeSelection.xAxis` select independent windows and interaction targets. The navigator defaults to the first eligible close field on its target X; explicitly selected series or `all` retain the existing raw-field overview policy. Generic Y references render once, category references use `xAxis`, and shapes select `xAxis`/`yAxis`. Source notes on close fields follow the group's actual slot and value projection, including when that close field also helps another group. Broadcast helpers can have several projected positions; use a close owner for an unambiguous source note or an explicit X/Y shape for a specific coordinate.
Current CSV windowing follows each group's owner for all four fields; shared helpers use the union of their owners' windows. `dataScope: 'all'` retains canonical loaded matrix cells. Saved bindings/axis assignments, server SVG, source activation and remote-detail/full-domain IDs preserve the same raw source contracts. Pivot preserves explicitly valid grouped bindings instead of reducing them to singleton semantic inference; singleton inference stays available. Author grouped role bindings through the model/API, then use the shared Pivot X/Y forms and close-owner selectors. Gallery transitions clear incompatible helper assignments.
`volumePrice` uses the paired collection contract described below: fixed price/volume Y roles with close-owned X frames and optional pane ratio. It does not acquire candlestick/OHLC named Y axes.
## Paired volume-price collections
`volumePrice` accepts parallel `bindings.open`, `high`, `low`, `close` and `volume` arrays for multiple price/volume pairs. The OHLC helper broadcast rules match candlestick/OHLC collections, but every pair needs one distinct close owner and one distinct volume field. Volume fields cannot also be price fields in any group. Inconsistent counts, duplicate close/volume owners and conflicting field roles are rejected before the model changes. The former five-series maximum is removed; normal chart limits still apply.
```ts
bindings: {
open: ['openA', 'openB'], high: ['highA', 'highB'],
low: ['lowA', 'lowB'], close: ['closeA', 'closeB'],
volume: ['volumeA', 'volumeB'],
},
axes: {
x: { scale: 'time', level: 0 },
xAxes: { elapsed: { scale: 'linear', level: 1, title: 'Elapsed' } },
y: { scale: 'log', title: 'Price' },
ySecondary: { scale: 'sqrt', title: 'Volume' },
},
seriesOverrides: { closeB: { xAxis: 'elapsed' } },
chartOptions: { volumePaneRatio: 0.4 },
navigation: { enabled: true, xAxis: 'elapsed', navigator: true },
```
A close field owns X for all four price fields and its paired volume. Use primary or a declared `axes.xAxes` ID, with the existing category/time/four numeric scales, mappings, reversal, formatting/ticks/styles and independent `viewport.xAxes[id]`. Helpers and volumes inherit X; conflicting explicit assignments and non-primary assignments on unbound fields are rejected. Each pair occupies the same slot in both panes, including pairs sharing an X frame. Continuous slots and bar widths use real observation spacing. Missing volume can omit a volume bar while retaining a complete price observation; an incomplete OHLC group exposes neither a price observation nor its paired volume at that category.
Y roles remain fixed: every price field uses primary `axes.y`; every bound volume uses secondary `axes.ySecondary`. Matching explicit Y roles are accepted, while swaps and named Y axes are rejected. Price and volume each share a scale across all accepted groups before X-window filtering. The existing scale/bounds/reversal/unit/format/tick/physical-axis controls remain independent per pane. Runtime/source/synchronized primary Y metadata covers prices only; secondary volume bounds are authored separately, and independent volume Y synchronization is not added.
`chartOptions.volumePaneRatio` is volume-price-only, accepts a number from **0.1 through 0.8**, and sets volume's fraction of usable height **after the fixed pane gap**. The generic Pivot control uses step `0.01` and displays default `0.24`. When the option is absent or reset, the renderer preserves automatic 24% sizing with the legacy small-pane minimum policy. An authored ratio, including `0.24`, requests that exact fraction. Price receives the remaining usable height. The Pivot control ID is `chartOptions.volumePaneRatio`; its label and help use `localeText.chartOptions.labels.volumePaneRatio` and `localeText.chartOptions.descriptions.volumePaneRatio`. This option persists in models and server SVG; it does not create extra panes or change the gap policy.
Generic value references render once in their pane; category references target `xAxis` and render in both panes. Shapes, close/volume source notes, hover/crosshair and canonical activation use the pair's actual X slot and owning Y projection. Volume hover publishes the paired close owner's raw X coordinate and stable axis ID. Current CSV follows X ownership for all five fields; shared OHLC helpers use the union of their owners' windows, while all-scope CSV retains canonical loaded values. Separate close/helper/volume-only overview datasets retain their authored raw values and use source bindings for X eligibility.
Pivot preserves valid explicit paired bindings, exposes X selectors on close owners, fixed price/volume forms, named X forms and the pane-ratio control. Grouped role arrays are authored through the model/API. Close-selector transitions drop redundant helper X overrides so the pair inherits its new owner axis; fixed Y role overrides and unrelated settings are preserved. Saved state, source/full-domain metadata, raw values and source IDs retain the existing financial contracts. Additional panes and per-pair or named price/volume Y axes remain separate capabilities.
## Source-point keyboard traversal
Line, smooth line, stepped line, area, stepped area, stacked area and normalized
area charts, plus the line/area layers of column-line and area-line combinations,
keep every accepted source observation reachable with one chart inspection Tab
stop. This is enabled automatically; there is no saved option or additional
inspector control. It applies in standalone Charts and Pivot through the shared
renderer.
| Key | Action |
| --- | --- |
| Tab / Shift+Tab | Enter or leave the chart's single inspection stop; other chart controls retain their own stops. |
| Right / Down | Next source observation or inspection mark; wraps at the end. |
| Left / Up | Previous observation or mark; wraps at the beginning. |
| Home / End | First / last logical inspection item in the chart. |
| Enter / Space | Activate the canonical source datum using the configured drill/selection behavior. Derived/synthetic observations remain inspection-only. |
| Escape | Dismiss tooltip and transient highlights; in selection mode also invoke the existing selection-clear behavior. Keyboard focus remains visible. |
Traversal follows layer/series registration order, then each path's source X
order. Numeric/time X uses ordered observations; axis reversal changes positions
without reversing that order. Mixed combinations retain other layers' existing
mark order. Missing/nonfinite values, hidden series and observations outside the
accepted X/Y plot domain are excluded. Clipped/interpolated boundary vertices are
never fabricated as source observations.
`markers: false`, `markers: 'start'` / `'end'`, runtime marker predicates and all
path sampling modes affect paint density, while keyboard traversal still reaches
eligible source observations. It uses raw canonical values for accessible names,
tooltips, hover, underlying records and activation, including on normalized or
stacked areas. Traversal does not rerun marker predicates or recreate regular source glyphs.
Transient highlight glyphs retain their existing runtime element styling.
Rendered eligible glyphs are reused. An omitted or visually unavailable glyph
uses a single temporary focus-ring inspection target per SVG, rather than hidden
DOM for every observation. The ring is independent of
`chartOptions.disableLineItemHighlight` and `seriesOverrides[id].disableHighlight`,
and excluded from element customization and SVG/image/PDF export snapshots. The
ordinary decorative hover highlight remains separate and cannot activate data.
Runtime formatters still apply to the inspected source values.
Updates and resizes restore focus by series/category identity, including after
reordering or value changes; removal or clipping chooses a nearby eligible
observation in the same series where possible. Updates never take focus away
from another control. Blur hides the temporary ring; destruction discards its
node and listeners. Preview charts remain noninteractive. Static SVG may contain
one transparent Tab-entry target; hydration restores runtime interactions, not
previous browser focus. No function or interaction state is added to saved models.
Pointer path inspection still chooses the nearest source X observation; continuous
curve-distance hit detection is a separate capability.
## Explicit histogram collections
For bound sample/metric groups, named X/Y assignments, scoped navigation/readouts, complete remote partitions and Pivot data views, see [histogram collections](/guides/charts/types/histogram/#histogram-collections-and-axes).
## Radar metric axes
Radar line and area share independent numeric metric domains/scales, reversible projection, authored grid bands and value labels through `axes.radar` and radar appearance options. See [radar axis contracts and Pivot editing](/guides/charts/tools/radar-axes/).
## Support and behavior
Axis support follows each chart family's capabilities described above. Named axes,
scale transforms and tick policies use the same contracts in standalone Charts and
Pivot. Keep raw data values when configuring bounds, navigation and annotations;
projection applies the selected scale and direction.
See [value-axis navigation](/guides/charts/tools/value-navigation/) for primary/secondary/named Y windows and scale-compatible limits.
Grouped columns/bars and scatter can opt into `chartOptions.rendererPolicy: 'batched-svg'` or `'webgl'`. Heatmap additionally accepts `'webgl'`; see the [GPU capability and fallback matrix](/guides/charts/tools/webgl/). Omission keeps individual SVG. See [batched SVG rendering](/guides/charts/tools/batched-svg/) for the supported families, keyboard/pointer inspection, saved policy and deterministic customization fallbacks.
## State and cleanup
Axis options participate in saved models and server-rendered SVG. Await
`chart.update()` before reading the updated model, and preserve the other axis
settings when replacing an axis configuration. Destroy the chart when its host
unmounts to release its controls, listeners and rendered content.
For bubble/sample-owned X and complete histogram bins in mixed Cartesian layers, see [bubble and histogram combinations](/guides/charts/tools/cartesian-sample-layers/).
See [measured polar axis layout](/guides/charts/tools/polar-axis-layout/) for named-axis gutters, collision omission and the runtime placement callback.
---
# Batched SVG rendering
URL: https://pro.rv-grid.com/guides/charts/tools/batched-svg/
Source: src/content/docs/guides/charts/tools/batched-svg.mdx
Description: Opt-in dense grouped bars and scatter, shared by standalone Charts and Pivot.
Set `chartOptions.rendererPolicy` to `'batched-svg'` for `groupedColumn`,
`groupedBar` or `scatter`. Omission and explicit `'svg'` retain individual SVG
marks. The policy is saved with the chart model. Public types are `ChartSvgRendererPolicy` and
`PivotChartSvgRendererPolicy`; `ChartTypeOptions` / `PivotChartOptions` expose the
optional field. Unknown values and the option
on other chart families reject atomically. There is no automatic size threshold.
Standalone Charts and Pivot share the policy and implementation; Pivot exposes
it through the existing chart appearance options.
```ts
await chart.update({ chartOptions: { rendererPolicy: 'batched-svg' } });
```
The renderer groups matching geometry into SVG paths with at most 2,048 shapes
per path. Canonical data, domains, raw CSV/data views and axis geometry remain
owned by the existing data/geometry pipeline. Scatter supports M1 numeric X/Y
windows, including named axes; bars retain their existing category navigation
and fixed numeric bounds, without adding Y-window navigation in M2. Native bar
scrolling remains available when no explicit window is supplied.
Accessible keyboard inspection uses a geometry index and a constant number of transient SVG proxies.
Arrow keys traverse eligible source observations; Home/End jump to the ends;
Enter/Space activate the current raw datum. Nulls and fully clipped geometry
have no inspection target. Pointer hits use visible shape geometry and resolve
coincident marks to the last painted observation. Scatter callbacks retain raw
X/Y channel values. Resizing/navigation rebuild the index from the same geometry
used to draw the paths. Focus restoration follows the canonical category/series.
Per-item DOM selectors, event listeners attached to individual marks, classes,
CSS rules keyed by datum attributes, and DOM mutation of individual shapes are
unsupported in batched mode. External stylesheets cannot be inspected reliably:
select `'svg'` explicitly when relying on them. A batched path is not one datum.
Deterministic whole-chart fallback to single-item SVG applies when runtime
`rendererOptions.elements` or `rendererOptions.onRender` is supplied, when saved
color maps/member/measure styling or a color binding requires per-item paint,
when authored stack groups are present on grouped bars, when the dataset contains
derived series or synthetic aggregate categories, or when selection/peer
highlight styling is active. The saved policy stays unchanged. The SVG reports
`data-chart-renderer-policy` and, on fallback, `data-chart-renderer-fallback`.
Clearing incompatible saved colors, stack groups or source selection restores
batching on the next paint. Runtime renderer options are captured at creation:
recreate the chart/renderer to remove `elements` or `onRender`. Runtime callbacks
remain runtime-only. Use `'svg'` when per-item customization
is required; batching does not silently discard supported styles.
Opacity and stroke are applied to each compound path: overlapping scatter shapes
within a chunk share compositing, so overlap darkness can differ from individual
circles. Use `svg` when per-point alpha accumulation is required.
SVG/image/PDF exports preserve the persistent batched paths; transient inspection
proxies are excluded from clean exports. CSV retains raw values and existing
viewport membership semantics. SSR uses the same native renderer and policy;
no browser-only resources are stored in the model.
See [measured performance and budgets](/guides/charts/tools/batched-svg-performance/) for reference hardware, timings and the 100k optimization results. Batched
SVG is opt-in. Optional WebGL has a separate capability and fallback contract; see [WebGL](/guides/charts/tools/webgl/).
Authored `seriesOverrides[id].encoding` or runtime `rendererOptions.datumStyle` forces individual SVG with `datum-style` fallback. See [datum styling](/guides/charts/tools/datum-styling/) for fields, precedence, persistence and capability limits.
---
# Batched SVG measured performance
URL: https://pro.rv-grid.com/guides/charts/tools/batched-svg-performance/
Source: src/content/docs/guides/charts/tools/batched-svg-performance.mdx
Description: Reference hardware, budgets and measured dense chart results.
Latest verification: [19 October CPU/memory regression](#cpumemory-optimization-regression--19-october-2026). Earlier measurements are historical.
M2 reference: Apple M1 Pro, MacBookPro18,3, 16 GiB RAM, macOS 26.4 (25E5207k).
Chromium version and actual results are recorded in the adjacent JSON artifacts.
Budgets below were fixed before production implementation. Single-item baseline
is built from e8ec9af61 plus the existing uncommitted M1 changes.
Fixtures: deterministic 1,000 / 10,000 / 100,000 observations, one series for
groupedColumn/groupedBar; one X/Y pair for scatter. No nulls in timing fixtures;
correctness fixtures cover nulls separately. A 960×440 host resizes to 800×440.
Three runs per combination, production ESM, headless Chromium, serial execution.
Create includes model cloning/validation, geometry, DOM, and two animation frames;
resize waits 100ms for ResizeObserver and two frames (100ms excluded). These are
blocking paint latencies, not a claim of 60fps. Nearest-rank p95 of three runs is
the maximum. Data construction/module loading precede the timer. GC before/after
uses CDP; retained JS heap delta includes model/index/scene. CDP native embedder
heap and backing storage are also reported. Process RSS is not measured.
| Gate | 10k | 100k |
| --- | --- | --- |
| Batched create and resize p95 | ≤750ms | ≤5000ms |
| Pointer/keyboard inspection p95 | ≤50ms | ≤100ms |
| Retained incremental JS heap | ≤128MiB | ≤512MiB |
Default single-item 1k create/resize median may grow by no more than 20% plus a
15ms noise allowance relative to the recorded baseline. Structural gate: batched
mark nodes grow by paint groups/chunks, never one per observation. Native memory
and long-session memory are limitations of this local benchmark, not implied
passed gates. No GPU, WebGL, Firefox, Safari, CI or deployed performance claim.
Run the opt-in `chart-batched-svg-benchmark.spec.ts` with `CHART_BENCHMARK` pointing
to a JSON output path; set `CHART_RENDERER_POLICY=batched-svg` for batching.
## CPU/memory optimization regression — 19 October 2026
All nine batched cases and all three default single-item SVG cases complete and
pass the original M2 gates on the same final production graph as the
[latest WebGL CPU/memory results](/guides/charts/tools/webgl-performance/#cpu-and-retained-memory-optimization--19-october-2026).
The run used the original headless Chromium shell protocol, without Metal/8 GiB
heap launch overrides, from 11:54:46 to 11:55:30 UTC inside the coordinated hold
on other workspace builds/tests. It remains separate from the matched CPU/GPU
full-Chromium stress protocol. Hardware and emitted-JavaScript hash are retained
in raw batched results (`packages/charts/docs/batched-svg-memory-optimized.json` in the source repository) and
raw 1k default SVG results (`packages/charts/docs/batched-svg-memory-default.json` in the source repository).
| Family | Rows | Create p95 ms | Resize p95 ms | JS heap MiB | Pointer / keyboard p95 ms | Nodes / batch paths | Budget |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| groupedColumn | 1000 | 77.9 | 29.2 | 4.5 | 0.1 / 0.6 | 56 / 1 | PASS |
| groupedColumn | 10000 | 168.0 | 22.6 | 17.3 | 0.1 / 0.6 | 69 / 5 | PASS |
| groupedColumn | 100000 | 1148.2 | 364.7 | 145.7 | 0.1 / 0.6 | 109 / 49 | PASS |
| groupedBar | 1000 | 41.3 | 31.5 | 4.5 | 0.1 / 0.8 | 48 / 1 | PASS |
| groupedBar | 10000 | 152.5 | 31.2 | 17.5 | 0.1 / 0.5 | 52 / 5 | PASS |
| groupedBar | 100000 | 1333.5 | 571.8 | 149.2 | 0.2 / 0.5 | 96 / 49 | PASS |
| scatter | 1000 | 43.2 | 23.7 | 5.2 | 0.1 / 0.7 | 56 / 1 | PASS |
| scatter | 10000 | 175.6 | 25.3 | 23.9 | 0.1 / 0.6 | 60 / 5 | PASS |
| scatter | 100000 | 1910.1 | 515.4 | 214.0 | 0.1 / 0.5 | 104 / 49 | PASS |
1k default SVG comparison (median; limit = baseline × 1.2 + 15 ms):
| Family | Baseline create / resize ms | Current create / resize ms | Gate |
| --- | ---: | ---: | --- |
| groupedColumn | 80.8 / 4.1 | 45.2 / 14.3 | PASS |
| groupedBar | 92.1 / 6.4 | 43.6 / 17.1 | PASS |
| scatter | 113.7 / 3.8 | 42.9 / 17.5 | PASS |
The 1k default-SVG comparison passes its original median limit (baseline × 1.2
plus 15 ms). Its resize medians are higher than the original baseline, within
that unchanged noise allowance; no resize improvement is claimed. The fixture
includes two RAFs, without waiting for the full duration of any SVG entrance
animation. It measures the recorded create/resize protocol, not completed
animation latency. Functional transition tests separately verify committed
geometry and exports. No observations or semantic inspection entries are dropped;
100k batched cases retain 49 batch paths rather than one mark node per datum.
```sh
node packages/charts/scripts/report-batched-svg.mjs packages/charts/docs/batched-svg-baseline.json packages/charts/docs/batched-svg-memory-optimized.json packages/charts/docs/batched-svg-memory-default.json --check
```
## Historical integrated verification — 18 October 2026
The final production graph, including optional GPU support and the private runtime
snapshot optimization, passes all original M2 gates without changing the protocol,
fixture or budgets. Runs started after package builds, workspace push hooks and
compatibility jobs finished. Hardware and Chromium remain the same reference.
At 100k observations, groupedBar creation p95 is **2,171.5 ms** and scatter
**3,462.6 ms**, versus current-build pre-optimization 5,155.1 / 7,862.4 ms.
Scatter now has 1,537.4 ms headroom against its 5,000 ms creation gate. This remains
a local measured result, not a guarantee on another host or dataset.
Raw final records: `packages/charts/docs/batched-svg-integrated.json` and
`batched-svg-integrated-default-svg.json` in the source repository. Both identify
source commit `462284b31` and the emitted-JavaScript SHA-256/hash protocol. Earlier
optimization and initial implementation artifacts below remain historical.
| Family | Rows | Create p95 ms | Resize p95 ms | JS heap MiB | Pointer / keyboard p95 ms | Nodes / batch paths | Budget |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| groupedBar | 1000 | 50.5 | 17.0 | 5.0 | 0.1 / 0.4 | 48 / 1 | PASS |
| groupedBar | 10000 | 242.9 | 21.5 | 23.9 | 0.1 / 0.5 | 52 / 5 | PASS |
| groupedBar | 100000 | 2171.5 | 783.8 | 215.0 | 0.2 / 0.5 | 96 / 49 | PASS |
| groupedColumn | 1000 | 50.5 | 15.8 | 5.0 | 0.1 / 0.6 | 38 / 1 | PASS |
| groupedColumn | 10000 | 220.3 | 156.9 | 23.6 | 0.1 / 0.5 | 42 / 5 | PASS |
| groupedColumn | 100000 | 2041.7 | 574.6 | 211.5 | 0.2 / 0.5 | 86 / 49 | PASS |
| scatter | 1000 | 56.0 | 20.2 | 5.8 | 0.1 / 0.6 | 56 / 1 | PASS |
| scatter | 10000 | 332.6 | 15.3 | 31.5 | 0.1 / 0.5 | 60 / 5 | PASS |
| scatter | 100000 | 3462.6 | 783.1 | 289.8 | 0.1 / 0.5 | 104 / 49 | PASS |
1k default SVG comparison (median; limit = baseline × 1.2 + 15 ms):
| Family | Baseline create / resize ms | Current create / resize ms | Gate |
| --- | ---: | ---: | --- |
| groupedBar | 92.1 / 6.4 | 39.2 / 13.7 | PASS |
| groupedColumn | 80.8 / 4.1 | 38.1 / 11.2 | PASS |
| scatter | 113.7 / 3.8 | 47.1 / 10.9 | PASS |
Recompute every gate, including the default-SVG medians:
```sh
node packages/charts/scripts/report-batched-svg.mjs packages/charts/docs/batched-svg-baseline.json packages/charts/docs/batched-svg-integrated.json packages/charts/docs/batched-svg-integrated-default-svg.json --check
```
Final compatibility: Charts 3,033 / 180 files; Pivot 4,069 / 326 files; 33 functional
cases across Chromium, Firefox and WebKit. Package builds, public types and imports
pass. These browser-engine checks establish functional compatibility, not measured
Firefox/WebKit performance. Remote CI and deployment are recorded separately.
## M3 closure regression verification — 18 October 2026
The shared parser/GPU resource changes retain every original M2 gate. All nine
batched cases and three 1k default SVG cases pass on the same production graph
as the [GPU closure](/guides/charts/tools/webgl-performance/). Three unprofiled serial runs per case
are retained in raw batched results (`packages/charts/docs/batched-svg-m3-closure.json` in the source repository) and
raw default SVG results (`packages/charts/docs/batched-svg-m3-closure-default-svg.json` in the source repository). Hardware,
browser, data, default-renderer baseline and budgets are unchanged.
| Family | Rows | Create p95 ms | Resize p95 ms | JS heap MiB | Pointer / keyboard p95 ms | Nodes / batch paths | Budget |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| groupedBar | 1000 | 46.9 | 20.0 | 5.1 | 0.1 / 0.5 | 48 / 1 | PASS |
| groupedBar | 10000 | 206.3 | 12.4 | 24.4 | 0.1 / 0.5 | 52 / 5 | PASS |
| groupedBar | 100000 | 1823.3 | 615.4 | 218.4 | 0.2 / 0.6 | 96 / 49 | PASS |
| groupedColumn | 1000 | 57.6 | 21.8 | 5.1 | 0.1 / 0.7 | 38 / 1 | PASS |
| groupedColumn | 10000 | 185.6 | 148.8 | 24.1 | 0.1 / 0.5 | 42 / 5 | PASS |
| groupedColumn | 100000 | 1651.6 | 511.5 | 214.9 | 0.2 / 0.5 | 86 / 49 | PASS |
| scatter | 1000 | 50.6 | 18.5 | 6.0 | 0.1 / 0.7 | 56 / 1 | PASS |
| scatter | 10000 | 270.1 | 17.4 | 31.9 | 0.1 / 0.6 | 60 / 5 | PASS |
| scatter | 100000 | 2654.3 | 484.3 | 296.3 | 0.1 / 0.5 | 104 / 49 | PASS |
1k default SVG comparison (median; limit = baseline × 1.2 + 15 ms):
| Family | Baseline create / resize ms | Current create / resize ms | Gate |
| --- | ---: | ---: | --- |
| groupedBar | 92.1 / 6.4 | 35.8 / 12.2 | PASS |
| groupedColumn | 80.8 / 4.1 | 34.2 / 14.3 | PASS |
| scatter | 113.7 / 3.8 | 40.4 / 14.4 | PASS |
---
# Bubble and histogram combinations
URL: https://pro.rv-grid.com/guides/charts/tools/cartesian-sample-layers/
Source: src/content/docs/guides/charts/tools/cartesian-sample-layers.mdx
Description: Compose genuine bubble observations and complete histogram bins with Cartesian layers in Charts and Pivot.
`columnLine` and `areaLine` share the existing combination owner and now accept `bubble` and `histogram` series assignments alongside `column`, `line`, `area`, `scatter` and `rangeArea`. Standalone Charts and Pivot use the same model, scales, source identities and native renderer.
```ts
const dataset = createChartDataset(rows, {
category: { field: 'region' },
series: ['distance', 'revenue', 'population', 'duration', 'weight'].map(field => ({ field })),
});
const chart = await createChart({
container, chartType: 'columnLine', dataset,
seriesOverrides: {
revenue: {
type: 'bubble',
bubble: { x: 'distance', size: 'population' },
xAxis: 'distanceAxis',
},
duration: {
type: 'histogram',
histogram: { value: 'weight', boundaries: [0, 5, 15, 30], aggregation: 'sum' },
xAxis: 'durationAxis', yAxis: 'frequency',
},
},
axes: {
xAxes: {
distanceAxis: { scale: 'linear', title: 'Distance', unit: 'km' },
durationAxis: { scale: 'linear', title: 'Duration', unit: 'minutes', position: 'top' },
},
y: { title: 'Revenue' },
yAxes: { frequency: { title: 'Total weight', position: 'right' } },
},
navigation: { tooltipMode: 'shared', axes: 'xy', yAxis: 'frequency', brush: true },
});
```
The `revenue` owner supplies raw Y; `distance` and `population` supply raw X and size. The `duration` owner supplies samples; `weight` supplies histogram values. Bound helper measures do not acquire implicit line layers or legend entries. They stay in the canonical dataset for activation, views and raw exports. Explicitly assigning a helper its own `type` intentionally plots it as a separate layer. Existing range helper behavior remains supported.
## Per-layer configuration
| Field | Contract |
| --- | --- |
| `seriesOverrides[id].type` | Existing five types plus `bubble` and `histogram`. Only the two combination chart types accept these new assignments. |
| `bubble` | Required with `type: 'bubble'`; exactly `{ x, size }`, nonempty known series IDs up to 2,048 characters. The owner is Y. Channels may reuse a known measure. |
| `histogram` | Required with `type: 'histogram'`, including `{}` for automatic count bins. |
| `histogram.value` | Nonempty known value/weight series ID up to 2,048 characters; omitted means the sample owner. |
| `histogram.binCount` | Integer 0–256; default 0 chooses automatic binning. |
| `histogram.boundaries` | Existing finite, strictly increasing bin-edge contract, at most 257 edges; explicit edges override bin count. |
| `histogram.aggregation` | `count` (default), `sum` or `mean`, using existing missing-weight and complete-summary semantics. |
| `xAxis`, `yAxis` | Existing primary/secondary/named Cartesian identities, owned by the layer, not its helper measures. |
| `visible`, `color`, `zIndex` | Existing visibility, palette override and layer order. |
Unknown channels, missing measures/configurations, malformed boundaries, negative/noninteger bin counts, negative bubble sizes and incompatible stack/range/interpolation/error-bar fields reject updates before replacing the visible chart. Hidden layers retain validation. Histograms do not accept marker shapes; bubble/histogram layers do not accept line dashes. Existing per-source line marker modes apply only to line/area layers. The bubble renderer uses the existing relationship area-proportional radius, minimum 3 and maximum 24 SVG units (12 in previews), normalized to eligible positive loaded sizes before window filtering. Null/nonfinite X/Y/size and zero size produce no bubble; negative size rejects. `marker` may choose an existing series marker shape for a bubble; `markerSize` does not replace size-channel geometry and is rejected.
Public types are `ChartBubbleLayer`, `ChartHistogramLayer` and `ChartBubbleLayerSourceRequest`; Pivot exports `PivotChartBubbleLayer`, `PivotChartHistogramLayer` and `PivotChartBubbleLayerSourceRequest` through both public entrypoints. Old models require no migration.
## X ownership and alignment
Column/line/area/scatter/range layers keep category-owned X identity, using category paths or authored `axes.x.data` / named axis maps. Bubble X and histogram samples are measure-owned numeric observations. Two layers may use different raw X values from the same canonical row; they do not borrow each other's visible category index as a coordinate.
All layers on one logical X axis share its physical domain, transform, direction, authored bounds and window. Automatic domains include the union of assigned numeric observations and complete bin boundaries, including source full-domain hints. Sample axes default to linear when no scale is authored. Linear, log, symlog and sqrt use the existing numeric contracts; log requires positive source observations and bin boundaries. Time/category scales are rejected for bubble/histogram layers. A category-owned layer sharing a sample axis needs numeric category coordinates or an explicit numeric axis data map; string categories should use a separate axis. Named axes remain independent. Generated sample coordinate maps and inferred bounds are rendering/preflight state and never replace authored `chart.model.axes`.
Histogram bins retain their complete original intervals and metrics under X navigation. Window/plot clipping controls paint; it does not rebin a visible slice. Axis reversal changes projection, not source ownership or bin order. Columns on a mixed sample axis use the common projection without category-edge inset; their edge geometry clips to the plot. Existing combinations without sample layers retain their previous inset and stack/range behavior.
Independent primary/secondary/named Y windows are supported by combination geometry through the shared value-navigation owner. Bubble raw Y and computed histogram metrics contribute to their assigned Y domains; source sample magnitudes do not inflate a histogram frequency axis. Existing stack partitions and range extents remain authoritative. X/Y windows and layer order survive restore and resize.
## Layer order, inspection and shared tooltips
Default order is range area, area, histogram, column, line, scatter, bubble. `zIndex` overrides it; canonical series order breaks ties, including layers on different named X axes. Every layer stays within the shared plot clip.
Bubble activation uses the genuine category and Y series with raw `channelValues: { x, y, size }` and `channelSeries`; radius and projection never replace those values. Bubble points are not path-sampled. Existing path sampling affects only the existing line/area/range owners and never invents raw records.
Histogram inspection reuses the existing native bin owner. A bin activates a raw record only if its complete count is exactly one and that genuine representative is loaded. Every other bin is inspection-only, including a singleton whose source representative is not loaded. A mean/count/sum, interpolated path vertex or bin index never becomes a raw row identity.
Shared tooltips align on the same logical axis and exact raw numeric X. Raw peers use their own source category/index, including when two peers match X in different rows. Histogram peers are exposed separately through `ChartTooltipContext.histogramItems`, while genuine raw peers use `sharedItems`. Bins contain the raw X coordinate; raw peers require an exact observation and are never interpolated into a source identity. The existing 20-item limit/omission count applies. Cross-axis peers do not join merely because they occupy the same pixel. Keyboard inspection retains genuine source order and aggregate inspection semantics.
## Complete sources, data views and exports
A local dataset without a histogram summary bins its loaded raw observations, using the existing histogram algorithm. If complete statistics are supplied, they remain authoritative independently of the loaded raw slice. Source-backed combinations with histogram layers require one complete matching summary for each requested histogram partition. Providers receive `request.histograms`, even for a single layer:
```ts
{
sampleSeriesId: 'duration', valueSeriesId: 'weight',
xAxis: 'durationAxis', yAxis: 'frequency',
binCount: 3, boundaries: [0, 5, 15, 30],
aggregation: 'sum', scale: 'linear',
}
```
Bubble composition providers also receive `request.bubbleLayers`: `{ seriesId, x, size, xAxis, yAxis }` for each explicit bubble owner. These stable query hints identify its raw Y/X/size fields and independent windows without changing global binding ownership. They participate in request identity, so changing channels refreshes the source rather than reusing a request for another field. Providers still return genuine canonical rows.
Return `dataset.analyticalFrame.derived.histograms` with `scope: 'all'`, genuine representatives and the existing summary statistics. Requested sample/value IDs, bin policy and aggregation must match. A full source X domain must contain the union of its complete partition boundaries; other layers may extend it. Missing/stale/mismatched partitions reject the response rather than silently binning the loaded slice. Changing bin policy against an old complete summary rejects the transaction: supply a matching accepted dataset/summary with the authoring change. This milestone does not introduce speculative source queries before model validation. Representable normalized histogram metrics convert back to raw axis units; unrepresentable raw metrics are rejected for mixed value axes; standalone histogram overflow normalization remains available.
`createChartHistogramDataView(model, { sampleSeriesId, dataScope })` and `createChartHistogramDataViews(model, options)` accept combination models. Their bins describe complete statistics, and `samples`/`loadedCount` describe loaded genuine observations only. Hidden histogram layers remain available to data views. Current scope follows that layer's X window; all scope includes every loaded row and full bin partition. Raw data views never fetch or materialize missing source rows.
CSV remains a raw matrix export. All scope exports loaded canonical cells. Current scope uses each owner layer's own X membership and helper fields inherit the union of their owners' windows. Bubble/path source values follow their independent Y window; histogram sample membership follows X, while aggregate Y clipping does not rewrite raw sample records. Bins, representative-only unloaded rows, radius and computed metrics are never inserted as raw CSV records. Use the histogram data-view API for bin metrics. SVG/image snapshots preserve the painted mixed geometry and remove transient inspection controls through the existing export pipeline.
## Pivot authoring and limitations
The existing series layer selector offers Bubble and Histogram. Switching to a sample layer creates an independent numeric `samplesN` axis atomically, preserving existing category axes. Bubble X/size selectors, histogram value/aggregation/bin-count controls and the shared validated bin-boundary input edit the same public overrides. Changing a layer type clears incompatible channel and path options. Existing X/Y assignment and layer-order controls apply. The Data tab exposes the shared sample/bin view when a histogram layer is present. Optional locale captions retain older complete locale-record compatibility.
The new locale fields under `chartOptions` are `layerBubble`, `layerHistogram`, `layerSource`, `bubbleX(series)`, `bubbleSize(series)` and `histogramValue(series)`. Existing histogram bin-count/boundary/aggregation captions are reused.
Native SVG is the supported backend. Batched SVG/WebGL equivalence is not claimed. M4's per-datum encoding does not yet apply to combination layers and is rejected by its capability matrix; use existing series appearance. Navigator overview is explicitly rejected for sample-owned compositions, preventing a raw-line miniature from misrepresenting a histogram. Numeric gestures/windows/shared hover remain available. No polar composition, arbitrary financial panes, mixed time/sample axes, component slots or new histogram area/density layer is included.
---
# Link charts
URL: https://pro.rv-grid.com/guides/charts/tools/chart-linking/
Source: src/content/docs/guides/charts/tools/chart-linking.mdx
Description: Synchronize chart windows and hover through the shared Charts API.
Synchronize chart windows and hover through the shared Charts API. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Link charts**, then **Zoom first chart** or hover either chart. The second chart follows the linked X window and hover. Choose **Unlink charts** and repeat: the charts now act independently.
The buttons here are example application UI. Pivot's built-in “Link charts” dropdown discovers open Pivot charts and calls the same shared synchronization API.
## API and configuration
```ts
import { createChartSyncGroup } from '@revolist/charts/core';
// firstChart and secondChart are mounted ChartRef controllers.
const group = createChartSyncGroup({
viewport: 'x', domain: false, hover: true,
onError: (error, targetId) => console.error(targetId, error),
});
const disconnectFirst = group.connect(firstChart);
const disconnectSecond = group.connect(secondChart);
await group.flush();
// Later: disconnectFirst(); disconnectSecond(); group.destroy();
```
## Support and behavior
Targets must expose compatible axes: scale types and declared numeric units must match, and categorical linking requires compatible category identities. X, Y or XY viewport/domain propagation is configurable; domains and hover are transient peer state. No unit conversion occurs. The API does not synchronize Pivot queries, grid filters or annotation shapes.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Named X axes
```ts
const group = createChartSyncGroup({
xAxes: ['elapsed'], // Default: ['primary']; up to 33 unique identities.
viewport: 'x', domain: 'x', hover: true,
});
```
Declare the selected `axes.xAxes` identity on every chart. Each selected axis must match scale, numeric unit and symlog `constant` (default 1); categories require identical ordered IDs. Unselected axes may differ and retain their windows, including on reset. A group can select `['primary', 'elapsed']`. Named hover includes `xAxis` and shows that axis's series values. Contributions from multiple groups merge per identity and clear independently on disconnect. Named Y domain synchronization remains unsupported; Y metadata describes the primary alias. See [named axes](/guides/charts/tools/axes/#named-x-axes).
---
# Colors & series styling
URL: https://pro.rv-grid.com/guides/charts/tools/colors/
Source: src/content/docs/guides/charts/tools/colors.mdx
Description: Style series using stable IDs and supported visual channels.
Style series using stable IDs and supported visual channels. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Change settings** to swap the series colors. The sample uses grouped columns and explicit `revenue`/`costs` series IDs.
Other supported contracts include semantic measure/member colors, patterns, conditional or continuous value maps, and line dash/marker settings. Their availability differs by type; inspect `getChartDefinition(type).appearance`.
## API and configuration
```ts
await chart.update({ seriesOverrides: {
...chart.model.seriesOverrides,
revenue: { ...chart.model.seriesOverrides?.revenue, color: '#009b72' },
costs: { ...chart.model.seriesOverrides?.costs, color: '#e65b7a' },
} });
```
## Support and behavior
Register only supported settings for the chosen type: columns do not have line markers, and not every chart supports value maps or patterns. IDs are stable data IDs, not labels. Renderer palettes are runtime configuration; serialized colors and series overrides belong to the model.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
For per-category markers/fills, directed line strokes, gradients and runtime callbacks, see [datum styling and encoding](/guides/charts/tools/datum-styling/).
---
# Crosshairs
URL: https://pro.rv-grid.com/guides/charts/tools/crosshairs/
Source: src/content/docs/guides/charts/tools/crosshairs.mdx
Description: Read aligned values with a shared crosshair and tooltip.
Read aligned values with a shared crosshair and tooltip. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Move the pointer over a point, or focus a chart mark using the keyboard. The crosshair aligns with nearby visible observations. Shared mode shows visible Y series for the same category.
The example enables crosshairs without zoom gestures. Hover feedback is transient and does not select or change data.
## API and configuration
```ts
await chart.update({ navigation: {
crosshair: true, tooltipMode: 'shared',
} });
```
## Support and behavior
Requires a type with native ordered-X or numeric-XY navigation support. Numeric plots snap to nearby visible observations; shared tooltips do not invent interpolated data. Other charts keep their supported datum tooltips.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Date range selector
URL: https://pro.rv-grid.com/guides/charts/tools/date-ranges/
Source: src/content/docs/guides/charts/tools/date-ranges.mdx
Description: Choose built-in or named calendar windows in UTC or an explicit IANA timezone.
Choose calendar presets or an inclusive date range. UTC is the default; an explicit IANA timezone can govern calendar arithmetic and date inputs. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Try **1M**, **3M**, **YTD**, **1Y** or **All**, or enter custom start/end dates. Presets anchor to the latest available full-domain date, not today's date. This sample contains January–December 2026.
Months/years use UTC calendar arithmetic with month-end clamping. YTD starts at UTC midnight on January 1. Custom dates include the final day's last millisecond, clipped to available bounds.
## API and configuration
```ts
await chart.update({
axes: { ...chart.model.axes, x: { scale: 'time' } },
navigation: { enabled: true, rangeSelector: {
presets: ['1M', { id: 'fortnight', label: 'Two weeks', unit: 'week', count: 2 }, 'YTD', 'all'], custom: true,
} },
});
```
## Support and behavior
### Preset definitions
`rangeSelector.presets` accepts a dense array of up to **32** built-in strings
or `ChartCalendarPresetDefinition` objects. Omission uses all five defaults;
`[]` removes preset choices while retaining custom inputs unless disabled.
Entries display in authored order. Each built-in can appear only once.
| Definition field | Required contract |
| --- | --- |
| `id` | Unique nonempty trimmed string, at most 128 characters. Built-in IDs (`1M`, `3M`, `YTD`, `1Y`, `all`) and `__proto__`, `constructor`, `prototype` are reserved. The ID controls selection/focus and is independent of its caption. |
| `label` | Nonblank string of at most 256 characters. Displayed as text, never HTML; applications supply localized captions. Leading/trailing spaces are preserved when the caption has nonwhitespace content. |
| `unit` | `'day'`, `'week'`, `'month'` or `'year'`. |
| `count` | Integer from `1` to `10000`. |
Objects accept exactly those fields and must have ordinary/null prototypes.
Duplicate IDs, sparse arrays, unsupported fields, invalid counts/units and
malformed definitions reject before a model update commits. Parsed arrays and
definitions are owned snapshots; later caller mutation cannot change saved or
queued settings. `ChartCalendarPreset` is the union of built-ins and definitions.
Pivot exports `PivotChartCalendarPreset` and `PivotChartCalendarPresetDefinition`
from its root and charts entry points.
### Calendar semantics
Every preset anchors to the latest available full-domain instant. Synchronized
or host-provided full-domain bounds take precedence over a loaded page's data;
wall-clock time never supplies the anchor. All windows are intersected with the
available domain. Built-in `all` has priority when the viewport matches the full
domain; otherwise the first authored matching preset is selected. Different
presets may resolve to the same range on a short domain.
With the timezone omitted, days and weeks select an exact trailing UTC duration while preserving the anchor
time of day; a week is seven UTC days. A seven-day duration anchored at midnight
includes both endpoint instants, so it can contain eight daily observations.
It is distinct from selecting seven inclusive calendar dates. Months and years
use UTC calendar shifts with month-end clamping, including leap days and years
below 100. A year is twelve calendar months. For example, one year before
February 29, 2024 resolves to February 28, 2023 at the same UTC time.
A window that cannot produce finite, increasing Date-representable bounds is
disabled and does not commit a viewport. This includes shifts beyond the supported
Date range. Custom date inputs retain their existing inclusive behavior: the
end date includes its final millisecond, clipped to the available domain.
Named zones are opt-in as described below. Explicit exchange-session calendars
and ordinal financial navigation use the separate [financial session policy](/guides/charts/tools/financial-sessions/).
## Named timezone policy
Set `navigation.rangeSelector.timeZone` to an Intl-supported IANA name such as
`'America/New_York'`. Omission uses UTC and retains the previous UTC preset/input
semantics. The option applies to all built-in and named presets and both date
inputs on `navigation.xAxis` (primary when omitted). Standalone and Pivot use the
same policy, independent of the machine timezone.
```ts
await chart.update({
navigation: {
...chart.model.navigation,
rangeSelector: {
...chart.model.navigation?.rangeSelector,
timeZone: 'America/New_York',
presets: ['1M', { id: 'day', label: 'Civil day', unit: 'day', count: 1 }, 'YTD', 'all'],
custom: true,
},
},
});
```
Names must be nonempty trimmed strings of at most 128 characters recognized by
the host's `Intl` timezone data. UTC and supported IANA aliases are accepted;
invalid names, offset-only strings, null and other types reject before an update
or restore commits. Persisted names are retained verbatim. Server/browser hosts
must support the authored zone; timezone rules follow their installed timezone
data. No silent machine-timezone fallback occurs.
Days/weeks subtract civil dates (seven days per week), so one day can span 23 or
25 elapsed hours. Months/years preserve civil clock fields and clamp month ends
and leap days. YTD starts at the zone's January 1 boundary. All presets still
anchor to the **full domain's maximum instant**, never the current viewport,
loaded page or current date. `all` returns that domain unchanged. A trailing
civil-day window includes both endpoint instants, distinct from inclusive date
input selection.
When a target clock time repeats in a fold, the earlier instant wins. Missing
clock times advance to the first valid civil minute, resetting seconds and
milliseconds to zero. A skipped target date advances similarly for presets.
Otherwise seconds and milliseconds are preserved. Custom date inputs include the
first valid civil minute of the start date through one millisecond before the next
civil day after the end date. Midnight folds choose the earlier instant;
midnight gaps advance to the first valid civil minute. A skipped **authored** start/end
date is invalid and does not commit; a skipped day immediately following a valid
end date is advanced over when resolving the exclusive boundary. For example,
New York March 10, 2024 selects `2024-03-10T05:00:00Z` through
`2024-03-11T03:59:59.999Z`.
The selector displays dates in this zone and shows the authored zone name beside the presets. Axis tick labels and tooltips retain
their existing formatting policy; setting the selector zone does not change
them. Instants remain the projection, event, export and viewport identity.
Custom inputs support civil years 0001–9999; outside that range fields are empty
and presets remain available where representable. Invalid/nonfinite or
unrepresentable shifts are unavailable, including Date-limit overflow; bounds
are never wrapped or saturated into fabricated instants.
Saved models retain the policy and exact viewport instants; restore does not
reinterpret them as wall times. Rejected asynchronous updates restore accepted
policy and instants together. Named-X controls preserve independent primary
windows. Enabling Pivot's date-range control retains an already-authored configuration.
Disabling it removes the range-selector configuration, including the zone and
preset definitions; supply them again when re-enabling. Exchange sessions, holidays and ordinal
trading time are outside this policy.
## State and cleanup
Preset selection commits a time viewport with reason `'calendar'`; named time
axes update their own viewport without replacing primary state. The viewport
participates in existing navigation, synchronization and async data requests.
Repaint derives the selected ID from committed bounds. Failed async commits
restore the previous selection and report the existing localized range error.
Controls retain their existing hidden/disabled UI policy and preview behavior.
Saved models retain definitions and committed viewport bounds. Pending custom
input edits are not saved. Normal chart exports exclude the interactive controls.
Pivot's date-range opt-in preserves authored preset definitions; applications
configure the same `navigation.rangeSelector` object through creation, update or
saved models. Enabling the inspector option retains an existing configuration; disabling it removes
the selector configuration. Settings are whole-object replacements: spread values you want to keep
and await updates before reading state. Destroy the chart on host unmount.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Datum styling and encoding
URL: https://pro.rv-grid.com/guides/charts/tools/datum-styling/
Source: src/content/docs/guides/charts/tools/datum-styling.mdx
Description: Stable source styling, gradients, patterns, marker policies and runtime callbacks for Charts and Pivot.
Standalone Charts and Pivot use the same native renderer contract. Add `seriesOverrides[seriesId].encoding` to saved models; add `rendererOptions.datumStyle` to runtime configuration. This is an API-level authoring feature; the generic appearance inspector does not edit rule collections.
```ts
const id = chart.model.dataset.categories[0].id;
await chart.update({
seriesOverrides: {
...chart.model.seriesOverrides,
revenue: {
...chart.model.seriesOverrides?.revenue,
encoding: {
style: { fill: '#3366cc', marker: 'circle', markerSize: 5 },
datums: [{ categoryId: id, style: {
marker: 'diamond', markerSize: 9,
fill: { type: 'pattern', pattern: 'diagonal', color: '#ff9900' },
} }],
},
},
},
});
```
Use actual category/series IDs from the dataset, never visible indexes or labels. Unmatched IDs remain dormant so restoring a window or reintroducing a category restores its style. Reordering does not retarget rules. Whole override records replace on update; preserve other fields explicitly.
## Capability matrix
| Native family | Datum fill/stroke | Shape/radius | Directed segment stroke |
| --- | --- | --- | --- |
| `line`, `smoothLine`, `steppedLine` | Painted markers | Yes | Yes |
| `area`, `steppedArea`, `stackedArea`, `normalizedArea` | Painted markers; default style also paints area | Yes | Yes, outline only |
| `groupedColumn`, `stackedColumn`, `normalizedColumn`, `groupedBar`, `stackedBar`, `normalizedBar` | Yes | Rejected | Rejected |
| `scatter` | Yes | Yes | Rejected |
| Combinations, bubble, range, polar, financial, heatmap, other families | Rejected | Rejected | Rejected |
| Batched SVG | Whole-scene fallback to individual SVG with `data-chart-renderer-fallback="datum-style"` | Same fallback | Same fallback |
Existing batched requests are preserved in saved state. Any encoding, even an empty object, or runtime `datumStyle` forces that fallback. Clearing saved encodings and recreating without runtime callbacks restores batching. WebGL requests also fall back to individual SVG for authored encodings or runtime `datumStyle`; GPU styling equivalence is not claimed. External adapters must define their own support. Unsupported native families reject saved encodings; a shared renderer's runtime `datumStyle` callback is not invoked for those families.
Marker rules style glyphs that the existing marker visibility/density policy paints. They do not force hidden or sampled-out markers into the scene, change numeric domains, alter raw activation/CSV, or change path inspection order. Bars retain their rectangle geometry. Shape/radius overrides for bar callbacks are invalid results, with local fallback and a diagnostic.
## Persistable fields and validation
`ChartDatumEncoding` supports only:
| Field | Meaning and limits |
| --- | --- |
| `style` | Default `ChartDatumStyle` for this series. Path fill applies to the area, stroke to its outline; marker fields apply to painted markers. |
| `datums` | Up to 10,000 `{ categoryId, style }` entries, unique category IDs per series. IDs are nonempty strings up to 2,048 characters. Applies to source marks, not an entire line. |
| `segments` | Up to 10,000 `{ from, to, style }` entries, unique directed ID pairs. Same ID limits. Line/area stroke fields only. |
`ChartDatumStyle` supports only:
| Field | Values |
| --- | --- |
| `fill`, `stroke` | Existing hexadecimal colors, or the paint objects below. No external URLs or arbitrary SVG. |
| `strokeWidth` | Finite 0–64 SVG units. |
| `strokeDasharray` | `none` or nonnegative decimal lengths separated by commas/spaces; at most 128 characters. |
| `fillOpacity`, `strokeOpacity` | Finite 0–1. |
| `marker` | `circle`, `square`, `diamond`, `triangle`, `cross`, `star`, `wye`. |
| `markerSize` | Finite radius 0–128 SVG units; zero paints no visible glyph. |
Paint objects:
```ts
const gradient = {
type: 'linearGradient',
direction: 'horizontal', // horizontal | vertical; default vertical
stops: [
{ offset: 0, color: '#3366cc' },
{ offset: 1, color: '#ff9900', opacity: 0.8 },
],
};
const pattern = {
type: 'pattern',
pattern: 'dots', // dots | diagonal
color: '#111111',
background: '#ffffff', // omitted: current chart theme background
size: 8, // finite tile size 2–128 SVG units; default 8
};
```
Gradients accept 2–32 stops, nondecreasing finite offsets 0–1, and optional stop opacity 0–1 (default 1). They use the current plot bounds in user-space coordinates, so horizontal/flat line segments remain visible and all marks share a coherent plot-wide gradient. Patterns use user-space tiles. Definitions are deduplicated per paint and recreated on update/resize/theme change; old definitions disappear with the old paint. Unknown keys, duplicate rules, unsupported family fields, invalid colors/numbers and functions in saved encodings reject the transaction before replacing the visible chart. Parsers own their returned objects.
## Runtime callback and precedence
```ts
const chart = await createChart({
container, chartType: 'scatter', dataset,
bindings: { x: ['distance'], y: ['revenue'] },
rendererOptions: {
datumStyle(context) {
if (context.kind === 'datum' && context.value !== null && context.value < 0)
return { marker: 'triangle', markerSize: 8, stroke: '#bb2233' };
return undefined;
},
},
});
```
`ChartDatumStyleContext` contains `chartId`, `chartType`, `renderMode` (`chart` or `preview`), `kind` (`datum` or `segment`), canonical `categoryIndex`/`seriesIndex`, immutable source `category`/`series` snapshots and raw `value`. A segment adds `to: { categoryIndex, category, value }` for its raw destination. Hiding/projecting series does not renumber canonical callback indexes. Normalized/stacked coordinates never replace raw callback values. Snapshot objects and nested arrays are frozen; callbacks cannot mutate model data through them.
Precedence, from low to high: native palette/value-map/member/series appearance → encoding default `style` → matching datum or directed segment rule → runtime callback fields → existing `elements` customization → `onRender`. Missing/undefined fields retain the preceding style. Existing selection/highlight interaction styles still apply after datum encoding, retaining their stroke/filter and dimming. Custom `elements`/`onRender` retain their existing advanced override semantics. New marker size/shape never affects the visible range/domain.
The callback runs synchronously once per painted eligible datum, and once per nonzero-X interval of each painted source line run, on complete paints including resize, navigation, theme changes and previews. Boundary intervals may include original source neighbors outside the visible window; clipping restricts paint. It does not run for null/hidden/nonpainted marks, hover/focus, clean export or CSV. Avoid side effects and expensive work; there is no cross-paint result cache or guaranteed invocation order. Returning `undefined` retains saved styling. Invalid return objects fall back to saved styling for that item and emit `invalid-datum-style` once per SVG lifetime via `onDiagnostic` (or console warning). Callback exceptions propagate, like existing formatters. `onDiagnostic` exceptions are isolated.
Runtime callbacks are captured by the renderer instance. Recreate the chart/renderer to replace or remove them. Functions are never serialized into `chart.model` or server results. After `renderChartToSVG`, pass them again to `hydrateChart({ container, model, rendererOptions })`; hydration paints the current runtime result. Saved functions are rejected rather than silently persisted.
## Segment semantics and limits
Directed rules match adjacent finite source observations in the accepted X order, including reverse projection. Reordering that changes source adjacency can make a rule dormant. Null/calendar gaps are not bridged unless the existing `connectNulls` policy explicitly joins that run. Equal-X pairs have no horizontal interval and keep the base stroke; they do not invoke segment callbacks.
Segments reuse the original full curve and clip it between source X coordinates, preserving smooth/stepped curve geometry, current plot clipping and area fill. They style the full curve within that X slab, not a newly fitted two-point curve; a curve that overshoots an endpoint follows that geometric slab policy. Zero/translucent stroke opacity replaces the original stroke beneath the interval. The first/last stepped overhang keeps the default series style. Per-segment fields are restricted to `stroke`, `strokeWidth`, `strokeOpacity`, `strokeDasharray`.
Any authored segment rules or runtime `datumStyle` disables line/area sampling for that plot, preserving source adjacency. This explicit opt-in can increase SVG/CPU/memory cost substantially: each styled interval uses a clip/use pair, while full-run geometry is stored once. Do not use segment callbacks as a dense-renderer acceleration API. Per-datum styling does not add general slots, user components or arbitrary geometry.
## SSR, accessibility, theme and exports
Declarative styles render through the same pipeline in SSR and the browser. Runtime callbacks can run during server rendering when supplied, but must be supplied again for hydration. Accessible names, raw activation, source keyboard traversal and focus rings keep the shared canonical interaction contract. Authored marker geometry is used for actual pointer hit regions; keyboard inspection remains available under existing density rules.
Theme-background pattern tiles follow the resolved light/dark background; explicitly supplied colors and backgrounds remain authored. Applications remain responsible for choosing readable contrast, including on gradients. A marker shape, pattern or stroke can supplement color without changing accessible names.
Clean SVG/image/PDF snapshots retain authored and already resolved runtime paints/markers/segments, including local definitions and references. They remove transient hover/focus/brush glyphs using the existing export pipeline, without invoking style callbacks. Segment definition geometry preserves instance paint inheritance in exported SVG. Raster and vector encoders consume this same clean SVG; third-party PDF encoders must support SVG `use`, clip paths, gradients and patterns. No new PDF dependency or feature-equivalence guarantee is introduced.
Public aliases in both Pivot entrypoints: `PivotChartDatumPaint`, `PivotChartDatumStyle`, `PivotChartDatumEncoding`, `PivotChartDatumStyleContext`. For Pivot, pass runtime options to `createPivotChartsRenderer({ datumStyle })`, then supply that instance as `renderer` to `createStandalonePivotChart` or the Pivot plugin. The legacy standalone Pivot wrapper does not accept `rendererOptions`. This shared feature does not add a Pivot-owned style engine.
---
# Select, move & resize
URL: https://pro.rv-grid.com/guides/charts/tools/drawing-tools/
Source: src/content/docs/guides/charts/tools/drawing-tools.mdx
Description: Edit committed annotations with the shared drawing toolbar.
Edit committed annotations with the shared drawing toolbar. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
The **Select** pointer edits annotation shapes. It is separate from [data selection](../selection/), which selects observations. This example starts with a saved rectangle.
1. Choose **Select**, then click the rectangle to open its editor and handles.
2. Drag its body to move it or a corner to resize it. Expand **Coordinates** for exact values.
3. Choose **Save** to commit, **Cancel** or Escape to discard, or **Delete** to remove it.
Focus a handle and use arrow keys; Shift moves a larger step. Ctrl/Command+Enter saves the draft. New drawing returns to Select after placement. Unsaved drafts are disposed when data or viewport changes.
## API and configuration
```ts
await chart.update({
annotations: { ...chart.model.annotations, editable: true },
});
// Read committed edits, including shapes, from the model.
const stop = chart.subscribe(model => console.log(model.annotations));
// Later: stop(); chart.destroy();
```
## Support and behavior
Drawing needs a native chart with Cartesian plot geometry and annotation editing support. The toolbar is opt-in through `annotations.editable`; it is not a Pivot dependency. Polar and geographic drawing are not supported. Editing gestures claim the pointer so the same drag does not also pan the chart.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts. Committed annotations appear in SVG/PNG/PDF output; CSV remains data-only. Shape IDs must be unique across shapes, references and datum notes. `{ annotations: {} }` clears annotations and editing opt-in.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Named-axis coordinates
Value references use `axisId` to select a declared `axes.yAxes` ID while retaining the `axis: 'primary' | 'secondary'` discriminator. Category references and drawing shapes use `xAxis`; shapes independently use `yAxis`. Numeric coordinates use `{ scale: 'linear' | 'log' | 'symlog' | 'sqrt', value }` and must match the selected X scale. Semantic category paths and UTC time coordinates remain supported. The shared drawing editor can reassign a draft through its actual projection; Save preserves identity and Cancel preserves the authored model. See [named annotations and examples](/guides/charts/tools/axes/#annotations-on-named-x-axes).
---
# Export & download
URL: https://pro.rv-grid.com/guides/charts/tools/exports/
Source: src/content/docs/guides/charts/tools/exports.mdx
Description: Download chart data and rendered images.
Download chart data and rendered images. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose a download button for **CSV**, **SVG**, **PNG** or **PDF**. These are application buttons using the native controller. PDF output is raster by default; the optional vector-PDF integration needs an application-supplied encoder.
Saved annotation shapes appear in image exports. Drawing controls, unfinished drafts and hover feedback are not exported. CSV contains data, not drawing shapes.
## API and configuration
```ts
await chart.download({ fileFormat: 'svg', fileName: 'monthly-performance' });
await chart.download({ fileFormat: 'png', fileName: 'monthly-performance' });
await chart.download({ fileFormat: 'pdf', fileName: 'monthly-performance' });
const csv = chart.getCSV({ dataScope: 'all' });
const imageUrl = await chart.getImageDataURL({ width: 1200, height: 600 });
```
## Support and behavior
Check `chart.capabilities.downloadFormats` for the active backend. Await committed updates before exporting. Current-scope CSV follows the visible window; `dataScope: 'all'` exports all canonical rows currently held by the controller. Export never fetches unloaded server data. Image output uses the displayed view.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Export lifecycle hooks
```ts
import { createChart, type ChartExportHooks } from '@revolist/charts';
const exportHooks: ChartExportHooks = {
async before(context) {
return await approveExport(context.options, context.signal);
},
after(context) { recordExport(context.operationId); },
error(error, context) { reportExportError(error, context.operationId); },
};
const chart = await createChart({ container, chartType: 'line', data, mapping, exportHooks });
// Also: createStandalonePivotChart({ ..., exportHooks })
// or grid.pivotCharts = { renderer: createPivotChartsRenderer(), exportHooks }.
```
| Hook | Result and lifetime |
| --- | --- |
| `before(context)?` | Sync/async `boolean` or `void`; `false` cancels before capture. Downloads resolve quietly, image data URL requests reject `AbortError` and notify error. |
| `after(context)?` | Sync/async `void`; once after successful capture/download initiation. It does not confirm a filesystem save. |
| `error(error, context)?` | Sync/async `void`; once for before/export/after failure or in-flight abort. Its errors cannot replace the primary rejection. On abort, notification starts without awaiting the host observer. |
`ChartExportContext` includes `chartId`, `chartType`, `host` (`standalone` or
`pivot`), `scope` (`chart` or `selection`), canonical `selection`, `bindings`,
`viewport`, full committed `dataset`, optional active Pivot `drillScope`,
`operationId`, `kind` (`download` or `imageDataURL`), copied export `options`
and an operation `signal`. The options' source signal is represented by
`context.signal`. Every callback receives an independent snapshot with the same
operation ID. Callback edits do not change the export or model.
Hooks cover controller downloads (including CSV) and PNG image data URLs,
subject to existing backend capabilities. Pure `getCSV`, `getChartCSV` and
`exportChartToCsv` serializers remain synchronous and do not invoke hooks.
Pivot validates download options before entering the lifecycle. A failed
operation rejects and later operations can proceed.
The source request signal or chart destruction/replacement aborts pending hooks
and encoders. Already initiated downloads and host side effects cannot be undone.
Await committed updates first. Standalone retains its operation queue: **do not
await another queued update/export on the same chart from a hook**. Schedule it
after the export settles. Pivot rejects capture if its model or dataset changed
while an async before hook was pending. Keep host async work signal-aware.
Existing `onBeforeDownload` remains synchronous; returning false still cancels.
Pivot's cancelable `pivot-chart-before-download` also stays supported. Ordering:
new before, legacy veto, exporter, legacy success notification, new after. A legacy
veto skips after without error. Existing Pivot events stay download-only; new
hook errors are observed independently. Without new hooks, Pivot preserves its
synchronous export startup. Existing transition, viewport and backend image/vector
export policies remain unchanged.
Hooks and [surface actions](/guides/charts/customization/#host-actions-and-dynamic-submenus)
are runtime-only: reattach after restore/hydration. Menus and controls are excluded
from normal exports. Pivot exposes shared contract aliases `PivotChartHostContext`,
`PivotChartHostAction`, `PivotChartHostActionResolver`, `PivotChartHostActionsOptions`,
`PivotChartExportContext` and `PivotChartExportHooks` from its root and `/charts`
entries. This does not add server export services or replace Pivot's record actions.
## Export options reference
`getCSV` accepts only `ChartCsvOptions`; `download` adds download fields to the
same CSV options. CSV-only settings apply to CSV output. Image capture is PNG
through `getImageDataURL`; it is not a CSV or SVG string serializer.
| Option | Default / contract |
| --- | --- |
| `dataScope` | `'current'`: visible series and window membership according to the family. `'all'`: complete committed canonical frame, including hidden series, without acquiring unloaded records. |
| `valueFormat` | `'raw'` finite numeric values. `'formatted'` uses provided display values or the locale number formatter. Nulls export as empty cells. |
| `columnDelimiter` | `','`; nonempty string of at most 16 UTF-16 units without NUL or double quote. |
| `rowDelimiter` | `'\r\n'`; the same validation. Neither delimiter may contain the other. |
| `bom` | `false`; `true` prepends a UTF-8 BOM character for spreadsheet consumers. |
| `escapeFormulas` | `false`; `true` prefixes formula-like text starting with `=`, `+`, `-`, `@`, tab or carriage return with an apostrophe. Numeric values remain numbers. |
| Download `fileFormat` | `'png'`; native support is `'png'`, `'svg'`, `'pdf'`, `'csv'`, subject to renderer capabilities. |
| Download `fileName` | Localized download name when omitted/blank. A recognized image/CSV extension is replaced with the selected format. |
| Image/download `width`, `height` | Current SVG view-box size when omitted, independently per dimension. Finite positive numbers up to 16,384 px, rounded to integer pixels; combined output must not exceed 64 × 1024 × 1024 pixels. This scales the committed snapshot rather than changing the live viewport. CSV rejects image dimensions. |
| Image/download `signal` | Optional operation `AbortSignal`. Already initiated browser saves cannot be undone; rejection does not corrupt later exports. |
| Image `fileFormat` | Only `'png'`, including when omitted. Use `download` for other supported formats. |
```ts
const csv = chart.getCSV({ dataScope: 'all', valueFormat: 'formatted',
columnDelimiter: ';', rowDelimiter: '\r\n', bom: true, escapeFormulas: true });
await chart.download({ fileFormat: 'csv', fileName: 'revenue',
dataScope: 'all', escapeFormulas: true });
```
CSV serializers quote delimiter/quote/newline-containing text. Source IDs and
values remain canonical regardless of sampled SVG geometry or GPU paint.
Histogram sample/bin views and combination sample-owned layers keep their
family-specific data-view contracts; CSV is not a dump of synthetic bin marks.
`exportChartToCsv(dataset, model, options, localeText)` from `/core` is the pure,
synchronous equivalent when no mounted controller is needed.
`rendererOptions.imageEncoder(svg, { width, height })` can replace PNG encoding;
`pdfEncoder` replaces PDF encoding and can declare `encoding: 'raster'`,
`'vector'` or `'custom'`. Encoders receive the clean committed SVG snapshot.
Default PDF is raster; [vector PDF integration](/guides/charts/installation/)
requires an application-supplied runtime. Check `capabilities.pdfEncoding` for
the installed encoder policy. Runtime encoders are not saved and must be
reattached after restore/hydration. Custom encoders own their asynchronous work;
chart cancellation prevents stale acceptance but cannot undo host side effects.
---
# Financial sessions and trading toolbar
URL: https://pro.rv-grid.com/guides/charts/tools/financial-sessions/
Source: src/content/docs/guides/charts/tools/financial-sessions.mdx
Description: Explicit exchange-session and ordinal policies for native financial charts, with timestamp-preserving navigation and drawings.
`candlestick`, `ohlc` and `volumePrice` support an explicit session policy on a time X axis. Omit `axes.x.sessions` to retain the existing continuous-time behavior. No exchange, weekday, holiday or extended-hours calendar is inferred. This is a chart projection policy; prices, volumes, category IDs and source timestamps do not change.
```ts
import {
createChart, createChartSessionCalendar,
type ChartFinancialSessionPolicy,
} from '@revolist/charts';
// Illustrative supplied sessions, not an exchange calendar service.
const calendar = createChartSessionCalendar({
timeZone: 'America/New_York',
sessions: [
{ date: '2024-03-08', open: '09:30', close: '16:00' },
{ date: '2024-03-11', open: '09:30', close: '16:00' },
{ date: '2024-03-12', open: '09:30', close: '13:00' },
],
});
const policy: ChartFinancialSessionPolicy = { mode: 'session', calendar };
const chart = await createChart({
container,
chartType: 'candlestick',
data: prices,
mapping: {
category: { field: 'timestamp' },
series: ['open', 'high', 'low', 'close'].map(field => ({ field })),
},
bindings: { open: ['open'], high: ['high'], low: ['low'], close: ['close'] },
axes: { x: { scale: 'time', sessions: policy } },
navigation: {
enabled: true, tradingToolbar: true, navigator: true, crosshair: true,
},
annotations: { editable: true },
});
```
## All policy options
| Option | Contract |
| --- | --- |
| `axes.x.sessions?` | Persistable `ChartFinancialSessionPolicy`. Also supported on each `axes.xAxes[id]`. Financial families and explicit `scale: 'time'` only. |
| `sessions.mode` | `session`: elapsed open-session time, with all supplied closures removed. `ordinal`: equal spacing of explicitly supplied observation anchors, independent of elapsed time or loaded pages. |
| `sessions.calendar` | Required `ChartSessionCalendar`: `{ timeZone, sessions: [{ open, close }] }`. `open`/`close` are actual epoch milliseconds. |
| `calendar.timeZone` | Required valid IANA zone (including UTC). Used for compressed-axis tick display; it does not reinterpret already stored instants. |
| `calendar.sessions` | 1–100,000 ordered, nonoverlapping, positive-duration intervals. Each boundary is a safe integer within JavaScript Date limits; summed open duration must be a safe integer. Adjacent intervals are allowed. |
| `sessions.instants` | **Required only for ordinal mode**: 2–100,000 strictly increasing actual timestamps, each inside a supplied interval and at a distinct open-time coordinate. Full-source anchors, not the current loaded page. Every observed timestamp must appear in this list. Repeated observations at the same timestamp may share one anchor. |
Intervals are **half-open `[open, close)`** for observations. A bar timestamp equal to `close` is outside that interval. Supply boundaries matching the source's timestamp convention, including extended hours or close-stamped bars, rather than relabeling their timestamps. Data outside the calendar or ordinal anchor set is rejected; it is never silently discarded. The last close is permitted as a **domain endpoint**, though it is not an observation timestamp.
Session mode preserves proportional spacing within open intervals. Ordinal mode assigns anchor `i` to coordinate `i`, interpolating between anchors in open-session time. Both modes collapse closures to a seam. The inverse at an exact seam chooses the next supplied open (the final seam resolves to the last close). Consequently, a time window endpoint authored inside a closure remains an actual stored instant, but an endpoint generated by navigation at that seam can resolve to the next open. Neither behavior invents an activated observation.
Ordinal anchors are global and explicit so switching detail pages cannot move the same timestamp to a different slot. Without full-source domain metadata, the first/last global anchors own the ordinal domain, even for a partially loaded frame. Explicit remote full-domain limits take precedence. Do not regenerate them from each page. A single seam cannot own two distinct ordinal anchors: for example, an interval close and the next interval open are not two different open-time positions. Use a calendar consistent with the source's bar timestamp convention. Policy changes deliberately change geometry; they do not change source records.
## Civil calendar helper
`createChartSessionCalendar(input)` returns the serializable instant calendar above. Public docs and applications use the Charts API; civil arithmetic remains owned by the shared dates package.
| Helper option | Contract |
| --- | --- |
| `timeZone` | Required named zone. |
| `sessions` | Explicit ordered civil sessions, 1–100,000 entries. No recurrence or holiday inference. |
| Entry `date` | Opening civil date, `YYYY-MM-DD`, including years below 100. Invalid/skipped dates reject. |
| Entry `open`, `close` | `HH:mm` or `HH:mm:ss`, 00–23 hours, 00–59 minutes/seconds. |
| Entry `endDate?` | Explicit closing civil date for overnight sessions. Omission uses `date`; a non-increasing interval rejects. |
| `disambiguation?` | `reject` (default), `earlier`, `later`. Ambiguous fold boundaries require an explicit choice. Gap/skipped boundaries always reject, including with earlier/later. |
DST changes affect the resolved actual instants and open duration. Short/long sessions and market closures must be explicitly supplied. The helper never generates missing dates or shifts a nonexistent clock forward. The chosen civil disambiguation is already resolved into stored boundaries; persist the returned calendar, not a runtime callback.
## Named windows, inspection and annotations
Assign the policy independently to a primary or named X axis. Bind a financial group through its close-owned `seriesOverrides[closeSeriesId].xAxis`, or the existing paired volume-price assignments. Set `navigation.xAxis` to select the controlled axis. Existing axis reversal is honored by marks, crosshairs, navigator handles and viewport inversion. Price/volume panes share the same session projection.
All viewports remain `{ scale: 'time', min, max }` in **actual epoch milliseconds**, including `viewport.xAxes[id]`. Never persist ordinal indices or session elapsed offsets as timestamps. Authored window bounds inside a closure are retained. A closed-only window displays an empty finite frame; it does not fabricate a nearest candle. Without larger full-domain metadata, a one-observation session frame has zero time extent and disables range selection. `missingPeriods` continues to describe authored path gaps; session policy controls spacing.
Crosshairs and raw activation use the existing canonical datum inspection owner. CSV/data views/export values retain actual source category timestamps and raw OHLC/volume values. Images and vectors retain the displayed compressed geometry. Tick callbacks receive actual instants and can override the calendar-zone fallback label. Existing calendar range presets keep their own explicit `rangeSelector.timeZone` policy; select the same zone when civil control boundaries should match session display.
Existing drawing controls persist time coordinates and named X identity. Pointer-to-time conversion uses the same inverse as navigation; authored annotations survive restore. Points in a closure project to its collapsed seam. Coordinates outside calendar coverage extrapolate for clipping; observations and full domains outside coverage still reject. No Fibonacci, channel tool or arbitrary pane composition is included.
## Trading toolbar
`navigation.tradingToolbar?: boolean` opts financial charts into a shared management strip with the existing zoom, pan and reset actions. The existing annotation editor joins that strip when `annotations.editable` is enabled. Omission keeps the existing SVG navigation controls. It does not enable a new navigation/drawing model or automatically enable gestures: use existing `navigation.enabled`, `navigator`, action policies and annotation options.
Mouse, touch and keyboard use ordinary buttons. Actions retain the existing `navigation.actions` hidden/disabled settings, selected X axis, cancellation and rollback behavior. Keyboard action focus is retained after repaint. Drawing tools retain their own keyboard/Escape/Save behavior and annotation UI policies. `uiControls['navigation.tradingToolbar']` hides/disables **navigation buttons**; annotation controls continue to follow `annotations.editable` and their own policies. Preview mode omits interactive controls. Toolbar DOM is outside SVG and excluded from normal image/vector exports.
`localeText.navigation.tradingToolbar?` overrides the group label (default `Trading toolbar`). Existing navigation labels/button texts and annotation editor locale options remain authoritative. No additional toolbar callback or saved DOM is required.
## Windowed sources and limits
The complete supplied calendar must cover the full remote source domain, not only returned rows. Full-domain limits, including named X domains, are validated against calendar coverage. An uncovered response fails and retains the previous committed frame. Returned observations must satisfy the calendar/ordinal list. No browser-local weekday arithmetic or loaded-page inference changes the remote domain.
`ChartDataSourceRequest.timePolicies?` is an optional record keyed by `primary` or named X ID containing each explicit policy. `viewport` remains actual timestamp ranges. Existing providers that do not need the hints can ignore them, but their responses must still satisfy the policy. Request/cache identity includes axis policies; replacement cancels stale work through the existing abort/generation owner. Detail and overview responses must use source timestamps consistent with the same global policy; synthetic overview timestamps missing from an ordinal anchor list reject.
Pivot forwards these hints as `PivotChartViewportDataRequest.chart.timePolicies`. Existing Pivot semantic scope/query/correlation checks remain in force. A financial time policy changes projection and source window hints; it does not change grouping, record identity or drill-through.
## Pivot, persistence and capability limits
Both `createStandalonePivotChart` and `PivotChartsPlugin` reuse the shared policy, toolbar and inverse projection. Pivot exports `createPivotChartSessionCalendar`, `PivotChartSessionCalendar`, `PivotChartSessionCalendarInput` and `PivotChartFinancialSessionPolicy` from its root and `/charts` entries.
```ts
import { createPivotChartSessionCalendar } from '@revolist/pivot/charts';
const calendar = createPivotChartSessionCalendar({
timeZone: 'UTC', sessions: [{ date: '2024-01-02', open: '09:00', close: '17:00' }],
});
// Pass axes/navigation/annotations through the existing chart create/update APIs.
```
Save the ordinary chart model: policies, returned calendars, ordinal anchors, named timestamp windows, toolbar flag and authored annotations are serializable. Restoring requires the same explicit calendar coverage and actual source timestamps. Runtime source handles still need reattachment. Update rejection retains the previous model; cancelled or stale source work cannot replace the current committed instants or policy.
This contract covers native financial SVG rendering and its existing exports. It makes no GPU, third-party-adapter, exchange-calendar-service or arbitrary trading-tool equivalence claim.
---
# Legend & series visibility
URL: https://pro.rv-grid.com/guides/charts/tools/legend/
Source: src/content/docs/guides/charts/tools/legend.mdx
Description: Hide and restore series through the native legend.
Hide and restore series through the native legend. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Click **Revenue** or **Costs** in the legend to toggle that series. Click again to restore it. Keyboard users can focus legend items and activate them.
`legendAction: 'toggleVisibility'` changes persisted series overrides. The default focus action emphasizes related marks instead of changing visibility.
## API and configuration
```ts
await chart.update({ presentation: {
...chart.model.presentation,
legendPosition: 'bottom', legendAction: 'toggleVisibility',
} });
// Equivalent authored visibility change:
await chart.update({ seriesOverrides: {
...chart.model.seriesOverrides,
costs: { ...chart.model.seriesOverrides?.costs, visible: false },
} });
```
## Support and behavior
Check the type's `appearance.seriesVisibility` and renderer capability before enabling visibility toggling. Legend placement can be top/right/bottom/left or hidden according to the public contract. Hiding a series changes displayed marks; it does not delete canonical data.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Live updates & transactions
URL: https://pro.rv-grid.com/guides/charts/tools/live-data/
Source: src/content/docs/guides/charts/tools/live-data.mdx
Description: Update row-backed charts without recreating the controller.
Update row-backed charts without recreating the controller. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Update first row** to increase January revenue by 30; restore it with the same button. The controller commits a validated row transaction and keeps the other rows.
This is an explicit user-triggered update. Applications can call the same API from their own event or streaming transport.
## API and configuration
```ts
await chart.applyTransaction({ update: [{
date: Date.UTC(2026, 0, 1), month: 'Jan', revenue: 95, costs: 45,
}] });
// add, remove and addIndex are also supported.
const stop = chart.subscribe(model => console.log(model.dataset.revision));
// Later: stop();
```
## Support and behavior
Transactions require row-backed mode with a category mapping. Updated keys must identify existing rows; add/remove keys must obey uniqueness rules. Dataset-only and async-source charts reject row transactions. `update({ data })` replaces the row set using the current mapping; invalid updates retain the previous successful frame.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Localization
URL: https://pro.rv-grid.com/guides/charts/tools/localization/
Source: src/content/docs/guides/charts/tools/localization.mdx
Description: Translate shared chart controls without changing the data.
Translate shared chart controls without changing the data. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
The drawing toolbar in this example uses French labels such as **Sélectionner**, **Ligne** and **Flèche**. Hover or keyboard-focus the icons to read the translated labels. The plotted data labels stay independent of control translations.
Pass partial locale overrides at creation; unspecified strings keep their defaults. Locale functions and callbacks are runtime configuration.
## API and configuration
```ts
const chart = await createChart({
container, data: rows, mapping,
annotations: { editable: true },
localeText: { annotations: { editor: {
select: 'Sélectionner', line: 'Ligne', arrow: 'Flèche',
save: 'Enregistrer', cancel: 'Annuler',
} } },
});
```
## Support and behavior
`localeText` is creation-time configuration rather than a chart model patch. It covers navigation, annotation editing, runtime messages and other shared labels. It does not translate application-authored titles, category labels or values; provide those yourself. Use the public locale types for the complete supported key set.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Locale sections and formatting
`createChartsLocaleText(localeText?, overrides?)` resolves partial nested
`ChartsLocaleTextOptions` against `DEFAULT_CHARTS_LOCALE_TEXT`. The second
argument wins over the first; supplied values win over built-in defaults.
Use the complete resolved object for pure data serializers that need
`ChartsLocaleText`. Keep formatter functions synchronous and deterministic.
| Public sections | Coverage |
| --- | --- |
| `renderer`, `data`, `dataTable`, `histogramData` | Numeric/date/observation text, hierarchy separators, source summaries, tabular and histogram data labels. |
| `navigation`, `interaction`, `legendPositions` | Navigation/range preset captions, inspection modes and legend placement names. |
| `annotations`, `colors` | Drawing/editor/status labels, color controls and nested picker labels. |
| `chartOptions.labels`, `.descriptions`, `.selectValues` | Option headings, help and select-value labels used by the shared inspector. |
| `chartTypes`, `chartDescriptions`, `gallery` | Native catalog names/descriptions and grouped gallery UI. |
| `inspector`, `context`, `dialog`, `actions`, `controls`, `status` | Shared surface controls, dialogs, management state and status messages. |
| `query`, `drill`, `totals`, `drillSyncModes`, `themes`, `contextMenu`, `uiErrors` | Shared contracts consumed by Pivot's query/drill/management adapters. These strings do not add those adapters to standalone Charts. |
| `links`, `dataSource`, `underlyingRecords` | Chart linking, async load/retry and source-record inspection messages. |
```ts
import { createChartsLocaleText } from '@revolist/charts/core';
const number = new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 1 });
const localeText = createChartsLocaleText({
renderer: { formatNumber: value => number.format(value) },
dataTable: { categoryHeader: 'Période' },
dataSource: { loading: 'Chargement…', retry: 'Réessayer' },
});
```
The public `ChartsLocaleTextOptions` type provides the exact leaf keys and
function signatures for each section; use it with `satisfies` when authoring
translation modules. Locale formatting changes display text, not instants,
source IDs or numeric values. Timezone-aware range arithmetic is independently
configured by `navigation.rangeSelector.timeZone`; axis date display uses its
own formatting policy. See [named-timezone calendar ranges](/guides/charts/tools/date-ranges/).
Recreate the renderer/controller (or replace a wrapper's creation options) to
apply new locale configuration; functions are omitted from saved models.
---
# Navigator
URL: https://pro.rv-grid.com/guides/charts/tools/navigator/
Source: src/content/docs/guides/charts/tools/navigator.mdx
Description: Adjust the visible X window using a full-domain overview.
Adjust the visible X window using a full-domain overview. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Drag either navigator handle to change the window, or drag the band to move it. The labels below the overview show its full domain and remain fixed when the selected window moves. Each handle exposes its selected value to assistive technology. Plot gestures and the navigator stay synchronized. Reset restores the full view.
The navigator is independent of plot-gesture opt-in: `navigator: true` can be used without enabling wheel/pan gestures. In this example both are enabled.
## Select a range without navigating
For a range directly on the main plot, see [Axis range selection](/guides/charts/tools/range-selection/). It uses the same handles and gestures but keeps the viewport unchanged. A navigator is a compact chart whose range events update the main chart's viewport.
The built-in navigator defaults to one visible series (the bound close measure on financial charts), a blue accent area, 35% selected opacity and 10% unselected opacity. Its height, series, chart style, colors, lines and selection appearance are configurable.
## API and configuration
```ts
await chart.update({ navigation: { enabled: true, navigator: true } });
await chart.update({ viewport: {
x: { scale: 'time', min: Date.UTC(2026, 3, 1), max: Date.UTC(2026, 8, 1) },
} });
```
## Support and behavior
Requires a native navigation-capable type with a supported X scale. Numeric XY navigators adjust X and retain the current Y window. Async sources can supply separate overview data; that overview never replaces detail data used for tooltips or exports.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Named axes and heatmaps
Set `navigation.xAxis` to a declared named X ID to choose the navigator, plot-gesture and toolbar target. It defaults to `primary`; reset preserves other X windows. `rangeSelection.xAxis` is independent. See [named X axes](/guides/charts/tools/axes/#named-x-axes).
Heatmaps use a full-matrix overview with separate row and column tracks. Their navigator height defaults to 96px (ordinary overviews default to 42px). Matrix navigator objects support `height` and basic `selectionStyle` paint/handle fields; line types, subset series, custom overview colors, opacity veils and handle grips reject. See [heatmap windows and gestures](/guides/charts/types/heatmap/#two-dimensional-navigation).
---
# Note tool
URL: https://pro.rv-grid.com/guides/charts/tools/notes/
Source: src/content/docs/guides/charts/tools/notes.mdx
Description: Place editable text at a chart coordinate.
Place editable text at a chart coordinate. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Note**, click the plot, type the explanation and choose **Save**. Select a committed note to edit its text or move its anchor. This example starts with a campaign note.
Coordinate notes belong to `annotations.shapes`. The separate `annotations.notes` contract attaches notes to semantic datum targets and measures.
## API and configuration
```ts
await chart.update({ annotations: { editable: true, shapes: [{
id: 'launch', kind: 'note', text: 'Campaign launch',
at: { x: { scale: 'time', value: Date.UTC(2026, 5, 1) }, y: 115 },
color: '#6647f0',
}] } });
```
## Support and behavior
Requires native Cartesian drawing support. Text is plain text. Shape notes persist their anchor coordinates rather than screen pixels; annotations do not alter data values or automatic domains.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts. Committed annotations appear in SVG/PNG/PDF output; CSV remains data-only. Shape IDs must be unique across shapes, references and datum notes. `{ annotations: {} }` clears annotations and editing opt-in.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Pie highlighting and fading
URL: https://pro.rv-grid.com/guides/charts/tools/pie-highlighting/
Source: src/content/docs/guides/charts/tools/pie-highlighting.mdx
Description: Shared pie interaction scopes, arc styles, controlled state and exports in standalone Charts and Pivot.
Standalone Charts and Pivot share saved interaction policies for `pie`, `donut`
and `semiDonut`. Highlighting changes presentation while preserving source
values, shares, sector order, selection, click/drill records, data views and CSV.
There are no transitions or animation in this API.
```ts
const dataset = createChartDataset(rows, mapping);
const chart = await createChart({
container,
chartType: 'donut',
dataset,
chartOptions: {
outerRadius: 0.85,
pieHighlight: 'item',
pieFade: 'global',
highlightedArc: { additionalRadius: 10, cornerRadius: 8 },
fadedArc: { additionalRadius: -15, color: '#9ca3af', opacity: 0.25 },
},
rendererOptions: {
pie: { onHighlightChange: item => console.log(item) },
},
});
```
## Saved scopes and styles
| `chartOptions` field | Default | Behavior |
| --- | --- | --- |
| `pieHighlight` | `'none'` | `'item'` highlights the inspected sector; `'series'` highlights its whole angular series/ring; `'none'` disables highlight and fade paint. |
| `pieFade` | `'none'` | `'series'` fades other unhighlighted sectors in the inspected ring; `'global'` fades every unhighlighted ring/sector; `'none'` retains other sectors. Requires a non-`none` highlight scope. |
| `highlightedArc` | `{}` | Style overrides for highlighted sectors. Omission retains their original geometry and color. |
| `fadedArc` | `{}` | Style overrides for faded sectors. Omitted opacity uses `0.2`; geometry/color remain unchanged unless authored. |
| `highlightedPieItem` | Omitted | Controlled semantic item `{ categoryId, seriesId }`, or explicit `null` to force no highlight. Omit the field for pointer/focus-controlled paint. |
`ChartPieArcStyle` supports these optional fields in **both** style objects:
| Field | Units and resolution |
| --- | --- |
| `innerRadius` | Nonnegative SVG pixels, or a decimal percentage string such as `'25%'`. Overrides the source inner radius for that state. |
| `outerRadius` | Same units; overrides the source outer radius before applying `additionalRadius`. |
| `additionalRadius` | Signed SVG pixels added to the resolved outer radius. Negative values shrink it. Default `0`. |
| `cornerRadius` | Nonnegative SVG pixels; bounds follow the actual arc angle and thickness. Defaults to the ordinary pie `cornerRadius`. |
| `color` | A validated chart color for the arc fill. Source palette/legend and tooltip identity remain unchanged. |
| `opacity` | `0`–`1`, multiplying existing source mark opacity through the sector group. Highlighted omission preserves opacity; faded omission uses `0.2`. |
`ChartPieRadius` is a number or percentage string. Percentages reference the
available fitted radius **before** the ordinary `outerRadius` ratio is applied.
This is different from the ordinary pie `innerRadius`/`outerRadius` ratio
options. Style lengths and their numeric percentage components are bounded to
1,000,000; `additionalRadius` accepts the corresponding signed interval.
Percentages use decimal syntax with `%`, without whitespace or units such as
`px`. Invalid values, unknown style/item keys and unsupported chart-type options
reject before replacing the current frame.
Resolved outer radii have a zero floor; inner radii are bounded to the resolved
outer radius. Large authored arcs can exceed the fitted plot and be clipped by
the SVG/export boundary: highlight does not reflow the chart. Reserve space with
ordinary `outerRadius` when expanding arcs. Rounding, spacing, source radius
bindings and concentric rings retain their normal contracts.
On-arc labels follow radius changes using their existing `arcLabelRadius` ratio,
or adaptive placement when omitted. Callout anchors follow the changed outer
radius. A color-, opacity- or corner-only change retains label placement. Label
content, typography, existing density rules and `arcLabelMinAngle` are unchanged.
## Pointer, keyboard and controlled state
Pointer inspection takes precedence over focused inspection. Leaving the chart
restores an eligible focused item, otherwise clears transient highlighting.
Keyboard focus uses the existing canonical sector targets and activation rules.
Escape dismisses transient paint; it does not clear authored controlled state.
Saved series/category IDs survive source reordering. Unknown, omitted, hidden or
non-plotted controlled identities remain dormant and do not fade unrelated
sectors. Use the actual dataset category ID, which may encode the category path;
do not substitute its displayed label or source-array index.
```ts
await chart.update({ chartOptions: {
...chart.model.chartOptions,
highlightedPieItem: { categoryId: dataset.categories[0].id, seriesId: dataset.series[0].id },
} });
```
`rendererOptions.pie.onHighlightChange` receives an owned, frozen
`ChartPieItemIdentifier` or `null` when requested pointer/focus identity changes.
Repeated events over the same item are deduplicated. Controlled paint can stay
on another item while notifications report the requested item. Mounting or
changing controlled state alone does not notify. The callback is runtime-owned,
is not serialized, and does not replace ordinary node-click callbacks. Scope
`'none'` disables paint while retaining inspection and these notifications.
Models persist all five saved fields. SVG server rendering, in-place hydration and restored charts
apply controlled state; transient pointer/focus state is runtime-only. Clean
SVG/PNG/PDF snapshots remove transient highlight geometry/paint and retain
authored controlled highlighting, without changing the live focused chart.
Runtime element styles are restored correctly in those snapshots. Previews
remain noninteractive and do not apply these highlight states.
## Pivot authoring
**Customize → Appearance** exposes **Pie highlight** and **Pie fading** through
the shared option schema, including field reset and appearance reset. The style
objects and controlled identity are model/API configuration rather than a new
style-object editor. They survive compatible pie/donut/semi-donut changes and
are pruned when switching to chart types that do not support them.
For Pivot runtime callbacks, use
`createPivotChartsRenderer({ pie: { onHighlightChange } })` as the renderer.
Public aliases are `PivotChartPieItemIdentifier`, `PivotChartPieArcStyle`,
`PivotChartPieRadius` and `PivotChartPieRendererOptions`. Localize the two control
captions/descriptions through `localeText.chartOptions.labels.pieHighlight`,
`.pieFade` and the matching `descriptions` entries. Use normal appearance policy
IDs `chartOptions.pieHighlight` and `chartOptions.pieFade`.
These controls do not add pie center offsets, per-ring composition/geometry
objects, sorting, animated radii or transitions. See [pie geometry](/guides/charts/customization/#pie-arc-geometry)
for ordinary radius, spacing, rounding and labels. The upstream [MUI pie
highlight reference](https://mui.com/x/react-charts/pie/#highlight) describes
the related interaction capability; the bindings and persisted contracts above
belong to the framework-neutral Charts engine.
---
# Polar axis layout
URL: https://pro.rv-grid.com/guides/charts/tools/polar-axis-layout/
Source: src/content/docs/guides/charts/tools/polar-axis-layout.mdx
Description: Measured gutters, bounded runtime placement and collision policy for named polar axes.
# Measured polar axis layout
Applies to `polarColumn`, `stackedPolarColumn`, `normalizedPolarColumn`, `radialColumn`, `radialBar` and `multiRingRadialBar` when `axes.polar` is authored. Standalone Charts and Pivot share the native owner. Radar and other axis collections are outside this contract.
Native text measurement first uses the spare space beside a wide or portrait frame, then reserves a symmetric gutter bounded to 60% of the available half-size before projecting marks. The additional gutter is capped so it cannot consume the whole frame; existing padding and authored radius ratios still apply. Browser text metrics use the inherited font; SSR uses the shared deterministic text fallback. Domains, raw records, cumulative partitions and events are unchanged. Resizing recomputes layout. Authored text determines the gutter even when a runtime filter or placement hides labels, so those callbacks do not move marks.
Coincident radial axes receive parallel furniture lanes; coincident angular axes receive concentric lanes. Lane widths derive from neighboring measured text. Automatic parallel lanes keep the axis line within the plot, and concentric lanes stay within the available inner/outer gutter; crowded later axes share the bounded lane and follow the omission policy. Grids keep the mark frame. Numeric tick candidates, formatted strings and runtime tick/label predicates are computed once per paint, then reused for both phases. Runtime placement does not re-run tick decisions.
When furniture cannot fit, whole labels are omitted: axis titles first, primary before named collection enumeration order; then numeric labels in the same axis/source-tick order; finally category labels. Each retained text box must lie within the plot bounds and avoid earlier accepted text with a three-pixel gap. Axes, short ticks and grids remain. Full-circle angular seams retain one physical endpoint label. Very long titles, dense explicit ticks, narrow hosts and too many named axes can therefore omit text. The native SVG records the additional omission count from the final polar pass in `data-polar-omitted-labels`. Values remain inspectable through marks, accessible keyboard inspection, data views and raw exports. The existing shared text owner may wrap or truncate labels while preserving their full accessible text; the final polar pass omits any remaining out-of-bounds or colliding labels after shared formatters and text layout. Distinct authored axis positions can give long labels more room than coincident lanes.
## Runtime placement
```ts
createChart({
// chartType, dataset, axes, ...
rendererOptions: {
polarAxis: {
placement: context => context.axisId === 'revenue'
? { offset: Math.min(32, context.maxOffset), visible: true }
: undefined,
},
},
});
```
Pivot uses the same `polarAxis` option on `createPivotChartsRenderer`. Existing `tickFilter` and `tickLabelFilter` retain their typed raw numeric contexts. Placement is separate from those per-tick predicates.
| Context field | Meaning |
| --- | --- |
| `chartId`, `chartType`, `axisId` | Canonical chart and numeric-axis identity |
| `coordinate` | `radius` for polar columns, `angle` for radial bars |
| `index`, `count` | Primary first, then named axes in collection enumeration order (`Object.keys`) |
| `bounds` | Frozen physical plot bounds in SVG pixels |
| `suggestedOffset` | Measured automatic furniture lane, in pixels |
| `maxOffset` | `min(512, max(bounds.width, bounds.height) / 2)` |
The frozen `ChartPolarAxisPlacementContext` is passed once for every authored numeric axis per full paint, including unused named axes. Return `undefined` for automatic placement, or an owned `ChartPolarAxisPlacement` object:
| Result field | Default and limits |
| --- | --- |
| `offset` | Suggested offset; finite number in `[-maxOffset, maxOffset]`. Radial axes move along their normal; angular axes move outward from their authored radius. Negative offsets move in the opposite direction. Angular radii clamp at zero. |
| `visible` | `true`; `false` hides axis line, short ticks, labels and title while preserving grid and data scale. |
Offsets do not reserve more plot space: furniture can extend beyond the plot, while retained labels must stay within its bounds. Placement visibility does not suppress tick predicates that were already computed. Result fields are captured once before validation and painting.
Unknown fields, null/nonobject results, nonboolean visibility, nonfinite or out-of-bound offsets reject the render. Throwing callbacks follow the existing renderer error/rollback contract; callbacks must remain valid when the accepted model is re-rendered during rollback. Preview mode ignores runtime predicates and placement. Runtime functions are not part of the model or saved state; reattach them when creating or hydrating a restored chart. No runtime placement changes data ownership or introduces navigation.
Image/vector exports clone the painted layout, including omissions and authored furniture placement. Clean exports remove transient inspection state. Fonts or arbitrary post-layout label/CSS mutations can change measured text; use stable loaded fonts before painting and rerender after changing them. This layout does not promise to fit unbounded authored content.
---
# Polar and radial geometry
URL: https://pro.rv-grid.com/guides/charts/tools/polar-geometry/
Source: src/content/docs/guides/charts/tools/polar-geometry.mdx
Description: Configure rotation, partial sweeps, holes and spacing in polar columns and radial bars.
# Polar and radial geometry
Standalone Charts and Pivot share geometry controls for `polarColumn`,
`stackedPolarColumn`, `normalizedPolarColumn`, `radialColumn`, `radialBar` and
`multiRingRadialBar`. These are saved `chartOptions`, available in Pivot under
**Customize → Appearance**.
```ts
const chart = await createChart({
container,
chartType: 'polarColumn',
dataset: createChartDataset(rows, {
category: { field: 'month' },
series: [{ field: 'actual' }, { field: 'planned' }],
}),
chartOptions: {
startAngle: -180,
sweepAngle: 180,
polarReverse: false,
innerRadius: 0.3,
outerRadius: 0.9,
groupPadding: 0.2,
barPadding: 0.15,
},
});
```
## Options and defaults
| Field | Default | Contract |
| --- | --- | --- |
| `startAngle` | `-90` | Integer `-360`–`360` degrees. Zero points right; `-90` points up. |
| `sweepAngle` | `360` | Integer `1`–`360` degrees. Restricts sectors and radial tracks to the same angular range. |
| `polarReverse` | `false` | Counterclockwise angular projection when true. Keeps canonical category/series order. |
| `innerRadius` | `0`; `0.25` for `radialColumn` | Fitted-radius ratio `0`–`0.95`, with editor step `0.05`. Defines the hole/radial baseline. |
| `outerRadius` | `1` | Fitted-radius ratio `0.05`–`1`, with editor step `0.05`. Defines the outer radial endpoint. |
| `groupPadding` | `0.1` when configured | Gap ratio `0`–`0.95` of a category sector or radial ring slot, split equally between its two edges; editor step `0.05`. |
| `barPadding` | `0.1` when configured | Only `polarColumn` and `radialColumn`: gap ratio `0`–`0.95` inside each grouped series slot after category padding; editor step `0.05`. Rejected on stacked sectors and angular radial bars. |
| `radialStacking` | `false` | Existing `multiRingRadialBar` option: accumulate series angularly in a category-owned ring. |
With all geometry fields omitted, existing full-circle shapes and legacy pixel
spacing remain unchanged. Authoring any of the seven geometry fields enables
the common configurable frame and its ratio-based spacing defaults. Resetting
all seven fields restores legacy spacing. `radialStacking` alone retains the
legacy frame. The editor's normal reset button removes an authored field.
The fitted radius comes from the available plot after ordinary title/legend
layout. Ratios use that radius, so resizing scales the hole and endpoints
consistently. If `innerRadius` exceeds `outerRadius`, the actual inner radius
clamps to the outer radius; geometry can collapse to zero thickness without
negative radii. The rectangular `radialColumn` uses radial endpoint distances
and tangential rectangles, retaining its 24-pixel width cap. Authored narrow
slots can produce widths below a pixel; no minimum width overrides their gaps.
## Sector and ring layouts
For polar columns, the sweep is divided into category sectors. `groupPadding`
insets those sectors. Grouped `polarColumn` and rectangular `radialColumn` then
partition the remaining angle into series slots and apply `barPadding`.
Stacked/normalized columns share the same category angles and accumulate along
the radius above the hole. Normalized columns fill the available radial span
for each category. Grid arcs, spokes and category labels follow the same
rotation, sweep, direction and fitted radii; partial sweeps leave an open gap.
For `radialBar`, each positive category value owns a ring. For unstacked
`multiRingRadialBar`, each positive category/series entry owns a ring. With
`radialStacking: true`, series share a category ring and accumulate along the
configured sweep. `groupPadding` separates those radial ring slots. Tracks use
the full authored sweep; values use their ordinary shared maximum (or shared
maximum category total when stacked). Category labels move to the authored
start angle on the open side of the sweep, while value labels follow the directed value endpoint.
These changes retain existing positive-value contracts: null, zero and negative
cells do not invent a visible bar, and ordinary chart-type validation still
applies. Raw values are never converted into geometry ratios in the dataset,
tooltips, activation callbacks or CSV. Large finite values use bounded weights
before accumulation, preserving relative geometry without summing overflowing
raw totals. Categories marked as subtotal/grand total remain excluded.
## Shared lifecycle and Pivot
Settings survive model saving/restoration and work with server SVG, in-place
hydration, resize and image/SVG/PDF export. Invalid types or out-of-range fields
reject before committing a new frame. Rendering uses one shared Charts owner;
Pivot consumes the same option schema and renderer. Gallery changes retain
supported fields and remove unsupported ones (for example `barPadding` when
switching to stacked polar columns).
Localize the normal `localeText.chartOptions.labels` and `descriptions` entries
for the field names above. The new captions are `polarReverse` and `barPadding`.
UI-policy IDs are `chartOptions.`, including each field's reset control.
This implements polar geometry authoring. Arbitrary placement callbacks, automatic multi-axis gutters,
polar navigation/gestures and mixed
polar composition remain separate capabilities. See [radar metric axes](/guides/charts/tools/radar-axes/)
for the distinct `radarLine`/`radarArea` contract. MUI's
[radial bars](https://mui.com/x/react-charts/radial-bars/) and
[radial axes](https://mui.com/x/react-charts/radial-axes/) describe the related
rotation/radius and spacing capabilities; the contracts above are our public API.
See [polar numeric value axes](/guides/charts/tools/polar-value-axes/) for radial/angular scale, bounds, ticks and clipping.
---
# Polar numeric value axes
URL: https://pro.rv-grid.com/guides/charts/tools/polar-value-axes/
Source: src/content/docs/guides/charts/tools/polar-value-axes.mdx
Description: Configure shared radial and angular value scales, bounds, ticks and clipping.
`axes.polar` configures numeric value axes shared by standalone Charts
and Pivot. It is supported by `polarColumn`, `stackedPolarColumn`,
`normalizedPolarColumn`, `radialColumn`, `radialBar` and `multiRingRadialBar`.
For polar columns and rectangular radial columns, the value axis runs along a
radius. For radial bars, the value axis runs along the angle. Categories and
series keep their canonical identities and source order in both layouts.
```ts
const chart = await createChart({
container,
chartType: 'polarColumn',
dataset,
chartOptions: { startAngle: -90, sweepAngle: 180, innerRadius: 0.25 },
axes: { polar: {
value: {
scale: 'log', min: 1, max: 1000,
tickValues: [1, 10, 100, 1000], unit: 'kg', title: 'Mass',
lineColor: '#475569', labelColor: '#334155',
},
gridColor: '#cbd5e1',
showValues: true,
showCategoryLabels: true,
showGrid: true,
} },
});
```
## Saved axis options
| `axes.polar` field | Default and contract |
| --- | --- |
| `value` | Optional `ChartPolarValueAxis`, the numeric contract below. |
| `position` | Radial axes: `'start'` (default), `'end'` or a finite angle from `-360` to `360` degrees, including fractions. Angular axes: `'outside'` (default) or `'inside'`. Other orientation/position combinations reject. |
| `tickSize` | `4`; finite `0`–`100` SVG pixels. Zero suppresses short tick marks while retaining labels. |
| `tickPosition` | `'after'`; `'before'`, `'center'` or `'after'` controls the short tick segment relative to the physical axis line. |
| `tickLabelPosition` | `'after'`; `'before'`, `'center'` or `'after'` places numeric labels independently of the tick segment. |
| `disableLine` | `false`; hides only the physical axis line. |
| `disableTicks` | `false`; hides short numeric tick marks while retaining numeric labels and title. |
| `disableTickLabel` | `false`; hides numeric text while retaining tick marks and title. |
| `showValues` | `true`; shows numeric ticks, labels and value-axis title. When false, the physical axis line remains. |
| `showCategoryLabels` | `true`; retains existing polar category labels independently of numeric labels. Radial-bar labels describe categories with plotted positive entries. |
| `showGrid` | `true`; draws grid rings or spokes at numeric tick positions. It does not hide radial-bar background tracks or the physical axis line. |
| `gridColor` | Resolved theme grid color; accepts the ordinary validated chart-color syntax. |
With `axes.polar` omitted, existing geometry and grid behavior remain unchanged.
Setting it to `{}` enables the shared numeric frame, default value labels and
ratio-based category spacing described in [polar geometry](/guides/charts/tools/polar-geometry/).
Removing it restores legacy behavior unless geometry options remain authored.
`ChartPolarValueAxis` shares the numeric `ChartValueAxis` contract, excluding
Cartesian `position`:
- `scale`: `'linear'`, `'log'`, `'symlog'` or `'sqrt'`; linear by default.
- `constant`: positive finite symlog transition constant; default `1`. Only
accepted with `scale: 'symlog'`.
- `min`, `max`: finite bounds, increasing when both are authored; logarithmic
bounds must be positive. Bounds use raw numeric units except for normalized
polar columns, which use percentage units (`0`–`100` automatically).
- `reverse`: reverses value projection without changing semantic bound order.
This is independent of `chartOptions.polarReverse`, which changes angular
direction, and of source-array order.
- `tickCount`: integer `2`–`1000`; bounded numeric tick density.
- `tickValues`: up to `1000` unique finite raw tick positions. Positions outside
the committed domain are omitted; source ordering is not changed.
- `tickMinStep`, `tickMaxStep`: positive finite raw step bounds; minimum must not
exceed maximum.
- `tickSpacing`: positive finite SVG-pixel spacing. Angular axes use arc length;
radial axes use the available radial span.
- `unit`, `tickFormat`: numeric tick formatting. `tickFormat` accepts ordinary
`notation` (`'standard'`, `'compact'` or `'scientific'`) and integer
`minimumFractionDigits` / `maximumFractionDigits` from `0` to `20`, with
minimum not exceeding maximum. A supplied format defaults maximum digits to
`3` or the authored minimum, whichever is larger. Omitting the format retains
ordinary precision-preserving ticks. These affect axis labels, while tooltips,
data labels and CSV retain source formatting.
- `title`, `titleColor`: value-axis title and color. The radial title follows the
selected ray; the angular title follows the midpoint of the physical axis arc.
Titles use ordinary `axisTitle` label customization with `axisId: 'polar'`.
- `lineColor`, `labelColor`: physical axis/tick color and numeric-label color.
Grid color is controlled separately by `gridColor`.
Omitting scale and bounds preserves the ordinary zero-to-maximum value domain.
Authoring scale/bounds uses the shared numeric-domain resolver, including its
ordinary automatic padding and degenerate-domain validation. Bounds must also
yield a finite increasing scaled projection; incompatible combinations reject
during preflight. Normalized polar
columns retain a `0`–`100` domain unless explicitly overridden. Logarithmic bars
start at the inferred/authored positive domain edge. Numeric reversal can make a
bar grow inward, or backward along the angle; raw values remain unchanged.
## Axis placement and tick presentation
Radial value axes follow the authored sweep's start or end ray. A numeric
`position` selects an absolute SVG angle in degrees: zero points right, `-90`
points up, and `90` points down. It does not rotate bars, category labels or grid
rings. The value-axis title follows the selected ray.
Angular value axes follow either the fitted outer radius (`'outside'`) or the
hole radius (`'inside'`). Inside tick marks and labels point toward the hole by
default. Tick density and `tickSpacing` use the physical axis arc length, so an
inside axis may show fewer ticks than the outer axis. Bars retain their same
value projection. Numeric grid spokes still extend over the data region at the
selected tick values. A zero-radius inside axis has no circumference; its
physical line, ticks, numeric labels and title are omitted, while data and grid
remain finite and available.
For a radial start/end axis, the `'after'` side points into the open side of that
sweep boundary. Numeric-angle rays follow the start-side normal. For angular
axes, `'after'` points outward for an outside axis and into the hole for an inside
axis. `'before'` reverses that side; `'center'` splits a tick equally across the
line, or centers a label on the line. Labels use their own side setting and an
offset of `tickSize + 3` pixels, so hiding tick marks does not shift labels.
Ordinary collision fitting still applies to centered or crowded labels.
`showValues: false` remains the combined switch for ticks, numeric text and the
title. `disableTickLabel` hides only numeric text. `disableLine` is independent
of both; `showGrid` continues to control only data-aligned numeric grid lines.
Changing physical presentation does not move source bars, change values or
change pointer/keyboard activation, saved models or exports.
```ts
// A radial value axis on a custom ray with centered short ticks.
await chart.update({ axes: { polar: {
...chart.model.axes?.polar,
position: 45,
tickSize: 8,
tickPosition: 'center',
tickLabelPosition: 'before',
disableLine: false,
disableTicks: false,
disableTickLabel: false,
} } });
```
`ChartPolarAxisPosition` and `ChartPolarTickPosition` are exported public types.
Their Pivot aliases are `PivotChartPolarAxisPosition` and
`PivotChartPolarTickPosition`. The placement belongs to `axes.polar.position`;
Cartesian `axes.polar.value.position` remains unsupported.
## Named numeric polar axes
Define up to 16 additional numeric frames in **`axes.polar.valueAxes`** and select
one per source series with **`seriesOverrides[id].polarAxis`**. The default
assignment is `'primary'`, which uses the existing `axes.polar.value` and its
presentation fields. Named axes work across all six polar/radial-bar types.
```ts
const chart = await createChart({
container, dataset, chartType: 'polarColumn',
axes: { polar: {
position: 'start',
value: { min: 0, max: 100, title: 'Units' },
valueAxes: {
revenue: {
position: 'end',
value: { scale: 'log', min: 1, max: 10000, title: 'Revenue', unit: 'USD' },
showGrid: false,
tickSize: 6,
},
},
} },
seriesOverrides: { revenueSeries: { polarAxis: 'revenue' } },
});
```
| New saved field | Contract |
| --- | --- |
| `axes.polar.valueAxes` | Optional object mapping stable IDs to `ChartPolarNumericAxis` definitions; empty by default. Each definition accepts `value`, `position`, `tickSize`, `tickPosition`, `tickLabelPosition`, `disableLine`, `disableTicks`, `disableTickLabel`, `showValues`, `showGrid` and `gridColor` with the same defaults and validation as the primary axis. It cannot contain `valueAxes` or `showCategoryLabels`. |
| `seriesOverrides[id].polarAxis` | Optional numeric-axis ID; defaults to `'primary'`. Nonprimary IDs must be declared in `valueAxes`, including assignments on hidden or currently absent series. Unsupported chart types reject this field. It does not select a Cartesian X/Y axis. |
IDs are nonempty, trimmed strings of at most 128 characters. `'primary'`,
`'__proto__'`, `'constructor'` and `'prototype'` cannot be declared as names.
`'secondary'` has no special meaning here and must be declared like any other
name. Each named definition owns its numeric scale, domain, reversal, unit,
ticks, colors, visibility and physical placement. It does not inherit primary
settings. All frames use the chart's shared fitted radii, rotation and sweep.
Measured gutters and automatic furniture lanes separate coincident axes. When
text cannot fit, the documented omission policy applies; see [polar axis layout](/guides/charts/tools/polar-axis-layout/)
for bounds, collision priority and runtime placement. Distinct authored positions
can provide additional room.
Inferred domains include only positive visible observations assigned to that
axis. Stacked totals, overflow divisors and normalized percentages are isolated
by axis and category. A large revenue series cannot rescale the primary units
series. Normalized polar columns normalize each axis partition to 100 percent;
callbacks, tooltips and CSV keep original observations. Stacked polar columns
assigned to different axes occupy separate angular slots within each category.
Stacked multi-ring radial bars use separate rings for each category/axis
partition. Source order determines partition order. Ordinary grouped columns
retain their source-series slots. An axis with no assigned visible observations
still renders using explicit bounds or the ordinary empty numeric domain; hide
its line/grid/values when it should be dormant visually.
Grids for all axes paint beneath data, and numeric labels/titles for all axes
paint above data. Runtime `tickFilter` and `tickLabelFilter` receive the new
**`axisId`** context field (`'primary'` or the declared name), with independent
candidate counts and indexes per axis. Use it to apply different selection rules
to different numeric frames. Numeric text customization retains `axisId: 'polar'`
for the primary axis and uses `axisId: 'polar:'` for a named axis. SVG
furniture and projected source marks expose `data-polar-axis-id` with the raw
numeric-axis ID. Named axes retain the same clipping, raw activation, export,
SSR and hydration contracts as the primary frame.
Direct updates are strict: removing a named definition while any series still
references it rejects before committing the new frame. Include reassignment in
the same update to remove it atomically. Named arrays/definitions are owned
snapshots in saved models and queued Pivot updates. The public definition type
is `ChartPolarNumericAxis`; Pivot exports `PivotChartPolarNumericAxis` from its
root and charts entry points.
In Pivot's **Axes and scales**, enable the polar numeric axis, then use **Named
polar value axes** to add an ID, edit its existing numeric/presentation controls
and select it for a series. **Remove polar value axis** returns every affected
series to primary in the same update. Disabling or resetting all polar axes
clears polar assignments. Gallery changes between the six compatible types
retain named definitions and assignments, pruning incompatible physical
positions on every definition when radial/angular roles switch. Changes to
unrelated families remove the namespace and polar assignments.
New locale captions are `chartOptions.namedPolarAxes`, `.polarAxisId`,
`.addPolarAxis` and `.removePolarAxis`; series assignment reuses `.seriesAxis`.
UI-policy IDs are `axes.polar.valueAxes`, `axes.polar.valueAxes.add`,
`axes.polar.valueAxes..`,
`axes.polar.valueAxes..value.`,
`axes.polar.valueAxes..remove` and `axes.polar.series.`.
## Runtime tick and label selection
Use `rendererOptions.polarAxis` with the default native standalone renderer, or
pass `polarAxis` to `createChartsRenderer` / Pivot's `createPivotChartsRenderer`.
These application callbacks apply to the configured numeric `axes.polar` across
all six supported types. They do not enable an omitted numeric axis, and are
inactive on other chart families and in gallery previews.
```ts
const polarAxis = {
// Keep alternate generated ticks and their numeric grid lines.
tickFilter: (tick) => tick.index % 2 === 0,
// Further hide the zero label while retaining its tick and grid line.
tickLabelFilter: (tick) => tick.value !== 0,
};
const chart = await createChart({
container, dataset, chartType: 'polarColumn',
axes: { polar: { value: {
min: 0, max: 100, tickValues: [0, 25, 50, 75, 100],
} } },
rendererOptions: { polarAxis },
});
// Pivot uses exactly the same engine and predicate contract:
// renderer: createPivotChartsRenderer({ polarAxis })
```
| Runtime field | Default and behavior |
| --- | --- |
| `polarAxis.tickFilter` | Omitted: keep every generated numeric candidate. A predicate returning `false` removes that candidate's grid line, short tick and numeric label together. |
| `polarAxis.tickLabelFilter` | Omitted: retain the existing label policy. A predicate returning `false` hides only numeric text; the tick, grid, category labels and axis title remain. |
Each predicate receives one frozen `ChartPolarAxisTickContext`:
| Context field | Meaning |
| --- | --- |
| `axisId` | `'primary'` or the declared named numeric-axis ID. Tick indexes/counts are independent per axis. |
| `chartId`, `chartType` | The rendered chart's identity and type. |
| `coordinate` | `'radius'` for polar/rectangular radial columns; `'angle'` for radial bars. |
| `index`, `count` | Zero-based index and total count of the generated, ascending numeric candidates after domain and density filtering, before either predicate. These are tick indexes, never category or series indexes. Removing ticks does not renumber survivors. |
| `value` | Finite raw numeric axis units, including percent for normalized polar columns. `null` when a stacked tick exceeds the representable raw numeric range. |
| `scaledValue`, `tickMultiplier` | Finite internal tick value and its common overflow divisor. For representable values, `value = scaledValue * tickMultiplier`. These let a predicate inspect an overflowing total without receiving `Infinity`. |
| `fraction` | Projected numeric-domain position from `0` to `1`, including numeric-axis reversal. Angular direction and physical axis placement do not change this fraction. |
Candidate generation still obeys the saved scale, domain, `tickValues`, count,
step and spacing controls. Index order follows ascending numeric values even on
reversed axes. Automatic candidate sets may change on resize; indexes identify
candidates in the current paint, rather than persistent data records. Use `value`
when a rule must follow a particular numeric value across paints.
`tickFilter` runs once per candidate per paint. The grid and foreground labels
reuse that decision. `tickLabelFilter` runs once for each retained label eligible
to paint, using the same context object and original index. It is not called for
labels disabled by `showValues`, `disableTickLabel`, previews, zero-radius inside
axes, or the duplicate endpoint of a full angular circle. `disableTicks` does not
prevent label filtering. Grid visibility, axis-line visibility and title rules
continue to apply independently. Text collision fitting can still omit a label
after the predicate accepts it; returning `true` does not force overlapping text.
Supplied filters must be functions returning synchronous booleans. Invalid types
or results throw `TypeError`; application exceptions propagate. Keep callbacks
pure and fast: paints can occur on resize, data/model changes and exports. The
frozen context has no mutable model or dataset references. Selection changes only
axis furniture, never bar geometry, numeric domains, category/series order,
activation payloads, tooltips or CSV observations.
The public types are `ChartPolarAxisRendererOptions`,
`ChartPolarAxisTickPredicate` and `ChartPolarAxisTickContext`. Pivot exports
`PivotChartPolarAxisRendererOptions`, `PivotChartPolarAxisTickPredicate` and
`PivotChartPolarAxisTickContext` from its root and charts entry points.
Runtime filters are not serialized into saved chart models. Supply them again
when restoring a model, rendering server SVG or hydrating it; hydration must use
the same selection to retain the server frame. Normal image/SVG/PDF exports use
the active renderer's selection. The inspector edits saved numeric options;
application predicates are supplied through the renderer API.
## Clipping, stacks and source values
Geometry clamps bar intervals to the committed domain. Completely clipped
zero-span marks are omitted from pointer/keyboard targets, while cumulative
stack bases still advance. Clipped bars retain their original values in
activation callbacks, tooltips and CSV. Existing positive-value requirements
remain: zero/null cells do not create bars, and this namespace does not enable
signed polar data.
Stacked polar columns and stacked multi-ring radial bars infer domains from
category totals. Normalized columns use per-category percentages for geometry
and axis labels, but their source values remain raw. Hidden profiles and total
categories do not contribute to inferred domains. Positive values are scaled
before accumulation when raw sums would overflow, preserving finite geometry.
Numeric ticks use the same projection as bars. The shared `formatScaledNumber`
locale fallback formats unrepresentable raw totals; those labels have no raw
numeric `value` customization context. Representable numeric labels expose
`axisId: 'polar'` and their raw tick value, with no Cartesian physical-axis
orientation. A full angular circle keeps one label at the coincident start/end
point. Unlike multi-spoke radar labels, a single radial axis can label its origin.
Numeric labels paint above filled data, including exported SVG. Ordinary density
and collision fitting still apply; authored tick positions do not guarantee that
every text label remains visible.
## Pivot and lifecycle
In **Customize → Axes and scales**, enable **Configure polar value axis** and
edit the shared scale, bounds, reversal, unit, title and tick controls. Use
**Show polar values**, **Show polar categories** and **Show polar value grid**
independently. **Polar axis position** selects start/end/angle for radial values
or outside/inside for angular values. **Polar tick size**, tick/label side selects
and independent hide switches control the physical axis furniture. Selecting
**Polar axis angle** begins at zero degrees; edit the numeric field to set a
custom ray. Clearing tick size resumes its default. Cartesian position controls
are omitted. **Reset polar axes**
removes `axes.polar`; appearance geometry resets separately. Invalid numeric
field combinations retain the last valid optimistic draft and show an error.
Gallery changes preserve these axes across the six compatible chart types and
remove the namespace for unrelated types. When a gallery change switches between
radial and angular values, it removes an incompatible position while retaining
tick size/sides/visibility and compatible numeric settings. Direct API updates
remain strict. Queued updates own their numeric
arrays before later chart-type transitions execute. Public Pivot aliases are
`PivotChartPolarAxes` and `PivotChartPolarValueAxis`.
UI-policy IDs are `axes.polar.enabled`, `axes.polar`, `axes.polar.value.*`,
`axes.polar.showValues`, `axes.polar.showCategoryLabels`, `axes.polar.showGrid`
and `axes.polar.reset`. Additional IDs are `axes.polar.position`,
`axes.polar.angle` (the editor-only numeric angle field), `axes.polar.tickSize`,
`axes.polar.tickPosition`, `axes.polar.tickLabelPosition`, `axes.polar.disableLine`,
`axes.polar.disableTicks` and `axes.polar.disableTickLabel`.
Localize the corresponding `chartOptions` captions
`polarAxes`, `polarEnable`, `polarValue`, `polarReset`, `polarShowValues`,
`polarShowCategories` and `polarShowGrid` through `localeText`. Presentation
captions are `polarPosition`, `polarAngle`, `polarTickSize`, `polarTickPosition`,
`polarTickLabelPosition`, `polarDisableLine`, `polarDisableTicks` and
`polarDisableTickLabel`. Position captions use `selectValues['polar.start']`,
`['polar.end']`, `['polar.inside']`, `['polar.outside']`; tick-side captions use
`selectValues.before`, `.center` and `.after`.
Settings persist in models and work with server SVG, in-place hydration, resize
and normal image/SVG/PDF export. Parsing and scale preflight reject invalid
configurations before committing a new frame. See [radar metric axes](/guides/charts/tools/radar-axes/)
for the separate category-ID metric-axis contract.
Runtime placement callbacks are available through the [measured polar layout contract](/guides/charts/tools/polar-axis-layout/).
Mixed polar composition and value-axis navigation/gestures are outside this
contract; radar per-metric axes use the separate guide linked above. The [MUI radial-axis reference](https://mui.com/x/react-charts/radial-axes/)
describes related radial and angular axis behavior; the API above belongs to the
shared Charts engine.
---
# Titles, labels & themes
URL: https://pro.rv-grid.com/guides/charts/tools/presentation/
Source: src/content/docs/guides/charts/tools/presentation.mdx
Description: Configure chart text, value labels and appearance.
Configure chart text, value labels and appearance. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Change settings** to change the title, hide value labels and show the dark chart theme. Restore the original settings to compare. The chart also follows the documentation theme when that changes.
Use a plaintext long description for additional chart context. Per-type label support and collision policies still apply.
## API and configuration
```ts
await chart.update({
presentation: { ...chart.model.presentation,
title: 'Forecast review', subtitle: 'Illustrative data',
description: 'Revenue and costs over twelve months.',
dataLabels: false, legendPosition: 'bottom',
},
theme: 'dark',
});
```
## Support and behavior
Supported themes are light, dark and auto. Auto follows the nearest host `data-theme`, then the system preference. Data labels require the selected type's label capability. `presentation` updates replace that object, so spread retained fields explicitly.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Presentation fields
| Field | Contract |
| --- | --- |
| `title`, `subtitle` | Authored chart headings. Layout/available space determine wrapping and omission. |
| `description` | Plain text, at most 4,096 UTF-16 units; empty string clears it. Exposed as the chart's accessible long description. |
| `legendPosition` | `'top'`, `'right'`, `'bottom'`, `'left'` or `'none'`; omitted uses the renderer's default. |
| `legendAction` | `'focus'` or `'toggleVisibility'`; see [legend behavior](/guides/charts/tools/legend/). |
| `dataLabels` | Boolean. `true` requires the selected type's label capability; `false`/omission are accepted without it. Density/collision policies can omit individual labels. |
| `xAxisTitle`, `yAxisTitle` | Primary Cartesian axis captions. Named axes use their own `title`; axis-free charts do not gain axes. |
Use [semantic labels](/guides/charts/customization/#semantic-labels) for measured
text, typography and accessible overrides. [Polar layout](/guides/charts/tools/polar-axis-layout/)
has an explicit omission policy when multiple axis labels cannot fit. A title
or data-label option does not change raw source values or CSV output.
---
# Radar metric axes and grid
URL: https://pro.rv-grid.com/guides/charts/tools/radar-axes/
Source: src/content/docs/guides/charts/tools/radar-axes.mdx
Description: Independent radar metric domains, numeric scales, grids and shared Pivot controls.
`radarLine` and `radarArea` share configurable metric axes in standalone Charts
and Pivot. A dataset category is one metric/spoke; dataset series are the
profiles compared on those metrics. Category IDs identify overrides independently
of source-array order and displayed labels.
```ts
const dataset = createChartDataset(rows, mapping);
const latencyId = dataset.categories[0].id;
const chart = await createChart({
container,
chartType: 'radarArea',
dataset,
chartOptions: {
radarDomain: 'metric',
radarDivisions: 5,
radarGridShape: 'polygon',
radarReverse: true,
radarValues: true,
startAngle: 0,
},
axes: { radar: {
value: { min: 0, max: 100, tickCount: 5 },
metrics: {
[latencyId]: { min: 1, max: 1000, scale: 'log', reverse: true,
title: 'Latency', unit: 'ms', tickValues: [1, 10, 100, 1000] },
},
stripeColors: ['#f3f4f6', '#ffffff'],
labelOrientation: 'radial',
} },
});
```
## Saved appearance options
| `chartOptions` field | Omitted/editor default | Accepted values and meaning |
| --- | --- | --- |
| `radarDomain` | `'shared'` | `'shared'` infers a common domain across displayed metrics/profiles; `'metric'` infers each metric's domain from its displayed profile values. Authored common/metric bounds take precedence. |
| `radarDivisions` | `4` | Integer `1`–`20`; normalized radial grid bands. |
| `radarGridShape` | `'circular'` | `'circular'` or `'polygon'`; polygon corners follow metric directions. |
| `radarReverse` | `false` | Counterclockwise projection when true, retaining canonical category order. |
| `radarValues` | `false` | Numeric tick marks and formatted values on the metric spokes. |
| `startAngle` | `-90` | Integer `-360`–`360` degrees; zero points right, `-90` points up. |
These controls appear in Pivot **Customize → Appearance**. Their normal reset
controls remove authored values. `startAngle` is also used by pie charts, with
each chart family's own defaults.
## `axes.radar`
| Field | Default and contract |
| --- | --- |
| `value` | Optional `ChartRadarValueAxis` defaults inherited by every metric. |
| `metrics` | Optional record of category ID → `ChartRadarValueAxis`. Up to 1,000 nonempty IDs of at most 32,768 characters. Unknown/not-yet-loaded category IDs remain dormant. |
| `gridColor` | Resolved theme grid color; validated chart color for ring/spoke defaults. |
| `stripeColors` | Empty by default; up to 32 validated colors repeated over grid bands, from the center outward. These are static saved colors. |
| `showRings` | `true`; hides ring outlines when false. Authored stripe fills remain. |
| `showSpokes` | `true`; hides metric spoke lines when false. Numeric ticks remain independently controlled by `radarValues`. |
| `showLabels` | `true`; shows category/metric labels independently of numeric values. |
| `labelOffset` | `14` SVG pixels beyond the fitted radius; accepts finite `0`–`100`. |
| `labelOrientation` | `'horizontal'`, `'radial'` or `'tangential'`; horizontal by default. Rotated labels flip on the left side to remain upright. |
`ChartRadarValueAxis` reuses the numeric-axis contract, excluding Cartesian
`position`. Its fields are:
- `scale`: `'linear'`, `'log'`, `'symlog'` or `'sqrt'` (linear by default).
- `constant`: positive finite symlog transition constant; default `1`.
- `min`, `max`: finite raw bounds, increasing when both are authored. Log bounds
and every displayed log observation must be positive.
- `reverse`: reverses the metric's radial projection while semantic bounds remain
increasing. It is separate from `radarReverse`, which reverses metric angles.
- `title`, `titleColor`: override the metric category label and its color.
- `unit`, `tickFormat`: shared numeric tick formatting. `tickFormat` accepts
`notation` (`standard`, `compact`, `scientific`) and minimum/maximum fraction
digits under the ordinary numeric-axis bounds.
- `tickCount`, `tickValues`, `tickMinStep`, `tickMaxStep`, `tickSpacing`: ordinary
bounded numeric tick policies, including exact raw positions and pixel spacing.
`tickCount` is an integer from 2 to 1,000; exact values are unique finite numbers
with at most 1,000 entries. Step/spacing values are positive and finite.
- `lineColor`, `labelColor`: metric spoke/tick color and numeric value-label color.
Renderer label/element customizations retain their normal precedence.
Metric fields override common fields individually. A metric that explicitly
changes scale away from symlog drops the inherited symlog constant. Other
inherited fields remain active: for example, a log metric inheriting a common
zero minimum needs its own positive minimum. Combined inherited domains and
scale/constant settings must remain valid. Clearing a metric field resumes its
common default; clearing a common field resumes automatic inference.
## Values, clipping and compatibility
With every new radar option and `axes.radar` omitted, the old global positive
maximum, four circular rings and original markup/geometry remain. Legacy radar
inputs still reject negative values and omit zero-value markers.
An explicit `axes.radar` (including `{}`), or any authored radar option from the
table above, enables the configurable-axis path. It accepts finite zero/signed
values for linear/symlog/sqrt scales. Automatic linear-family domains include
zero and the displayed raw extrema; shared mode uses all displayed metrics and
metric mode uses only that metric. Degenerate/empty domains expand through the
shared numeric-domain owner. Explicit bounds retain authority, and extreme
finite domains use the shared overflow-safe coordinate transform.
Values outside authored domains are projected onto the nearest radial boundary.
This changes geometry, never raw data, counts, source IDs or activation values.
Nulls remain center corners in the polygon and do not create markers, labels or
click/drill records. Subtotals/grand totals do not become metric spokes or affect
domains. Hidden profile measures do not contribute to domain inference.
Grid bands represent equal fractions of **transformed radial distance**. They
are not fixed raw-value intervals when axes have different domains or nonlinear
scales. Numeric ticks use each metric's own transform, unit and formatting.
Center value labels are omitted to avoid overlapping labels at the common
origin. Category labels and numeric labels retain normal layout/density limits;
many metrics/ticks can still require application-level simplification.
`unit` formats axis values. Tooltips, data labels and CSV retain the dataset's
ordinary raw/formatted source values; use the existing formatter APIs when
different source display text is required. Numeric label customization contexts
use `axisId: 'radar'` and the metric category descriptor, with no Cartesian
physical-axis orientation. Rotation and domain reversal do not reorder keyboard
targets or fabricate records.
All settings persist in models and work with SVG server rendering, in-place
hydration, normal resize, saved restoration and SVG/image/PDF export. Invalid
fields or incompatible inherited axes reject atomically. Inference describes the
committed displayed dataset; it does not infer global statistics from unrequested
provider rows. Ordinary minimum category requirements remain in force.
## Pivot metric editing
In **Customize → Axes and scales**, enable **Configure radar axes**. Choose
**Common metric defaults** or one **Radar metric**, then use the shared scale,
minimum/maximum, reversal, unit, title and tick controls. Metric values inherit
common defaults. Cartesian position controls are omitted. **Reset radar axes**
removes `axes.radar`; appearance options reset separately. Signed data still needs
an explicit configurable-axis setting, so removing its last opt-in can reject
until the data/configuration is compatible.
The gallery enables `axes.radar: {}` when selecting a radar for signed data.
Switching between radar line/area retains axes; unrelated chart types prune
radar-specific axes/options. The adapter shares the Charts renderer and
optimistic axis editor, with no Pivot-specific geometry implementation.
Public aliases are `PivotChartRadarAxes` and `PivotChartRadarValueAxis`.
Appearance fields use ordinary `chartOptions.` policy IDs. Axis policies
include `axes.radar.enabled`, `axes.radar`, `axes.radar.metricSelector`,
`axes.radar.value.*`, `axes.radar.metric.*` and `axes.radar.reset`.
Localize captions with `localeText.chartOptions.radarAxes`, `radarEnable`,
`radarCommon`, `radarMetric`, `radarReset`, and the normal option
`labels`/`descriptions`. `selectValues['radarDomain.shared']` supplies the scoped
shared-domain caption; field-specific select captions take precedence over
generic value captions.
This adds radar metric axes and grid authoring. General polar/radial-bar axis
collections, angular/radial brushing and zoom, radar area/axis hover modes,
striping callbacks and animation remain separate capabilities. The official
[MUI radar reference](https://mui.com/x/react-charts/radar/) describes the related
metric-domain and grid controls; the contracts above belong to this shared engine.
---
# Axis range selection
URL: https://pro.rv-grid.com/guides/charts/tools/range-selection/
Source: src/content/docs/guides/charts/tools/range-selection.mdx
Description: Select, move and resize a category, time or numeric interval directly on a chart, with keyboard and touch support.
Enable `rangeSelection` to select an interval directly on a chart. Drag the plot to create a range, drag its body to move it, and drag either handle to resize it. The chart dims the area outside the selection. Mouse, pen, touch and keyboard use the same control.
Range selection belongs to shared Charts and works in standalone charts and Pivot. It keeps the source data and viewport unchanged. Use [Data selection](/guides/charts/tools/selection/) for individual observations, or a [Navigator](/guides/charts/tools/navigator/) to change the visible window.
## Try it live
This example starts with April–September selected. Move or resize the range, then inspect `rangeSelection` in the model below. Choose **Clear range** and drag anywhere on the plot to create another range. **Select Apr–Sep** sets a range through the public API. Both series remain visible; range selection does not choose which series to show.
## API and configuration
Create an ordinary chart and enable range selection:
```ts
import { createChart } from '@revolist/charts';
const chart = await createChart({
container: document.getElementById('chart')!,
chartType: 'line',
data: [
{ date: Date.UTC(2026, 0, 1), revenue: 65 },
{ date: Date.UTC(2026, 1, 1), revenue: 82 },
{ date: Date.UTC(2026, 2, 1), revenue: 76 },
{ date: Date.UTC(2026, 3, 1), revenue: 95 },
],
mapping: {
category: { field: 'date' },
series: [{ field: 'revenue' }],
},
axes: { x: { scale: 'time' } },
rangeSelection: { enabled: true },
onRangeSelectionEnd({ range, origin }) {
// The committed range is already available in chart.model.
console.log('Selected range', range, origin);
},
});
```
Give the chart host a height, for example `
`. Framework component options accept the same settings and callbacks.
### Public controller API
| API | Purpose |
| --- | --- |
| `rangeSelection: { enabled: true, range?, style? }` | Configure the control at creation or through `update()`. |
| `chart.setRangeSelection(range)` | Enable the control and set an owned category/time/numeric interval. Retains styling. |
| `chart.setRangeSelection(null)` | Enable the control with no selected interval. |
| `chart.clearRangeSelection()` | Clear the interval while retaining the enabled state and styling. |
| `chart.model.rangeSelection` | Read a snapshot of the committed range and authored settings. |
| `chart.subscribe(listener)` | Observe committed standalone chart changes; returns an unsubscribe function. |
| `onRangeSelectionStart/Change/End` | Observe pointer and keyboard gestures. |
| `chart.update({ rangeSelection: {} })` | Disable the control. |
```ts
await chart.setRangeSelection({
scale: 'time',
min: Date.UTC(2026, 0, 1),
max: Date.UTC(2026, 2, 1),
});
const selected = chart.model.rangeSelection?.range;
await chart.clearRangeSelection();
```
Both methods return promises. They validate against the chart's configured axis, own the supplied bounds before queuing, and preserve current styling. Await them before reading the result. Invalid input rejects without changing the committed model, and calls after destruction reject. The methods leave the viewport, source data and semantic point selection unchanged.
Programmatic changes notify standalone model subscribers; they do not synthesize pointer/keyboard gesture callbacks. In Pivot, use the existing `pivot-chart-updated` event to observe API updates.
### Set an initial range
Time ranges use UTC epoch milliseconds, with `min < max`:
```ts
await chart.update({
rangeSelection: {
enabled: true,
range: {
scale: 'time',
min: Date.UTC(2026, 0, 1),
max: Date.UTC(2026, 2, 1),
},
},
});
```
Category ranges use stable category IDs, with both endpoints included. Use IDs from the dataset rather than display labels. For a category-axis chart:
```ts
const categories = chart.getDataset().categories;
await chart.update({
axes: { x: { scale: 'category' } },
rangeSelection: {
enabled: true,
range: {
scale: 'category',
startId: categories[0].id,
endId: categories[2].id,
},
},
});
```
Numeric X ranges use `{ scale: 'linear', min, max }` or `{ scale: 'log', min, max }` to match the chart's configured axis. Logarithmic bounds must be positive.
### Listen for changes
| Callback | When it runs |
| --- | --- |
| `onRangeSelectionStart` | A pointer or keyboard interaction starts. |
| `onRangeSelectionChange` | The transient range changes during the interaction. |
| `onRangeSelectionEnd` | The range commits successfully, after the model updates. |
Each receives `{ range, origin }`, where `origin` is `'pointer'` or `'keyboard'`. Clearing supplies `range: null`. Cancelling a drag restores the previous range without committing it. Use the change callback for temporary previews and the end callback for committed application work.
You can also observe the committed range through `chart.subscribe(model => console.log(model.rangeSelection?.range))`.
### Clear or disable
```ts
// Clear the range while retaining styling and interaction.
await chart.clearRangeSelection();
// Disable the control and remove its overlay.
await chart.update({ rangeSelection: {} });
```
Updates replace the whole `rangeSelection` record. Spread the previous record when retaining its other fields.
### Customize the selection
```ts
await chart.update({ rangeSelection: {
...chart.model.rangeSelection,
enabled: true,
style: {
selectedOpacity: 1,
unselectedOpacity: 0.25,
borderColor: '#64748b',
borderWidth: 1,
handleBorderColor: '#cbd5e1',
handleBorderWidth: 1,
activeColor: '#2563eb',
},
} });
```
`selectedOpacity` and `unselectedOpacity` independently control the plot opacity inside and outside the range, from 0 to 1. They multiply any per-series area `fillOpacity`. Main charts default to full selected opacity and 25% outside opacity. Navigator areas default to 35% selected and 10% outside opacity.
The shared style also accepts `fillColor`, `fillOpacity`, `outsideColor`, `outsideOpacity`, `handleColor`, `handleGripColor`, `handleGripWidth`, `handleWidth`, `handleHeight`, and `handleRadius`. `outsideOpacity` controls the outside veil; an explicit `unselectedOpacity` takes precedence. A zero `borderWidth` hides the selection boundary. Use `navigator.selectionStyle` for the same styling in a built-in navigator.
## Support and behavior
Use a native axis-navigation-capable chart with a supported category, time, linear, logarithmic, symlog or signed square-root axis. A range control does not apply to axis-free charts such as pies. Check `chart.capabilities.rangeSelection` for backend support and `getChartDefinition(type)` from `@revolist/charts/catalog` for type/axis support.
Enabling range selection reserves the plot's drag gesture for creating and editing a range. It does not filter rows, change a Pivot query, populate semantic `model.selection`, or move the viewport automatically. Navigator controls remain separate and available when configured.
### Keyboard and touch
| Control | Action |
| --- | --- |
| Tab, then Enter or Space on the plot selection control | Create a range without a pointer. |
| Arrow keys on the range body | Move the range. |
| Arrow keys on a handle | Resize that endpoint. |
| Shift + Arrow | Use a larger step; on the range body, resize the end. |
| Home / End | Move the range or focused endpoint to a domain boundary. |
| Escape during a drag | Cancel and restore the previous range. |
| Escape on a committed plot range | Clear the range. |
Touch supports creating, moving and resizing with the same gestures as a mouse. Touch input inside an enabled main-plot selection is reserved for chart interaction. The built-in navigator reserves horizontal range gestures; the surrounding page remains scrollable.
### Use it with Pivot
Pass `rangeSelection: { enabled: true }` when creating or updating a chart through `PivotChartsPlugin`. The grid emits `pivot-chart-range-selection-start`, `pivot-chart-range-selection-change` and `pivot-chart-range-selection-end`. Their details include `chartId`, `range` and `origin`.
Pivot chart references expose the same `setRangeSelection()` and `clearRangeSelection()` methods. The plugin also exposes `setChartRangeSelection(chartId, range)` and `clearChartRangeSelection(chartId)` for applications working by chart ID. `ChartAxisRange`, `ChartRangeSelection`, `ChartRangeSelectionStyle`, and `ChartRangeSelectionEvent` are exported public types.
Pivot persists the same range with the chart model. Range events alone do not apply a grid filter; the application decides how to use them.
## State and cleanup
Committed ranges and authored styles are saved in `chart.model`. Transient drags are not saved. Normal chart creation validates restored models, including range scale and bounds. Interactive main-plot selection overlays are omitted from chart image exports.
Destroy the chart when its host unmounts, and unsubscribe any application listeners:
```ts
const stop = chart.subscribe(model => {
console.log(model.rangeSelection?.range);
});
// On unmount:
stop();
chart.destroy();
```
[Navigator](/guides/charts/tools/navigator/) · [Data selection](/guides/charts/tools/selection/) · [All chart tools](/guides/charts/tools/)
## Named and signed numeric axes
`rangeSelection.xAxis` chooses a declared X identity independently of `navigation.xAxis`. It defaults to `primary`. Ranges use that axis's category IDs, UTC instants or raw numeric values; numeric `scale` accepts `linear`, `log`, `symlog` and `sqrt` and must match the selected axis. The symlog constant is inherited from the axis. Unsupported types, including heatmaps, reject this independent brush. See [axis configuration](/guides/charts/tools/axes/).
XY source-point selection is a separate opt-in from this one-dimensional range API. See [value-axis navigation and XY brushing](/guides/charts/tools/value-navigation/).
---
# Rectangle tool
URL: https://pro.rv-grid.com/guides/charts/tools/rectangles/
Source: src/content/docs/guides/charts/tools/rectangles.mdx
Description: Draw and edit bounded rectangular regions on a chart.
Draw and edit bounded rectangular regions on a chart. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Rectangle** in the toolbar, drag across the plot, and choose **Save**. Select a saved region to move or resize it; the coordinate form can set exact endpoints, fill and opacity. Use **Reset example annotations** to restore the starting region.
Rectangles mark an X-and-Y region. For an axis-wide threshold range, use a [reference band](../reference-bands/) instead.
## API and configuration
```ts
await chart.update({ annotations: {
editable: true,
shapes: [{ id: 'campaign-window', kind: 'rectangle',
from: { x: { scale: 'time', value: Date.UTC(2026, 3, 1) }, y: 80 },
to: { x: { scale: 'time', value: Date.UTC(2026, 7, 1) }, y: 125 },
color: '#6647f0', fill: '#6647f0', opacity: 0.15,
}],
} });
```
## Support and behavior
Uses the same Cartesian drawing support as Select. Endpoints use matching time, numeric or semantic category coordinates. Category offsets are expressed in category intervals. Rectangles are clipped to the plot and never expand the axis domain.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts. Committed annotations appear in SVG/PNG/PDF output; CSV remains data-only. Shape IDs must be unique across shapes, references and datum notes. `{ annotations: {} }` clears annotations and editing opt-in.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
---
# Reference bands
URL: https://pro.rv-grid.com/guides/charts/tools/reference-bands/
Source: src/content/docs/guides/charts/tools/reference-bands.mdx
Description: Shade an axis-wide target or event range.
Shade an axis-wide target or event range. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
The shaded band marks the range 80–120 across the full plot. Change its bounds to show a different acceptable range. Unlike a [rectangle](../rectangles/), it spans the other axis automatically.
Reference bands use the API; the native drawing toolbar works with `shapes`, not `references`.
## API and configuration
```ts
await chart.update({ annotations: { references: [{
id: 'target-range', kind: 'band', axis: 'primary',
min: 80, max: 120, opacity: 0.15,
label: 'Target range', color: '#6647f0',
}] } });
```
## Support and behavior
Supported axes come from the type's annotation definition. Category bands use `from`/`to` semantic or time coordinates instead of numeric `min`/`max`. Bounds must be valid for their axis scale, including positive logarithmic coordinates.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts. Committed annotations appear in SVG/PNG/PDF output; CSV remains data-only. Shape IDs must be unique across shapes, references and datum notes. `{ annotations: {} }` clears annotations and editing opt-in.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Named-axis coordinates
Value references use `axisId` to select a declared `axes.yAxes` ID while retaining the `axis: 'primary' | 'secondary'` discriminator. Category references and drawing shapes use `xAxis`; shapes independently use `yAxis`. Numeric coordinates use `{ scale: 'linear' | 'log' | 'symlog' | 'sqrt', value }` and must match the selected X scale. Semantic category paths and UTC time coordinates remain supported. The shared drawing editor can reassign a draft through its actual projection; Save preserves identity and Cancel preserves the authored model. See [named annotations and examples](/guides/charts/tools/axes/#annotations-on-named-x-axes).
---
# Reference lines
URL: https://pro.rv-grid.com/guides/charts/tools/reference-lines/
Source: src/content/docs/guides/charts/tools/reference-lines.mdx
Description: Add axis thresholds without changing the dataset.
Add axis thresholds without changing the dataset. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
The dashed line marks a target of 100. Change the `value` below to move it. Reference lines span the plot along an axis, so they do not need two chart coordinates.
The reference is configured through the API. The drawing toolbar edits shapes; it is not a reference-line inspector.
## API and configuration
```ts
await chart.update({ annotations: { references: [{
id: 'target', kind: 'line', axis: 'primary', value: 100,
label: 'Target', color: '#6647f0', lineDash: 'dash',
}] } });
```
## Support and behavior
Check the selected type's `getChartDefinition(type).annotations.axes` before using references. Primary/secondary value references use numeric values; category-axis references use semantic paths or UTC time coordinates. An unconfigured secondary axis cannot be targeted.
## Calculate a reference from the data
Use `annotations.calculated` for a mean, median or percentile line, or a
population-standard-deviation band. The shared engine recalculates references
from canonical finite values after updates and selection changes.
```ts
await chart.update({ annotations: {
...chart.model.annotations,
calculated: [{
id: 'mean', seriesId: 'revenue', statistic: 'mean',
scope: 'selected', label: 'Selected average',
}],
} });
```
Scopes are `all` (the chart dataset), `visible` (the viewport), or `selected`
(semantic chart selection). Empty scopes omit the reference. Source totals and
synthetic Others are excluded. IDs must also be unique across calculated
references. The existing shape drawing toolbar does not author these calculations;
applications configure them through the API. See the dedicated
[Analysis guide](/guides/charts/analysis/) for methods, examples and limitations.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts. Committed annotations appear in SVG/PNG/PDF output; CSV remains data-only. Shape IDs must be unique across shapes, references and datum notes. `{ annotations: {} }` clears annotations and editing opt-in.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Named-axis coordinates
Value references use `axisId` to select a declared `axes.yAxes` ID while retaining the `axis: 'primary' | 'secondary'` discriminator. Category references and drawing shapes use `xAxis`; shapes independently use `yAxis`. Numeric coordinates use `{ scale: 'linear' | 'log' | 'symlog' | 'sqrt', value }` and must match the selected X scale. Semantic category paths and UTC time coordinates remain supported. The shared drawing editor can reassign a draft through its actual projection; Save preserves identity and Cancel preserves the authored model. See [named annotations and examples](/guides/charts/tools/axes/#annotations-on-named-x-axes).
---
# Save & restore
URL: https://pro.rv-grid.com/guides/charts/tools/saved-state/
Source: src/content/docs/guides/charts/tools/saved-state.mdx
Description: Serialize authored chart state and recreate a controller.
Serialize authored chart state and recreate a controller. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Choose **Save and change title**. The example captures the model, then changes the visible title. Choose **Restore saved chart** to destroy the current controller and recreate the saved chart.
The demo holds the snapshot in memory. Your application chooses its own storage and versioning policy.
## API and configuration
```ts
const saved = JSON.stringify(chart.model);
chart.destroy();
// createChart is the factory from the complete setup above.
const restored = await createChart({ container, model: JSON.parse(saved) });
// When the host unmounts: restored.destroy();
```
## Support and behavior
Standalone models are version 1 and include canonical data, authored settings, viewport, committed selection and annotations. Supply a model or data/settings, not both. Source functions, tooltip callbacks, link groups and editor drafts are runtime state and need separate setup. Validate untrusted saved input through chart creation.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
Selected value-axis policy, gesture limits, named Y windows and brush opt-in persist alongside semantic source selection. Active brush rectangles do not persist; see [value navigation](/guides/charts/tools/value-navigation/).
`chartOptions.rendererPolicy` persists for grouped columns/bars and scatter (`svg`, `batched-svg`, `webgl`) and heatmap (`svg`, `webgl`). GPU contexts/canvases are runtime-only; restore rechecks GPU availability. The effective renderer can fall back to individual SVG for incompatible runtime or per-item styling; the saved policy remains unchanged. See [WebGL](/guides/charts/tools/webgl/) and [batched SVG](/guides/charts/tools/batched-svg/).
---
# Data selection
URL: https://pro.rv-grid.com/guides/charts/tools/selection/
Source: src/content/docs/guides/charts/tools/selection.mdx
Description: Select chart observations without changing the source data.
Select chart observations without changing the source data. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
To select an interval on an axis by dragging a band or handles, use [Axis range selection](/guides/charts/tools/range-selection/). This page covers selecting individual data marks.
## Try it live
Click a data mark, then inspect the selected targets below. This example uses multiple selection. Use Ctrl/Meta-click to add or toggle individual targets in multiple mode, or choose **Clear selection**.
Selection uses stable row/column members and measure identity. It is different from the drawing toolbar's Select pointer, which edits annotation shapes.
## API and configuration
```ts
await chart.update({ interaction: {
mode: 'select', selectionMode: 'multiple',
} });
const stop = chart.subscribe(model => console.log(model.selection));
await chart.clearSelection();
// You can also await chart.setSelection(validSemanticTargets).
// Later: stop();
```
## Support and behavior
The type and backend must support select interaction. Standalone Charts maintains local selection; Pivot's drill and grid-filter adapters are separate. A local selection does not change source rows or a remote query. Saved models include committed selection.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Local and host-controlled selection
`interaction.mode: 'select'` commits local selection after source activation.
`selectionMode: 'single'` selects one semantic target; `'multiple'` enables
Ctrl/Meta toggling. A plain activation replaces the selection even in multiple
mode. Selection identity uses row member paths, column member paths and measure
identity, so labels and displayed order do not become source IDs.
For host control, use `interaction.mode: 'event'` and
`onSelectionRequest(selection)`. This notifies the proposed semantic targets
without committing local selection. Validate or transform the request, then
call `setSelection` when the host accepts it. `onNodeClick` continues to report
canonical raw activation independently of the host's decision.
```ts
const chart = await createChart({ container, chartType: 'line', data, mapping,
interaction: { mode: 'event', selectionMode: 'multiple' },
onSelectionRequest: selection => { void chart.setSelection(selection); },
});
```
Check both the catalog type and renderer interaction capabilities before enabling
selection. XY brushing is a separate opt-in interaction with its own supported
families; see [value-axis navigation and brushing](/guides/charts/tools/value-navigation/).
---
# Sparklines
URL: https://pro.rv-grid.com/guides/charts/tools/sparklines/
Source: src/content/docs/guides/charts/tools/sparklines.mdx
Description: Compact interactive line, area and bar charts shared by Charts and Pivot.
Use a small native chart in a table, card or inline trend summary. The standalone
factory reuses the same line, area and column geometry, scales, inspection and
export owners as Charts and Pivot. Give the container an explicit CSS height.
```ts
import { createSparklineChart } from '@revolist/charts/sparkline';
const chart = await createSparklineChart({
container,
data: [3, 7, null, -2, 9, 4],
plotType: 'area',
interpolation: 'monotoneX',
color: '#2563eb',
showTooltip: true,
showHighlight: true,
presentation: { title: 'Weekly revenue trend' },
axes: { y: { min: -10, max: 10 } },
});
await chart.setData([4, 8, null, -1, 10, 5]);
// Use the normal lifecycle, selection, CSV, downloads and subscriptions.
const csv = chart.getCSV();
chart.destroy();
```
## Factory options
`createSparklineChart` and `createSparklineDataset` are exported from the package
root and `@revolist/charts/sparkline`. The subpath loads only its three plot
families. `SparklineChartOptions` and `SparklineChartRef` describe the factory.
| Field | Default | Contract |
| --- | --- | --- |
| `data` | Required | Readonly array of finite numbers or `null`. Nulls keep their positions and normal path gaps. Empty/all-null arrays are accepted. Invalid values reject; they are never filtered or coerced. |
| `x` | `0, 1, 2, …` | Same-length readonly array of string, finite number, boolean or null. Choose `axes.x.scale` for category/time/numeric interpretation. Duplicate X values keep separate index-based source IDs. Numeric X applies to line/area, with existing column scale restrictions retained. |
| `plotType` | `'line'` | `'line'`, `'area'` or `'bar'`. Bar uses the native vertical `groupedColumn` chart. |
| `interpolation` | Native linear | Existing `ChartInterpolation` policy for line/area. Rejected on bar. |
| `color` | Theme palette | Saved `seriesOverrides.value.color`; shared color validation applies. |
| `showTooltip` | `false` | Enable the shared semantic tooltip. `rendererOptions.tooltip` customizes it when enabled. An explicit `tooltip: false` still disables it. |
| `showHighlight` | `false` | Enable transient line/area source-point glyphs. Authored regular markers are separate. Rejected when true on bar. |
Other ordinary `ChartOptions` remain available: axes and fixed Y domains,
callbacks, theme, renderer styling, limits, presentation and per-series settings.
The source series ID is **`value`**. Regular line/area markers default to false;
`seriesOverrides.value.markers` can change that. Convenience interpolation/color
fields take precedence over their corresponding series override. The factory
sets `sparkline: true` and the highlight-disable policy from `showHighlight`.
Dataset/model/row mapping/data source/custom renderer/chartType are excluded from
this array-based facade; use ordinary `createChart` for those authoring flows.
`setData(data, x?)` validates and updates atomically, preserving settings and
index-based identities. Omitting X restores integer indexes. Ordinary
`chart.update` remains available for chart settings or canonical datasets.
`createSparklineDataset(data, x?)` builds the same canonical single-series
snapshot for `createChart`, Pivot or server rendering without mounting a chart.
## Shared compact mode
For an existing Charts or Pivot model, set **`chartOptions.sparkline: true`**.
Default is false. It is supported on `line`, `smoothLine`, `steppedLine`, `area`,
`steppedArea`, `stackedArea`, `normalizedArea` and `groupedColumn`. Unsupported
chart types reject this option. Pivot exposes **Customize → Chart appearance →
Sparkline** through the ordinary chart-option controls; localize its label and
help with `chartOptions.labels.sparkline` and `chartOptions.descriptions.sparkline`.
Normal appearance UI policies apply.
Compact mode uses the host's actual dimensions, rounded to SVG pixels with a
1px minimum. An unmeasurable host uses the renderer's existing default size
until it is measured. The plot has a maximum 4px inset, reduced on very small hosts. It
hides headings, axes/ticks/grid, legend, data labels, category-group bands,
automatic category scrollbars, navigation/navigator/brush controls, range selector,
annotation editor and sampling explanation furniture. All loaded categories fit
in the current configured viewport; bars do not acquire an automatic four-item
scroll window. Source limits and explicit path sampling remain active. Compact
mode does not change the semantics of noninteractive internal previews.
Axis bounds/scales/reversal and saved viewport still determine the plot.
Source values/IDs, null policies, stacks, curves, selection, raw tooltips,
keyboard inspection, references and static annotations retain their shared
contracts. The saved title still names the accessible chart even though it is
not painted. The shared SVG description, raw source summary and keyboard
inspection instructions remain available through `aria-describedby`. Renderer callbacks receive canonical authored settings and raw data.
The mode neither serializes runtime callbacks nor rewrites the dataset.
Authored titles, legend, data labels, navigation and editable annotation settings
remain in the model. Set `sparkline: false` to restore ordinary layout and its
320×240 minimum viewBox. Compact is not a rendering backend or a dense-data
performance guarantee; use existing limits/sampling deliberately.
## Persistence and export
The mode is persisted with the ordinary native chart model and works in Pivot,
SVG server rendering and hydration. Restore with `createChart({ container,
model: savedModel })` or the Pivot model API; the factory itself accepts source
arrays, not saved models. Runtime tooltip/style/callback settings must be supplied
again when restoring. Model restoration returns the ordinary ChartRef; rebuild
source arrays with `createSparklineDataset` for later dataset updates.
SVG downloads retain compact geometry. CSV retains raw loaded observations,
including gaps, under the usual data-scope policy. PNG/PDF use the existing
encoders and export size limits. Always destroy the ref when its host unmounts.
The [MUI sparkline reference](https://mui.com/x/react-charts/sparkline/) describes
the upstream compact line/area/bar use cases. This API follows the framework-neutral
Charts contracts; it does not add React-specific slots or MUI domain-limit aliases.
---
# Tooltips
URL: https://pro.rv-grid.com/guides/charts/tools/tooltips/
Source: src/content/docs/guides/charts/tools/tooltips.mdx
Description: Inspect semantic point values and customize tooltip content.
Inspect semantic point values and customize tooltip content. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Hover or keyboard-focus a chart mark. The footer in this example comes from a custom tooltip formatter. The native renderer owns positioning and accessibility; the formatter receives semantic context and default content.
Tooltips can return structured rows, plain text, an element, or null. Keep custom content readable and avoid treating untrusted labels as HTML.
## API and configuration
```ts
import { createNativeChartsRenderer } from '@revolist/charts/core';
import { lineChart } from '@revolist/charts/line';
const renderer = createNativeChartsRenderer([lineChart], {
tooltip: {
showDelayMs: 0, hideDelayMs: 100, followPointer: true,
formatter: (_context, content) => ({
...content, footer: 'Illustrative data · € thousands',
}),
},
});
// Pass renderer when creating a chart registered with lineChart.
// tooltip: false disables the native tooltip.
```
## Support and behavior
Datum content follows each type's semantic bindings. Shared mode is a navigation capability rather than a universal tooltip layout. Formatter functions are runtime configuration; reattach them after restoring a serialized model.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Tooltip options and formatter results
| `rendererOptions.tooltip` field | Default and behavior |
| --- | --- |
| `false` | Disable the tooltip surface; crosshair/peer-hover inspection can remain active independently. |
| `showDelayMs` | 120 ms before pointer opening; keyboard inspection opens immediately. Finite negative delays clamp to zero; nonfinite values use the default. |
| `hideDelayMs` | 80 ms pointer closing delay, with existing focus/pointer transfer allowances. |
| `followPointer` | `true`; `false` stops pointer-follow repositioning while the same target remains active. |
| `className` | Additional classes on the HTML tooltip root, separate from SVG `className`. |
| `formatter(context, defaultContent)` | Optional synchronous formatter. Return `defaultContent` (or a copy) to preserve defaults. |
`context` contains `chartId`, `chartType`, canonical `category`, `series`, raw
`value`, `displayValue`, `color` and `drillable`. Relationship points add
`channelValues`, `channelSeries` and `channelDisplayValues` for X/Y/size. Shared
modes add `sharedItems` and `omittedSharedItemCount`; complete histogram
inspection adds `aggregate`/`histogramItems` with bin metrics and genuine
representative provenance. These contexts do not grant raw-record access or
turn aggregate bins into clickable source rows.
Return plain text, an `HTMLElement`, or `{ title?, rows?, footer?, ariaLabel? }`.
Each row accepts `{ label, value, color? }`. `null`, `undefined` or an empty
string suppresses tooltip content; `undefined` does **not** mean retain the
default. Invalid result shapes report `invalid-tooltip-result` and use the
default content. Strings are rendered as text. An application-created element
is application-owned content: avoid unsafe HTML and retain readable,
noninteractive inspection semantics.
Tooltips are transient HTML, absent from model snapshots and image exports.
Reattach the formatter after restore. Resize, repaint and destroy release
pending timers/listeners with the inspection surface; preview mode has no
interactive tooltip. The formatter may run repeatedly as inspection changes,
so keep it synchronous and free of chart updates or retained source mutations.
---
# Underlying records
URL: https://pro.rv-grid.com/guides/charts/tools/underlying-records/
Source: src/content/docs/guides/charts/tools/underlying-records.mdx
Description: Inspect the original facts behind a chart observation through an accessible, paginated source-record view.
**View underlying records** opens the original facts behind a real chart
observation. This differs from **View data**, which displays the chart's
aggregated dataset. Charts owns the action, table, loading/error states,
pagination and keyboard focus. Your application or Pivot owns the source query.
## Standalone example
The application below supplies both the aggregate chart rows and the original
orders. Charts does not reverse an aggregate into invented transactions.
```ts
import { createGroupedColumnChart } from '@revolist/charts/grouped-column';
const orders = [
{ orderId: 'N-1', region: 'North', revenue: 100 },
{ orderId: 'N-2', region: 'North', revenue: 50 },
{ orderId: 'S-1', region: 'South', revenue: 80 },
];
const chart = await createGroupedColumnChart({
container: document.getElementById('chart')!, // Give the host an explicit height.
data: [
{ region: 'North', revenue: 150 },
{ region: 'South', revenue: 80 },
],
mapping: {
category: { field: 'region' },
series: [{ field: 'revenue', label: 'Revenue' }],
},
underlyingRecords: async ({ target, offset, limit, signal }) => {
signal.throwIfAborted();
const matching = orders.filter(order => order.region === target.category.path[0]);
return {
records: matching.slice(offset, offset + limit),
totalCount: matching.length,
columns: [
{ key: 'orderId', label: 'Order' },
{ key: 'region', label: 'Region' },
{ key: 'revenue', label: 'Revenue' },
],
};
},
});
```
Inspect an observation by pointer or keyboard, then activate **View underlying
records**. The action is disabled until a real observation has been inspected.
Synthetic Others, cumulative Pareto shares, analytical overlays and forecasts
cannot request source transactions.
For remote records, pass the provided `signal` to your fetch/query and apply
pagination at the source. Use stable semantic fields and measure IDs rather
than display labels. The provider controls authorized fields and source access.
## Pivot integration
With Pivot and Pivot Charts already registered, opt into native source queries:
```ts
const chart = await charts.createChart({
container,
chartType: 'groupedColumn',
totals: 'none',
underlyingRecords: true,
});
```
The built-in provider uses Pivot's existing paginated drillthrough API. It
requires a current linked chart without an independent chart query, so the
visible chart and source query share the same scope. For independent queries,
unlinked snapshots or custom source policies, supply the callback used in the
standalone example. A chart's aggregated data is never used as a substitute
for missing source facts.
## Provider contract
The request contains `target`, zero-based `offset`, `limit` and an `AbortSignal`.
`target` carries the real category, series, value and semantic paths. Return
`records` and optionally a consistent `totalCount` and a column whitelist.
The shared view requests pages of 50 records. A response must not exceed the
requested limit; known totals must agree with nonempty returned pages. Explicit
columns need unique nonempty keys and labels and are limited to 100 columns.
Invalid responses show the localized error state. The package does not execute
HTML from records: values and labels are displayed as text.
## Keyboard, cancellation and saved state
The record table has a labelled dialog, loading/error status, Previous/Next
page controls and a close action. Escape closes it and returns focus to its
opening action. Paging preserves usable keyboard focus when the last page
disables Next. Closing, rerendering or destroying the chart aborts pending
requests; superseded responses cannot overwrite the current view.
Providers are runtime callbacks and are not serialized into saved models.
Reattach a standalone provider when recreating a chart. For Pivot restore:
```ts
const restored = await charts.restoreChart(savedModel, {
container,
underlyingRecords: true,
});
```
Use `localeText.underlyingRecords` to translate the shared view. Pivot's UI
policy `actions.underlyingRecords` can hide or disable its action.
[Analysis](/guides/charts/analysis/) · [Chart tools](/guides/charts/tools/)
---
# Value-axis navigation & XY brushing
URL: https://pro.rv-grid.com/guides/charts/tools/value-navigation/
Source: src/content/docs/guides/charts/tools/value-navigation.mdx
Description: Independent numeric Y windows and semantic source-point selection in standalone Charts and Pivot.
Standalone Charts and Pivot share these native SVG capabilities. Supported chart types are `line`, `smoothLine`, `steppedLine`, `area`, `steppedArea`, `stackedArea`, `normalizedArea`, `scatter`, `bubble` and `quadrant`. Other families reject the new M1 policy fields. Existing heatmap navigation, range selectors and histogram navigation retain their own contracts.
## Configure independent windows
```ts
await chart.update({
axes: {
...chart.model.axes,
y: { min: 0, max: 100, title: 'Units' },
yAxes: { amount: { scale: 'log', min: 1, max: 10000, title: 'Amount' } },
},
seriesOverrides: {
...chart.model.seriesOverrides,
revenue: { ...chart.model.seriesOverrides?.revenue, yAxis: 'amount' },
},
navigation: {
enabled: true,
axes: 'xy',
xAxis: 'primary',
yAxis: 'amount',
yLimits: { min: 10, max: 10000, minSpan: 0.05, maxSpan: 0.9 },
brush: true,
},
viewport: {
...chart.model.viewport,
yAxes: { ...chart.model.viewport?.yAxes, amount: { scale: 'log', min: 100, max: 1000 } },
},
});
```
The `revenue` override must name a real source series. Author named axes before assigning them, or commit definitions and assignments atomically. Primary and secondary aliases continue to work; the primary axis can remain implicit. `secondary` uses `axes.ySecondary` or its existing implicit default. Named IDs must resolve in `axes.yAxes`.
| Navigation option | Contract and default |
| --- | --- |
| `axes` | `'x'`, `'y'` or `'xy'`. Omission preserves existing behavior: X only for line/area, XY for relationship charts. Controls, wheel, drag, pinch and rectangle zoom change only enabled dimensions. This does not hide axes or discard dormant saved windows. |
| `xAxis` | Existing selected X identity; default `'primary'`. Named-X requirements remain unchanged. |
| `yAxis` | Selected numeric Y identity; default `'primary'`. Accepts `'secondary'` or an authored named axis. A nonempty trimmed string up to 128 characters; prototype names are rejected. Selecting an axis does not reassign series. |
| `yLimits` | Optional owned `ChartValueNavigationLimits` object with exactly `min`, `max`, `minSpan`, `maxSpan`. Omission allows the full Y domain and the existing minimum gesture span. Applies to the selected Y owner. |
| `yLimits.min`, `yLimits.max` | Optional finite bounds in raw public Y units, intersected with the full axis domain. Both authored requires `min < max`. Logarithmic lower bounds must be positive. Nonintersecting or nonrepresentable limits reject. |
| `yLimits.minSpan` | Smallest gesture window as a fraction of the full **transformed** domain; greater than zero and at most one. Default `0.0001`. Must fit in the permitted domain intersection. |
| `yLimits.maxSpan` | Largest gesture window in the same fractions; greater than zero and at most one. Default `1`. Must be at least `minSpan` when both are authored. |
| `brush` | Boolean, default false. Enables separate XY source selection by Alt-drag or keyboard. Independent of `enabled`; selection works with navigation gestures disabled. |
Limits constrain navigation gestures, not explicit API-authored viewport ranges. Adding limits does not silently change an existing viewport. The next gesture clamps its window. Reset removes the selected enabled dimensions' windows and returns them to their full domains; it retains limits for subsequent gestures. In Y-only mode, X windows remain untouched; in X-only mode, Y windows remain untouched. Reset and commits preserve all other named axes. Values are absolute public units; span constraints operate after linear/log/sqrt/symlog transformation. Axis reversal changes physical direction while saved `min < max` remains semantic.
`enabled`, `actions` and `gestures` keep their existing defaults. Hidden/disabled actions cannot activate. `gestures.rectangle: false` disables rectangle zoom; `wheel`, `pan` and `pinch` can be disabled independently. There are no new rendering backends or dense-data performance claims.
## Viewport persistence and projection
`viewport.y` remains the primary numeric Y window. `viewport.yAxes.secondary` addresses the secondary alias; `viewport.yAxes[id]` addresses a named value axis. Each entry is `{ scale: 'linear' | 'log' | 'sqrt' | 'symlog', min, max }` with finite increasing bounds and a scale matching that axis. Log windows require positive bounds. Up to 32 own entries are accepted; IDs are trimmed nonempty strings of at most 128 characters. Primary and prototype names are reserved in `yAxes`; use `viewport.y` for primary. The enclosing viewport/configuration is a replacement, so spread windows you intend to retain.
Older saved models without these fields keep their defaults. Definitions, selected axis policy, limits, brush opt-in, committed windows and semantic selections persist. Runtime callbacks, active pointers and brush/zoom drafts do not. Invalid type/axis/scale updates reject atomically. A removed named axis must have its window and assignments removed or reassigned in the same update. Hidden series can retain dormant assignments and windows.
The same current projections feed paths, marks, axes, clipping, annotations, error bars and inspection. Full scales remain separate from windows. Overflow-safe scales preserve public units and symlog constants. Stacked and normalized area windows use the displayed cumulative/percentage coordinates; selections and data export still identify raw source observations. Preview/overview rendering retains full-domain geometry.
## Zoom and pan
Wheel zooms around the pointer; drag pans and pinch zooms around the touch center. Shift-drag creates a zoom rectangle on supported XY geometry. It is a **viewport** operation and does not select or activate source points. The existing `gestures.rectangle` policy also applies to the supported ordered line/area families when Y navigation is enabled.
Focus the plot/navigation controls: arrows pan, `+`/`−` zoom and `Home` resets enabled dimensions. Up/down controls are available when value navigation is active. Shift increases the keyboard pan step. Escape cancels an active zoom gesture and restores the committed viewport. Programmatic windows remain usable when interactive navigation is disabled. An authored X navigator remains visible but read-only in Y-only mode; its handles are disabled. Use the plot/toolbar for Y navigation. This milestone does not add a separate Y slider.
## XY source selection
Alt-drag inside the plot selects eligible point centers in a rectangle and **replaces** the semantic selection. A drag must cover at least six SVG units in both dimensions; a click is not a brush. Only the configured `navigation.xAxis` and `navigation.yAxis` participate, even when overlapping layers or other axes occupy the same plot.
Eligibility comes from canonical geometry, not SVG glyphs: sampled/omitted markers remain selectable, duplicate X positions keep distinct category/series identities, and hidden series, null/nonfinite values, clipped points, synthetic categories and derived series are excluded. Selection uses point centers, not bubble radius intersection or line-segment intersection. Closed rectangle edges are inclusive. For stacked/normalized areas the rendered source endpoint determines membership.
Tab to the localized XY selection surface. Arrow keys move its box by 5% of plot size; Shift+arrows resize its right/bottom edge by 5%. The initial box spans the visible plot. Enter commits, Delete/Backspace clears selection, and Escape cancels a draft. Committing or clearing preserves keyboard focus through repaint. Pointer cancellation, lost capture, host updates and destroy cancel transient drafts. Keyboard blur cancels an uncommitted keyboard draft. The next independent pointer action is not consumed by stale click suppression.
Brushing does not fire `onNodeClick` or drill-through. Native charts commit through their existing `setSelection` policy. In `interaction.mode: 'event'`, the existing `onSelectionRequest` callback receives the proposed semantic targets and the host controls the commit. Pivot uses its existing selection transaction/events and filter/highlight policy; it retains query/source-record authority. Selecting an aggregated Pivot point identifies its semantic cell/measure, not a fabricated list of original records. Runtime adapters expose `ChartRenderInput.onSelectionChange(selection, origin)` with `'pointer' | 'keyboard'`; hosts supplying custom render inputs own this callback. Renderers must advertise navigation and semantic selection support.
The existing one-dimensional `rangeSelection` API remains separate. Its range events and persisted interval are unchanged. Alt-drag uses shared gesture ownership so it cannot simultaneously pan, draw annotations, or modify that range. Clear source selection through the brush keyboard surface or existing selection API.
## Providers, exports and Pivot
Windowed sources receive the complete `viewport.y`/`yAxes` alongside X windows. Supply full-source numeric bounds in `fullDomain.y` and `fullDomain.yAxes` to keep gesture domains independent of the loaded page. Requested named Y windows require matching named full-domain metadata; omitted or mismatched scales reject the response. Pivot viewport providers must echo every named Y window exactly, preserving the existing correlation/revision/cancellation rules. Full-domain metadata is runtime state and must be supplied again after restore.
Current-view CSV keeps canonical rows with visible source points and leaves clipped path cells empty; Y membership uses displayed stack coordinates but emitted numbers remain raw. `{ dataScope: 'all' }` retains the accepted complete local dataset, without fetching missing remote rows. Normal SVG/image exports omit brush surfaces/drafts and retain committed chart content. SSR and hydration retain declarative options/windows; hosts reattach runtime callbacks. No server rows are reconstructed by brushing or export.
The Pivot navigation inspector edits dimensions, selected Y axis, all four limit fields and brush opt-in through the same options. Switching the selected Y axis clears the previous axis's gesture limits to avoid applying unrelated units. Compatible gallery changes retain Y policy/windows; unsupported families prune them. Public types are `ChartNavigation`, `ChartValueNavigationLimits`, `ChartViewport` and their Pivot aliases, including `PivotChartValueNavigationLimits` through both root and charts entry points.
New localized captions under `localeText.navigation` are `navigationAxes`, `navigationDefault`, `navigationYAxis`, `xyBrush`, `navigationYMin`, `navigationYMax`, `navigationYMinSpan`, `navigationYMaxSpan` and `xyBrushInstructions`. These new captions are optional on older manually constructed full locale records and fall back to shared defaults. Existing numeric navigation instructions continue to describe zoom/pan keys. Labels are text; they do not change gesture bindings.
M1 ends here. Polar navigation, cross-chart unit conversion, batched SVG and WebGL remain separate milestones.
---
# Optional WebGL geometry rendering
URL: https://pro.rv-grid.com/guides/charts/tools/webgl/
Source: src/content/docs/guides/charts/tools/webgl.mdx
Set `chartOptions.rendererPolicy: 'webgl'` to opt in. Standalone Charts and Pivot
use the same owner and appearance control. Omission remains SVG; no size threshold
silently enables a GPU. Existing category/series/data-point limits still apply;
GPU selection does not raise them. The stress fixtures explicitly set
`limits: { maxCategories: count, maxSeries: 2000, maxDataPoints: count * 3 }`
for their known input shape. `ChartRendererPolicy` / `PivotChartRendererPolicy` include
this value; the existing `ChartSvgRendererPolicy` aliases remain SVG-only.
```ts
await chart.update({ chartOptions: { rendererPolicy: 'webgl' } });
// Explicitly return to the compatible default:
await chart.update({ chartOptions: { rendererPolicy: 'svg' } });
```
| Native family | Accepted policies | CPU fallback after GPU failure |
| --- | --- | --- |
| `groupedColumn`, `groupedBar`, `scatter` | `svg`, `batched-svg`, `webgl` | Batched SVG |
| `heatmap` | `svg`, `webgl` | Existing SVG heatmap buckets |
| Other families, including combination, line, polar and geographic | Existing policies; `webgl` rejects atomically | No GPU port |
This policy belongs to the native renderer. ECharts and custom adapters retain
their own rendering contracts; this option does not enable WebGL in them.
Canonical CPU data, domains, navigation, clipping and inspection remain authoritative.
Bars/scatter retain the batched geometry index and a bounded number of SVG paths,
with no individual DOM node per observation. Arrow/Home/End inspection and
Enter/Space activation resolve canonical raw data. Heatmaps retain existing bounded
row/column bucket reduction: an aggregate bucket is inspection-only, while a
single-source cell retains raw activation. A million input matrix cells does not
mean a million GPU rectangles. Existing category/bar navigation, scatter numeric
X/Y windows and heatmap row/column windows retain their semantics.
## Styling and interaction limits
The following requests deterministically use individual SVG: authored datum or
segment encodings (including an empty encoding), runtime `datumStyle`, `elements`
or `onRender`, saved member/measure/value-map colors, color-channel binding,
authored bar stack groups, derived series, synthetic aggregate categories, and
selection or peer-highlight appearance. WebGL previews/sparklines and authored
`markOpacity` other than `1` also use SVG. Clearing incompatible saved options
restores eligibility; runtime options require recreating the renderer.
GPU geometry supports the existing basic rectangular/rounded bar and circular
scatter marks, theme/palette fills, heatmap intensity/grid strokes and clipping.
Named/custom CSS colors outside supported hex and numeric RGB/RGBA paint forms
fall back to CPU paint. Per-item DOM selectors/listeners/classes and external
per-datum CSS are unsupported. Select SVG when relying on them. Hover CSS/filter
paint effects are not mirrored by GPU geometry. CPU tooltips, crosshair, keyboard
focus and activation remain available; selection updates can trigger an SVG
fallback and a full CPU repaint. Transitions remain immediate on this backend.
GPU float precision, edge antialiasing and scatter alpha compositing can differ
from SVG; pixel-identical output and SVG feature equivalence are not promised.
## Availability, resize and recovery
One WebGL2 context belongs to each active chart surface. The backing canvas follows
actual size and device pixel ratio; plot clipping uses the same CPU bounds. GPU
size limits, resource allocation, shader/draw errors and unavailable WebGL2/SSR
use CPU SVG. A surface whose initial GPU probe fails stays on CPU through data
updates; recreate it, or switch to SVG and back, to retry availability. Subpixel
grouped bars use a floating-point coverage buffer to avoid
rounding tiny marks to transparent. This additionally requires
[`EXT_color_buffer_float`](https://registry.khronos.org/webgl/extensions/EXT_color_buffer_float/)
and [`EXT_float_blend`](https://registry.khronos.org/webgl/extensions/EXT_float_blend/);
unavailable extensions produce CPU fallback.
Context loss shows CPU geometry immediately. After `webglcontextrestored`, shaders,
buffers and dense-bar coverage resources are recreated from the latest committed
scene, including windows updated while the context was lost. Recovery follows the
[`WEBGL_lose_context` lifecycle](https://registry.khronos.org/webgl/extensions/WEBGL_lose_context/).
Resize reuses the context; switching backend or destroying the chart removes
listeners, releases GPU resources and loses the old context. The model stores
only the requested policy and existing data/windows, never a GPU context, canvas
or callback. Hydration rechecks host GPU availability.
The SVG surface reports the **effective** `data-chart-renderer-policy` and optional
`data-chart-renderer-fallback`. These diagnostics do not change the saved policy.
Failures use reasons such as `webgl-unavailable`, `webgl-context-lost`,
`webgl-initialization`, `webgl-size-limit`, `webgl-float-coverage`, `webgl-draw`,
`webgl-paint` or `webgl-empty-frame`. Customization fallbacks report their existing
reason (for example `datum-style`, `runtime-customization` or `selection-style`).
## Export and performance policy
SVG, PNG and PDF exports reconstruct CPU SVG from the committed geometry and
exclude GPU canvases, controls and transient focus. They never screenshot the GPU
framebuffer. Bars/scatter export batched SVG; heatmaps export native SVG buckets.
Raw data views and CSV retain their existing source/window semantics. SVG and
raster exports can therefore differ at edges or overlapping translucent marks
from the interactive GPU view; authored data and domains remain the same.
See [performance protocol and results](/guides/charts/tools/webgl-performance/) and
[verification](/guides/charts/tools/webgl-verification/). CPU preparation and semantic-index costs
remain; selecting WebGL alone does not establish frame-rate, native GPU memory or
million-observation performance parity.
---
# WebGL performance protocol and budgets
URL: https://pro.rv-grid.com/guides/charts/tools/webgl-performance/
Source: src/content/docs/guides/charts/tools/webgl-performance.mdx
Latest result: the [19 October CPU/memory optimization](#cpu-and-retained-memory-optimization--19-october-2026) reduces measured 1m creation and retained JS for all three selected families. Three 1m GPU-submission budget misses remain; the full GPU performance gate is open. Earlier closure tables are historical.
Reference: Apple M1 Pro, MacBookPro18,3, 16 GiB RAM, macOS 26.4 (25E5207k),
Chromium 147.0.7727.15, full `chromium` channel, ANGLE Metal / Apple M1 Pro.
Both CPU baseline and GPU use `--use-angle=metal` and an explicit 8 GiB V8 heap
ceiling (`--js-flags=--max-old-space-size=8192`). This is a stress-test launch
configuration, not a claim that default browser heap limits accept 1m inputs.
The retained incremental JS budget remains 4 GiB. Software SwiftShader exploratory
runs are excluded from hardware performance claims.
The 19 October CPU/memory optimization targets a reduction of at least 25% in
both completed creation p95 and retained incremental JS heap for each 1m
`groupedColumn`, `groupedBar` and `scatter` case, compared with fresh, unprofiled
reference triples on the same machine and production graph before optimization.
All existing absolute gates below remain unchanged. The 100k, CPU SVG, 1k
default-SVG and functional compatibility checks remain regression requirements.
CPU and sampled-allocation profiles are diagnostic runs and are excluded from
performance gate claims.
Budgets were recorded before GPU implementation in the local feature plan:
| Gate | 100k | 1m |
| --- | ---: | ---: |
| Completed CPU-inclusive creation p95 | 5,000 ms | 45,000 ms |
| Completed resize p95 | 2,000 ms | 20,000 ms |
| Synchronous pointer / keyboard inspection p95 | 50 ms | 100 ms |
| Retained incremental JS heap | 512 MiB | 4,096 MiB |
| CPU GPU-submission p95 | 33 ms | 100 ms |
| Vertex buffer storage | 16 MiB | 128 MiB |
| Dense-bar floating coverage storage at reference DPR 1 | 32 MiB | 64 MiB |
Acceptance protocol: three serial standalone production-ESM runs per case, with no concurrent build/test work. Execution notes identify runs where that isolation was not established. Pivot has separate functional compatibility coverage; this is not a Pivot host performance measurement.
Nearest-rank p95 of three is their maximum. The 960×440 host resizes to 800×440.
Create includes model normalization/validation/snapshots, CPU geometry, semantic
index, CPU fallback paths, GPU upload/draw, two RAFs and a benchmark-only
`gl.finish()` completion fence. Module/data construction precedes the timer.
Resize waits 100 ms for observation and two RAFs (the fixed 100 ms is excluded),
then finishes GPU work. Ordinary interactive paints never call `finish()`.
CPU baselines use batched SVG for bars/scatter and native SVG for heatmap under
the same fixture/launch protocol. Their values are comparable within this matrix;
the original [M2 protocol](/guides/charts/tools/batched-svg-performance/) is retained separately.
Bar inputs are one value per row; scatter has one numeric X/Y pair per row
(two analytical measures). Heatmap counts are **matrix cells**: 1,000 series ×
100/1,000 rows. Existing bounded row/column reduction is retained, so heatmap
paints at most 2,400 buckets, rather than 100k/1m raw GPU marks. Functional
fixtures separately verify single-source raw activation and bucket semantics.
No nulls are used in timing fixtures; functional tests cover nulls/clipping.
Each case pools 60 synthetic synchronous pointer/key measurements; these exclude
tooltip delay, browser input scheduling and next-frame presentation. Pointer
measurements include canonical CPU hit/description resolution. Heatmap aggregate
bucket measurements are inspection, not activation of an invented raw record.
Real input activation has separate browser coverage. These measurements do not
establish smooth panning, 60fps or generalized interaction-latency parity. Export
latency is not benchmarked.
CDP GC precedes/follows each run. Retained JS includes the committed CPU model,
index and scene; raw records also include embedder/backing storage, node/batch
counts, actual driver, shape dimensions, device pixel ratio and GPU byte metrics.
The tracked vertex-buffer metric is retained allocated capacity of the shared
upload buffer. Uniform-paint/shape plots use 16 bytes per instance with exact constant
shape and color attributes. Mixed paints/shapes expand once to 64 bytes per instance, retaining
one instanced draw per plot and source-over order even for alternating series.
Vertex capacity grows when needed and is reused for smaller frames. Uniform
CPU typed staging is also reused by plot index, avoiding fresh geometric growth
on resize. Unused staging is discarded after collection; destroy clears it,
and context loss retains only the live CPU geometry needed for recovery. Floating coverage is width × height × 16 bytes. Drawing color storage
is width × height × 4 **an estimate**, not a GPU-process allocation measurement.
Preserved drawing buffers, driver copies, native process RSS, OS/GPU allocations
and long-session memory are not measured. Do not add these estimates to JS heap
and present them as total GPU/application memory. GPU-submission time measures CPU work in the draw/upload loop (typed view setup,
upload and draw calls), not GPU execution alone. Earlier canonical geometry
collection/staging and resource initialization are outside this narrow loop;
completed create/resize timings include them. The timing boundaries remain the
same between the initial and final matrices. Typed staging removes the duplicate
JS-number scene and late Float32 copy. The closure keeps the same timer and
budgets; attribute setup now also occurs inside the measured upload/draw loop.
Assertion-only validation checks mapped metadata without constructing discarded
owned frames; public parsing still owns every snapshot. Derived/hierarchy frames
retain their existing validation/materialization owner. Scatter point collection
and lookup avoid temporary per-observation arrays and string tuple keys.
Floating coverage textures also retain capacity on shrink; texture-coordinate
scaling samples only the current viewport. Completeness is checked on allocation
or growth and again after recovery, avoiding a synchronous framebuffer check on
every redraw. The existing coverage kernel is unchanged; resize and DPR 1/2
fixtures verify the resulting pixels.
## Reproduction
```sh
E2E_PACKAGE=charts E2E_PORT=5421 CHART_GPU_BACKEND=metal CHART_WEBGL_BENCHMARK=/private/tmp/charts-gpu pnpm --filter @revolist/revogrid-e2e-enterprise exec playwright test tests/chart-webgl-benchmark.spec.ts --config=playwright.webgl.config.ts --project=charts --workers=1
# Repeat serially with CHART_WEBGL_BASELINE=1 and a different output prefix.
node packages/charts/scripts/report-webgl.mjs packages/charts/docs/webgl-optimized.json --check
```
GPU fallback is an error in this benchmark, never a GPU pass. Partial cases,
timeouts and crashes remain explicitly incomplete. Timing-fixture completion
is separate from satisfying the performance gates.
For diagnostics only, set `CHART_WEBGL_PROFILE=/private/tmp/chart-cpu` and/or
`CHART_WEBGL_HEAP_PROFILE=/private/tmp/chart-heap`. The first run records a CDP
CPU profile and/or a retained-allocation profile sampled at 1 MiB intervals.
The report checker rejects these instrumented matrices. `callerDatasetRetained`
records whether the input dataset remains reachable through a benchmark-only
WeakRef after the evaluation returns and CDP GC completes. This checks internal
retention; an application can still intentionally retain its own input data.
## CPU and retained-memory optimization — 19 October 2026
The latest implementation removes redundant work without reducing the number of
observations, canonical inspection entries or CPU fallback paths:
- Ordered one-cell-per-observation frames validate and clone positionally, avoiding
several full-size lookup maps. Reordered/shared mappings use the general parser.
- Standalone runtime closures retain callback references rather than the caller's
complete options/frame. Unobserved change notifications avoid an unused snapshot;
public getters, subscribers and custom-renderer inputs retain independent ownership.
- Only a freshly parsed native-renderer snapshot can reuse the private validation
proof. Direct, decorated and custom renderer calls retain their validation path.
- Grouped bars reuse value rows and resolve inspection lazily instead of allocating
per-category maps and stacked endpoint matrices. Contiguous X coordinates use
positional lookup; reordered numeric windows keep explicit maps.
- Batched inspection uses numeric spatial buckets and ordered source arrays with
binary lookup, retaining a map fallback for unordered collectors.
- Scatter retains group references rather than copied group fields, releases its
temporary point collection after paint, resolves inspection from the canonical
matrix, and computes named-axis extrema without million-element filter/map arrays.
Diagnostic CPU profiles identified GC, snapshot cloning and metadata lookup/index
construction as the dominant CPU costs. Sampled allocation profiles and a post-GC
WeakRef check confirmed that the original caller dataset was retained before these
changes and released afterward. An application can still retain its own input.
The chart continues to retain owned model/render data and a semantic inspection
index; this is not a zero-copy or constant-memory implementation.
The fresh reference matrix (`packages/charts/docs/webgl-memory-reference.json` in the source repository) records the pre-change
production graph. The contended final-graph matrix (`packages/charts/docs/webgl-memory-contended.json` in the source repository)
retains all eight completed cases run while unrelated Node jobs competed for CPU;
it misses the 1m GPU-submission gates and is not quiet-machine acceptance evidence.
The 18 October results below remain historical and do not override newer misses.
The reference task had no acknowledged quiet window, so CPU before/after percentages
are local observed differences, not a controlled paired speedup. The optimized
acceptance run uses the separately coordinated quiet window. Every matrix
identifies its emitted JavaScript hash. Instrumented profiles are
excluded from gate checks. Retained JS is measured after GC, not peak memory,
native process RSS, total application memory or GPU-driver allocation.
### Latest measured GPU matrix
The final graph was measured between 11:39:52 and 11:47:03 UTC, inside the
coordinated 11:39–11:57 UTC hold on other workspace builds/tests (extended to finish CPU/SVG controls). All eight timing
fixtures complete. Each selected 1m case exceeds the observed 25% creation/retained-JS
reduction target, subject to the reference-isolation limitation above.
| 1m family | Create p95 before → after ms | Observed change | Retained JS before → after MiB | Observed change | Resize p95 before → after ms |
| --- | ---: | ---: | ---: | ---: | ---: |
| groupedColumn | 28,618.6 → 20,018.2 | -30.1% | 2,104.9 → 1,436.1 | -31.8% | 15,835.2 → 8,203.4 |
| groupedBar | 31,622.4 → 21,003.2 | -33.6% | 2,134.8 → 1,467.5 | -31.3% | 17,869.0 → 11,938.3 |
| scatter | 50,021.1 → 31,630.2 | -36.8% | 2,939.7 → 2,120.7 | -27.9% | 11,884.8 → 15,282.6 |
Scatter resize is slower than the fresh reference (15,282.6 vs 11,884.8 ms),
although below its 20,000 ms absolute gate. This change does not claim a scatter
resize improvement. All creation, resize, inspection, retained-JS, vertex and
coverage gates pass. The 1m GPU-submission gates remain open for groupedColumn
(108.9 ms), groupedBar (103.0 ms) and scatter (124.2 ms), against 100 ms.
Therefore the full GPU performance checker intentionally exits nonzero; these
results are an improvement, not closure of all M3 performance requirements.
| Family | Inputs | Create p95 ms | Resize p95 ms | JS MiB | Pointer / key p95 ms | Submit p95 ms | Vertex / coverage MiB | Gates |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| groupedColumn | 100000 | 1522.9 | 485.1 | 145.9 | 0.2 / 0.6 | 2.6 | 1.5 / 6.4 | pass |
| groupedColumn | 1000000 | 20018.2 | 8203.4 | 1436.1 | 1.4 / 2.0 | 108.9 | 15.3 / 6.4 | submit |
| groupedBar | 100000 | 1350.2 | 613.0 | 149.1 | 0.2 / 0.6 | 1.5 | 1.5 / 6.4 | pass |
| groupedBar | 1000000 | 21003.2 | 11938.3 | 1467.5 | 2.3 / 1.6 | 103.0 | 15.3 / 6.4 | submit |
| scatter | 100000 | 1890.0 | 521.1 | 214.2 | 0.1 / 0.5 | 1.2 | 1.5 / 0.0 | pass |
| scatter | 1000000 | 31630.2 | 15282.6 | 2120.7 | 0.9 / 2.3 | 124.2 | 15.3 / 0.0 | submit |
| heatmap | 100000 | 702.0 | 128.9 | 86.2 | 0.0 / 0.5 | 0.4 | 0.1 / 0.0 | pass |
| heatmap | 1000000 | 8023.0 | 2068.9 | 820.0 | 0.0 / 0.4 | 6.1 | 0.1 / 0.0 | pass |
Raw optimized GPU matrix (`packages/charts/docs/webgl-memory-optimized.json` in the source repository) preserves every run,
including all outliers, the confirmed hardware driver and caller-frame reachability
checks. Caller datasets are released in all optimized runs. GPU byte counts and
DOM/batch counts retain the same full-observation geometry contract.
```sh
node packages/charts/scripts/report-webgl.mjs packages/charts/docs/webgl-memory-optimized.json --check
# Expected nonzero: three remaining 1m submission-budget misses.
```
### Matched CPU comparison
All eight CPU baseline cases complete and pass their applicable unchanged gates
under the same full-Chromium/Metal/8 GiB heap protocol. Bars/scatter use batched
SVG; heatmap uses native SVG and its existing bounded bucket reduction.
| 1m family | CPU / GPU create p95 ms | CPU / GPU resize p95 ms | CPU / GPU retained JS MiB |
| --- | ---: | ---: | ---: |
| groupedColumn | 26,866.5 / 20,018.2 | 14,521.6 / 8,203.4 | 1,435.9 / 1,436.1 |
| groupedBar | 23,905.8 / 21,003.2 | 11,175.3 / 11,938.3 | 1,467.3 / 1,467.5 |
| scatter | 28,524.2 / 31,630.2 | 14,622.8 / 15,282.6 | 2,120.4 / 2,120.7 |
| heatmap | 7,658.6 / 8,023.0 | 2,089.4 / 2,068.9 | 823.6 / 820.0 |
Raw matched CPU matrix (`packages/charts/docs/webgl-memory-cpu.json` in the source repository) includes 100k and 1m triples.
GPU mode does not consistently beat CPU mode: scatter creation is slower in this
GPU matrix, and canonical parsing, snapshots, domains, inspection and fallback
paths remain CPU work. The retained-JS figures remain similar; GPU storage is a
separate metric. These results do not establish general GPU/SVG speed equivalence.
The original M2 protocol and 1k single-item SVG comparison are reported separately
in [batched SVG performance](/guides/charts/tools/batched-svg-performance/).
```sh
node packages/charts/scripts/report-webgl.mjs packages/charts/docs/webgl-memory-cpu.json --check
```
Functional verification for this change: 3,388 Charts unit tests; 65 Chromium
browser cases covering standalone/Pivot raw inspection, named bar/relationship axes, navigation,
context loss/recovery, unavailable-GPU fallback, DPR/resize, default/opt-out
transitions, exports and package consumers; 18 Firefox/WebKit SVG/fallback cases. Charts and Pivot public-type
checks, the Charts production build and isolated package-consumer checks pass.
Firefox/WebKit cases do not establish hardware WebGL performance on those browsers.
## Historical GPU results — 18 October 2026 closure
All eight workloads complete on real Metal hardware and meet every unchanged
reference gate. The two selected misses are closed:
- 1m `groupedBar` submission p95: **96.7 ms**, budget **100 ms** (previously 110.3 ms).
- 1m `scatter` completed creation p95: **38,406.7 ms**, budget **45,000 ms** (previously 50,546.3 ms).
| Family | Inputs | Create p95 ms | Resize p95 ms | JS MiB | Pointer / key p95 ms | Submit p95 ms | Vertex / coverage MiB | Gates |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
| groupedBar | 100000 | 1912.2 | 635.4 | 218.6 | 0.1 / 0.5 | 1.4 | 1.5 / 6.4 | pass |
| groupedBar | 1000000 | 26510.7 | 10717.9 | 2134.8 | 0.8 / 1.3 | 96.7 | 15.3 / 6.4 | pass |
| groupedColumn | 100000 | 1722.6 | 451.6 | 215.1 | 0.2 / 0.5 | 2.9 | 1.5 / 6.4 | pass |
| groupedColumn | 1000000 | 23329.2 | 8437.5 | 2105.4 | 1.0 / 1.6 | 97.8 | 15.3 / 6.4 | pass |
| heatmap | 100000 | 957.5 | 99.6 | 115.0 | 0.0 / 0.4 | 0.6 | 0.1 / 0.0 | pass |
| heatmap | 1000000 | 11535.9 | 1627.7 | 1107.4 | 0.0 / 0.5 | 1.0 | 0.1 / 0.0 | pass |
| scatter | 100000 | 2743.6 | 487.1 | 296.5 | 0.1 / 0.7 | 1.2 | 1.5 / 0.0 | pass |
| scatter | 1000000 | 38406.7 | 9419.0 | 2935.8 | 0.4 / 0.9 | 82.1 | 15.3 / 0.0 | pass |
Raw GPU closure matrix (`packages/charts/docs/webgl-optimized.json` in the source repository) preserves all three runs per case,
actual driver, fixture shape and emitted-JavaScript hash. The previous integrated
matrix (`packages/charts/docs/webgl-measured.json` in the source repository) retains the two misses; the initial hardware
matrix (`packages/charts/docs/webgl-initial.json` in the source repository) retains earlier submission and argument-limit failures.
Profiled, interrupted and experimental runs are excluded from these closure
claims. No observations, inspection entries or CPU fallback paths were removed.
```sh
node packages/charts/scripts/report-webgl.mjs packages/charts/docs/webgl-optimized.json --check
```
This is one named machine/browser and three runs per case, not cross-host or
cross-driver performance parity. The public styling/interaction capability
matrix is unchanged; passing these gates does not establish SVG feature
equivalence, generalized 60fps navigation or performance for other chart families.
## Historical matched CPU SVG comparison — 18 October 2026
The CPU matrix uses the same production graph, fixtures, launch flags and serial
protocol. Raw CPU reference (`packages/charts/docs/webgl-optimized-cpu-baseline.json` in the source repository) preserves every
run. Heatmap bucket reduction and explicit source limits are identical. SVG uses
the browser's normal compositor; hardware acceleration is not disabled.
| Family | Inputs | SVG / WebGL create p95 ms | SVG / WebGL resize p95 ms | SVG / WebGL retained JS MiB |
| --- | ---: | ---: | ---: | ---: |
| groupedBar | 100000 | 1897.0 / 1912.2 | 624.2 / 635.4 | 218.4 / 218.6 |
| groupedBar | 1000000 | 25546.2 / 26510.7 | 9709.7 / 10717.9 | 2134.5 / 2134.8 |
| groupedColumn | 100000 | 1673.3 / 1722.6 | 448.0 / 451.6 | 214.9 / 215.1 |
| groupedColumn | 1000000 | 22547.8 / 23329.2 | 8286.3 / 8437.5 | 2104.6 / 2105.4 |
| heatmap | 100000 | 939.2 / 957.5 | 81.3 / 99.6 | 114.5 / 115.0 |
| heatmap | 1000000 | 11399.3 / 11535.9 | 1625.2 / 1627.7 | 1107.0 / 1107.4 |
| scatter | 100000 | 2679.0 / 2743.6 | 473.4 / 487.1 | 296.3 / 296.5 |
| scatter | 1000000 | 35538.1 / 38406.7 | 9527.8 / 9419.0 | 2935.6 / 2935.8 |
Both policies meet the common creation, resize, synchronous inspection, retained
JS and structural gates in this reference matrix. GPU-only submission and buffer
gates do not apply to SVG. The CPU model, semantic index and retained SVG fallback
remain authoritative and contribute substantially to creation/resize. These
results do not establish a general GPU speedup. Native/process memory, peak GPU
allocation and long-session retention are not measured.
The previous CPU matrix (`packages/charts/docs/webgl-cpu-baseline.json` in the source repository) is retained for comparison.
M2's separate SVG and 1k default-renderer gates are rechecked on this same graph;
see [batched SVG verification](/guides/charts/tools/batched-svg-performance/).
---
# Optional WebGL verification
URL: https://pro.rv-grid.com/guides/charts/tools/webgl-verification/
Source: src/content/docs/guides/charts/tools/webgl-verification.mdx
## 18 October 2026 performance closure
Both selected million-observation gaps are closed. All eight GPU cases and all
eight matched CPU reference cases meet their unchanged gates on Apple M1 Pro,
MacBookPro18,3, 16 GiB RAM, macOS 26.4 (25E5207k), Chromium 147.0.7727.15 / ANGLE
Metal. See [all measurements, budgets and limitations](/guides/charts/tools/webgl-performance/).
The complete raw matrices retain all three runs per case and the same emitted
JavaScript hash. Profiled/interrupted experiments are excluded from these claims.
The shared Charts owner reduces discarded validation copies and large temporary
lookup tables, avoids per-point scatter array/string-key churn, and uses compact
GPU rectangle storage when shape/paint are uniform. Mixed geometry/paint retains
the original full layout and one instanced draw. Vertex, uniform typed-staging
and floating coverage resources are reused across redraw/resize. No source
observations, CPU inspection entries or SVG fallback/export geometry are dropped.
Runtime validation, public snapshot ownership and supported option values remain
unchanged. Reordered/shared-observation analytical mappings retain full lookup
validation; ordered mappings still check every identity, schema and matrix value.
## Current local checks
| Check | Result |
| --- | --- |
| Charts full unit suite | 3,038 passed / 180 files |
| Pivot full unit suite | 4,069 passed / 326 files |
| Dense SVG, navigation/XY brush and built-package compatibility | 33 passed across Chromium, Firefox 148.0.2 and WebKit |
| Metal GPU functional, coverage, export and resource lifecycle | 19 passed |
| Same GPU cohort in standard headless Chromium | 19 passed; functional evidence only |
| Forced unavailable GPU, all three browser engines | 3 passed |
| M3 GPU + matched CPU reference matrices | 8 + 8 completed; every applicable gate passes |
| M2 batched SVG + 1k default SVG regression | 9 + 3 completed; every original gate passes |
| Charts/Pivot ESM and UMD builds; source and built public types | Passed |
| Packed Charts per-type imports, isolated declarations and line-only graph | Passed; 223 modules |
| Public framework SSR checks | 3 passed |
| Portal docs contracts and production build | 9 passed; build passed |
The 19 GPU cases include four families in standalone/Pivot, raw pointer/keyboard
activation, nulls/clipping/reversal, navigated/restored windows, DPR 2 and narrow
resize, real context loss and updates while lost, CPU SVG/PNG exports and PDF
encoder input, policy switch and destroy. Additional cases verify dense subpixel
coverage before/after shrink and recovery, absent float blending/unavailable GPU,
DPR 1/2 hairlines, ordered mixed-color blending in one draw, varying radii after
layout expansion, shared typed-buffer/vertex/texture reuse, zero-area recovery
and injected upload-failure fallback/recovery. Context/listener accounting shows
no extra contexts or attached handlers after switch/destroy in these fixtures.
The PDF test verifies the canonical clean CPU-SVG payload in both products; it
does not validate a third-party PDF encoder's output bytes. Native/vector-PDF
pipeline tests pass in the full suite. Center pixels agree within four channel
values in the product fixtures; this is not whole-image or SVG feature equality.
Actual GPU recovery/performance is measured on Chromium/Metal. Firefox/WebKit
checks cover CPU rendering and unavailable-GPU behavior, not untested driver
recovery. Standard-headless/software GPU checks are functional evidence only.
Retained heap/buffer budgets do not measure native process RSS, peak driver/GPU
allocations or long-session leaks. Three-run p95 is the maximum of those samples;
the million-bar/column submission margins are small and are not cross-host guarantees.
## Release and compatibility boundary
The source and public package checks are local. The pre-closure base had green
[CI run 39084633849](https://github.com/revolist/revogrid-pro/actions/runs/39084633849);
that run predates this performance closure. The closure is committed separately
on `codex/chart-rendering-followthrough`; its source-matched CI is recorded on
[PR #733](https://github.com/revolist/revogrid-pro/pull/733), which was already
merged before this follow-up. No additional merge, package publication, version
bump or deployment is part of this request.
The package version remains 3.0.10. The separate docs examples checkout was not
edited by this performance change; its earlier build/browser evidence remains
separate from the current portal reference checks. Matching installed-package
release verification remains release work, outside this request.
## Reproduction
```sh
E2E_PACKAGE=charts E2E_PORT=5421 CHART_GPU_BACKEND=metal pnpm --filter @revolist/revogrid-e2e-enterprise exec playwright test tests/chart-webgl.spec.ts tests/chart-webgl-coverage.spec.ts --config=playwright.webgl.config.ts --project=charts --workers=1
node packages/charts/scripts/report-webgl.mjs packages/charts/docs/webgl-optimized.json --check
```
Use `playwright.charts-compat.config.ts` for the three-engine CPU and unavailable
GPU cases. The [renderer capability matrix](/guides/charts/tools/webgl/) continues to define every
style/interaction/export fallback. No GPU line, polar or geographic family was added.
---
# Zoom & pan
URL: https://pro.rv-grid.com/guides/charts/tools/zoom-and-pan/
Source: src/content/docs/guides/charts/tools/zoom-and-pan.mdx
Description: Explore a chart window with native gestures and keyboard controls.
Explore a chart window with native gestures and keyboard controls. This feature belongs to `@revolist/charts` and can be used without Pivot or a grid.
## Try it live
Use the plot's zoom, pan and reset buttons. Wheel or pinch zooms around the pointer/touch center; drag pans. Focus the plot and use arrow keys to pan, +/− to zoom, and Home to reset. Escape cancels an active gesture. Shift increases the keyboard pan step.
Numeric scatter/bubble/quadrant plots additionally support Y navigation and Shift-drag rectangle zoom. This time-series example navigates X.
## API and configuration
```ts
await chart.update({ navigation: {
enabled: true,
gestures: { wheel: true, pan: true, pinch: true },
actions: { zoomIn: 'enabled', zoomOut: 'enabled', reset: 'enabled' },
} });
await chart.update({ viewport: {} }); // Reset the window.
```
## Support and behavior
Check `getChartDefinition(type).navigation` and renderer navigation capabilities. Category windows use stable category IDs; time windows use epoch milliseconds; numeric windows use matching linear/log/symlog/sqrt scales. Navigation changes viewport, not source rows, axis settings or Pivot query scope. Individual actions can be disabled or hidden; gesture policy is configured separately.
## State and cleanup
Authored settings are available through `chart.model`. Updates replace whole settings objects: spread fields you want to retain, and await the returned promise before reading state or exporting. Destroy the controller when its host unmounts.
[All chart tools](/guides/charts/tools/) · [Chart types](/guides/charts/#available-chart-types) · [Data & Configuration](/guides/charts/data-and-configuration/)
## Independent windows
`navigation.xAxis` selects a declared X identity for toolbar actions, gestures and the navigator, defaulting to `primary`. Store its window in `viewport.xAxes[id]`; `viewport.x` remains primary. Numeric windows keep raw values under linear, log, symlog and signed square-root display transforms. See [named X axes](/guides/charts/tools/axes/#named-x-axes).
[Heatmaps](/guides/charts/types/heatmap/#two-dimensional-navigation) use `viewport.x` category ranges with **series IDs** for columns and `viewport.y` category ranges with **category IDs** for rows. Both endpoints are inclusive. Their rectangular gestures navigate the matrix; they do not create a semantic brush.
See [value-axis navigation and XY brushing](/guides/charts/tools/value-navigation/) for independent Y windows, selected X/Y gestures, all limit fields and separate source selection.
---
# Annotated time series
URL: https://pro.rv-grid.com/guides/charts/types/annotated-time-series/
Source: src/content/docs/guides/charts/types/annotated-time-series.mdx
Description: Highlight notable points along an ordered time trend. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Area
URL: https://pro.rv-grid.com/guides/charts/types/area/
Source: src/content/docs/guides/charts/types/area.mdx
Description: Emphasize the magnitude of change over an ordered axis. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Fill baseline
Unstacked areas accept `seriesOverrides[id].baseline: number | 'min' | 'max'`. Numeric baselines participate in the automatic domain; endpoint policies use the final axis bounds. See [baseline options and Pivot controls](/guides/charts/tools/axes/#area-fill-baselines).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Native stack groups
Use `seriesOverrides[id].stackGroup` for independent groups, with shared
`stackOrder` and `stackOffset` policies. Regular paths without a group remain raw;
dedicated stacked/normalized areas retain an implicit default stack. See
[normalization, defaults, source identity and Pivot controls](/guides/charts/customization/#native-line-and-area-stack-groups).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# Area and line
URL: https://pro.rv-grid.com/guides/charts/types/area-line/
Source: src/content/docs/guides/charts/types/area-line.mdx
Description: Compare one filled area series with one or more line series. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Normalized column stacks
Set `chartOptions.normalizeStacks: true` and assign column layers the same `stackGroup` to normalize their positive and negative contributions independently. `chartOptions.normalizedTo` defaults to 100 (range 1 through 1,000,000). Raw source tooltips/CSV and noncolumn layers retain their values. See [normalization domains, navigator behavior and Pivot controls](/guides/charts/tools/axes/#normalized-combination-column-stacks).
## Grouped area and line layers
Column, area and line layers accept `seriesOverrides[id].stackGroup`. Area/line groups share cumulative paths independently of column groups; `normalizeStacks` and `normalizedTo` apply to both families. See [sign boundaries, sampling, axes and Pivot controls](/guides/charts/tools/axes/#grouped-combination-line-and-area-layers).
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Fill baseline
Unstacked areas accept `seriesOverrides[id].baseline: number | 'min' | 'max'`. Numeric baselines participate in the automatic domain; endpoint policies use the final axis bounds. See [baseline options and Pivot controls](/guides/charts/tools/axes/#area-fill-baselines).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Bubble and histogram layers
Set `seriesOverrides[id].type` to `'bubble'` or `'histogram'` in this combination.
A bubble owner supplies raw Y and requires `bubble: { x, size }` with source
measure IDs. A histogram owner supplies samples and requires `histogram: {}` or
an explicit bin/metric policy. Bound helpers remain canonical fields and do not
become implicit line layers. Sample-owned layers require numeric X; use named
axes when other layers use categorical/time X or different numeric units.
Bins retain complete intervals under navigation. Bubble activation retains the
real X/Y/size observation; aggregate bins do not invent raw records. Saved layer
order, named axes, shared tooltips, complete-source validation, data views and
Pivot authoring use the shared combination owner. Native SVG is supported;
batched/GPU equivalence and combination per-datum encoding are not provided.
See [all sample-layer options and limitations](/guides/charts/tools/cartesian-sample-layers/).
---
# Box plot
URL: https://pro.rv-grid.com/guides/charts/types/box-plot/
Source: src/content/docs/guides/charts/types/box-plot.mdx
Description: Summarize a distribution with quartiles, whiskers, and outliers. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Compare multiple distributions
Bind several distribution series to render side-by-side boxes in each category.
Supply actual samples or valid precomputed summaries for each category/series
pair; an aggregate measure alone cannot establish a distribution.
```ts
import { createChartDataset, createChartFactory } from '@revolist/charts/core';
import { boxPlotChart } from '@revolist/charts/box-plot';
// Illustrative source samples for two plans within the North category.
const standard = [12, 15, 18, 27];
const premium = [20, 24, 28, 36];
const mean = (values: number[]) =>
values.reduce((a, b) => a + b, 0) / values.length;
const dataset = createChartDataset(
[{ region: 'North', standard: mean(standard), premium: mean(premium) }],
{
category: { field: 'region' },
series: [{ field: 'standard' }, { field: 'premium' }],
},
);
dataset.analyticalFrame!.derived = {
distributions: [
{
categoryId: dataset.categories[0].id,
seriesId: 'standard',
kind: 'samples',
samples: standard,
},
{
categoryId: dataset.categories[0].id,
seriesId: 'premium',
kind: 'samples',
samples: premium,
},
],
};
const createChart = createChartFactory([boxPlotChart]);
const chart = await createChart({
container,
dataset,
chartType: 'boxPlot',
bindings: { value: ['standard', 'premium'] },
});
```
Boxes retain stable series colors, and inspection provides sample and outlier
context. `chartOptions: { orientation: 'horizontal' }` changes orientation.
In Pivot, the automatic default remains one eligible measure; explicitly pass
several eligible `seriesIds` or `bindings.value` to compare distributions.
Pivot's source-sample budgets still apply. Keep sample definitions and units
comparable across groups; never manufacture samples from summed values.
See [Analysis](/guides/charts/analysis/#compare-distributions-with-grouped-box-plots)
for interpretation and limitations.
Value axes can be primary, secondary or named. Assign each selected series with `seriesOverrides[id].yAxis`; configure it in `axes.y`, `axes.ySecondary` or `axes.yAxes`. See [statistical value-axis collections](/guides/charts/tools/axes/#statistical-value-axis-collections) for grouping, source domains, orientation and Pivot controls.
## Related charts
Assign independent category/time/numeric projections with `axes.xAxes` and `seriesOverrides[id].xAxis`. Both primary and named X axes support `data` maps or path `level` selection. See [named statistical X axes](/guides/charts/tools/axes/#named-statistical-x-axes) for windows, interval grouping and physical placement.
---
# Bubble
URL: https://pro.rv-grid.com/guides/charts/types/bubble/
Source: src/content/docs/guides/charts/types/bubble.mdx
Description: Compare two measures while bubble area represents a third measure. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Missing values, identities and composition
Bubble size is a non-negative source measure. A null X, Y or size omits the mark;
zero size produces no mark, and negative sizes reject validation. Mark radius is
a visual encoding; activation and export retain raw X/Y/size values and the real
source category. Independent X/Y assignments belong to the bound Y series, not
the size helper. See [relationship axes](/guides/charts/tools/axes/#multiple-value-axes-on-relationship-charts).
To add bubbles to a line/area/column/range composition, use `columnLine` or
`areaLine` with an explicit bubble layer. This differs from setting the chart's
standalone `bindings`: see [sample-owned layer channels, named axes and complete
sources](/guides/charts/tools/cartesian-sample-layers/). Bubble is not one of the
[optional batched SVG/WebGL families](/guides/charts/tools/webgl/).
---
# Bullet
URL: https://pro.rv-grid.com/guides/charts/types/bullet/
Source: src/content/docs/guides/charts/types/bullet.mdx
Description: Compare actual values with explicit targets. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Performance ranges and target variance
Use qualitative ranges to explain performance thresholds, and show the difference
between the existing actual and target measures:
```ts
await chart.update({
chartType: 'bullet',
bindings: { actual: ['actual'], target: ['target'] },
bullet: {
minimum: 0,
maximum: 120,
ranges: [
{ to: 50, label: 'Below target', color: '#dedee5' },
{ to: 90, label: 'On track', color: '#b6a9ff' },
{ to: 120, label: 'Strong', color: '#8971f5' },
],
showVariance: true,
varianceFormat: 'percent',
},
});
```
Replace `actual` and `target` with your dataset's stable series IDs. Range upper
boundaries must be strictly increasing. The chart displays the range key and
actual/target context; authored bounds clip geometry without changing source
values. Percentage variance is undefined when the target is zero. Use
`varianceFormat: 'absolute'` when an absolute difference is more useful.
These settings work in standalone Charts and the native Pivot renderer. See
[Analysis](/guides/charts/analysis/#compare-actuals-with-targets) for method choices
and [Chart Analysis API](/api/chart-analysis/) for the typed contract.
## Related charts
---
# Calendar heatmap
URL: https://pro.rv-grid.com/guides/charts/types/calendar-heatmap/
Source: src/content/docs/guides/charts/types/calendar-heatmap.mdx
Description: Show one daily measure across calendar weeks. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Civil dates and calendar cells
This layout uses the authored ISO civil date, including the date portion of an
accepted timestamp with an offset. It does not move a cell to the timestamp's
UTC date. The renderer searches the category label, labels and path for an
accepted date, orders cells by day, and places Sunday first. Invalid date
categories and null values do not create cells; the bound value measure (or the
first series when omitted) supplies cell values. Multiple observations on one
date remain distinct canonical observations rather than an automatic daily sum.
The calendar layout is separate from ordinary heatmap row/column viewports and
from [named-timezone range presets](/guides/charts/tools/date-ranges/).
Those controls do not change calendar-cell identity. Aggregate daily data before
mapping it when the product needs one total per date.
---
# Candlestick
URL: https://pro.rv-grid.com/guides/charts/types/candlestick/
Source: src/content/docs/guides/charts/types/candlestick.mdx
Description: Compare open, high, low, and close values over time. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
Use primary `axes.x` for category, time or numeric (`linear`, `log`, `symlog`, `sqrt`) observation coordinates. `data` maps and path `level` selection preserve source identities. See [financial numeric X axes](/guides/charts/tools/axes/#financial-numeric-x-axes) for ticks, windows, aligned price/volume panes, Pivot controls and limits.
Bind multiple OHLC groups with parallel `open`/`high`/`low`/`close` arrays. Each close field owns its group and its X/Y assignments. See [candlestick and OHLC collections](/guides/charts/tools/axes/#candlestick-and-ohlc-collections) for helper broadcasts, named axes, grouped slots and Pivot controls.
Use [Financial sessions and the trading toolbar](/guides/charts/tools/financial-sessions/) for explicit exchange-session or ordinal time spacing, supplied named-zone calendars and the shared navigation/drawing controls. Omission retains continuous time and source timestamps.
---
# Chord
URL: https://pro.rv-grid.com/guides/charts/types/chord/
Source: src/content/docs/guides/charts/types/chord.mdx
Description: Compare weighted relationships through circular ribbons. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Choropleth
URL: https://pro.rv-grid.com/guides/charts/types/choropleth/
Source: src/content/docs/guides/charts/types/choropleth.mdx
Description: Explore a working choropleth with geographic provider data, projection and lifecycle instructions.
## Provider overlays and appearance
The response can include `layers` (at most 128): `background` SVG paths, `regions`,
`symbols`, `heatmap` points or `lines` routes. Backgrounds paint before the base
series; other overlays paint afterward in declared order. Routes supply their
projected `path`, stable ID/label, genuine `categoryId`/`seriesId` and source
`value`, plus an optional non-negative `size`. These are provider-projected
paths, not a built-in geographic routing or projection engine.
The request viewport has positive finite `width`/`height` and optional positive
`pixelRatio`. The provider owns projected positions and paths on resize. Heat
point `radius` is optional (18 viewport units when omitted); an authored radius
must be positive. `projectionId`, response type and dataset revision are
validated; source-bound overlay values must match the canonical dataset.
Pass `rendererOptions` on `pack.render` and each complete `instance.update`
input for theme, fonts, class names, element styles/formatters, diagnostics and
`onRender`. See [geographic customization](/guides/charts/customization/#geographic-charts).
Locale callbacks format values, accessibility and load states. Click or
Enter/Space activation returns `{ chartType, datum, originalEvent }` through
`onNodeClick`; application code decides what to do with the provider datum.
The capability instance exposes `ready`, `update`, `focus` and `destroy`;
it does not expose native chart model, CSV/image export or navigation methods.
---
# Circle packing
URL: https://pro.rv-grid.com/guides/charts/types/circle-packing/
Source: src/content/docs/guides/charts/types/circle-packing.mdx
Description: Explore hierarchy through proportional nested circles. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Column and line
URL: https://pro.rv-grid.com/guides/charts/types/column-line/
Source: src/content/docs/guides/charts/types/column-line.mdx
Description: Compare one column series with one or more line series. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Normalized column stacks
Set `chartOptions.normalizeStacks: true` and assign column layers the same `stackGroup` to normalize their positive and negative contributions independently. `chartOptions.normalizedTo` defaults to 100 (range 1 through 1,000,000). Raw source tooltips/CSV and noncolumn layers retain their values. See [normalization domains, navigator behavior and Pivot controls](/guides/charts/tools/axes/#normalized-combination-column-stacks).
## Grouped area and line layers
Column, area and line layers accept `seriesOverrides[id].stackGroup`. Area/line groups share cumulative paths independently of column groups; `normalizeStacks` and `normalizedTo` apply to both families. See [sign boundaries, sampling, axes and Pivot controls](/guides/charts/tools/axes/#grouped-combination-line-and-area-layers).
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Fill baseline
Unstacked areas accept `seriesOverrides[id].baseline: number | 'min' | 'max'`. Numeric baselines participate in the automatic domain; endpoint policies use the final axis bounds. See [baseline options and Pivot controls](/guides/charts/tools/axes/#area-fill-baselines).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Bubble and histogram layers
Set `seriesOverrides[id].type` to `'bubble'` or `'histogram'` in this combination.
A bubble owner supplies raw Y and requires `bubble: { x, size }` with source
measure IDs. A histogram owner supplies samples and requires `histogram: {}` or
an explicit bin/metric policy. Bound helpers remain canonical fields and do not
become implicit line layers. Sample-owned layers require numeric X; use named
axes when other layers use categorical/time X or different numeric units.
Bins retain complete intervals under navigation. Bubble activation retains the
real X/Y/size observation; aggregate bins do not invent raw records. Saved layer
order, named axes, shared tooltips, complete-source validation, data views and
Pivot authoring use the shared combination owner. Native SVG is supported;
batched/GPU equivalence and combination per-datum encoding are not provided.
See [all sample-layer options and limitations](/guides/charts/tools/cartesian-sample-layers/).
---
# Cone funnel
URL: https://pro.rv-grid.com/guides/charts/types/cone-funnel/
Source: src/content/docs/guides/charts/types/cone-funnel.mdx
Description: Compare stage lines and the transitions between them. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Confidence band
URL: https://pro.rv-grid.com/guides/charts/types/confidence-band/
Source: src/content/docs/guides/charts/types/confidence-band.mdx
Description: Show an uncertainty band between lower and upper bounds. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Connected scatter
URL: https://pro.rv-grid.com/guides/charts/types/connected-scatter/
Source: src/content/docs/guides/charts/types/connected-scatter.mdx
Description: Show the ordered path between paired X/Y observations. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Ordered source path
The chart connects paired X/Y observations in source order; it does not sort by
X or infer chronology from point positions. Bind one X and one Y measure.
Missing pairs are omitted. Dense paths retain a bounded representative set of
source points (up to 1,200); rendered vertices still identify genuine source
observations and do not become invented interpolation records. This family is
distinct from a line chart with numeric X and from scatter's batched/GPU backend.
---
# Density scatter
URL: https://pro.rv-grid.com/guides/charts/types/density-scatter/
Source: src/content/docs/guides/charts/types/density-scatter.mdx
Description: Aggregate dense X/Y observations into spatial density. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Density bins and raw observations
The renderer groups projected X/Y observations into spatial bins and encodes
bin counts. A singleton bin can activate its genuine source observation;
multiple-point bins expose aggregate inspection rather than a fabricated row.
The original dataset and raw export values remain authoritative. Resize changes
screen-space bin membership, so do not treat a rendered density bin as a stable
source ID. This is distinct from [scatter's explicit renderer policy](/guides/charts/types/scatter/#explicit-renderer-policy)
and does not opt into GPU rendering.
---
# Donut
URL: https://pro.rv-grid.com/guides/charts/types/donut/
Source: src/content/docs/guides/charts/types/donut.mdx
Description: Show part-to-whole values in one or more rings. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Slice geometry and labels
Set saved `outerRadius`, `paddingAngle`, `cornerRadius`, `arcLabelRadius` and
`arcLabelMinAngle` to size, separate, round and label the slices. Defaults preserve
existing rendering; the label-radius editor preset differs from adaptive omitted
placement. See [all units, ranges, defaults, resets and ring behavior](/guides/charts/customization/#pie-arc-geometry).
## Highlighting and fading
Use saved `pieHighlight`, `pieFade`, `highlightedArc`, `fadedArc` and `highlightedPieItem` for source-safe hover/focus geometry and controlled highlighting. See [scopes, radius units, callbacks, Pivot controls and export behavior](/guides/charts/tools/pie-highlighting/).
---
# Error interval
URL: https://pro.rv-grid.com/guides/charts/types/error-interval/
Source: src/content/docs/guides/charts/types/error-interval.mdx
Description: Compare a center value with its lower and upper bounds. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Funnel
URL: https://pro.rv-grid.com/guides/charts/types/funnel/
Source: src/content/docs/guides/charts/types/funnel.mdx
Description: Show sequential stages that narrow toward an outcome. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Gauge
URL: https://pro.rv-grid.com/guides/charts/types/gauge/
Source: src/content/docs/guides/charts/types/gauge.mdx
Description: Compare an actual value with its target on a radial scale. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Geographic heatmap
URL: https://pro.rv-grid.com/guides/charts/types/geographic-heatmap/
Source: src/content/docs/guides/charts/types/geographic-heatmap.mdx
Description: Explore a working geographic heatmap with geographic provider data, projection and lifecycle instructions.
## Provider overlays and appearance
The response can include `layers` (at most 128): `background` SVG paths, `regions`,
`symbols`, `heatmap` points or `lines` routes. Backgrounds paint before the base
series; other overlays paint afterward in declared order. Routes supply their
projected `path`, stable ID/label, genuine `categoryId`/`seriesId` and source
`value`, plus an optional non-negative `size`. These are provider-projected
paths, not a built-in geographic routing or projection engine.
The request viewport has positive finite `width`/`height` and optional positive
`pixelRatio`. The provider owns projected positions and paths on resize. Heat
point `radius` is optional (18 viewport units when omitted); an authored radius
must be positive. `projectionId`, response type and dataset revision are
validated; source-bound overlay values must match the canonical dataset.
Pass `rendererOptions` on `pack.render` and each complete `instance.update`
input for theme, fonts, class names, element styles/formatters, diagnostics and
`onRender`. See [geographic customization](/guides/charts/customization/#geographic-charts).
Locale callbacks format values, accessibility and load states. Click or
Enter/Space activation returns `{ chartType, datum, originalEvent }` through
`onNodeClick`; application code decides what to do with the provider datum.
The capability instance exposes `ready`, `update`, `focus` and `destroy`;
it does not expose native chart model, CSV/image export or navigation methods.
---
# Grouped bar
URL: https://pro.rv-grid.com/guides/charts/types/grouped-bar/
Source: src/content/docs/guides/charts/types/grouped-bar.mdx
Description: Compare categories with horizontal bars. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Explicit renderer policy
`chartOptions.rendererPolicy` accepts `'svg' | 'batched-svg' | 'webgl'`. Omission keeps native SVG. See the [WebGL capability, inspection, fallback and export contract](/guides/charts/tools/webgl/) and [measured budgets](/guides/charts/tools/webgl-performance/). GPU selection does not raise existing source limits or imply SVG styling equivalence.
## Datum and segment encodings
Saved `seriesOverrides[id].encoding` policies and runtime
`rendererOptions.datumStyle` support scoped source styling. Use stable category
IDs for datum rules; directed segment rules apply to the supported native line
family only. Gradient/pattern fills, marker support, precedence, raw callback
contexts and clean-export behavior follow the [datum styling capability
matrix](/guides/charts/tools/datum-styling/). Authored encodings or runtime datum
callbacks force an individual-SVG fallback for batched/GPU requests; saved
renderer policy remains intact.
---
# Grouped column
URL: https://pro.rv-grid.com/guides/charts/types/grouped-column/
Source: src/content/docs/guides/charts/types/grouped-column.mdx
Description: Compare values side by side across categories. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Explicit renderer policy
`chartOptions.rendererPolicy` accepts `'svg' | 'batched-svg' | 'webgl'`. Omission keeps native SVG. See the [WebGL capability, inspection, fallback and export contract](/guides/charts/tools/webgl/) and [measured budgets](/guides/charts/tools/webgl-performance/). GPU selection does not raise existing source limits or imply SVG styling equivalence.
## Datum and segment encodings
Saved `seriesOverrides[id].encoding` policies and runtime
`rendererOptions.datumStyle` support scoped source styling. Use stable category
IDs for datum rules; directed segment rules apply to the supported native line
family only. Gradient/pattern fills, marker support, precedence, raw callback
contexts and clean-export behavior follow the [datum styling capability
matrix](/guides/charts/tools/datum-styling/). Authored encodings or runtime datum
callbacks force an individual-SVG fallback for batched/GPU requests; saved
renderer policy remains intact.
---
# Heatmap
URL: https://pro.rv-grid.com/guides/charts/types/heatmap/
Source: src/content/docs/guides/charts/types/heatmap.mdx
Description: Compare category and series intersections through color intensity. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Two-dimensional navigation
Heatmap navigation is shared by standalone Charts and Pivot. Public windows use stable IDs rather than matrix offsets. X refers to **series (columns)**; Y refers to **categories (rows)**. Both endpoints are inclusive.
```ts
const chart = await createChart({
container,
chartType: 'heatmap',
dataset,
navigation: { enabled: true, navigator: true },
viewport: {
x: {
scale: 'category',
startId: dataset.series[1].id,
endId: dataset.series[4].id,
},
y: {
scale: 'category',
startId: dataset.categories[10].id,
endId: dataset.categories[19].id,
},
},
});
```
Wheel and pinch gestures zoom both dimensions. Drag pans; Shift-drag zooms to a rectangle. Focus the plot to use arrow keys to pan, `+`/`−` to zoom, and Home to reset. Keyboard and button pans always move at least one cell. Escape cancels a gesture. Existing action and gesture policies apply.
The navigator shows the full matrix with separate column and row tracks. Drag or keyboard-adjust their boundary handles independently. Arrow keys move one row/column; Shift increases the step. A heatmap navigator object supports `height` (default 96px) and `selectionStyle` fields `fillColor`, `fillOpacity`, `borderColor`, `borderWidth`, `activeColor`, `handleColor`, `handleBorderColor`, `handleBorderWidth`, `handleWidth`, `handleHeight` and `handleRadius`. Line overview types, subset series, custom overview colors, opacity veils and handle grips are unsupported for matrix navigators and are rejected rather than silently ignored.
Navigation never filters or renumbers the authoritative dataset. Tooltips and click events keep canonical row/series identity. The visible window is bucketed only when it exceeds rendering limits; zooming reveals individual cells. Empty rows and columns retain their positions, and implicit intensity uses the full matrix's value extent so colors stay stable while zooming. Shared tooltips show the visible columns for the inspected row; crosshairs follow the inspected cell.
`chart.getCSV()` exports the current rows and columns. Use `chart.getCSV({ dataScope: 'all' })` for the complete matrix. SVG export and server rendering use the visible window. Model save/restore retains both ID ranges. `chart.update({ viewport: {} })` resets both dimensions.
If an endpoint disappears after a data update, that axis renders its available full range while retaining the saved IDs. The window reactivates when those IDs return. Endpoint order follows the current dataset order. Charts sync groups with `viewport: 'xy'` can link compatible heatmap windows.
Independent semantic range selection is not yet supported for heatmaps; navigation rectangles change the viewport. General XY brushing remains separate roadmap work.
## Heatmap matrix axis directions
Heatmap X denotes series/columns and Y denotes categories/rows. Their categorical direction settings live in **`axes.matrix`**, independently of numeric value-axis definitions:
```ts
axes: {
matrix: {
columns: { reverse: true },
rows: { reverse: true },
},
},
```
`ChartMatrixAxes` has optional **`columns`** and **`rows`** objects. Each is a `ChartMatrixAxis` with optional boolean **`reverse`**, default `false`. Columns normally run left to right and rows top to bottom; each `true` mirrors that dimension's projection. The settings apply to cells, category/series labels, aggregate bucket extents, hover/crosshair points, pointer gestures and the full-matrix navigator. Dense/partial buckets keep their original memberships, counts, means and source provenance.
Viewport `x`/`y` still uses inclusive canonical series/category IDs with `scale: 'category'`. Reversal never reorders matrix cells, renumbers IDs, changes CSV/data-table order or reverses saved endpoints. Missing endpoints remain dormant and reactivate when their IDs return. Sync groups retain canonical identities, so compatible peers may use different directions. Navigator start/end handles retain their semantic endpoint roles as physical positions mirror; their keyboard increments follow canonical row/column order. Pointer dragging uses the reversed projection to resolve those same IDs.
Standalone Charts and Pivot share the parser and renderer. Save/restore, SVG/image export and navigator previews preserve directions. Pivot's **Customize → Axes and scales** exposes **Reverse heatmap columns** and **Reverse heatmap rows**. Locale strings are `localeText.chartOptions.matrixColumnsReverse` and `localeText.chartOptions.matrixRowsReverse`; UI policy keys are `axes.matrix.columns.reverse` and `axes.matrix.rows.reverse`. Gallery changes to another chart family clear matrix-only settings. Pivot exports `PivotChartMatrixAxes` and `PivotChartMatrixAxis` aliases through its root and charts facade.
This matrix configuration is currently heatmap-only. It also supports physical placement, titles, colors and measured gutters as described below. Numeric row/column scales/bounds, additional matrix axes remain separate axis work. The cell-value color scale remains independent of row/column direction. Calendar heatmaps retain their calendar layout. General semantic XY brushing also remains separate from viewport navigation.
## Heatmap axis appearance and sizing
Configure physical heatmap axes through `axes.matrix.columns` and `axes.matrix.rows`. Each `ChartMatrixAxis` supports these fields, also exposed through the equivalent Pivot types:
| Field | Meaning and default |
| --- | --- |
| `reverse` | Independent categorical direction; default `false`. |
| `groups` | Optional inner-to-outer categorical group bands; omitted/empty retains leaf captions. See [grouped heatmap axes](#grouped-heatmap-axes). |
| `position` | Columns: `'top'` (default), `'bottom'` or `'none'`. Rows: `'left'` (default), `'right'` or `'none'`. Physical placement is independent of reversal. |
| `title` | Optional string, at most 4096 characters. Overrides the corresponding legacy `presentation.xAxisTitle`/`presentation.yAxisTitle` fallback. An explicit empty string suppresses that fallback. |
| `lineColor` | Shared authored hex-color vocabulary; defaults to the theme's muted axis color. |
| `labelColor` | Row/column label color in the same hex vocabulary; defaults to theme muted text. |
| `titleColor` | Title color in the same hex vocabulary; defaults to theme foreground text. |
```ts
axes: {
matrix: {
columns: { position: 'bottom', title: 'Sample columns', lineColor: '#445566' },
rows: { position: 'right', title: 'Source categories', labelColor: '#334455' },
},
},
```
`position: 'none'` removes that axis's line, labels, title and measured gutter. Matrix cell geometry, source identity, reversal, hover, windows and navigator remain active. Titles and colors affect decorations, independently of the numeric cell-value color mapping. Source data and CSV order stay canonical.
Heatmaps use the shared measured-axis layout. It measures final label/title text, renderer font styles and formatter results before laying out the cells; hidden axes release their space. Row/column label regions fit their allocated gutters/slots, preserving full accessible captions when text is truncated. Gutters are bounded to keep a usable plot on narrow hosts. Browser measurements use SVG text metrics; server-rendered SVG uses deterministic font-metric fallback and retains full captions in accessible labels/titles. Navigator and pointer geometry uses the resulting measured plot bounds. Overview previews retain categorical direction with decorations omitted.
Existing renderer label/element overrides retain precedence over axis colors. Matrix row labels have `kind: 'category'`, `axis: 'y'`, their genuine `category` and original `categoryIndex`. Column labels have `kind: 'category'`, `axis: 'x'`, genuine `series` and `seriesIndex`. Titles use `kind: 'axisTitle'`. Formatter results are reused between measurement and painting per semantic source identity, including when several source labels have identical text. No synthetic numeric row/column values enter formatter provenance.
Pivot reuses its physical axis forms for **Heatmap columns** and **Heatmap rows**, exposing position and title alongside the existing reversal controls. Numeric scale, bound, tick and unit controls are omitted for these categorical axes. Configure colors through the API or existing renderer styles. Localize the group captions with **`localeText.chartOptions.matrixColumnsAxis`** and **`localeText.chartOptions.matrixRowsAxis`**; common `axisPosition`, `axisTop`, `axisBottom`, `axisLeft`, `axisRight`, `axisHidden`, `axisTitle` and `axisField` locale hooks still apply. UI policy keys are **`axes.matrix.columns.position`**, **`axes.matrix.columns.title`**, **`axes.matrix.rows.position`** and **`axes.matrix.rows.title`**. Their `axes.matrix.columns`/`axes.matrix.rows` parents and the existing `axes` policy also apply.
Save/restore and SVG/image exports retain these settings. Queued Pivot updates own matrix appearance before validating the resulting chart type; gallery transitions to another family clear matrix-only settings. Numeric matrix row/column scales/bounds, additional matrix axes remain separate work. Calendar heatmaps continue to use their calendar layout.
## Grouped heatmap axes
**`axes.matrix.columns.groups`** and **`axes.matrix.rows.groups`** accept up to eight `ChartCategoryAxisGroup` objects, ordered from the innermost band next to the cells to the outermost band. A nonempty list replaces that dimension's ordinary leaf captions; include the leaf path level as the first group when both leaf and parent labels are needed. An omitted or empty list retains ordinary captions.
```ts
axes: {
matrix: {
columns: {
position: 'bottom',
groups: [
{ values: { revenue: 'Revenue', costs: 'Costs' }, size: 32 },
{ values: { revenue: 'Financial', costs: 'Financial' }, size: 32 },
],
},
rows: { position: 'right', groups: [{ level: 1 }, { level: 0 }] },
},
},
```
The lookup keys in the example are stable **series IDs**, not labels or row indices. Row groups use stable **category IDs**. Each group supports:
| Field | Behavior |
| --- | --- |
| `level` | Zero-based path index, an integer from 0 through 127. Defaults to the final member of the row category's `path` or column series' `columnPath`. Corresponding `labels` supply display text. |
| `values` | Optional lookup of stable IDs to string, finite number, boolean or `null`, at most 100,000 entries. Missing IDs fall back to the chosen path level. Authored values supply their own text; `null` uses the localized missing-value caption. |
| `size` | Band thickness, 16 through 160 SVG pixels. Defaults to 32 for columns and 80 for rows. Shared measured layout bounds total gutters to retain a usable plot; bands can shrink and captions can truncate or be omitted on small hosts. |
| `tickSize` | Boundary tick length, 0 through 24 pixels; default 6, capped by the fitted band thickness. |
| `lineColor`, `labelColor` | Existing authored hex-color vocabulary. Each band overrides the matrix axis color; absent overrides use the axis color and then the renderer theme. Renderer label styles/formatters retain their existing final precedence. |
Groups merge only adjacent members with the same **typed value and outer-group identities**. Numeric `1` and string `'1'` remain separate even when their captions match. Repeated nonadjacent groups remain separate. Windowing clips groups to visible canonical members; reversal mirrors their physical projection without changing IDs, cell values or CSV. Group boundaries use source row/column positions, independently of dense cell bucket aggregation. Navigator previews omit decorations and preserve the full-matrix overview.
Both physical sides, titles, hidden axes, save/restore, server SVG and image export use the shared axis pipeline. `position: 'none'` hides all bands and releases their gutter. Grouping does not aggregate, reorder or add data. Persisted group definitions contain data, without callback functions. Runtime matrix grouping callbacks are described below; additional matrix axes remain separate work.
Pivot **Customize → Axes and scales** reuses the group editor under **Heatmap columns** and **Heatmap rows**. Enable **Group category labels** to initialize up to eight levels from that dimension's path depth, then edit levels, add or remove bands. When columns contain measures without a column dimension path, enabling grouping initializes an explicit stable series-ID-to-label lookup so each measure retains its caption. Path-only API groups on an empty column path have a missing group value and no invented path caption. Explicit `values` lookups are API-configured and disable the corresponding path-level editor. These controls use **`axes.matrix.columns.groups`** and **`axes.matrix.rows.groups`** UI policy keys plus existing parents. Existing `chartOptions.categoryGroups`, `categoryGroupLevel`, `categoryGroupAdd`, `categoryGroupRemove` and `axisField` locale hooks apply; accessible field labels include the heatmap dimension name. No new locale strings are required.
## Runtime heatmap grouping callbacks
Use **`ChartsRendererOptions.matrixAxes.rows.groups`** and **`matrixAxes.columns.groups`** to derive group values or captions from source metadata. Each dimension independently overrides its persisted `axes.matrix` group list. An explicit empty array disables grouping for that dimension and restores ordinary leaf captions; an omitted `groups` keeps the persisted list. Runtime definitions replace the whole list, including its sizes/styles, without merging by index.
```ts
import { createChartsRenderer } from '@revolist/charts';
const renderer = createChartsRenderer({
matrixAxes: {
rows: {
groups: [
{ getValue: category => ({ value: category.id, label: category.label }) },
{ getValue: category => category.path[0] ?? null },
],
},
columns: {
groups: [
{
size: 70,
getValue: (series, seriesIndex) => ({
value: series.measure?.id ?? series.id,
label: `${seriesIndex + 1}: ${series.measure?.label ?? series.label}`,
}),
},
],
},
},
});
```
**`ChartCategoryAxisRuntimeGroup`** is the row type: **`getValue(category, categoryIndex)`** receives a read-only `ChartCategory` and its original index in the canonical accepted dataset. **`ChartSeriesAxisRuntimeGroup`** is the column type: **`getValue(series, seriesIndex)`** receives a read-only `ChartSeries` with `columnPath`, `labels` and optional measure metadata, plus its original index in the canonical accepted dataset. Columns are genuine series, without fabricated category paths. Windows and physical reversal preserve these indexes and IDs. For remote sources, indexes refer to the accepted frame, without inventing positions or members for unloaded records.
Each runtime group accepts all persisted group fields (`level`, `values`, `size`, `tickSize`, `lineColor`, `labelColor`) plus optional **`getValue`**. Without a callback, that group's own stable-ID lookup/path configuration supplies values and labels. A callback returns a string, finite number, boolean, `null`, or **`{ value, label }`** with a scalar `value` and string `label`. Scalar results display their string form; scalar `null` uses `localeText.renderer.missingValue`. A value/label pair separates typed grouping identity from its caption. Adjacent values merge only when both their type and all outer group identities match. `undefined`, nonfinite numbers and malformed pairs are invalid; invalid configuration/results throw `TypeError`, and callback exceptions propagate. The eight-band and existing size/tick/color limits still apply.
The renderer caches each visible member's result by stable ID **per group, per render**. Measurement and painting share those results; it does not call a row callback once for every matrix cell. A new render after data, viewport, resize or chart updates starts a fresh cache, including when IDs remain the same. Keep callback input read-only. Hidden axes and navigator previews do not evaluate callbacks. Runtime matrix options are used only by heatmaps; other chart families ignore them. Hidden-axis group configuration is still validated.
All band projection, measured gutters, physical sides, reversal, caption fitting and renderer style/label precedence remain shared with persisted grouping. Callbacks do not aggregate/reorder source cells or affect CSV. Runtime functions and derived captions are not serialized into the model; configure the same renderer when restoring a chart. Standalone Charts accepts the options through `rendererOptions` or `createChartsRenderer`. Pivot uses **`createPivotChartsRenderer({ matrixAxes: ... })`** and its public **`renderer`** option. Pivot exports **`PivotChartCategoryAxisRuntimeGroup`** and **`PivotChartSeriesAxisRuntimeGroup`** through both its root and charts facade. The existing Pivot group forms edit persisted groups; renderer runtime overrides keep their normal precedence.
Server rendering accepts the same configuration through **`renderChartToSVG({ model, width, height, rendererOptions: { matrixAxes: ... } })`**. It evaluates callbacks using the server model's real metadata and deterministic text fitting. SVG/image output includes the resulting labels; saved models still contain only persisted settings. Reapply matching renderer options for client hydration. No additional locale or UI policy keys are introduced by these renderer callbacks.
## Explicit renderer policy
`chartOptions.rendererPolicy` accepts `'svg' | 'webgl'`. Omission keeps native SVG. See the [WebGL capability, inspection, fallback and export contract](/guides/charts/tools/webgl/) and [measured budgets](/guides/charts/tools/webgl-performance/). GPU selection does not raise existing source limits or imply SVG styling equivalence.
## Viewports and dense-cell inspection
Heatmaps have independent row and column windows. Use the [matrix navigation
contract](#two-dimensional-navigation) for
scrolling, selected-series projections and canonical window identities.
Dense geometry can combine adjacent source cells into bounded mean-valued
buckets. A one-source bucket retains raw activation; a multi-source bucket is an
aggregate inspection target. A bucket mean never becomes a fabricated source
cell or a raw CSV value. The optional GPU backend paints this existing bounded
geometry; it does not turn every input cell into a GPU datum or raise source
limits. See [WebGL limitations and exports](/guides/charts/tools/webgl/).
---
# Histogram
URL: https://pro.rv-grid.com/guides/charts/types/histogram/
Source: src/content/docs/guides/charts/types/histogram.mdx
Description: Summarize one measure in deterministic numeric bins. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
For bin sample scales and aggregate metric bounds, transforms, reversal, ticks and placement, see [Histogram bin and metric axes](/guides/charts/tools/axes/#histogram-bin-and-metric-axes). See [Histogram sample navigation](/guides/charts/tools/axes/#histogram-sample-navigation) for local sample-window zoom/pan, navigator, brushes, CSV and synchronization. See [Histogram shared hover and crosshair](/guides/charts/tools/axes/#histogram-shared-hover-and-crosshair) for bin-metric readouts and formatter metadata. Remote detail loading uses [complete source frames](/guides/charts/tools/axes/#complete-histogram-source-frames), which also support precomputed equal/irregular intervals. Named sample and metric axes are supported through the collection contract below.
## Related charts
Use `chartOptions.histogramBinBoundaries` to configure [explicit raw-sample intervals](/guides/charts/tools/axes/#explicit-histogram-intervals), including irregular widths. It overrides automatic bin counts and is also available in Pivot's Bin boundaries appearance field.
Set `chartOptions.histogramArea: true` for [raw-width area encoding](/guides/charts/tools/axes/#histogram-area-encoding), including density axes, navigator values and typed tooltip metadata.
Inspect loaded records and complete bin statistics with [histogram sample/bin data views](/guides/charts/tools/axes/#histogram-sample-and-bin-data-views), shared with Pivot’s Samples/Bins Data tab.
## Histogram collections and axes
A histogram can display several explicitly bound distributions, using the shared
Charts engine in standalone Charts and Pivot. Each sample measure owns its
partition and X/Y-axis assignments. Raw observations and metric/helper measures
remain in the canonical dataset.
```ts
const chart = await createChart({
container,
chartType: 'histogram',
dataset,
bindings: {
x: ['duration', 'delay'],
y: ['weight', 'cost'],
},
chartOptions: { binCount: 12, histogramAggregation: 'sum' },
axes: {
x: { scale: 'linear', unit: 's', title: 'Duration' },
xAxes: {
delayAxis: { scale: 'symlog', constant: 1, reverse: true, unit: 'ms', title: 'Delay' },
},
y: { min: 0, unit: 'kg' },
yAxes: { money: { min: 0, unit: 'EUR', title: 'Cost' } },
},
seriesOverrides: {
duration: { color: '#2563eb' },
delay: { xAxis: 'delayAxis', yAxis: 'money', color: '#d97706' },
},
navigation: { enabled: true, navigator: { series: 'all' }, xAxis: 'delayAxis' },
rangeSelection: { enabled: true, xAxis: 'delayAxis' },
});
```
### Binding and geometry contracts
| Field | Meaning |
| --- | --- |
| `bindings.x` | Ordered list of sample measure IDs. |
| `bindings.value` | Equivalent sample-binding form. If both it and X are nonempty, their IDs/order must agree. |
| `bindings.y` | Omit to use each sample measure as its own sum/mean metric; use one ID for a shared metric, or one distinct ID per sample for paired metrics. Count still counts sample observations. |
| `seriesOverrides[sampleId].xAxis` | `'primary'` or a configured `axes.xAxes` ID. Default primary. |
| `seriesOverrides[sampleId].yAxis` | `'primary'`, `'secondary'` or a configured `axes.yAxes` ID. Default primary. |
| `seriesOverrides[sampleId].color`, `.visible` | Saved sample paint and visibility. Helpers do not acquire histogram marks or legend entries. |
Without explicit nonempty sample bindings, the first dataset measure remains the
single legacy sample. Extra measures are not automatically added as distributions.
Binding IDs must exist. Duplicate sample IDs, conflicting value/X lists and
invalid metric cardinality reject before replacing the frame. The raw measure
count is now governed by the normal chart limits rather than a two-measure cap.
Every member independently uses `binCount`, `histogramBinBoundaries`,
`histogramAggregation` and `histogramArea`. Automatic partitions can differ even
when members share X. Explicit raw boundaries give them a common partition.
Same-axis distributions overlay; they are not stacked, added, dodged or merged.
Use `markOpacity`, sample colors, visibility or separate named axes deliberately.
A logical X axis uses the union of its visible complete partition bounds, with
its authored bounds taking precedence. A logical Y axis uses all visible member
metrics on that axis. Overflowed sums share one finite geometry unit per Y axis;
this does not add distributions together or change source values. Empty/null
samples and missing metric contributors keep their existing histogram semantics.
Hidden groups do not influence inferred geometry, and hiding a sample never
removes its raw sample/metric fields or complete source statistics.
Primary/secondary/named metric axes and named sample axes support existing
numeric scale, domain, reversal, placement, tick, title, formatting and color
controls. Numeric X still supports linear/log/symlog/sqrt; category is the legacy
bin presentation. X comes from the bound sample, so category `data`/`level`
mappings, grouped category bands and time X are not histogram options. Logs
require positive samples/metrics under the existing rules. Unknown axes reject
atomically, including assignments on hidden members.
Area encoding divides each member's aggregate by its raw interval width. A
shared density Y axis displays a unit only when all contributing density units
agree; otherwise per-bin tooltips retain each unit and the axis is numeric.
Synchronization uses the full density-unit set to prevent matching a mixed axis
to an unrelated raw or single-unit axis. Primary Y remains the only synchronized
value axis; secondary/named Y projections use complete bins and authored bounds.
### Inspection, navigation and export
Plot gestures/navigator use `navigation.xAxis`; a brush can independently use
`rangeSelection.xAxis`. `viewport.xAxes[id]` stores each named window. Reversal
changes projection, not source identities or increasing semantic bounds.
Navigator `series` can choose the first visible sample (default), `'all'` or
explicit sample IDs on its selected X. Existing navigator series overrides and
colors apply to the derived metric paths; these remain noninteractive previews
of bin metrics rather than replacement source records.
Shared/crosshair readouts and peer hover use only distributions on the same
logical X axis. Each bin retains its own interval even when partitions differ.
`context.aggregate` now includes `sampleSeriesId`, `valueSeriesId`, `xAxis` and
`yAxis` alongside the existing raw count/sum/mean and interval fields. The
representative `context.series` remains the metric measure for sum/mean.
Shared tooltip formatters can read `context.histogramItems`, an array of
`ChartHistogramTooltipItem` values containing `category`, `series`, `value`,
`displayValue`, `color` and `aggregate`. Representatives may be outside the loaded
row slice, so these are not fabricated matrix-cell `sharedItems`. The display is
bounded to 20 aggregate peers and uses `omittedSharedItemCount` for additional
members. The main hovered `aggregate` remains available separately. Genuine
singleton activation still sends its canonical sample value; aggregate bins do
not invent click/drill records.
CSV with `dataScope: 'all'` remains the raw loaded matrix. Current-scope raw CSV
uses the union of rows whose sample is visible in any bound distribution's own
window. It does not replace cells with counts, densities or overflowed aggregate
values. Complete remote statistics never fabricate unloaded CSV observations.
Models, SVG server rendering, hydration and SVG/image exports retain the normal
shared lifecycle and persisted axes/options.
### Data views and Pivot controls
```ts
import { createChartHistogramDataView, createChartHistogramDataViews } from '@revolist/charts';
const allDistributions = createChartHistogramDataViews(chart.model, { dataScope: 'current' });
const delays = createChartHistogramDataView(chart.model, {
sampleSeriesId: 'delay',
dataScope: 'all',
});
```
`createChartHistogramDataView` keeps its first-bound-member default and adds
optional `sampleSeriesId`. `createChartHistogramDataViews` returns every bound
member in binding order and accepts `dataScope` only. Both validate the model,
return owned snapshots, and use each sample's own X assignment/window.
Unknown requested samples reject. Existing `samples`/`bins` fields retain their
raw/complete-statistics distinction. Empty collections return an empty plural
array; the singular empty-data snapshot remains available.
The Pivot chart facade exports the corresponding
`createPivotChartHistogramDataView` / `createPivotChartHistogramDataViews` and
shared types. In **Data**, **Sample series** chooses the member whose raw samples
and complete bin table are inspected. The choice is transient UI state. Its UI
policy is `dataView.histogramSeries`; parent `dataView` policies still apply.
Localize the caption with `localeText.histogramData.sampleSeries`.
Existing **Customize → Axes and scales** controls edit configured primary,
secondary and named axes. Per-series X/Y assignments are shown only for sample
members. Chart/legend color and visibility controls target the sample, not its
weight helper. Bind collection members through the model/API; this does not add
a separate histogram binding-builder UI or a Pivot-specific rendering engine.
### Complete remote collection summaries
The singleton primary-axis source protocol is unchanged: request `histogram`,
response `analyticalFrame.derived.histogram`. Multiple members, or a member with
a nonprimary X/Y assignment, use request **`histograms`** and response
**`analyticalFrame.derived.histograms`**. Arrays are keyed by sample ID; response
summary order does not determine plot order. Singular/plural summary metadata
are mutually exclusive.
Each `ChartHistogramCollectionSourceRequest` adds these fields to the existing
bin-count/boundaries/aggregation/scale/symlog-constant hints:
| Field | Contract |
| --- | --- |
| `sampleSeriesId` | Requested source sample measure. |
| `valueSeriesId` | Its shared or paired aggregation measure. |
| `xAxis` | Identity used for the partition's full X metadata. |
| `yAxis` | Identity of its plotted metric axis. |
The provider returns exactly one validated `ChartHistogramSummary` per requested
sample, with matching sample/value IDs and applied options. Counts, sums, means,
representatives, boundaries and finite sum multipliers are global statistics,
independent of loaded rows. Use `fullDomain.x` for primary X and
`fullDomain.xAxes[id]` for named X. Each domain equals the union of complete raw
partition boundaries assigned to that axis and has the requested transform.
Named-only source domains are accepted when no primary X is used. There must be
at least one full numeric/time X domain under the shared source contract.
Histogram source assignments require explicit sample bindings when the field
identities cannot be inferred before the first load.
`fullDomain.y`, when supplied, still describes primary Y in the applied raw
aggregation units. Area encoding ignores that raw hint and derives global
densities from complete bins. Secondary/named-Y global metrics are likewise
inferred from their complete summaries. Authored axis bounds still win.
A missing/mismatched partition or domain rejects the response without replacing
the accepted chart. Global statistics stay stable within one source revision
and partition/field request; changing only the loaded window, summary order or
axis identity must not alter them. A changed bin policy/transform is a new
partition request. Paint/visibility changes do not reload the source. The last
accepted complete frame stays visible through loading or errors. Saved models
retain accepted summaries; source handles/controllers remain application-owned.
Pivot viewport providers receive the same `chart.histogram` /
`chart.histograms` hints, with cloned request ownership and exact primary/named
window echoes. Normal Pivot scope/query/revision correlation remains in force.
`PivotChartHistogramCollectionSourceRequest` and
`PivotChartHistogramTooltipItem` alias the shared public types.
Standalone histogram collections do not add stacking, independent Y navigation
or generic histogram analytical overlays. To combine complete histogram bins
with other Cartesian layers, use the distinct [bubble and histogram combination
contract](/guides/charts/tools/cartesian-sample-layers/). The [MUI axes reference](https://mui.com/x/react-charts/axis/)
describes the upstream independent-axis model; these histogram contracts retain
the framework-neutral Charts bindings and complete-source semantics.
---
# Icicle
URL: https://pro.rv-grid.com/guides/charts/types/icicle/
Source: src/content/docs/guides/charts/types/icicle.mdx
Description: Explore hierarchy through proportional stacked rectangles. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# KPI value
URL: https://pro.rv-grid.com/guides/charts/types/kpi/
Source: src/content/docs/guides/charts/types/kpi.mdx
Description: Summarize actual, target, and variance in a compact KPI card. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Line
URL: https://pro.rv-grid.com/guides/charts/types/line/
Source: src/content/docs/guides/charts/types/line.mdx
Description: Show change and trends across an ordered axis. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Native stack groups
Use `seriesOverrides[id].stackGroup` for independent groups, with shared
`stackOrder` and `stackOffset` policies. Regular paths without a group remain raw;
dedicated stacked/normalized areas retain an implicit default stack. See
[normalization, defaults, source identity and Pivot controls](/guides/charts/customization/#native-line-and-area-stack-groups).
## Datum and segment encodings
Saved `seriesOverrides[id].encoding` policies and runtime
`rendererOptions.datumStyle` support scoped source styling. Use stable category
IDs for datum rules; directed segment rules apply to the supported native line
family only. Gradient/pattern fills, marker support, precedence, raw callback
contexts and clean-export behavior follow the [datum styling capability
matrix](/guides/charts/tools/datum-styling/). Authored encodings or runtime datum
callbacks force an individual-SVG fallback for batched/GPU requests; saved
renderer policy remains intact.
---
# Linear gauge
URL: https://pro.rv-grid.com/guides/charts/types/linear-gauge/
Source: src/content/docs/guides/charts/types/linear-gauge.mdx
Description: Compare a value with an explicit linear scale and target. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Lollipop
URL: https://pro.rv-grid.com/guides/charts/types/lollipop/
Source: src/content/docs/guides/charts/types/lollipop.mdx
Description: Compare single values with compact stems and endpoints. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Multi-ring radial bar
URL: https://pro.rv-grid.com/guides/charts/types/multi-ring-radial-bar/
Source: src/content/docs/guides/charts/types/multi-ring-radial-bar.mdx
Description: Compare multiple series as concentric radial tracks. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
See [polar and radial geometry](/guides/charts/tools/polar-geometry/) for saved rotation, sweep, direction, radius and spacing controls shared with Pivot.
Use [polar numeric value axes](/guides/charts/tools/polar-value-axes/) for shared scale, bounds, reversal, tick, grid and clipping policies.
## Named axes and measured layout
Add numeric definitions in `axes.polar.valueAxes` and assign each series with
`seriesOverrides[id].polarAxis`; omitted assignments use the primary polar
axis. Axis domains, clipping and stack partitions follow that identity. See
[named numeric polar axes](/guides/charts/tools/polar-value-axes/#named-numeric-polar-axes).
[Measured polar layout](/guides/charts/tools/polar-axis-layout/) shares tick
decisions across labels, grids, resize and export. Authored axes reserve bounded
gutters; labels/titles that cannot fit follow a documented omission order while
marks and raw inspection remain available. Runtime placement uses
`rendererOptions.polarAxis.placement`, is bounded and validated, and must be
reattached after restoring a saved model. This does not add polar navigation or
mixed polar composition.
---
# 100% stacked area
URL: https://pro.rv-grid.com/guides/charts/types/normalized-area/
Source: src/content/docs/guides/charts/types/normalized-area.mdx
Description: Show percentage contribution over an ordered axis. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
### Native stack groups
Use `seriesOverrides[id].stackGroup` for independent groups, with shared
`stackOrder` and `stackOffset` policies. Regular paths without a group remain raw;
dedicated stacked/normalized areas retain an implicit default stack. See
[normalization, defaults, source identity and Pivot controls](/guides/charts/customization/#native-line-and-area-stack-groups).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# 100% stacked bar
URL: https://pro.rv-grid.com/guides/charts/types/normalized-bar/
Source: src/content/docs/guides/charts/types/normalized-bar.mdx
Description: Compare percentage composition horizontally. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# 100% stacked column
URL: https://pro.rv-grid.com/guides/charts/types/normalized-column/
Source: src/content/docs/guides/charts/types/normalized-column.mdx
Description: Compare percentage composition across categories. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# 100% stacked polar column
URL: https://pro.rv-grid.com/guides/charts/types/normalized-polar-column/
Source: src/content/docs/guides/charts/types/normalized-polar-column.mdx
Description: Compare percentage composition in radial columns. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
See [polar and radial geometry](/guides/charts/tools/polar-geometry/) for saved rotation, sweep, direction, radius and spacing controls shared with Pivot.
Use [polar numeric value axes](/guides/charts/tools/polar-value-axes/) for shared scale, bounds, reversal, tick, grid and clipping policies.
## Named axes and measured layout
Add numeric definitions in `axes.polar.valueAxes` and assign each series with
`seriesOverrides[id].polarAxis`; omitted assignments use the primary polar
axis. Axis domains, clipping and stack partitions follow that identity. See
[named numeric polar axes](/guides/charts/tools/polar-value-axes/#named-numeric-polar-axes).
[Measured polar layout](/guides/charts/tools/polar-axis-layout/) shares tick
decisions across labels, grids, resize and export. Authored axes reserve bounded
gutters; labels/titles that cannot fit follow a documented omission order while
marks and raw inspection remain available. Runtime placement uses
`rendererOptions.polarAxis.placement`, is bounded and validated, and must be
reattached after restoring a saved model. This does not add polar navigation or
mixed polar composition.
---
# OHLC
URL: https://pro.rv-grid.com/guides/charts/types/ohlc/
Source: src/content/docs/guides/charts/types/ohlc.mdx
Description: Show open, high, low, and close values with compact ticks. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
Use primary `axes.x` for category, time or numeric (`linear`, `log`, `symlog`, `sqrt`) observation coordinates. `data` maps and path `level` selection preserve source identities. See [financial numeric X axes](/guides/charts/tools/axes/#financial-numeric-x-axes) for ticks, windows, aligned price/volume panes, Pivot controls and limits.
Bind multiple OHLC groups with parallel `open`/`high`/`low`/`close` arrays. Each close field owns its group and its X/Y assignments. See [candlestick and OHLC collections](/guides/charts/tools/axes/#candlestick-and-ohlc-collections) for helper broadcasts, named axes, grouped slots and Pivot controls.
Use [Financial sessions and the trading toolbar](/guides/charts/tools/financial-sessions/) for explicit exchange-session or ordinal time spacing, supplied named-zone calendars and the shared navigation/drawing controls. Omission retains continuous time and source timestamps.
---
# Organization chart
URL: https://pro.rv-grid.com/guides/charts/types/organization/
Source: src/content/docs/guides/charts/types/organization.mdx
Description: Show connected parent and child cards. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Expansion state and canonical data
`chartOptions.collapsedNodes` saves collapsed semantic tree IDs. The built-in
expander supports click and Enter/Space and retains other authored options when
it changes that state. Collapsing a branch changes the visible cards, not the
canonical source dataset or raw CSV. Preserve `collapsedNodes` when replacing
`chartOptions` if the expansion state should survive an appearance update; use
an empty array to expand all branches. Tree IDs follow semantic path identity,
so display labels do not merge distinct source paths.
---
# Pie
URL: https://pro.rv-grid.com/guides/charts/types/pie/
Source: src/content/docs/guides/charts/types/pie.mdx
Description: Show the share of a single series as slices. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Slice geometry and labels
Set saved `outerRadius`, `paddingAngle`, `cornerRadius`, `arcLabelRadius` and
`arcLabelMinAngle` to size, separate, round and label the slices. Defaults preserve
existing rendering; the label-radius editor preset differs from adaptive omitted
placement. See [all units, ranges, defaults, resets and ring behavior](/guides/charts/customization/#pie-arc-geometry).
## Highlighting and fading
Use saved `pieHighlight`, `pieFade`, `highlightedArc`, `fadedArc` and `highlightedPieItem` for source-safe hover/focus geometry and controlled highlighting. See [scopes, radius units, callbacks, Pivot controls and export behavior](/guides/charts/tools/pie-highlighting/).
---
# Polar column
URL: https://pro.rv-grid.com/guides/charts/types/polar-column/
Source: src/content/docs/guides/charts/types/polar-column.mdx
Description: Compare grouped values as radial columns. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
See [polar and radial geometry](/guides/charts/tools/polar-geometry/) for saved rotation, sweep, direction, radius and spacing controls shared with Pivot.
Use [polar numeric value axes](/guides/charts/tools/polar-value-axes/) for shared scale, bounds, reversal, tick, grid and clipping policies.
## Named axes and measured layout
Add numeric definitions in `axes.polar.valueAxes` and assign each series with
`seriesOverrides[id].polarAxis`; omitted assignments use the primary polar
axis. Axis domains, clipping and stack partitions follow that identity. See
[named numeric polar axes](/guides/charts/tools/polar-value-axes/#named-numeric-polar-axes).
[Measured polar layout](/guides/charts/tools/polar-axis-layout/) shares tick
decisions across labels, grids, resize and export. Authored axes reserve bounded
gutters; labels/titles that cannot fit follow a documented omission order while
marks and raw inspection remain available. Runtime placement uses
`rendererOptions.polarAxis.placement`, is bounded and validated, and must be
reattached after restoring a saved model. This does not add polar navigation or
mixed polar composition.
---
# Progress gauge
URL: https://pro.rv-grid.com/guides/charts/types/progress-gauge/
Source: src/content/docs/guides/charts/types/progress-gauge.mdx
Description: Show progress toward a target on a radial track. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Proportional symbol map
URL: https://pro.rv-grid.com/guides/charts/types/proportional-symbol-map/
Source: src/content/docs/guides/charts/types/proportional-symbol-map.mdx
Description: Explore a working proportional symbol map with geographic provider data, projection and lifecycle instructions.
## Provider overlays and appearance
The response can include `layers` (at most 128): `background` SVG paths, `regions`,
`symbols`, `heatmap` points or `lines` routes. Backgrounds paint before the base
series; other overlays paint afterward in declared order. Routes supply their
projected `path`, stable ID/label, genuine `categoryId`/`seriesId` and source
`value`, plus an optional non-negative `size`. These are provider-projected
paths, not a built-in geographic routing or projection engine.
The request viewport has positive finite `width`/`height` and optional positive
`pixelRatio`. The provider owns projected positions and paths on resize. Heat
point `radius` is optional (18 viewport units when omitted); an authored radius
must be positive. `projectionId`, response type and dataset revision are
validated; source-bound overlay values must match the canonical dataset.
Pass `rendererOptions` on `pack.render` and each complete `instance.update`
input for theme, fonts, class names, element styles/formatters, diagnostics and
`onRender`. See [geographic customization](/guides/charts/customization/#geographic-charts).
Locale callbacks format values, accessibility and load states. Click or
Enter/Space activation returns `{ chartType, datum, originalEvent }` through
`onNodeClick`; application code decides what to do with the provider datum.
The capability instance exposes `ready`, `update`, `focus` and `destroy`;
it does not expose native chart model, CSV/image export or navigation methods.
---
# Pyramid
URL: https://pro.rv-grid.com/guides/charts/types/pyramid/
Source: src/content/docs/guides/charts/types/pyramid.mdx
Description: Compare staged values through centered proportional layers. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Quadrant chart
URL: https://pro.rv-grid.com/guides/charts/types/quadrant/
Source: src/content/docs/guides/charts/types/quadrant.mdx
Description: Compare observations across four decision regions. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Radar area
URL: https://pro.rv-grid.com/guides/charts/types/radar-area/
Source: src/content/docs/guides/charts/types/radar-area.mdx
Description: Compare filled multivariate profiles across circular axes. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Metric axes and grid
Use `axes.radar` for common/per-category numeric domains, scales, ticks, labels and grid styles. Saved `radarDomain`, `radarDivisions`, `radarGridShape`, `radarReverse`, `radarValues` and `startAngle` configure radial projection. See [all fields, defaults, clipping, signed data and Pivot controls](/guides/charts/tools/radar-axes/).
---
# Radar line
URL: https://pro.rv-grid.com/guides/charts/types/radar-line/
Source: src/content/docs/guides/charts/types/radar-line.mdx
Description: Compare multiple series across circular category axes. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Metric axes and grid
Use `axes.radar` for common/per-category numeric domains, scales, ticks, labels and grid styles. Saved `radarDomain`, `radarDivisions`, `radarGridShape`, `radarReverse`, `radarValues` and `startAngle` configure radial projection. See [all fields, defaults, clipping, signed data and Pivot controls](/guides/charts/tools/radar-axes/).
---
# Radial bar
URL: https://pro.rv-grid.com/guides/charts/types/radial-bar/
Source: src/content/docs/guides/charts/types/radial-bar.mdx
Description: Show one series as progress around radial tracks. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
See [polar and radial geometry](/guides/charts/tools/polar-geometry/) for saved rotation, sweep, direction, radius and spacing controls shared with Pivot.
Use [polar numeric value axes](/guides/charts/tools/polar-value-axes/) for shared scale, bounds, reversal, tick, grid and clipping policies.
## Named axes and measured layout
Add numeric definitions in `axes.polar.valueAxes` and assign each series with
`seriesOverrides[id].polarAxis`; omitted assignments use the primary polar
axis. Axis domains, clipping and stack partitions follow that identity. See
[named numeric polar axes](/guides/charts/tools/polar-value-axes/#named-numeric-polar-axes).
[Measured polar layout](/guides/charts/tools/polar-axis-layout/) shares tick
decisions across labels, grids, resize and export. Authored axes reserve bounded
gutters; labels/titles that cannot fit follow a documented omission order while
marks and raw inspection remain available. Runtime placement uses
`rendererOptions.polarAxis.placement`, is bounded and validated, and must be
reattached after restoring a saved model. This does not add polar navigation or
mixed polar composition.
---
# Radial column
URL: https://pro.rv-grid.com/guides/charts/types/radial-column/
Source: src/content/docs/guides/charts/types/radial-column.mdx
Description: Compare rectangular columns arranged around a polar axis. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
See [polar and radial geometry](/guides/charts/tools/polar-geometry/) for saved rotation, sweep, direction, radius and spacing controls shared with Pivot.
Use [polar numeric value axes](/guides/charts/tools/polar-value-axes/) for shared scale, bounds, reversal, tick, grid and clipping policies.
## Named axes and measured layout
Add numeric definitions in `axes.polar.valueAxes` and assign each series with
`seriesOverrides[id].polarAxis`; omitted assignments use the primary polar
axis. Axis domains, clipping and stack partitions follow that identity. See
[named numeric polar axes](/guides/charts/tools/polar-value-axes/#named-numeric-polar-axes).
[Measured polar layout](/guides/charts/tools/polar-axis-layout/) shares tick
decisions across labels, grids, resize and export. Authored axes reserve bounded
gutters; labels/titles that cannot fit follow a documented omission order while
marks and raw inspection remain available. Runtime placement uses
`rendererOptions.polarAxis.placement`, is bounded and validated, and must be
reattached after restoring a saved model. This does not add polar navigation or
mixed polar composition.
---
# Range area
URL: https://pro.rv-grid.com/guides/charts/types/range-area/
Source: src/content/docs/guides/charts/types/range-area.mdx
Description: Show how a lower-to-upper interval changes over categories. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Range bar
URL: https://pro.rv-grid.com/guides/charts/types/range-bar/
Source: src/content/docs/guides/charts/types/range-bar.mdx
Description: Compare lower-to-upper intervals as horizontal bars. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Range column
URL: https://pro.rv-grid.com/guides/charts/types/range-column/
Source: src/content/docs/guides/charts/types/range-column.mdx
Description: Compare lower-to-upper intervals as vertical columns. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Range and interval
URL: https://pro.rv-grid.com/guides/charts/types/range-interval/
Source: src/content/docs/guides/charts/types/range-interval.mdx
Description: Compare minimum, maximum, and mean values by category. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
Value axes can be primary, secondary or named. Assign each selected series with `seriesOverrides[id].yAxis`; configure it in `axes.y`, `axes.ySecondary` or `axes.yAxes`. See [statistical value-axis collections](/guides/charts/tools/axes/#statistical-value-axis-collections) for grouping, source domains, orientation and Pivot controls.
## Related charts
Assign independent category/time/numeric projections with `axes.xAxes` and `seriesOverrides[id].xAxis`. Both primary and named X axes support `data` maps or path `level` selection. See [named statistical X axes](/guides/charts/tools/axes/#named-statistical-x-axes) for windows, interval grouping and physical placement.
---
# Rose
URL: https://pro.rv-grid.com/guides/charts/types/rose/
Source: src/content/docs/guides/charts/types/rose.mdx
Description: Compare category magnitude through radial sector length. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Sankey
URL: https://pro.rv-grid.com/guides/charts/types/sankey/
Source: src/content/docs/guides/charts/types/sankey.mdx
Description: Trace weighted flow through a directed acyclic graph or a category-to-series matrix. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## General graphs, configuration and runtime controls
`createChartFlowData` converts a directed node/link graph to the shared Charts dataset and semantic bindings. The same data can be used with standalone Charts, an unlinked Pivot chart, or a Pivot chart data provider. Existing category-to-series Sankey datasets remain supported.
```ts
import { createChart, createChartFlowData } from '@revolist/charts';
const flow = createChartFlowData({
nodes: [
{ id: 'revenue', label: 'Revenue' },
{ id: 'gross', label: 'Gross profit' },
{ id: 'costs', label: 'Operating costs' },
{ id: 'net', label: 'Net profit' },
],
links: [
{ id: 'sales', source: 'revenue', target: 'gross', value: 100 },
{ id: 'expenses', source: 'gross', target: 'costs', value: 40 },
{ id: 'profit', source: 'gross', target: 'net', value: 60 },
],
});
const chart = await createChart({
container: document.getElementById('chart')!,
chartType: 'sankey',
...flow,
chartOptions: {
nodeAlignment: 'justify', // left (default), right, center, justify
nodeWidth: 14, // pixels; constrained to fit the drawing area
nodePadding: 8, // pixels; constrained to fit dense columns
layoutIterations: 8, // 0 (default) preserves first-link appearance order
linkCurvature: 0.48, // 0..1
},
});
```
Layout option limits are validated before rendering:
| Option | Default | Accepted values |
| --- | --- | --- |
| `nodeWidth` | 14 | 1..100 pixels |
| `nodePadding` | 8 | 0..100 pixels |
| `layoutIterations` | 0 | Integer 0..64 |
| `linkCurvature` | 0.48 | 0..1 |
| `nodeAlignment` | `left` | `left`, `right`, `center`, `justify` |
Rendered widths/padding can shrink to fit the host. These fields are Sankey-only; unsupported chart options reject.
Nodes are inferred when `nodes` is omitted. When supplied, node IDs must be unique. Endpoints absent from a partial node list are inferred from links. Equal labels do not merge different IDs. Repeated links remain separate canonical observations. Supply stable link IDs for reorder/update identity; omitted IDs follow input order.
Weights must be finite and non-negative. Zero-weight links remain in data/CSV but have no geometry. Nodes without positive connections are omitted from geometry. Positive links must form a directed acyclic graph; self-links and cycles are rejected. Node heights use the greater of incoming and outgoing weight, with one shared width scale for all links. Overflowing raw totals do not produce infinite SVG coordinates.
The helper returns `{ dataset, bindings }`. Link IDs are category IDs; numeric values remain in the authoritative category-by-series matrix. Source and target identities/labels live in a mapped analytical frame. Saved models retain that mapping; CSV, tooltips and link activation use the ordinary chart pipeline. Nodes expose aggregate keyboard inspection and do not invent a source-cell drill target.
For an existing mapped analytical frame, bind `source` and `target` to dimension field IDs and `weight` to mapped measure/series IDs. Matching endpoint paths (or scalar values when no path is supplied) identify the same node across stages. This lets Pivot data providers and hierarchy dimensions use the shared graph renderer without converting the graph back to category/series partitions. Pivot compatibility preserves valid explicit source/target bindings.
Update both the dataset and bindings when replacing the graph:
```ts
await chart.update(createChartFlowData({
links: [{ id: 'sales', source: 'Revenue', target: 'Profit', value: 65 }],
}));
```
For Pivot, pass the same dataset and bindings through `createStandalonePivotChart` from `@revolist/pivot/charts` or return the mapped dataset from the chart data provider with corresponding model bindings. Source shaping, selection and drill remain owned by Pivot.
## Colors, ordering and labels
Node data accepts `color`; link data accepts a color or `'source'` / `'target'`. Colors use the shared hexadecimal color contract. The helper retains declared node order and colors in `dataset.analyticalFrame.derived.flowAppearance`, so save/restore, SSR and Pivot providers preserve them without duplicating matrix values. Inferred nodes follow declared nodes, in first-link appearance order. Connected nodes alone acquire geometry.
`chartOptions` also accepts:
- `nodeSort`: `'auto'` (default), `'fixed'`, `'valueAscending'`, `'valueDescending'`, `'label'`. Auto uses `layoutIterations`; fixed and explicit orders skip barycentric reordering. The existing default of zero iterations preserves authored order.
- `linkSort`: `'fixed'` (default), `'auto'`, `'valueAscending'`, `'valueDescending'`. Auto orders slots by the opposite node's vertical position. Incoming and outgoing slots sort independently, with stable ties and conserved widths. Rendering and canonical link data stay in dataset order.
- `linkColorMode`: `'default'` (default), `'source'`, `'target'`; `linkOpacity`: `0..1` (default `0.46`). Per-item colors win over defaults.
- `showNodeLabels` (default `true`), `showNodeValues` and `showLinkValues` (default `false`). Dense/small nodes retain their accessible descriptions when visible labels are omitted.
Pivot exposes the same controls from the shared schema. Chart-type transitions remove unsupported options and controlled highlight state while retaining compatible appearance settings.
## Runtime callbacks
Pass `sankey` to `rendererOptions` in standalone Charts, or to `createPivotChartsRenderer` in Pivot:
```ts
const rendererOptions = {
sankey: {
nodeSort: (a, b) => a.label.localeCompare(b.label),
linkSort: (a, b) => a.value - b.value,
nodeColor: (node) => node.label === 'Profit' ? '#16834a' : undefined,
linkColor: 'source',
valueFormatter: (value, { kind, location }) =>
location === 'tooltip' ? `${kind}: ${value} USD` : `${value} USD`,
onNodeClick: ({ node, originalEvent }) => inspectNode(node),
onLinkClick: ({ link, originalEvent }) => inspectLink(link.category.id),
onHighlightChange: (item) => showRelatedDetails(item),
},
};
```
Comparators receive read-only canonical graph descriptors and must return finite numbers; equal values retain authored order. Runtime comparators override the corresponding built-in sort policy. `nodeColor` also accepts a literal hexadecimal color. Color callbacks return a hexadecimal color or `undefined` to retain the default. `linkColor` also accepts `'source'` or `'target'`. Authored per-item colors take precedence. `valueFormatter` receives a context containing `kind`, `item` (the node/link descriptor) and `location`. The location is a `'tooltip'`/`'label'` location; node label values require `showNodeValues`. Returned values must be strings. Callbacks and renderer options remain runtime-only and must be supplied again when restoring a saved model.
`rendererOptions.sankey.onNodeClick` handles aggregate graph nodes, including keyboard Enter/Space, without inventing a source cell to drill. `onLinkClick` handles links. Links also retain the ordinary top-level `onNodeClick` canonical data/drill pipeline, once per activation. Aggregate nodes and links share the chart's existing roving keyboard targets.
## Highlighting
Node `nodeHighlight` policies are `'nodes'`, `'links'` (default), `'incoming'`, `'outgoing'` and `'none'`. Link `linkHighlight` policies are `'links'` (default), `'nodes'`, `'source'`, `'target'` and `'none'`. The active item remains highlighted with its related items. `'none'` disables highlighting and fading. `nodeFade` / `linkFade` accept `'global'` or `'none'` (default); global fades other flow shapes to 20 percent through a wrapper, preserving authored mark paint. Enabled node captions and link values stay at their authored opacity in a foreground text layer during pointer, keyboard and controlled highlighting; fading does not hide the labels.
Pointer and keyboard focus highlight transiently. Repaint retains an existing semantic target; removing that target clears the transient highlight. `onHighlightChange` reports item requests and programmatic highlight changes. Use `chartOptions.highlightedFlowItem` to control/programmatically set highlighting:
```ts
await chart.update({
chartOptions: {
...chart.model.chartOptions,
highlightedFlowItem: { kind: 'link', categoryId: 'sales', seriesId: 'flow:value' },
},
});
```
Node identifiers are `{ kind: 'node', nodeId }`, where the graph's canonical node ID comes from callbacks or the rendered node. For helper scalar IDs, it is `node:` followed by the JSON array of that ID (for example `node:["revenue"]`). Link identifiers use canonical category and series IDs, so parallel links remain distinguishable and reordering does not change their identity. An unknown saved target remains dormant and does not fade the graph. Explicit `null` controls the graph with no highlight; omit the option to restore transient hover/focus behavior. Controlled state is saved; pointer/focus state is not. SVG export captures the current painted state.
These controls cover the corresponding sorting, styling, callbacks and highlight policies in the [MUI Sankey reference](https://mui.com/x/react-charts/sankey/). Public composition primitives and the remaining general chart roadmap are separate work.
---
# Scatter
URL: https://pro.rv-grid.com/guides/charts/types/scatter/
Source: src/content/docs/guides/charts/types/scatter.mdx
Description: Compare two measures as points across categories. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Explicit renderer policy
`chartOptions.rendererPolicy` accepts `'svg' | 'batched-svg' | 'webgl'`. Omission keeps native SVG. See the [WebGL capability, inspection, fallback and export contract](/guides/charts/tools/webgl/) and [measured budgets](/guides/charts/tools/webgl-performance/). GPU selection does not raise existing source limits or imply SVG styling equivalence.
## Datum and segment encodings
Saved `seriesOverrides[id].encoding` policies and runtime
`rendererOptions.datumStyle` support scoped source styling. Use stable category
IDs for datum rules; directed segment rules apply to the supported native line
family only. Gradient/pattern fills, marker support, precedence, raw callback
contexts and clean-export behavior follow the [datum styling capability
matrix](/guides/charts/tools/datum-styling/). Authored encodings or runtime datum
callbacks force an individual-SVG fallback for batched/GPU requests; saved
renderer policy remains intact.
---
# Semi-donut
URL: https://pro.rv-grid.com/guides/charts/types/semi-donut/
Source: src/content/docs/guides/charts/types/semi-donut.mdx
Description: Show part-to-whole values across a compact semicircle. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Slice geometry and labels
Set saved `outerRadius`, `paddingAngle`, `cornerRadius`, `arcLabelRadius` and
`arcLabelMinAngle` to size, separate, round and label the slices. Defaults preserve
existing rendering; the label-radius editor preset differs from adaptive omitted
placement. See [all units, ranges, defaults, resets and ring behavior](/guides/charts/customization/#pie-arc-geometry).
## Highlighting and fading
Use saved `pieHighlight`, `pieFade`, `highlightedArc`, `fadedArc` and `highlightedPieItem` for source-safe hover/focus geometry and controlled highlighting. See [scopes, radius units, callbacks, Pivot controls and export behavior](/guides/charts/tools/pie-highlighting/).
---
# Small multiples
URL: https://pro.rv-grid.com/guides/charts/types/small-multiples/
Source: src/content/docs/guides/charts/types/small-multiples.mdx
Description: Compare dimension groups or series in aligned panels with shared or independent scales. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Facet by a dimension
A facet splits the dataset by a real analytical-frame dimension. Discover the
available field IDs rather than guessing a display label:
```ts
import { getChartFacetDimensions } from '@revolist/charts/core';
const dimensions = getChartFacetDimensions(chart.getDataset());
// Populate your dimension selector from these descriptors.
if (dimensions.length) {
await chart.update({
chartType: 'smallMultiples',
facets: {
dimension: dimensions[0].id,
type: 'column',
scaleMode: 'shared',
pageSize: 6,
page: 0,
},
});
}
```
Register `smallMultiplesChart` from `@revolist/charts/small-multiples` in your
standalone factory before changing the chart type. The native Pivot renderer
uses the same facet settings; its row hierarchy supplies dimension descriptors.
Explicit analytical frames can supply additional dimensions and matrix-cell
mappings. The requested dimension must exist on each mapped source observation.
`type` supports `line`, `bar`, `column`, and `area`. Shared scales allow magnitude
comparisons and consider panels on other pages. Independent scales emphasize
each group's shape and require closer attention to axis labels. Omit `dimension`
to keep one panel per measure. The renderer adapts panel capacity to the host;
`pageSize` is an upper bound, and accessible previous/next controls keep the
remaining panels reachable. Source selection and CSV identities remain canonical.
See [Analysis](/guides/charts/analysis/#compare-groups-with-small-multiples) for
choosing a comparison and [Chart Analysis API](/api/chart-analysis/) for options.
## Related charts
---
# Smooth line
URL: https://pro.rv-grid.com/guides/charts/types/smooth-line/
Source: src/content/docs/guides/charts/types/smooth-line.mdx
Description: Show trends with a smooth curve through each value. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Native stack groups
Use `seriesOverrides[id].stackGroup` for independent groups, with shared
`stackOrder` and `stackOffset` policies. Regular paths without a group remain raw;
dedicated stacked/normalized areas retain an implicit default stack. See
[normalization, defaults, source identity and Pivot controls](/guides/charts/customization/#native-line-and-area-stack-groups).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# Stacked area
URL: https://pro.rv-grid.com/guides/charts/types/stacked-area/
Source: src/content/docs/guides/charts/types/stacked-area.mdx
Description: Show how multiple series contribute to a changing total. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
### Native stack groups
Use `seriesOverrides[id].stackGroup` for independent groups, with shared
`stackOrder` and `stackOffset` policies. Regular paths without a group remain raw;
dedicated stacked/normalized areas retain an implicit default stack. See
[normalization, defaults, source identity and Pivot controls](/guides/charts/customization/#native-line-and-area-stack-groups).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# Stacked bar
URL: https://pro.rv-grid.com/guides/charts/types/stacked-bar/
Source: src/content/docs/guides/charts/types/stacked-bar.mdx
Description: Compare horizontal totals and their composition. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# Stacked column
URL: https://pro.rv-grid.com/guides/charts/types/stacked-column/
Source: src/content/docs/guides/charts/types/stacked-column.mdx
Description: Compare totals and their contributing series. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Stack order
Use `chartOptions.stackOrder` or `seriesOverrides[id].stackOrder` to choose source, reverse, total-based or peak-appearance accumulation while preserving source identities. See [policies, per-stack overrides and Pivot controls](/guides/charts/tools/axes/#configurable-stack-order).
### Stack baselines
Set `chartOptions.stackOffset` or a stack member's `stackOffset` to `diverging`
(default), `none` or `expand`. Expansion uses algebraic unit totals and keeps raw
source inspection. See [supported partitions, signed values, defaults and limits](/guides/charts/customization/#stack-baseline-policies).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# Stacked polar column
URL: https://pro.rv-grid.com/guides/charts/types/stacked-polar-column/
Source: src/content/docs/guides/charts/types/stacked-polar-column.mdx
Description: Compare radial totals and their contributing series. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
See [polar and radial geometry](/guides/charts/tools/polar-geometry/) for saved rotation, sweep, direction, radius and spacing controls shared with Pivot.
Use [polar numeric value axes](/guides/charts/tools/polar-value-axes/) for shared scale, bounds, reversal, tick, grid and clipping policies.
## Named axes and measured layout
Add numeric definitions in `axes.polar.valueAxes` and assign each series with
`seriesOverrides[id].polarAxis`; omitted assignments use the primary polar
axis. Axis domains, clipping and stack partitions follow that identity. See
[named numeric polar axes](/guides/charts/tools/polar-value-axes/#named-numeric-polar-axes).
[Measured polar layout](/guides/charts/tools/polar-axis-layout/) shares tick
decisions across labels, grids, resize and export. Authored axes reserve bounded
gutters; labels/titles that cannot fit follow a documented omission order while
marks and raw inspection remain available. Runtime placement uses
`rendererOptions.polarAxis.placement`, is bounded and validated, and must be
reattached after restoring a saved model. This does not add polar navigation or
mixed polar composition.
---
# Stepped area
URL: https://pro.rv-grid.com/guides/charts/types/stepped-area/
Source: src/content/docs/guides/charts/types/stepped-area.mdx
Description: Emphasize stepwise changes with a filled baseline. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Fill baseline
Unstacked areas accept `seriesOverrides[id].baseline: number | 'min' | 'max'`. Numeric baselines participate in the automatic domain; endpoint policies use the final axis bounds. See [baseline options and Pivot controls](/guides/charts/tools/axes/#area-fill-baselines).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Native stack groups
Use `seriesOverrides[id].stackGroup` for independent groups, with shared
`stackOrder` and `stackOffset` policies. Regular paths without a group remain raw;
dedicated stacked/normalized areas retain an implicit default stack. See
[normalization, defaults, source identity and Pivot controls](/guides/charts/customization/#native-line-and-area-stack-groups).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# Stepped line
URL: https://pro.rv-grid.com/guides/charts/types/stepped-line/
Source: src/content/docs/guides/charts/types/stepped-line.mdx
Description: Show values that remain constant between categories. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Per-series curves
Use `seriesOverrides[id].interpolation` to choose linear, legacy smooth, directional/midpoint steps, monotone X/Y, natural, Catmull–Rom or bump X/Y curves. Combination charts accept this on line/area layers. See [all curve choices, defaults and Pivot controls](/guides/charts/tools/axes/#per-series-line-and-area-interpolation).
Set `seriesOverrides[id].connectNulls: true` to join finite path endpoints across null values. It defaults to false and preserves explicit calendar gaps. See [null connections, viewport behavior and Pivot controls](/guides/charts/tools/axes/#connecting-paths-across-null-values).
## Source markers
Use `seriesOverrides[id].markers: true | false | 'start' | 'end'` for all, hidden or accepted-frame endpoint glyphs. Marker shapes include cross, star and wye alongside the four existing choices. See [marker modes, sampling and Pivot controls](/guides/charts/tools/axes/#path-marker-visibility-and-shapes).
Application code can refine those modes with `rendererOptions.markers.isVisible(context)`. See [runtime source predicates and sampling](/guides/charts/tools/axes/#runtime-source-marker-predicates).
### Path sampling
`chartOptions.sampling` accepts `'auto'` (default), `'none'`, `'m4'`, `'minmax'`,
and `'lttb'`. See [path sampling](/guides/charts/tools/axes/#path-sampling) for
budgets, stacked-edge alignment, viewport behavior, and marker handling.
### Step endpoints
`seriesOverrides[id].strictStepCurve` defaults to `false`. Stepped paths expand
across categorical bands; `true` retains exact observation endpoints. Default
native point layouts and numeric/time axes keep their existing geometry. Explicit
category band placement enables expansion for native line/area paths. See [step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy)
for combination bands, null runs, sampling, areas, and Pivot controls.
### Category placement
`axes.x.categoryPlacement` and `axes.xAxes[id].categoryPlacement` accept `'point'`
or `'band'` for categorical X axes. Omitted settings keep native line/area point
positions and combination band centers. Band placement enables expanded native
steps; point placement retains exact stepped endpoints. See
[category point and band placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for singletons, column margins, grouped labels, selection, preview, save/SSR and
Pivot controls.
### Transient point highlights
`chartOptions.disableLineItemHighlight` and
`seriesOverrides[id].disableHighlight` default to `false`. Highlights inspect
source observations independently of regular marker visibility/predicates and
can temporarily replace saved endpoint glyphs. See
[transient source-point highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for nearest-source policy, tooltip/focus/activation limits, snapshots and Pivot
controls.
### Source keyboard inspection
Line/area observations omitted by marker settings, runtime predicates or sampling
remain reachable through the chart's single inspection Tab stop. Arrow keys wrap;
Home/End select the first/last item and Enter/Space use raw source activation.
Null, hidden and clipped observations are excluded.
See [source-point keyboard traversal](/guides/charts/tools/axes/#source-point-keyboard-traversal)
for ordering, focus rings, updates and inspection-only derived values.
### Native stack groups
Use `seriesOverrides[id].stackGroup` for independent groups, with shared
`stackOrder` and `stackOffset` policies. Regular paths without a group remain raw;
dedicated stacked/normalized areas retain an implicit default stack. See
[normalization, defaults, source identity and Pivot controls](/guides/charts/customization/#native-line-and-area-stack-groups).
## Stable source styling
Use `seriesOverrides[id].encoding` for saved per-datum styling, gradients and
patterns. Supported line/area paths also accept directed segment rules; bars
accept datum rules without segment/marker fields. Runtime `datumStyle` callbacks
are reattached after restore. Rules follow canonical category IDs through
sorting, hiding and navigation; source values and raw activation remain intact.
See [the complete styling capability matrix, precedence and callback
contract](/guides/charts/tools/datum-styling/).
---
# Strip and dot plot
URL: https://pro.rv-grid.com/guides/charts/types/strip-dot/
Source: src/content/docs/guides/charts/types/strip-dot.mdx
Description: Show the distribution of individual values along one axis. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
## Dense sample inspection
At more than 400 eligible samples the renderer uses at most 48 nonempty numeric
bins instead of one dot per source observation. Bin size encodes count. Singleton
bins keep genuine source activation; bins with multiple samples remain aggregate
inspection targets. The raw sample matrix and CSV stay unchanged. Use
[histogram](/guides/charts/types/histogram/) when authored boundaries, complete
remote partitions or a sample/bin data view are required.
---
# Sunburst
URL: https://pro.rv-grid.com/guides/charts/types/sunburst/
Source: src/content/docs/guides/charts/types/sunburst.mdx
Description: Explore the current hierarchy level as proportional radial sectors. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Timeline range
URL: https://pro.rv-grid.com/guides/charts/types/timeline-range/
Source: src/content/docs/guides/charts/types/timeline-range.mdx
Description: Compare start-to-end intervals across categories. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Treemap
URL: https://pro.rv-grid.com/guides/charts/types/treemap/
Source: src/content/docs/guides/charts/types/treemap.mdx
Description: Compare hierarchical values as proportional nested tiles. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Violin plot
URL: https://pro.rv-grid.com/guides/charts/types/violin/
Source: src/content/docs/guides/charts/types/violin.mdx
Description: Compare distribution shape and concentration across categories. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
Value axes can be primary, secondary or named. Assign each selected series with `seriesOverrides[id].yAxis`; configure it in `axes.y`, `axes.ySecondary` or `axes.yAxes`. See [statistical value-axis collections](/guides/charts/tools/axes/#statistical-value-axis-collections) for grouping, source domains, orientation and Pivot controls.
## Related charts
Assign independent category/time/numeric projections with `axes.xAxes` and `seriesOverrides[id].xAxis`. Both primary and named X axes support `data` maps or path `level` selection. See [named statistical X axes](/guides/charts/tools/axes/#named-statistical-x-axes) for windows, interval grouping and physical placement.
---
# Volume and price
URL: https://pro.rv-grid.com/guides/charts/types/volume-price/
Source: src/content/docs/guides/charts/types/volume-price.mdx
Description: Compare price movement with trading volume. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
Use primary `axes.x` for category, time or numeric (`linear`, `log`, `symlog`, `sqrt`) observation coordinates. `data` maps and path `level` selection preserve source identities. See [financial numeric X axes](/guides/charts/tools/axes/#financial-numeric-x-axes) for ticks, windows, aligned price/volume panes, Pivot controls and limits.
Use [paired volume-price collections](/guides/charts/tools/axes/#paired-volume-price-collections) for multiple OHLC/volume pairs, close-owned named X, fixed Y roles and `chartOptions.volumePaneRatio` (`0.1`–`0.8`). Resetting the ratio preserves automatic sizing.
Use [Financial sessions and the trading toolbar](/guides/charts/tools/financial-sessions/) for explicit exchange-session or ordinal time spacing, supplied named-zone calendars and the shared navigation/drawing controls. Omission retains continuous time and source timestamps.
---
# Waffle
URL: https://pro.rv-grid.com/guides/charts/types/waffle/
Source: src/content/docs/guides/charts/types/waffle.mdx
Description: Compare part-to-whole values on a compact 100-cell grid. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Waterfall
URL: https://pro.rv-grid.com/guides/charts/types/waterfall/
Source: src/content/docs/guides/charts/types/waterfall.mdx
Description: Show how positive and negative changes build to a result. Data, options and a rendered example.
## Live example
## Reading the chart
## Data requirements
## Create this chart
## Supported options
## Related charts
---
# Column Collapse
URL: https://pro.rv-grid.com/guides/columns/column-collapse/
Source: src/content/docs/guides/columns/column-collapse.mdx
Description: Make grouped columns collapsible with the ability to show/hide child columns
The Column Collapse plugin allows you to make grouped columns collapsible.
When a group is collapsed, only columns marked as "sealed" remain visible, while other columns are hidden.
If no sealed column exists, the group falls back to a single placeholder column that keeps the expand affordance visible.
## Features
- Add collapse/expand functionality to column groups
- Maintain "sealed" columns visible even when group is collapsed
- Fall back to a single placeholder column when no sealed child exists
- Support for nested column groups
- Customizable initial collapse state
- Smooth animations and intuitive UI
## Installation
The Column Collapse plugin is included in the RevoGrid Pro package. To use it, simply import and add it to your grid's plugins:
```javascript
import { ColumnCollapsePlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnCollapsePlugin];
```
## Usage
To make a column group collapsible:
1. Add the `collapsible` property to the group configuration
2. Mark important columns as `sealed` to keep them visible when collapsed
3. Optionally provide `placeholder` on the group to customize the fallback placeholder header or cell
```javascript
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnCollapsePlugin];
// Configure columns with collapsible groups
grid.columns = [
{
id: 'personalInfo',
name: 'Personal Information',
collapsible: true, // Enable collapse for this group
children: [
{
prop: 'name',
name: 'Name',
sealed: true // This column will stay visible when collapsed
},
{
prop: 'email',
name: 'Email'
},
{
prop: 'phone',
name: 'Phone'
}
]
},
{
id: 'addressInfo',
name: 'Address',
collapsible: true,
children: [
{
prop: 'street',
name: 'Street',
sealed: true
},
{
prop: 'city',
name: 'City'
},
{
prop: 'country',
name: 'Country'
}
]
}
];
// Optional placeholder customization when no sealed column exists
grid.columns.push({
name: 'Contact',
collapsible: true,
collapsed: true,
placeholder: {
header: 'Contact hidden',
cellTemplate: (h) => h('span', undefined, 'Expand to view'),
},
children: [
{ prop: 'altEmail', name: 'Alt Email' },
{ prop: 'mobile', name: 'Mobile' },
],
});
// Sample data
grid.source = [
{
name: 'John Doe',
email: 'john@example.com',
phone: '123-456-7890',
street: '123 Main St',
city: 'New York',
country: 'USA'
},
// ... more rows
];
```
## Best Practices
1. Use `sealed` for essential columns that should always be visible
2. Keep group names concise to avoid UI clutter
3. Use `placeholder` when you want custom fallback content for groups without sealed children
4. Use nested groups sparingly to maintain clarity
5. Consider your users' needs when deciding which columns to make collapsible
The Column Collapse plugin helps manage complex data grids by allowing users to focus on relevant information while keeping essential data visible at all times.
---
# Column Hide
URL: https://pro.rv-grid.com/guides/columns/column-hide/
Source: src/content/docs/guides/columns/column-hide.mdx
Description: Implementing Column Hiding
RevoGrid provides the ability to hide specific columns in your grid, allowing you to focus on the most relevant data for your users.
This feature is particularly useful when working with large datasets where not all columns need to be visible at once.
`Column hiding` is a feature that allows you to hide specific columns in your grid, making them invisible to users. This is useful for:
- Focusing on specific data points
- Reducing visual clutter
- Creating different views of the same dataset
- Implementing column visibility toggles
The `ColumnHidePlugin` supports three ways to hide columns:
## Using the `hide-columns` attribute
You can specify which columns to hide using the `hide-columns` attribute on your RevoGrid element:
```html
```
## Using the `hideColumns` property
Framework wrappers typically bind properties instead of attributes:
```tsx
```
You can also update it at runtime:
```javascript
grid.hideColumns = ['name', 'age'];
```
## Using `additionalData.hiddenColumns`
You can also hide columns programmatically using the `additionalData` property:
```javascript
const grid = document.querySelector('revo-grid');
grid.additionalData = {
hiddenColumns: ['name', 'age']
};
```
This approach is particularly useful when you need to dynamically update which columns are hidden based on user interactions or application state.
## Events
The `ColumnHidePlugin` emits a `hiddencolumnsupdated` event whenever the hidden columns are updated:
```javascript
grid.addEventListener('hiddencolumnsupdated', (event) => {
const { hiddenColumns } = event.detail;
console.log('Hidden columns updated:', hiddenColumns);
});
```
This event can be used to synchronize the UI with the current state of hidden columns.
## Implementation Details
The `ColumnHidePlugin` works by:
1. Tracking which columns should be hidden in a `Set`
2. Updating the column visibility when the configuration changes
3. Preserving the hidden state when columns are updated
4. Supporting attribute, property, and programmatic configuration
---
# Column Move With Groups
URL: https://pro.rv-grid.com/guides/columns/column-move-with-groups/
Source: src/content/docs/guides/columns/column-move-with-groups.mdx
Description: Move grouped column headers without breaking the column-group structure.
`ColumnMoveAdvancedPlugin` enables drag-and-drop for grouped column headers. It builds on RevoGrid's core column move behavior and adds the group-aware rules needed for `ColumnGrouping` columns.
Use it when your grid has nested column groups and users need to reorder whole header bands without manually rebuilding the `columns` array.
## Setup
Import the plugin from `@revolist/revogrid-pro` and register it in the grid `plugins` array.
```ts
import { ColumnMoveAdvancedPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [ColumnMoveAdvancedPlugin];
grid.columns = [
{
name: 'Personal Information',
children: [
{ prop: 'firstName', name: 'First Name' },
{ prop: 'lastName', name: 'Last Name' },
{ prop: 'age', name: 'Age' },
],
},
{
name: 'Address',
children: [
{ prop: 'street', name: 'Street' },
{ prop: 'city', name: 'City' },
{ prop: 'country', name: 'Country' },
],
},
];
```
`ColumnMoveAdvancedPlugin` extends the core `ColumnMovePlugin` implementation, so you do not need a separate column-move plugin entry for the common setup.
## Column Groups
Groups use the standard RevoGrid `ColumnGrouping` shape: a header object with a `name` and a `children` array. Children can be regular columns or another nested group.
```ts
grid.columns = [
{
name: 'Customer',
children: [
{ prop: 'name', name: 'Name' },
{
name: 'Contact',
children: [
{ prop: 'email', name: 'Email' },
{ prop: 'phone', name: 'Phone' },
],
},
],
},
{
name: 'Location',
children: [
{ prop: 'city', name: 'City' },
{ prop: 'country', name: 'Country' },
],
},
];
```
Leaf columns keep their normal column behavior: data binding, templates, sorting, filtering, resizing, and editing still belong to the regular column definitions.
## Move Behavior
Dragging a group header moves all leaf columns owned by that group as one block. During the drag, the drop indicator snaps to valid group boundaries instead of arbitrary leaf-column positions.
When the move completes, the plugin remaps the internal group indexes for every group level. This is the part plain `ColumnMovePlugin` does not handle for grouped headers.
Dragging an individual leaf column still works, but the plugin keeps the column inside its current group at the active grouping depth. This prevents a single leaf column from being dropped into a different group and leaving the header tree inconsistent.
Move a group by dragging the group header. Move a single leaf column by dragging the leaf header. Leaf-column moves are constrained to the compatible group area.
## Events
The plugin uses the standard column drag event names.
| Event | When it fires | Cancelable |
| --- | --- | --- |
| `columndragstart` | Before drag state is created. The detail is the dragged column or group data. | Yes |
| `beforecolumndragend` | Before the order is committed. The detail includes `startPosition`, `newPosition`, and moved items. | Yes |
| `columndragend` | After a successful drag flow finishes. | No |
```ts
grid.addEventListener('columndragstart', (event) => {
const data = event.detail;
if ('children' in data) {
console.log('Group drag started:', data.name);
}
});
grid.addEventListener('beforecolumndragend', (event) => {
const { startPosition, newPosition } = event.detail;
if (!canMoveColumns(startPosition, newPosition)) {
event.preventDefault();
}
});
```
## Framework Usage
For framework wrappers, pass the plugin class through the wrapper's `plugins` prop or input.
```tsx
import { RevoGrid } from '@revolist/react-datagrid';
import { ColumnMoveAdvancedPlugin } from '@revolist/revogrid-pro';
export function CustomerGrid({ rows, columns }) {
return (
);
}
```
```vue
```
## Compatibility
- Works with the standard nested `ColumnGrouping` structure.
- Supports nested group levels by remapping indexes across all group levels after a group move.
- Preserves regular leaf-column drag behavior, constrained to compatible group boundaries.
- Ignores row-header drag attempts.
- Uses the same `columndragstart`, `beforecolumndragend`, and `columndragend` event names as core column moving.
Use [Multi Row Headers](/guides/columns/multi-row-header/) when you need spreadsheet-style multi-level header spanning. Use [Column Collapse](/guides/columns/column-collapse/) when the same grouped headers should collapse or expand.
---
# Column Stretch
URL: https://pro.rv-grid.com/guides/columns/column-stretch/
Source: src/content/docs/guides/columns/column-stretch.mdx
Description: Implementing Column Stretching.
RevoGrid provides the ability to automatically adjust the width of columns to fit the table's width.
This is particularly useful when you have fewer columns than needed to enable the horizontal scrollbar, and you want to distribute the available space evenly or according to specific criteria.
In this article, we’ll walk you through the process of implementing column stretching in RevoGrid using the `ColumnStretchPlugin`. We'll also explore the different configurations available for stretching the columns.
## What is it?
`Column stretching` is a feature which allows you to automatically adjust the width of columns to fit the entire width of the table. The width of each column is calculated based on the number of columns and their respective sizes, ensuring the grid's width is fully utilized. This feature is especially useful when working with a small number of columns.
### Stretch Configurations
The Column Stretch plugin may conflict with other column size configurations. The following cases will prevent column stretching:
- Columns with explicit `size` property set
- Columns with `autoSize` enabled
- When the total width of all columns exceeds the available space (negative remaining width)
In these cases, the stretch configuration will be ignored for those specific columns to preserve their intended sizing behavior.
RevoGrid supports different types of stretch configurations through the `StretchConfig` type:
- **`'none'`**: No column stretching will be applied. Columns retain their default width.
- **`'last'`**: Only the last column will stretch to fill the remaining width.
- **`'all'`**: All columns will stretch evenly to fill the table's width.
- **`number`**: A specific column (by index) will stretch to fill the remaining width.
- To stretch only the last column:
```javascript
grid.additionalData = { stretch: 'last' };
```
- To stretch a specific column by index (e.g., the second column):
```javascript
grid.additionalData = { stretch: 1 };
```
- To stretch all columns evenly:
```javascript
grid.additionalData = { stretch: 'all' };
```
By using the `ColumnStretchPlugin`, you can easily manage the width of columns in RevoGrid, making sure your table layout looks clean and is fully utilized. Whether you want to stretch all columns evenly or just the last one, this plugin offers flexibility to suit your needs.
Experiment with the different stretch configurations to see which works best for your data grid!
---
# Column Type Renderer
URL: https://pro.rv-grid.com/guides/columns/column-type-renderer/
Source: src/content/docs/guides/columns/column-type-renderer.mdx
Description: Show type icons in column headers with columnTypeRenderer.
`columnTypeRenderer` is a Pro column header template that displays an icon next to the header label based on the column's `columnType`.
Use it when users should quickly recognize typed fields, such as IDs, numeric values, currency amounts, decimals, booleans, or text columns. It is assigned through `columnTemplate`, while `columnType` tells the renderer which icon to show.
```typescript
import { columnTypeRenderer } from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'id',
name: 'Identifier',
columnType: 'id',
columnTemplate: columnTypeRenderer,
},
{
prop: 'amount',
name: 'Amount',
columnType: 'currency',
columnTemplate: columnTypeRenderer,
},
];
```
Supported built-in icons are selected for `id`, `integer`, `currency`, `decimal`, and `boolean`. Any other value, or a missing `columnType`, falls back to the string icon.
`columnTypeRenderer` only changes the column header rendering. Cell formatting, editing, validation, and export behavior still come from the column definition or from entries registered in `grid.columnTypes`.
---
# Multi Row Headers
URL: https://pro.rv-grid.com/guides/columns/multi-row-header/
Source: src/content/docs/guides/columns/multi-row-header.mdx
Description: Render spreadsheet-style multi-level headers from nested column groups.
The Multi Row Header plugin renders banded, multi-level headers from the same nested `ColumnGrouping` structure RevoGrid already uses for grouped columns. Use it when shallow leaf columns should fill the unused header depth while deeper branches continue to show stacked group bands.
This is the header-focused feature to use for banded, multi-level headers. It is separate from `CellMergePlugin`, which only merges body and pinned data cells.
## Setup
```ts
import { MultiRowHeaderPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [MultiRowHeaderPlugin];
grid.multiRowHeader = true;
```
## Nested Columns
Define headers with the existing `children` column-group structure. Leaf columns still own data binding, sorting, filtering, editing, templates, and resizing.
```ts
grid.columns = [
{
name: 'Group A',
children: [
{ name: 'Athlete 1', prop: 'athlete' },
{
name: 'Group B',
children: [
{ name: 'Country 1', prop: 'country' },
{
name: 'Group C',
children: [
{ name: 'Sport 1', prop: 'sport' },
],
},
],
},
],
},
];
```
## Header Spanning
By default, shallow leaf headers span unused group rows. This matches the common spreadsheet behavior where a leaf header under a shallower branch fills the remaining vertical header space.
Disable spanning for a single column when you want balanced empty group space above the leaf:
```ts
grid.columns = [
{
name: 'Balanced',
children: [
{
name: 'Flat Leaf',
prop: 'flat',
suppressSpanHeaderHeight: true,
},
{
name: 'Nested',
children: [{ name: 'Nested Leaf', prop: 'nested' }],
},
],
},
];
```
You can also disable spanning for the whole plugin:
```ts
grid.multiRowHeader = {
spanLeafHeaders: false,
};
```
## Group Header Spanning
Set `spanHeaderHeight` on a column group when that group should fill every
remaining grouped-header row plus the leaf-header row:
```ts
grid.columns = [
{
name: 'January',
spanHeaderHeight: true,
children: [
{ name: 'Period Total', prop: 'januaryTotal' },
],
},
{
name: 'February',
children: [
{ name: 'Sales', prop: 'februarySales' },
{ name: 'Profit', prop: 'februaryProfit' },
],
},
];
```
The descendant leaf remains the data column, but its header is covered by the
spanning group. Explicit group spans are independent of `spanLeafHeaders`.
Pivot uses this option for generated collapsed column groups. With
`MultiRowHeaderPlugin` enabled, the collapsed group label fills the remaining
header depth. Without it, the regular collapsed placeholder header remains
visible as a fallback.
When Pivot row-axis headers contain sorting, selection counts, or filter
controls, keep those leaf headers at their normal height while retaining
collapsed group spanning:
```ts
grid.multiRowHeader = {
spanLeafHeaders: false,
};
```
`spanHeaderHeight` remains active because explicit group spanning is
independent of `spanLeafHeaders`.
## Configuration
```ts
grid.multiRowHeader = {
spanLeafHeaders: true,
};
```
| Option | Default | Description |
| --- | --- | --- |
| `spanLeafHeaders` | `true` | Lets shallow leaf headers fill unused group depth. |
## Compatibility
- Works with nested `ColumnGrouping` definitions.
- Leaf headers keep sorting, filtering, custom templates, and resize behavior.
- Group headers continue through RevoGrid's normal group header render hooks.
- `ColumnCollapsePlugin` can be registered with `MultiRowHeaderPlugin` to add expand/collapse controls to groups.
- `spanHeaderHeight` lets a terminal group fill the remaining header depth while retaining its descendant data column.
- Pinned columns can split a group into pinned and unpinned visible bands, matching the behavior used by major data grids.
---
# Blank Values
URL: https://pro.rv-grid.com/guides/data-filter/blank-values/
Source: src/content/docs/guides/data-filter/blank-values.mdx
Description: Configure source-preserving blank semantics for Core and Pro filters without changing saved operator IDs.
RevoGrid filtering preserves the source value and own-property presence. It does
not implicitly turn `null`, `undefined`, `false`, `0`, arrays, or missing
properties into an empty display string before evaluating filters.
## Default policy
| Source state | Blank by default |
| --- | --- |
| `null` | Yes |
| Owned `undefined` | Yes |
| Exactly `""` | Yes |
| Missing own property | Yes |
| Whitespace-only string | No |
| `false` | No |
| `0` | No |
| `NaN` | No |
| `[]` | No |
| Non-empty array | No |
| Object or other value | No |
Only an own property counts as present. A value inherited through the row
prototype is treated as missing. `null` and `undefined` can both match **Is
blank** while remaining distinct raw values.
The stable saved-state IDs remain `empty` and `notEmpty`; no state migration is
required. Their default labels are **Is blank** and **Is not blank**, and **Is
not blank** is the exact inverse of the resolved blank predicate.
## Configuration and precedence
Set the grid policy in the filter configuration. A column can override any
individual field; resolution is Core defaults, then grid policy, then column
policy.
```ts
grid.filter = {
blankSemantics: {
whitespaceOnlyString: true,
emptyArray: true,
isBlank(value, context, fallbackResult) {
// Final application override after the configured fallback.
return value === 'N/A' || fallbackResult;
},
},
};
grid.columns = [{
prop: 'tags',
filter: ['array'],
blankSemantics: {
// Other fields continue to inherit from the grid policy.
emptyArray: false,
},
}];
```
The callback receives the raw source value, row model, column, property, parsed
value, own-property status, effective policy, and fallback result.
## Parsers and typed operators
A `cellParser` still supplies the value used by ordinary comparisons. Blank
evaluation uses the source value and missing-property context captured before
the parser runs.
Typed operators remain strict. `isTrue` matches only `true`; `isFalse` matches
only `false`; `isEmptyArray` and `isNotEmptyArray` match only arrays. Enabling
`emptyArray: true` intentionally allows `[]` to match both **Is blank** and **Is
empty array**. Invalid nonblank date values, including `false` and `NaN`, do not
match date comparisons and are not blank by default.
## Related guides
- [Array Filter](/guides/data-filter/filter-types/array-filter/)
- [Boolean Filter](/guides/data-filter/filter-types/boolean-filter/)
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Custom Filter Operators
URL: https://pro.rv-grid.com/guides/data-filter/custom-filter-operators/
Source: src/content/docs/guides/data-filter/custom-filter-operators.mdx
Description: Add application-specific filter predicates, value controls, parsed-value normalization, AST conditions, and remote filtering workflows.
Surface: grid
Use a custom filter operator when the standard text and number comparisons do not express an application rule. Custom operators use the Core `customFilters` configuration and work with both `FilterPlugin` and Pro's `AdvanceFilterPlugin`.
A custom operator has three parts:
1. A stable operator ID, used by saved filter state and Pro `FilterAst` conditions.
2. A `columnFilterType` identifying the column family that can show the operator.
3. A synchronous predicate that returns `true` when a row should remain visible.
## Quick start: add an operator to a built-in family
This operator appears with the ordinary string operators because its `columnFilterType` is `string` and the column uses `filter: 'string'`:
```ts
import type { LogicFunction } from '@revolist/revogrid';
const endsWithCode: LogicFunction = (value, expected) => {
const suffix = String(expected ?? '')
.trim()
.toLowerCase();
if (!suffix) return true;
return String(value ?? '')
.toLowerCase()
.endsWith(suffix);
};
endsWithCode.extra = 'input';
grid.columns = [
{ prop: 'sku', name: 'SKU', filter: 'string' },
{ prop: 'name', name: 'Product', filter: 'string' },
];
grid.filter = {
customFilters: {
endsWithCode: {
columnFilterType: 'string',
name: 'Ends with code',
func: endsWithCode,
},
},
};
```
Open the SKU filter, choose **Ends with code**, and enter a suffix. Returning `true` for an empty input keeps dynamic editing from temporarily hiding every row.
Use a custom family when an operator should appear only on explicitly opted-in columns:
```ts
const matchesPriority: LogicFunction = (value, expected) =>
!expected || String(value).toLowerCase() === String(expected).toLowerCase();
matchesPriority.extra = 'input';
grid.columns = [
{ prop: 'priority', name: 'Priority', filter: 'ticketPriority' },
];
grid.filter = {
customFilters: {
matchesPriority: {
columnFilterType: 'ticketPriority',
name: 'Matches priority',
func: matchesPriority,
},
},
};
```
The family ID must match exactly. An operator registered for `ticketPriority` is not offered by a column configured with `filter: 'string'`.
## Choose the value control
Set `func.extra` to tell the standard filter panel how to collect the operator value:
| `func.extra` | Panel control | Typical use |
| --------------- | --------------------------------- | -------------------------------------------------- |
| `'input'` | Text input | Codes, thresholds, and search values |
| `'datepicker'` | Native date input | One calendar-date value |
| Render function | Application-defined VNode content | Selects or compound values |
| Omitted | No value control | Valueless rules whose predicate ignores `expected` |
For example, change the quick-start operator to a date input without replacing the filter panel:
```ts
const onOrAfter: LogicFunction = (value, expected) => {
if (!expected) return true;
return new Date(value).getTime() >= new Date(String(expected)).getTime();
};
onOrAfter.extra = 'datepicker';
```
A custom renderer receives the current value and controlled callbacks. Call `onInput()` with the next filter value; do not mutate the filter item directly:
```ts
import type { ExtraField } from '@revolist/revogrid';
const priorityControl: ExtraField = (h, context) =>
h(
'select',
{
value: context.value ?? '',
onInput: (event: Event) =>
context.onInput((event.target as HTMLSelectElement).value),
onFocus: context.onFocus,
},
[
h('option', { value: '' }, 'Any priority'),
h('option', { value: 'low' }, 'Low'),
h('option', { value: 'high' }, 'High'),
],
);
matchesPriority.extra = priorityControl;
```
Use `include` only when the entire filter popup should be restricted to a known list of operator IDs. It is global to the filter configuration, so omitting another required built-in or custom ID removes that operation from the available families.
## Parsed values and evaluation context
RevoGrid calls a predicate as `func(parsedValue, conditionValue, context)`. When the column defines `cellParser`, both built-in and custom comparisons receive its result:
```ts
const within: LogicFunction = (value, expected) => {
const target = Number(expected);
return !Number.isFinite(target) || Math.abs(Number(value) - target) <= 5;
};
within.extra = 'input';
grid.columns = [
{
prop: 'price',
name: 'Price',
filter: 'number',
cellParser: (model) =>
Number(String(model.price ?? '').replace(/[^0-9.-]/g, '')),
},
];
grid.filter = {
customFilters: {
withinFive: {
columnFilterType: 'number',
name: 'Within 5',
func: within,
},
},
};
```
Use `cellParser` for reusable normalization rather than repeating source conversion in every predicate. The optional third argument provides source-aware details when a rule needs them:
```ts
const isOriginalNull: LogicFunction = (_value, _expected, context) =>
context?.hasOwnProperty === true && context.sourceValue === null;
```
The context includes `model`, `column`, `property`, `sourceValue`, `parsedValue`, `hasOwnProperty`, and the resolved blank policy. Ordinary custom predicates receive `parsedValue`; Core blank operators intentionally use the original source value. See [Blank Values](/guides/data-filter/blank-values/) for that distinction.
## Restore and combine custom conditions
Use the operator ID in the same compatible state model as a built-in condition:
```ts
grid.filter = {
customFilters: {
endsWithCode: {
columnFilterType: 'string',
name: 'Ends with code',
func: endsWithCode,
},
},
multiFilterItems: {
sku: [
{
id: 1,
type: 'endsWithCode',
value: '-EU',
relation: 'and',
},
],
},
};
```
Keep `customFilters` present whenever restoring its conditions. Unknown operation IDs cannot be evaluated and are not a portable replacement for registering the predicate.
## Use the operator in Filter AST
Pro accepts an application-defined AST operator only when the same ID is registered in `customFilters`. The condition value must also be JSON-safe:
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
grid.plugins = [AdvanceFilterPlugin];
grid.filter = {
customFilters: {
endsWithCode: {
columnFilterType: 'string',
name: 'Ends with code',
func: endsWithCode,
},
},
};
const plugins = await grid.getPlugins();
const filter = plugins.find(
(plugin) => plugin instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin;
await filter.setFilterAst({
type: 'condition',
field: 'sku',
operator: 'endsWithCode',
valueType: 'string',
value: '-EU',
});
```
Registering the predicate supplies both validation and execution for that custom AST operator. Without the registration, `setFilterAst()` rejects the tree as containing an unknown operator and leaves the previous filter active.
## Predicate or structured filter type?
| Requirement | Extension point |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| Add one Boolean row test using the standard condition UI | `customFilters` predicate |
| Normalize stored data for several operators | Column `cellParser` |
| Add a small input, date, or custom field beside one condition | `func.extra` |
| Build a purpose-specific Pro popup body that owns several operators or a structured JSON value | `StructuredFilterType` plus matching `customFilters` predicates |
| Load the filtered dataset from a backend | Cancel `beforefilterapply` and reload `source` |
A custom structured type does not replace its evaluator. Its `operatorIds` define the UI-owned conditions, while matching `customFilters` entries define how those conditions evaluate. See [Custom Structured Filter Types](/guides/data-filter/structured-filter-types/) for the registry and commit contract.
## Keep asynchronous work outside predicates
Predicates are synchronous and may run once per row for every active condition. Keep them pure and fast: avoid requests, DOM work, shared-state mutation, and large allocations. Declaring a predicate `async` returns a `Promise`, not a deferred Boolean result, and therefore does not implement asynchronous filtering.
When the backend owns filtering, cancel local evaluation synchronously in `beforefilterapply`, serialize the requested state, and let the application's data controller replace the source:
```ts
let latestRequest = 0;
grid.addEventListener('beforefilterapply', (event) => {
event.preventDefault();
const request = ++latestRequest;
const detail = (
event as CustomEvent<{ filterItems: Record }>
).detail;
void fetch('/api/orders/filter', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(detail.filterItems),
})
.then((response) => response.json())
.then((rows) => {
if (request === latestRequest) grid.source = rows;
});
});
```
Production controllers should also cancel stale requests and guard source replacement from triggering the same remote request again. For Pro, prefer the canonical `FilterAst` payload when the query can contain cross-column OR, nested groups, or NOT. See [Filter Events](/guides/data-filter/filter-events/) for a guarded remote example.
The direct `JSON.stringify()` above is appropriate for this scalar custom value. Compatible filter state from other filter families can contain values such as `Set`; use the persistence guidance in [Filter State and Presets](/guides/data-filter/filter-state/) before treating arbitrary `multiFilterItems` as a wire payload.
## Common mistakes
- Registering the operator under `customFilters` but using a different `columnFilterType` from the column's `filter` value.
- Forgetting `func.extra`, then expecting the standard popup to render an input.
- Returning a truthy `Promise` from an `async` predicate instead of handling remote work through `beforefilterapply`.
- Filtering formatted source strings in every predicate instead of normalizing once with `cellParser`.
- Mutating rows, filter state, or external state from a predicate that can run many times.
- Restoring a custom `multiFilterItems` or `FilterAst` operator without registering its predicate first.
- Using a custom renderer for a complete multi-operator popup experience; use a structured filter type for that scope.
- Putting `Date`, `Set`, functions, cycles, or non-finite numbers in a public `FilterAst` value; AST input must be JSON-safe.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom Structured Filter Types](/guides/data-filter/structured-filter-types/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Core filtering guide](https://rv-grid.com/guide/filters#create-a-custom-filter)
---
# Canonical Filter AST
URL: https://pro.rv-grid.com/guides/data-filter/filter-ast/
Source: src/content/docs/guides/data-filter/filter-ast.mdx
Description: Build, validate, edit, persist, and transport one canonical RevoGrid Pro filter tree.
Surface: grid
`FilterAst` is RevoGrid Pro's validated, JSON-safe Boolean filter tree. Use the same tree for local row evaluation, the grouped visual editor, saved views, and remote requests.
New to ASTs? Start with [What Is a Filter AST?](/guides/data-filter/what-is-filter-ast/) for a plain-language explanation, business use cases, and a smaller first example. This page is the complete technical contract.
> **Choose the simplest state model that fits.** Use `multiFilterItems` for ordinary per-column filtering and existing column controls. Use `FilterAst` for cross-column `OR`, nested groups, `NOT`, canonical saved views, or a transport-safe remote query. Canonical execution requires `AdvanceFilterPlugin`; Core `FilterPlugin` remains the right choice for ordinary Core filtering.
## Try the complete lifecycle
Start with **Nested routing**, inspect the effective tree and transport envelope, then type a quick search and apply another preset with **Preserve quick search** off and on. **Projectable** demonstrates a tree that ordinary column controls can reopen. **Try invalid AST** proves that validation leaves the active tree and visible rows unchanged. Open any column filter and select **Groups** to edit the canonical tree visually.
## Register the Pro plugin
```ts
import { AdvanceFilterPlugin, type FilterAst } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.filter = { groupedFilter: {} }; // optional Groups launcher
grid.columns = columns;
grid.source = rows;
const plugin = (await grid.getPlugins()).find(
(item) => item instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin;
```
Retrieve the runtime plugin from `await grid.getPlugins()`. Do not attach application callbacks or custom methods to the grid element.
## Build a tree
A condition is the smallest tree:
```ts
const openTickets: FilterAst = {
type: 'condition',
field: 'status',
operator: 'equal',
valueType: 'string',
value: 'Open',
};
```
Join children with `and` or `or`, then negate any subtree with `not`:
```ts
const routingRule: FilterAst = {
type: 'group',
operator: 'or',
children: [
{
type: 'group',
operator: 'and',
children: [
openTickets,
{
type: 'condition',
field: 'priority',
operator: 'greaterThanOrEqual',
valueType: 'number',
value: 3,
},
],
},
{
type: 'not',
child: {
type: 'condition',
field: 'vip',
operator: 'isTrue',
valueType: 'boolean',
},
},
],
};
```
Groups must contain at least one child. Evaluation short-circuits: `and` stops on the first false child, and `or` stops on the first true child.
## Node and value contract
| Node | Required shape | Meaning |
| ----------- | ---------------------------------------------- | ---------------------------------------------------------------------- |
| `condition` | `{ type, field, operator, valueType, value? }` | Evaluates one field. `field` accepts a string or numeric `ColumnProp`. |
| `group` | `{ type, operator: 'and' \| 'or', children }` | Joins one or more subtrees. |
| `not` | `{ type, child }` | Negates exactly one subtree. |
`valueType` is one of `string`, `number`, `boolean`, `date`, `datetime`, `array`, or `unknown`. It describes the operand representation; it does not cast source values. Validation enforces scalar/date/datetime representations directly, while `array` and `unknown` rely on the selected operator's shape rules plus JSON safety. Values may contain only finite JSON primitives, arrays, and plain objects. Public input rejects functions, `undefined` operands, `Date`, `Set`, cycles, non-plain objects, and `NaN`/infinities.
- Date operands use `YYYY-MM-DD`.
- Ordinary datetime operands use UTC ISO strings ending in `Z`, such as `2026-09-03T08:30:00Z`.
- Use `unknown` for an opaque, JSON-safe structured operand. It does not disable JSON validation.
- Omit `evaluationMode` in application-authored canonical state. `selectionExclusion`, `dateObject`, `coercedNumericRange`, and `localTemporal` are compatibility metadata emitted when older UI/config models need evaluator-specific behavior.
When `field` matches a grid column, the local evaluator uses the column's parsed cell value. A field without a column reads the raw row property, which makes hidden model fields usable without adding hidden grid columns.
## Built-in operator reference
Every ID below is exported through `CANONICAL_FILTER_OPERATORS`.
| Family | Operators | `valueType` and operand |
| ---------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Text and equality | `equal`, `notEqual`, `beginsWith`, `contains`, `quickContains`, `notContains` | Usually `string`; one required JSON value matching the declared type. Equality may use another declared scalar type. |
| Ordered and numeric | `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual` | Usually `number`; one required finite number. |
| Range | `between` | Usually `number`; a required two-item array. Numeric row values are strict in canonical mode. |
| Membership | `in`, `notIn` | `array`; a required array. An empty array is structurally valid. |
| Blank, Boolean, array | `isBlank`, `isNotBlank`, `isTrue`, `isFalse`, `isEmptyArray`, `isNotEmptyArray` | No operand. Keep the field's semantic `valueType`, but omit `value`. |
| Explicit date/datetime | `dateEquals`, `dateBefore`, `dateAfter`, `dateOnOrBefore`, `dateOnOrAfter`, `dateNotEqual` | `date` or `datetime`; one required date/datetime string. |
| Date range | `dateBetween` | `date` or `datetime`; a required two-item array of valid date/datetime strings. |
| Relative day/window | `today`, `yesterday`, `last7Days`, `next30Days` | `date` or `datetime`; omit `value`. |
| Relative week/month | `thisWeek`, `lastWeek`, `nextWeek`, `thisMonth`, `lastMonth` | `date` or `datetime`; omit `value`. |
| Relative quarter/year | `thisQuarter`, `nextQuarter`, `previousQuarter`, `thisYear`, `nextYear`, `previousYear` | `date` or `datetime`; omit `value`. |
| Fiscal quarter | `thisFiscalQuarter`, `nextFiscalQuarter`, `previousFiscalQuarter` | `date` or `datetime`; omit `value`. |
| Fiscal year | `thisFiscalYear`, `nextFiscalYear`, `previousFiscalYear` | `date` or `datetime`; omit `value`. |
Important operator boundaries:
- Valueless operators must omit `value`. Even `value: ''` is rejected.
- Canonical `in`/`notIn` use strict `Object.is` membership; an array cell matches when any member matches. `selectionExclusion` is a normalized, exclusion-oriented compatibility mode and is not the same contract as hand-authored canonical `notIn`.
- Numeric `between` accepts only numeric row values in canonical mode. Blank-like values and numeric strings are not numbers. Compatibility state may carry `coercedNumericRange` to preserve older slider behavior.
- `quickContains` is the canonical form of global quick search and retains quick-filter string coercion. Use ordinary `contains` for Core text-filter semantics.
- Relative operators use one `now` captured for the whole apply operation plus the resolved IANA timezone and fiscal policy. See [Date Filter](/guides/data-filter/filter-types/date-filter/) and [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) for temporal policy.
- A custom or structured operator ID is valid only when the resolved plugin configuration registers its evaluator. Its operand must still be JSON-safe.
## Apply, read, initialize, and clear
```ts
await plugin.setFilterAst(routingRule);
const effective = plugin.getFilterAst(); // defensive clone
await plugin.setFilterAst(undefined); // clear canonical state
```
`setFilterAst()` validates first and replaces the tree atomically. `getFilterAst()` returns a defensive clone of the effective tree, including an applied quick-filter branch. Mutating either the input object after application or a returned object does not update grid state; construct a replacement and call `setFilterAst()` again.
For initial state, assign the AST through `grid.filter`:
```ts
grid.filter = {
filterAst: routingRule,
multiFilterItems: previousColumnState,
collection: legacyCollection,
};
```
When `filterAst`, `multiFilterItems`, and `collection` coexist, `filterAst` wins.
## Projectable and canonical-only trees
`projectable` means “can be represented as `MultiFilterItem` column controls,” not “valid.” A root `and` with independent single-field clauses can usually project:
```json
{
"type": "group",
"operator": "and",
"children": [
{
"type": "condition",
"field": "status",
"operator": "equal",
"valueType": "string",
"value": "Open"
},
{
"type": "condition",
"field": "priority",
"operator": "greaterThan",
"valueType": "number",
"value": 2
}
]
}
```
A cross-field `or`, `not`, or unsupported shape remains canonical-only:
```json
{
"type": "group",
"operator": "or",
"children": [
{
"type": "condition",
"field": "status",
"operator": "equal",
"valueType": "string",
"value": "Open"
},
{
"type": "not",
"child": {
"type": "condition",
"field": "vip",
"operator": "isTrue",
"valueType": "boolean"
}
}
]
}
```
Both trees are locally executable and transport-safe. Only the first can round-trip through ordinary independent column controls.
## Enable the visual grouped editor
```ts
grid.filter = { groupedFilter: {} };
```
`groupedFilter` opts the filter popup into a **Groups** launcher. The editor supports nested groups, `NOT`, rule reordering, rule/text mirrors, validation, preview counts, **Apply**, **Cancel**, and **Reset**. Edits are transactional: Apply replaces the active tree, Cancel leaves it untouched, and Reset clears the draft until applied.
Local preview counts stop at 25,000 eligible rows. For partial or remote data, never present the loaded page as the full dataset:
```ts
grid.filter = {
groupedFilter: {
remote: true,
preview: async ({ ast, signal }) => {
const response = await fetch('/tickets/filter-preview', {
method: 'POST',
signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ filter: ast }),
});
return response.json() as Promise<{ matching: number; total: number }>;
},
},
};
```
The callback receives an `AbortSignal`; abort superseded requests and let abort errors propagate normally. `remote: true` without a callback displays a remote preview label rather than a misleading local total.
## Save, restore, and update by replacement
```ts
const saved = JSON.stringify(plugin.getFilterAst());
localStorage.setItem('ticket-filter', saved);
try {
const json = localStorage.getItem('ticket-filter');
await plugin.setFilterAst(json ? JSON.parse(json) : undefined);
} catch (error) {
console.error('The saved filter is invalid.', error);
}
```
To update one part, clone/build the next tree and reapply it:
```ts
const current = plugin.getFilterAst();
if (current?.type === 'group') {
await plugin.setFilterAst({
...current,
children: [...current.children, anotherCondition],
});
}
```
Do not mutate applied or returned objects. Compatibility conversion helpers preserve today's existing filter behavior, but they are not a guarantee that every future Pro operator will losslessly round-trip through an older saved-view schema. Persist the canonical tree you actually apply.
## Compose quick search
```ts
grid.quickFilter = { text: 'timeout', debounceMs: 0 };
await plugin.setFilterAst(routingRule);
// Default: clears the current or pending quick filter.
grid.quickFilter = { text: 'timeout', debounceMs: 0 };
await plugin.setFilterAst(routingRule, { preserveQuickFilter: true });
// Effective tree is routingRule AND the generated quickContains branch.
```
`getFilterAst()` returns that effective composition. Preserve mode uses the latest applied or pending `grid.quickFilter` value.
## Events and remote ownership
`filterastchange` fires immediately before the existing cancelable `beforefilterapply` event. Its detail contains `{ filterAst, executionContext, origin, projectable }`; `origin` is `config`, `api`, `ui`, `quickFilter`, or `clear`.
```ts
import type {
FilterAstChangeEventDetail,
FilterExecutionContext,
} from '@revolist/revogrid-pro';
type FilterRequest = {
filter: FilterAstChangeEventDetail['filterAst'];
context: FilterExecutionContext;
};
let request: FilterRequest | undefined;
grid.addEventListener('filterastchange', (event) => {
const { filterAst, executionContext, origin, projectable } = event.detail;
request = { filter: filterAst, context: executionContext };
console.log({ origin, projectable, request });
});
grid.addEventListener('beforefilterapply', (event) => {
// This event carries matching filterAst/executionContext semantic inputs.
// Cancel when the remote datasource owns filtering and row replacement.
event.preventDefault();
void fetch('/tickets/search', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
});
```
`executionContext` contains one ISO `now`, an IANA `timeZone`, and JSON-safe resolved blank semantics per column. A non-serializable custom `isBlank` callback appears only as `customEvaluator: true`; the backend must implement an equivalent policy. RevoGrid intentionally does not compile AST to SQL, REST, or GraphQL—that translation belongs to the application/backend boundary.
## Atomic validation and diagnostics
Invalid input rejects without partially updating rows or state:
```ts
grid.addEventListener('filterasterror', (event) => {
const { attemptedAst, diagnostics, origin } = event.detail;
console.table(diagnostics); // [{ path, code, message }]
reportInvalidSavedView({ attemptedAst, diagnostics, origin });
});
try {
await plugin.setFilterAst({
type: 'group',
operator: 'and',
children: [],
} as FilterAst);
} catch {
// Previous AST and visible rows are still active.
}
```
Paths use JSONPath-like locations such as `$.children[0].value`.
| Code | Cause |
| ------------------------ | ------------------------------------------------------------------------- |
| `invalid-node` | A node is not an object. |
| `max-depth` | Nesting exceeds 100 levels. |
| `cycle` | The AST node graph contains a cycle. |
| `invalid-group-operator` | A group operator is not `and` or `or`. |
| `empty-group` | A group has no children. |
| `missing-child` | A `not` node has no child. |
| `invalid-discriminator` | `type` is not `condition`, `group`, or `not`. |
| `invalid-field` | `field` is not a string or number. |
| `invalid-operator` | `operator` is missing or empty. |
| `unknown-operator` | No built-in or registered evaluator matches the ID. |
| `invalid-value-type` | `valueType` is not supported. |
| `unexpected-value` | A valueless operator received `value`. |
| `missing-value` | A value-taking operator omitted `value`. |
| `invalid-array-value` | A range is not a two-item array, or membership is not an array. |
| `value-type-mismatch` | The operand does not match its declared type. |
| `non-finite-number` | A number is `NaN` or infinite. |
| `non-json-value` | A value contains a function, `undefined`, or another non-JSON value. |
| `value-cycle` | An operand array/object contains a cycle. |
| `non-plain-object` | An operand is a class instance, `Date`, `Set`, or other non-plain object. |
Registered structured filter types may also return `invalid-structured-value` when their own operand validator rejects a value.
## Parsed fields, blanks, dates, and custom operators
Parsed columns evaluate their parsed cell value; hidden/raw model fields evaluate the raw property. Blank operators additionally preserve Core's source identity, property-presence check, and resolved grid/column blank policy. If local policy uses `blankSemantics.isBlank`, remote code sees `customEvaluator: true` and must reproduce it.
Relative time uses the operation's captured clock rather than calling `new Date()` for every row. Configure timezones, week starts, and fiscal periods through the [Date Filter](/guides/data-filter/filter-types/date-filter/) and [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) settings.
Register an evaluator before applying its canonical ID:
```ts
grid.filter = {
customFilters: {
riskAtLeast: {
columnFilterType: 'number',
name: 'Risk at least',
func: (value, operand) => {
const minimum = Number((operand as { minimum: number }).minimum);
return typeof value === 'number' && value >= minimum;
},
},
},
};
await plugin.setFilterAst({
type: 'condition',
field: 'internalRisk', // not a displayed column
operator: 'riskAtLeast',
valueType: 'unknown',
value: { minimum: 7 },
});
```
Custom predicates run synchronously in the filtering hot path. Keep them pure: avoid DOM access, requests, mutation, and avoidable allocation. Group ordering matters for short-circuit performance, so put cheap/selective clauses early when practical. A remote or virtual datasource must evaluate the canonical request against the whole backend dataset—not only the page currently loaded in the browser.
## Common mistakes
- Using `FilterAst` for a simple column filter that `multiFilterItems` already represents.
- Supplying `value: ''` to a valueless operator instead of omitting `value`.
- Expecting `valueType` to cast source data.
- Sending `Date`, `Set`, `undefined`, non-finite numbers, class instances, or cyclic objects.
- Treating `projectable: false` as invalid instead of canonical-only.
- Mutating the original or returned AST instead of applying a replacement.
- Forgetting that `setFilterAst()` clears quick search unless preservation is requested.
- Evaluating only the currently loaded page for a remote dataset.
- Translating relative dates remotely without the event's exact execution context.
Continue with [What Is a Filter AST?](/guides/data-filter/what-is-filter-ast/) for the conceptual and business overview, [Filtering Quick Start](/guides/data-filter/filter-basics/) for column-state basics, [Advanced Filtering Overview](/guides/data-filter/filter-showcase/) for the wider Pro filtering surface, [Filter State and Presets](/guides/data-filter/filter-state/) for view persistence, [Filter Events](/guides/data-filter/filter-events/) for lifecycle integration, and [Custom Filter Operators](/guides/data-filter/custom-filter-operators/) for custom UI and evaluator patterns.
---
# Filter AST Side Panel
URL: https://pro.rv-grid.com/guides/data-filter/filter-ast-side-panel/
Source: src/content/docs/guides/data-filter/filter-ast-side-panel.mdx
Description: Mount RevoGrid Pro's canonical grouped filter editor in an application side panel.
Surface: grid
The grouped Filter AST editor is not limited to a column popup. Mount the same editor in a persistent side panel, drawer, or inspector and keep the grid plugin responsible for configured fields and operators, validation, preview, and applying the canonical tree.
Resolve the installed plugin after the grid initializes, mount the editor into a connected host, and destroy the returned handle with your framework component:
```ts
const plugin = (await grid.getPlugins()).find(
item => item instanceof AdvanceFilterPlugin,
);
const editor = plugin.mountFilterAstEditor(editorHost, {
preset: 'fixed-list',
conditionSlots: initialAst.children,
});
// Component teardown
editor.destroy();
```
When the mounted editor is the only filtering surface, set
`filter.columnFilterButton` to `false`. This removes the compact button and its
hover/focus layout reservation from every column header while leaving filtering,
the active AST, and the mounted editor enabled.
The `fixed-list` preset accepts a flat root **AND** tree of predeclared conditions. Pass those definitions as `conditionSlots` so their field and predicate remain visible after clearing. Fields stay fixed, while each predicate badge can select another operator supported by that column. Each active row has a compact, accessible clear action. Clearing removes its value from the draft and omits the condition from the canonical AST; entering or selecting a new valid value applies it again. A cleared valueless predicate, such as “VIP is false,” makes its predicate badge the explicit Apply action. **Clear all** clears every slot, applies `undefined`, and restores every grid row without removing the predefined controls.
Choose reusable value controls in the column configuration. The example renders
Priority’s compatible scalar numeric predicates with the shared single-value
slider without adding editor configuration at the mount site:
```ts
{
name: 'Priority',
prop: 'priority',
filter: 'number',
filterAstValueEditor: { kind: 'slider', min: 1, max: 5, step: 1 },
}
```
Omitted slider bounds are derived from the grid source. Use mount-level
`valueEditors` only when a particular surface needs a field- or
field/operator-specific override; those overrides take precedence over the
column default without changing the canonical predicate.
The preset keeps each field fixed as a plain anchor, presents each read-only operator phrase as a compact theme-aware badge, exposes only the configured value editor, omits placeholder boxes for operators that need no value, and applies effective changes after a short debounce. Builder chrome, grouping, add/remove/reorder actions, mode switch, and footer are intentionally absent. `editor.reset(ast)` remains the explicit synchronization boundary: it maps conditions in the supplied effective AST back onto the stable slots and clears omitted slots. Successful changes continue through the existing `filterastchange` event.
Use the default `builder` preset for the complete nested AND/OR/NOT rule builder, or `read-only` to inspect a tree without mutation. Nested configuration can override structure, capabilities, presentation, workflow, and interaction independently; incompatible flat trees are rejected rather than silently flattened.
This surface reuses the grouped editor's configured operators, selection sources, structured filter mounts, localization, validation, preview, and canonical apply path. It does not embed every complete classic per-column popup; reusable controls converge through the same lower-level filter contracts.
The mounted editor belongs to its application host and inherits the public `--rv-filter-ast-editor-*` variables defined there. Use `surface-bg`, `surface-color`, `control-bg`, `text-color`, and the corresponding `control-border`, `divider`, `active-*`, or `muted-color` variables to align it with the surrounding application theme. Unset variables use the editor's standalone defaults.
For the underlying model and business case, read [What Is a Filter AST?](/guides/data-filter/what-is-filter-ast/). For the complete API and operator contract, continue with [Canonical Filter AST](/guides/data-filter/filter-ast/).
---
# Filter Badges
URL: https://pro.rv-grid.com/guides/data-filter/filter-badges/
Source: src/content/docs/guides/data-filter/filter-badges.mdx
Description: Show synchronized active-filter badges, customize their labels and details, or replace their complete rendering.
Surface: grid
Filter badges give users an always-visible summary of the active filter model. `AdvanceFilterPlugin` owns the badge list, refreshes it when filters change, and routes remove and clear actions through the same filtering workflow as the popup and programmatic APIs.
## Quick start
Register `AdvanceFilterPlugin`, then enable its plugin-owned badge list through the grid property:
```typescript
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.filter = true;
grid.filterBadges = true;
```
The plugin mounts the list in the grid's header slot. Set `grid.filterBadges = false` to remove it. You can also assign an options object instead of `true`:
```typescript
grid.filterBadges = {
ariaLabel: 'Order filters',
emptyLabel: 'All orders are visible',
className: 'order-filter-list',
badgeClassName: 'order-filter-badge',
};
```
The list starts with **No active filters** by default. Each active condition becomes its own badge; multiple conditions for one column remain separate and include their `AND` or `OR` relation in the default label.
## Remove and clear filters
Every default badge has a remove button. Removing one condition preserves the remaining conditions and deletes the column entry when its last condition is removed. The badge list stays synchronized even when `beforefilterapply` is canceled for remote filtering.
The default renderer does not add a **Clear all** button. To clear filters programmatically, use the active plugin:
```typescript
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin | undefined;
await filterPlugin?.clearFiltering();
```
A complete custom renderer also receives a `clear()` action. Its `items` expose individual `remove()` actions:
```typescript
grid.filterBadges = {
render: ({ items, clear }) => {
const region = document.createElement('section');
region.setAttribute('aria-label', 'Active filters');
const list = document.createElement('div');
list.setAttribute('role', 'list');
for (const item of items) {
const entry = document.createElement('div');
entry.setAttribute('role', 'listitem');
const removeButton = document.createElement('button');
removeButton.type = 'button';
removeButton.textContent = `${item.label} — remove`;
removeButton.addEventListener('click', () => void item.remove());
entry.append(removeButton);
list.append(entry);
}
const clearButton = document.createElement('button');
clearButton.type = 'button';
clearButton.textContent = 'Clear all filters';
clearButton.disabled = items.length === 0;
clearButton.addEventListener('click', () => void clear());
region.append(list, clearButton);
return region;
},
};
```
## Default labels and details
Default labels use the column name, localized operator name, and a compact value summary. Selection filters report the number of excluded values, ranges show their endpoints, and structured filter types can supply their own compact summaries.
When a condition has a longer explanation, the badge includes an info button. It toggles a plain-text tooltip, exposes `aria-controls` and `aria-expanded`, and closes on Escape. Unknown object values are summarized without exposing transport JSON.
Use `formatLabel` and `formatDetails` when the default badge structure is correct but its text is application-specific:
```typescript
grid.filterBadges = {
formatLabel: ({ column, prop, operatorName, filter, index }) => {
const name = column?.name ?? String(prop);
const relation = index ? `${filter.relation?.toUpperCase() ?? 'AND'} ` : '';
return `${name}: ${relation}${operatorName}`;
},
formatDetails: ({ filter }) =>
`Applied value: ${String(filter.value ?? 'none')}`,
removeAriaLabel: (item) => `Remove condition ${item.label}`,
detailsAriaLabel: (item) => `Explain condition ${item.label}`,
};
```
Both format callbacks receive `prop`, `filter`, the condition `index`, the matching `column` when available, and `operatorName`. `formatDetails` should return an empty string to suppress the info control; returning `undefined` allows the built-in details to be used.
## Accessibility and styling options
The default renderer provides these semantics:
- The root has `aria-label="Active filters"` and `aria-live="polite"`.
- An empty list uses `role="status"`; an active list uses `role="list"` with `role="listitem"` badges.
- Remove buttons default to `Remove {label} filter`.
- Details buttons default to `Show details for {label}`.
Customize the root label with `ariaLabel`, the empty message with `emptyLabel`, and the two button labels with `removeAriaLabel` and `detailsAriaLabel`. Add classes without replacing the built-in styles through `className`, `badgeClassName`, `removeButtonClassName`, and `emptyClassName`.
The default shell owns these semantics. A complete `render` callback replaces the list and releases its managed `role`, so your renderer must provide suitable roles, labels, focus behavior, and controls.
## Customize badge content
`renderBadge` replaces only the label content inside the default badge shell. The plugin still renders the list semantics, optional details control, and remove button:
```typescript
grid.filterBadges = {
renderBadge: ({ item }) => {
const strong = document.createElement('strong');
strong.textContent = item.label;
return strong;
},
};
```
Use `renderEmpty` to replace only the content inside the default empty-state element:
```typescript
grid.filterBadges = {
renderEmpty: () => {
const message = document.createElement('span');
message.textContent = 'No filters — showing every row';
return message;
},
};
```
`renderBadge`, `renderEmpty`, and `render` can return DOM nodes, a `DocumentFragment`, numbers, or nested arrays of supported values. Returned strings are always inserted as text, never parsed as HTML:
```typescript
grid.filterBadges = {
renderBadge: ({ item }) => [item.label, 'text, not markup '],
};
```
To render HTML or Markdown, sanitize it with your application's renderer and return the resulting DOM nodes or fragment. RevoGrid does not parse renderer strings as markup.
## Observe badge-model changes
`onChange` runs after each badge refresh, including the initial render. It receives the current read-only badge items:
```typescript
grid.filterBadges = {
onChange: (items) => {
activeFilterCount.textContent = String(items.length);
},
};
```
Each item contains a render key, label, optional details, source filter context, and `remove()` action. Treat the array and items as read-only; update filtering through the provided actions or the filter plugin APIs.
## Options reference
| Option | Purpose |
| ----------------------- | --------------------------------------------------------------------------------- |
| `className` | Adds classes to the plugin-owned root. |
| `badgeClassName` | Adds classes to each default badge. |
| `removeButtonClassName` | Adds classes to each default remove button. |
| `emptyClassName` | Adds classes to the default empty-state element. |
| `ariaLabel` | Labels the live badge region. |
| `emptyLabel` | Replaces the default **No active filters** message. |
| `removeAriaLabel` | Produces the accessible label for a remove button. |
| `detailsAriaLabel` | Produces the accessible label for a details button. |
| `formatLabel` | Produces the badge's plain-text label. |
| `formatDetails` | Produces the complete plain-text details explanation. |
| `renderBadge` | Replaces label content while retaining the default shell and actions. |
| `renderEmpty` | Replaces content inside the default empty-state shell. |
| `render` | Replaces the complete list; use `item.remove()` and `clear()` to retain behavior. |
| `onChange` | Observes the current badge items after every refresh. |
## Common mistakes
- Registering `filterBadges` without `AdvanceFilterPlugin`. The property is implemented and managed by that plugin.
- Expecting a string returned by a render callback to become HTML. Strings are intentionally text-safe; return trusted DOM nodes for rich content.
- Using full `render` without wiring `item.remove()` and `clear()`. Replacing the complete UI also makes your application responsible for its controls.
- Assuming the default renderer includes a clear-all action. It provides per-condition removal; add a custom control or call `clearFiltering()`.
- Omitting accessibility from a full renderer. Unlike `renderBadge` and `renderEmpty`, full `render` replaces the list structure and its managed role.
- Mutating the items received by `onChange`. Use the filter APIs so the grid, remote filtering, and badges stay synchronized.
- Treating badge actions as an editor for every canonical AST. Badges can summarize a grouped AST that cannot project to `MultiFilterItem`, but per-condition removal is available only for projectable state; update that tree with `setFilterAst()` or clear it with `clearFiltering()`.
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Events](/guides/data-filter/filter-events/)
---
# Quick Start
URL: https://pro.rv-grid.com/guides/data-filter/filter-basics/
Source: src/content/docs/guides/data-filter/filter-basics.mdx
Description: Enable RevoGrid filtering, choose text and number operators, set initial conditions, clear filters, and react to filter changes.
Surface: grid
Use this guide when you need the shortest path from an unfiltered grid to a useful filtering workflow. RevoGrid Core already includes text and number conditions. Add `AdvanceFilterPlugin` only when you need selection lists, sliders, dates, expressions, grouped logic, quick filtering, or the [structured filter types](/guides/data-filter/filter-types/overview/).
## Choose a filter model
Use the smallest model that expresses the product requirement:
| Need | Recommended model | Why |
| --- | --- | --- |
| Ordinary local column filters | `multiFilterItems` | The smallest Core-compatible model; columns combine with AND. |
| Existing Core or legacy remote integration | `multiFilterItems` | Keeps the familiar column-condition format. |
| Saved views, remote requests, cross-column OR, nested groups, or NOT | `FilterAst` | JSON-safe canonical tree with one portable meaning. |
For a new remote integration, start with `FilterAst` and the accompanying `executionContext`; do not build a new backend contract around runtime-only values in `multiFilterItems`. The [Filter State and Presets](/guides/data-filter/filter-state/) guide shows both shapes, and the [Canonical Filter AST](/guides/data-filter/filter-ast/) guide defines the remote contract.
## 1. Enable filtering
Set the grid-level `filter` property explicitly, then choose a filter family on each column that needs filtering:
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = [
{ prop: 'name', name: 'Name', filter: 'string' },
{ prop: 'status', name: 'Status', filter: 'string' },
{ prop: 'amount', name: 'Amount', filter: 'number' },
{ prop: 'internalId', name: 'Internal ID', filter: false },
];
grid.filter = true;
grid.source = [
{ name: 'Anna', status: 'Open', amount: 120 },
{ name: 'John', status: 'Pending', amount: 80 },
{ name: 'Steve', status: 'Closed', amount: 240 },
];
```
The grid-level property enables the filter plugin. It defaults to `true`; setting it explicitly makes application intent clear. The column-level property controls the button and available operators for that column. Do not initialize the grid with `filter = false` and expect a later `true` assignment to install the plugin dynamically.
| Column `filter` value | Use it for |
| --------------------- | -------------------------------------------- |
| `true` or `'string'` | Text values |
| `'number'` | Numeric comparisons |
| `false` | No filter button |
| `string[]` | Several Core, Pro, or custom filter families |
## 2. Choose an operator
Core string columns provide these saved operator IDs:
| Operator | Meaning |
| -------------------------- | ------------------------- |
| `contains` / `notContains` | Includes or excludes text |
| `begins` | Starts with text |
| `eq` / `notEq` | Equal or not equal |
| `empty` / `notEmpty` | Is blank or is not blank |
Core number columns provide `eqN`, `neqN`, `gt`, `gte`, `lt`, `lte`, `empty`, and `notEmpty`. Operator IDs are used in saved state, configuration, and event payloads; the popup shows readable labels such as **Contains**, **>=**, and **Is blank**.
## 3. Start with an applied filter
Use `multiFilterItems` for both single and multiple conditions. The following configuration shows rows whose status is Open and whose amount is from 100 through 500:
```ts
grid.filter = {
multiFilterItems: {
status: [{ id: 1, type: 'eq', value: 'Open', relation: 'and' }],
amount: [
{ id: 2, type: 'gte', value: 100, relation: 'and' },
{ id: 3, type: 'lte', value: 500, relation: 'and' },
],
},
};
```
Conditions inside one column can use `and` or `or`. Different columns combine with AND. Use the [Canonical Filter AST](/guides/data-filter/filter-ast/) when you need cross-column OR, nested groups, or NOT.
### Update conditions reactively without replacing configuration
The `filter` property is reactive. When the grid already has localization, Pro options, or custom filters, spread the current object into a new configuration before replacing its active conditions:
```ts
const currentFilter = typeof grid.filter === 'object' ? grid.filter : {};
grid.filter = {
...currentFilter,
multiFilterItems: {
amount: [
{ id: 1, type: 'gte', value: 100, relation: 'and' },
{ id: 2, type: 'lte', value: 500, relation: 'and' },
],
},
};
```
Assign a new object rather than mutating `grid.filter.multiFilterItems` in place; the new reference triggers the reactive update. Framework applications should use the equivalent state update. For a Pro tree with cross-column logic, use the same pattern with `filterAst` instead of `multiFilterItems`.
## 4. Apply only after confirmation
Filtering updates while the user edits by default. For a Save/Cancel workflow, disable dynamic filtering:
```ts
grid.filter = {
disableDynamicFiltering: true,
};
```
## 5. Read and clear the result
Read the currently visible models with `getVisibleSource()`:
```ts
const visibleRows = await grid.getVisibleSource();
```
Clear through the active plugin to preserve configuration such as localization, selection options, expressions, and structured type registrations:
```ts
import { FilterPlugin } from '@revolist/revogrid';
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof FilterPlugin,
);
await filterPlugin?.clearFiltering();
```
`AdvanceFilterPlugin` extends `FilterPlugin`, so the same lookup works for Pro and also clears its quick-filter and canonical AST state. If your application controls `grid.filter` by assignment instead, recreate the complete configuration with `multiFilterItems: {}`; assigning only the empty model would replace the other options.
## 6. React to changes
Use `afterfilterapply` for UI that depends on the filtered result. Use the cancelable `beforefilterapply` event when the application needs to inspect, rewrite, or delegate a request to a backend.
```ts
grid.addEventListener('afterfilterapply', async (event) => {
const visibleRows = await grid.getVisibleSource();
console.log(event.detail.multiFilterItems, visibleRows.length);
});
grid.addEventListener('beforefilterapply', (event) => {
console.log('About to apply', event.detail.filterItems);
});
```
## 7. Add Pro filter controls
Register `AdvanceFilterPlugin`, then add the filter type to each matching column:
```ts
import {
AdvanceFilterPlugin,
FIlTER_SELECTION,
FIlTER_SLIDER,
FILTER_DATE,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'status', filter: ['string', FIlTER_SELECTION] },
{ prop: 'amount', filter: ['number', FIlTER_SLIDER] },
{ prop: 'createdAt', filter: [FILTER_DATE] },
];
grid.filter = true;
```
## Customize the experience
Each concern has a focused customization boundary; combine only the ones your UI needs.
| Need | Configure or call |
| --- | --- |
| Application-specific operator | `filter.customFilters` |
| Purpose-built filter popup | `filter.structuredFilterTypes` |
| Server-backed selection values | `filter.selection.getItems(prop, { search, signal })` |
| Global toolbar search | `grid.quickFilter` |
| Visual nested-rule editor | `filter.groupedFilter` or `plugin.mountFilterAstEditor()` |
| Saved view or remote query | `plugin.getFilterAst()` and `plugin.setFilterAst()` |
Use the [Filter Events](/guides/data-filter/filter-events/) guide when the application owns remote loading. Its example covers canceling local evaluation, sending the canonical request, and accepting only the latest response.
Choose a focused guide next:
- [Text Filter](/guides/data-filter/filter-types/text-filter/) for contains, equality, and begins-with conditions
- [Number Filter](/guides/data-filter/filter-types/number-filter/) for comparisons and numeric ranges
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/) for checklists of values
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) for staged Apply/Cancel checklists
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/) for numeric ranges
- [Date Filter](/guides/data-filter/filter-types/date-filter/) for calendar-day rules
- [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) for exact instants
- [Boolean Filter](/guides/data-filter/filter-types/boolean-filter/) for strict Yes/No conditions
- [Array Filter](/guides/data-filter/filter-types/array-filter/) for empty and non-empty arrays
- [Structured Filter Types](/guides/data-filter/filter-types/overview/) for purpose-built popup controls
- [Quick Filter](/guides/data-filter/filter-types/quick-filter/) for multi-word search across columns
- [Filter Events](/guides/data-filter/filter-events/) for observation and remote delegation
- [Filter Badges](/guides/data-filter/filter-badges/) for synchronized active-filter summaries
- [Filter State and Presets](/guides/data-filter/filter-state/) for reusable and persisted views
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/) for application-specific predicates
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/) for quick filtering, badges, expressions, presets, and grouped logic
## Common mistakes
- Setting a column's `filter` but not enabling `grid.filter` or registering the Pro plugin.
- Using string operators such as `eq` on a numeric column; use `eqN` for Core numeric equality.
- Expecting `multiFilterItems` to express cross-column OR; use `FilterAst` for that shape.
- Filtering formatted display text without a `cellParser`; conditions evaluate the parsed value when a parser exists.
- Treating `0` or `false` as blank. The default blank policy does not.
## Related guides
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Clear Filtering demo](https://demo.rv-grid.com/clear-filtering/ts)
- [Core filtering guide](https://rv-grid.com/guide/filters)
---
# Filter Events
URL: https://pro.rv-grid.com/guides/data-filter/filter-events/
Source: src/content/docs/guides/data-filter/filter-events.mdx
Description: Observe, modify, cancel, or delegate Core and Pro filtering through runtime events.
Surface: grid
Filter events let application code react after visible rows change, adjust the rows that will be trimmed, or cancel local evaluation and delegate a request to a backend.
## Local Core event sequence
A normal Core filter run emits these filter-plugin events in order:
| Order | Event | Cancelable | Important runtime detail |
| ----- | --------------------- | ---------- | ------------------------------------------------------------------- |
| 1 | `beforefilterapply` | Yes | `collection`, full `filterItems`, source, and columns |
| 2 | `beforefiltertrimmed` | Yes | Filter model plus physical `itemsToFilter` indexes |
| 3 | `afterfilterapply` | No | `multiFilterItems`, original source, and first-condition collection |
Canceling `beforefilterapply` stops evaluation. Canceling `beforefiltertrimmed` stops the calculated filter trim from being installed. `beforetrimmed` and `aftertrimmed` are general grid events and can also run for grouping, tree visibility, or application-owned trims.
The Core filter plugin writes its calculated trim through the data provider, so its normal apply path does not emit the public `beforetrimmed` and `aftertrimmed` element events. Those events wrap calls through the grid's general `setTrimmed()` method; do not include them when reasoning about the filter-plugin sequence.
`filterconfigchanged` is different: it reports a new grid filter configuration and is not a per-run completion event.
## Read visible rows after filtering
`afterfilterapply.detail.source` is the original local source, not the filtered result. Read the provider-backed visible models with `getVisibleSource()`:
```ts
grid.addEventListener('afterfilterapply', async (event) => {
const visibleRows = await grid.getVisibleSource();
const detail = (
event as CustomEvent<{
multiFilterItems: Record;
}>
).detail;
console.log('Active filters', detail.multiFilterItems);
console.log('Visible rows', visibleRows.length);
});
```
The cast is intentional. The runtime Core plugin emits `afterfilterapply`, but the generated Core element event map in some current packages does not declare that event yet, so TypeScript may otherwise infer only the base `Event` type. Applications can also augment `HTMLRevoGridElementEventMap` centrally with the runtime detail type.
## Modify the calculated trim
Use `beforefiltertrimmed` when the application needs final control over physical row indexes:
```ts
grid.addEventListener('beforefiltertrimmed', (event) => {
// Keep physical row 0 visible even when ordinary conditions reject it.
delete event.detail.itemsToFilter[0];
});
```
Use this hook sparingly. A custom filter predicate or canonical AST condition is easier to persist and transport when the requirement is ordinary business logic.
## Cancel and delegate to a backend
Prevent `beforefilterapply` to stop local trimming, then load rows from the server. Guard the source replacement so reapplying the same active model does not create a request loop:
```ts
let loadedSignature = '';
let requestId = 0;
grid.addEventListener('beforefilterapply', async (event) => {
const detail = (
event as CustomEvent<{
filterItems: Record;
filterAst?: unknown;
executionContext?: unknown;
}>
).detail;
const transportState = detail.filterAst
? {
filterAst: detail.filterAst,
executionContext: detail.executionContext,
}
: { filterItems: detail.filterItems };
const signature = JSON.stringify(transportState, (_key, value) =>
value instanceof Set ? [...value] : value,
);
if (signature === loadedSignature) return;
event.preventDefault();
const activeRequest = ++requestId;
const response = await fetch('/api/orders/filter', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: signature,
});
const rows = await response.json();
if (activeRequest !== requestId) return;
loadedSignature = signature;
grid.source = rows;
});
```
With `AdvanceFilterPlugin`, set `filter.external: true` when the backend always owns filtering. The plugin then prevents local trimming itself while still emitting the same `beforefilterapply` payload. Your handler only needs to send the model and install the latest response; it should not emit `afterfilterapply`, because that event remains a local-filter completion signal.
```ts
grid.filter = { external: true };
grid.addEventListener('beforefilterapply', async (event) => {
const { filterAst, executionContext } = event.detail;
grid.source = await loadOrders({ filterAst, executionContext });
});
```
In production, also handle request failures and abort stale network work. The Pro Pagination, Infinity Scroll, and Server-Side Grouping plugins already implement this delegation flow.
Prefer Pro's canonical `filterAst` for new remote integrations. Compatible `filterItems` can contain runtime values such as the selection filter's excluded-value `Set`; the fallback above converts sets to arrays, but applications should still define and validate their complete wire contract. See [Filter State and Presets](/guides/data-filter/filter-state/).
## Pro AST events
`AdvanceFilterPlugin` emits canonical events in addition to the Core lifecycle:
| Event | Detail | Use it for |
| ----------------- | -------------------------------------------------------- | ---------------------------------------------------------- |
| `filterastchange` | `filterAst`, `executionContext`, `origin`, `projectable` | Persisting or transporting the effective canonical tree |
| `filterasterror` | Attempted AST, diagnostics, and origin | Showing validation errors without applying an invalid tree |
For a local Pro run, `filterastchange` is emitted before `beforefilterapply`. Pro also adds `filterAst`, `executionContext`, and temporal transport context to the Core before/after event details at runtime. Origins distinguish `config`, `api`, `ui`, `quickFilter`, and `clear` changes.
```ts
grid.addEventListener('filterastchange', (event) => {
console.log(event.detail.origin, event.detail.filterAst);
});
grid.addEventListener('filterasterror', (event) => {
console.error(event.detail.diagnostics);
});
```
## Quick Filter events
Changing `grid.quickFilter` wraps the local filter run with two Pro events:
```text
beforequickfilterapply
→ filterastchange
→ beforefilterapply
→ beforefiltertrimmed
→ afterfilterapply
afterquickfilterapply
```
`beforequickfilterapply` is cancelable. Its detail and `afterquickfilterapply` contain the normalized `quickFilter`, source, and columns. Remote Pro data plugins cancel the local quick-filter run and emit the completion event after the latest remote response wins.
## Common mistakes
- Do not treat `aftertrimmed` as filter-only; other grid features also trim rows.
- Do not read the original `source` field as the filtered result; call `getVisibleSource()`.
- Do not perform a remote request without `preventDefault()` or local filtering will run too.
- Do not assign remote rows without a loop guard and stale-request protection.
- Do not mutate `beforequickfilterapply.detail.quickFilter` expecting it to replace the property value; observe it or cancel and assign a new `grid.quickFilter` value.
- Do not assume generated element typings expose every plugin-emitted runtime field. Narrow or augment event detail types where needed.
## Related guides
- [Quick Filter](/guides/data-filter/filter-types/quick-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Infinity Scroll](/guides/infinity-scroll/)
---
# Filter Expressions
URL: https://pro.rv-grid.com/guides/data-filter/filter-expression/
Source: src/content/docs/guides/data-filter/filter-expression.mdx
Description: Enable a current-column expression editor for RevoGrid Pro advanced filters.
The Pro advanced filter can expose an optional expression editor inside each filter popup. It is disabled by default. When enabled, the popup adds an **Expression** button and expandable editor panel at the bottom, after the regular filter controls. Users can type current-column filter logic as text; valid expressions apply live and compile into the grid's standard `multiFilterItems` model.
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'status', name: 'Status', filter: ['string', 'selection'] },
{ prop: 'score', name: 'Score', filter: ['number', 'slider'] },
{ prop: 'updated', name: 'Updated', filter: ['date'] },
];
grid.filter = {
expressions: {
enabled: true,
applyDebounceMs: 300,
buttonLabel: 'Expression',
placeholder: 'between 10 and 50\ncontains "north" AND not empty',
},
};
```
## Current-column scope
Expressions are scoped to the column whose filter popup is open. You can omit the column name:
```txt
between 2024-01-01 and 2024-12-31 AND after 2024-06-01
```
You can also include the current column prop or name for readability:
```txt
updated between 2024-01-01 and 2024-12-31 AND updated after 2024-06-01
```
References to another column are invalid and do not change active filters.
For application-defined cross-column `OR`, nested groups, and `NOT`, use the public [Canonical Filter AST](/guides/data-filter/filter-ast/). The popup editor remains deliberately scoped to its current column.
## Parsed cell values
Expressions evaluate the current column's parsed value when `cellParser` is defined, just like ordinary comparisons. Blank expression operators use the raw source value and property-presence context from Core. See [Blank Values](/guides/data-filter/blank-values/) for the source-preserving contract.
## Syntax
Use `AND`, `OR`, and parentheses to group expressions:
```txt
(contains "north" OR contains "west") AND not empty
```
String columns support:
```txt
empty
not empty
= "Active"
!= "Archived"
begins "A"
contains "north"
not contains "draft"
```
Array columns support strict, valueless length checks:
```txt
empty array
not empty array
is empty array
is not empty array
```
Number and slider columns support:
```txt
> 10
>= 25
< 100
<= 500
between 250 and 640.42
```
Date columns support:
```txt
equals 2024-06-01
before 2024-12-31
after 2024-01-01
on or before 2024-12-31
on or after 2024-01-01
not equal 2024-07-04
between 2024-01-01 and 2024-12-31
today
yesterday
last 7 days
this month
last month
this quarter
next quarter
previous quarter
this year
next year
previous year
```
Selection-style text supports:
```txt
is "Open"
in ("Open", "Pending")
not in ("Closed", "Archived")
```
Registered structured filters use the same lossless mirror without exposing their transport JSON.
Objects, arrays, strings, numbers, booleans, and blank values remain typed:
```txt
structured "arrayTagsMatch" {
mode: "none",
values: ["renewal", "priority"],
emptyOnly: false,
exact: false
}
```
The editor highlights keywords, operators, properties, strings, numbers, booleans, and invalid
tokens. The grouped filter's Text view uses the same token presentation; when a structured filter
provides `describeCondition`, that filter-owned readable description replaces its transport value.
Legacy saved expressions containing a quoted JSON payload remain accepted.
## Validation and applying
The editor validates while users type. Valid expressions apply after `applyDebounceMs`; invalid expressions show diagnostics and keep the previous valid filters unchanged. Press `Ctrl+Enter` or `Cmd+Enter` to apply immediately.
The expression panel does not replace the normal popup content. Selection lists, slider controls, date inputs, and condition controls remain visible while the expression panel is expanded.
Expression filters are stored as hidden Pro filter items, so reset, active header state, filter events, and server-side filter payload handling continue to use the standard `multiFilterItems` flow.
## Localization
Expression labels use the existing filter localization path:
```ts
grid.filter = {
expressions: true,
localization: {
captions: {
expressionButton: 'Expression',
expressionTitle: 'Filter expression',
expressionPlaceholder: 'contains "north"',
expressionApply: 'Apply',
expressionInvalid: 'Invalid expression',
},
},
};
```
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Blank Values](/guides/data-filter/blank-values/)
---
# Filter Header
URL: https://pro.rv-grid.com/guides/data-filter/filter-header/
Source: src/content/docs/guides/data-filter/filter-header.mdx
Description: Add accessible text inputs, range controls, popup triggers, and optional compact templates directly to column headers.
`FilterHeaderPlugin` places common filter controls directly below each column label. Use it when users need fast, always-visible filtering; the normal filter icon remains available for the complete advanced-filter popup.
## Quick start
Register `FilterHeaderPlugin`, then configure filter families on the columns. It installs `AdvanceFilterPlugin` automatically:
```typescript
import {
FilterHeaderPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [FilterHeaderPlugin];
grid.columns = [
{ name: 'Name', prop: 'name', filter: 'string' },
{ name: 'Status', prop: 'status', filter: ['selection'] },
{ name: 'Age', prop: 'age', filter: ['number', 'slider'] },
{ name: 'Tags', prop: 'tags', filter: ['array'] },
];
```
Text columns receive a debounced input. Popup-style and registered structured filters receive a compact trigger. Slider columns receive either an inline range control or a formatted range summary.
| Column filter family | Header control |
| -------------------- | ------------------------------------------------- |
| Text and number | Debounced inline input |
| Selection | Popup trigger with included-value summary |
| Date | Popup trigger with active-state indicator |
| Boolean | Popup trigger for the valueless Yes/No operators |
| Array | Popup trigger for empty/non-empty array operators |
| Slider | Inline range control or formatted range summary |
| Structured types | Popup trigger using the type's readable summary |
## Slider headers
A column containing the `slider` filter type renders the shared range slider directly in the header. The regular filter icon remains available for opening the full advanced-filter popup.
```ts
const columns = [{ name: 'Age', prop: 'age', filter: ['number', 'slider'] }];
```
Moving either handle applies the range immediately. Returning both handles to the full data range clears the slider filter.
## Popup trigger headers
For selection, date, boolean, and array popup filters, the header renders a popup trigger instead of a free text input. Clicking it opens the same advanced-filter popup available from the normal filter icon.
Selection triggers show `All` while no values are excluded. Opening a selection popup and closing it without changes keeps the header value as `All`; it does not expand to every selected item.
Customize that fallback through the standard advanced-filter localization captions:
```ts
const filter = {
localization: {
captions: {
selectionAll: 'Any value',
},
},
};
```
When values are unchecked in the selection popup, the trigger shows a compact selected/total badge such as `8/9`. This is the default presentation and requires no additional configuration. Hover the badge to see the full selected-value summary. By default, that summary includes the number of matching rows for each value; set `hideFilterHeaderCount` on the column to hide those row counts.
Date triggers become active when a date condition has a value. Boolean and array triggers become active when one of their valueless operators is applied. Array support requires the array family in the column's filter array:
```ts
grid.columns = [{ name: 'Tags', prop: 'tags', filter: ['array'] }];
```
The array popup offers **Is empty array** and **Is not empty array**. See [Array Filter](/guides/data-filter/filter-types/array-filter/) for their strict array-value behavior.
## Custom header presentation
Every popup-style filter uses the same `FilterHeaderTemplateFunc` contract. A column template has the highest priority:
```tsx
import type { FilterHeaderTemplateFunc } from '@revolist/revogrid-pro';
const compactStatusHeader: FilterHeaderTemplateFunc = (h, { presentation }) => (
{presentation.summary}
);
const columns = [{
name: 'Status',
prop: 'status',
filter: ['selection'],
filterHeaderTemplate: compactStatusHeader,
}];
```
The grid owns the actual button, focus behavior, tooltip, keyboard activation, and accessible name. A template only renders non-interactive visual content inside that button. Use `presentation.summary` for compact text and `presentation.details` for the full human-readable meaning. Returning `undefined` keeps the standard text or count fallback.
A custom registered structured type may provide the same template once for every column using that type:
```tsx
const temperatureFilter: StructuredFilterType = {
id: 'temperatureRange',
operatorIds: ['temperatureBetween'],
render: context => ,
describeCondition: ({ condition }) => ({
summary: `${condition.value.min}–${condition.value.max} °C`,
details: `Temperature from ${condition.value.min} to ${condition.value.max} °C`,
}),
headerControl: {
kind: 'popup',
template: (h, { presentation }) => (
{presentation.summary}
),
},
};
```
Use `kind: 'popup'` when the filter needs custom badge, star, or summary content inside the grid-owned trigger. Use a `headerControl` resolver with `kind: 'inline'` when the type needs an interactive control such as a range slider. The resolver receives the same controlled body context as the popup, including aggregates and `replaceConditions()`, while the grid owns the outer non-sorting interaction boundary.
`describeCondition` is the source of truth for filter badges, grouped-filter text, header labels, and accessibility text. Keep it data-independent: it receives the condition and column configuration, not grid rows. This makes compact presentation safe for remote and very large datasets. A column-level `filterHeaderTemplate` overrides a type-owned popup template.
The template props also expose the column, canonical conditions, selection compatibility values, and the complete `presentation` object. The former top-level `active` and `text` fields remain as compatibility aliases; new code should use `presentation`.
Localize the surrounding accessible text with the normal filter captions:
```ts
const filter = {
localization: {
captions: {
filterHeaderLabel: column => `Filter ${column.name ?? column.prop}`,
filterHeaderSelectionSummary: (selected, total, details) =>
`${selected} of ${total} selected${details ? `: ${details}` : ''}`,
},
},
};
```
## When to use the popup instead
Header controls prioritize quick changes and limited space. Keep the normal popup workflow for multiple conditions, expressions, Excel-style staged selection, grouped AST editing, or any control that needs explanatory content.
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/)
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/)
- [Array Filter](/guides/data-filter/filter-types/array-filter/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Overview
URL: https://pro.rv-grid.com/guides/data-filter/filter-showcase/
Source: src/content/docs/guides/data-filter/filter-showcase.mdx
Description: Combine RevoGrid Core column filters with Pro structured controls, selection, slider, date, expressions, presets, and quick-filter workflows.
RevoGrid filtering narrows the visible row set without replacing the original source. Core provides the filter model and standard text/number operators; RevoGrid Pro adds richer inputs around the same model.
New to filtering? Begin with the [Filtering Quick Start](/guides/data-filter/filter-basics/) to enable filters, choose text and number operators, apply initial conditions, listen for changes, and clear the result.
For a scenario-led tour, read [Advanced Data Grid Filtering for E-commerce and Project Management](https://rv-grid.com/blog/advanced-data-grid-filtering-ecommerce-project-management) to see how the same capabilities support customer segmentation, order operations, and project-delivery queues.
[Open the standalone Advanced Filtering showcase](https://demo.rv-grid.com/filter-showcase/ts) to compare the TypeScript, React, Vue, and Angular implementations.
## Core and Pro at a glance
| Capability | Core | Pro |
| -------------------------------------------------------------------------------------------------------------------------------- | ---- | ---------------------------------------------------- |
| Text and number operators | Yes | Uses the same operators |
| Multiple conditions per column | Yes | Enhanced popup and header workflows |
| Programmatic `multiFilterItems` | Yes | Used by selection, slider, date, and expressions too |
| Parsed values through `cellParser` | Yes | Shared by advanced filters |
| Canonical JSON-safe filter tree | — | `FilterAst` with nested `and`, `or`, and `not` |
| Global multi-word quick filter | — | `AdvanceFilterPlugin` |
| Selection and cascading option lists | — | Yes |
| Editable numeric range slider | — | Yes |
| Calendar and relative-date operators | — | Yes |
| Structured filter bodies for tokens, fuzzy search, regex, facets, histograms, ratings, timelines, matrices, booleans, and arrays | — | Yes |
| Inline header controls | — | `FilterHeaderPlugin` |
| Current-column expression editor | — | Optional |
| Pagination, infinite scroll, and server-side grouping adapters | — | Yes |
## Choose the right filtering workflow
Start with the smallest workflow that matches the user's task:
| User need | Start with | Add when needed |
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Text or numeric conditions in a popup | [Text](/guides/data-filter/filter-types/text-filter/) or [Number](/guides/data-filter/filter-types/number-filter/) filters | `AdvanceFilterPlugin` for richer controls and a canonical model |
| A checklist of known values | [Selection Filter](/guides/data-filter/filter-types/filter-selection/) | [Excel mode](/guides/data-filter/filter-types/filter-selection-excel-mode/) for staged Apply/Cancel; cascade for dependent lists |
| A numeric interval | [Slider Filter](/guides/data-filter/filter-types/filter-slider/) | Editable inputs or inline header controls |
| Calendar days or exact instants | [Date](/guides/data-filter/filter-types/date-filter/) or [Datetime](/guides/data-filter/filter-types/datetime-filter/) filter | Timezone, week, and fiscal-calendar policy |
| Fast controls inside headers | [Filter Header](/guides/data-filter/filter-header/) | Selection, slider, date, or boolean popup triggers |
| Current-column power-user syntax | [Filter Expressions](/guides/data-filter/filter-expression/) | Use AST instead for logic across columns |
| Cross-column OR, nested groups, NOT, or remote transport | [Canonical Filter AST](/guides/data-filter/filter-ast/) | The grouped editor for a visual workflow |
| A data-shaped popup control | [Structured Filter Types](/guides/data-filter/filter-types/overview/) | Register a custom type when the built-ins do not fit |
| Search words across several columns | [Quick Filter](/guides/data-filter/filter-types/quick-filter/) | Restrict columns or tune the debounce |
| Show removable active-filter summaries | [Filter Badges](/guides/data-filter/filter-badges/) | Custom labels, details, rendering, and clear actions |
| Save, restore, or package reusable views | [Filter State and Presets](/guides/data-filter/filter-state/) | Canonical `FilterAst` for JSON-safe nested queries |
| Add an application-specific row test | [Custom Filter Operators](/guides/data-filter/custom-filter-operators/) | Structured types for purpose-built popup bodies |
## How Filter v2 improves existing filtering
“Filter v2” is an informal name for the canonical filtering architecture in Pro's `AdvanceFilterPlugin`; it is not a separate `FilterV2` class or a replacement Core plugin. Existing text, number, selection, slider, date, expression, and quick filters continue to use their current column configuration and UI. The advanced plugin converts their state into a JSON-safe `FilterAst` automatically.
This gives existing filtering a consistent model for local evaluation, saved views, and remote requests. It also adds nested `and`/`or` groups, cross-column `or`, `not`, atomic validation, and a shared date/time execution context. Simple trees are projected back into `multiFilterItems`, so existing integrations remain compatible; applications only need to use the AST API when they need the more expressive tree model.
This is primarily an improvement in capability, validation, and transport consistency. It does not promise that ordinary column filters will execute faster. Simple filtering still participates in the existing Core trimming and large-source lifecycle. See [Canonical Filter AST](/guides/data-filter/filter-ast/) for the tree shape, API, grouped editor, and remote event payloads.
## Explore the three presets
The Order Explorer opens with the **High-value Europe** filters already applied, making the active chips and filtered result count immediately visible. Use **Clear All** to reveal all 1,000 deterministic orders. Its presets are ordinary `multiFilterItems` models, so they can also be saved as views or created by application code.
- **High-value Europe** combines a Region selection with a Total slider range.
- **Recent expedited** combines the relative `last7Days` date operator with the Expedited selection.
- **Review queue** includes selected Status values and adds `gte` plus `lte` conditions to Total.
The result count comes from `grid.getVisibleSource()` after filtering. The grid-synced badge component mirrors the current model; removing a badge updates the plugin's canonical filter state, while **Clear All** assigns an empty filter model.
The toolbar's global search uses the public `grid.quickFilter` property. Try `Lisbon pending`: each word can match a different visible column in the same row. Search is whitespace-normalized, case-insensitive, and uses parsed cell values. Every word must match somewhere in the row, and the result is ANDed with all active column filters.
Scroll horizontally through the expanded order schema to try all fourteen [structured filter types](/guides/data-filter/filter-types/overview/) in context. Order, Customer, SKU, Region, Status, Total, Rating, Margin Change, Order Date, Renewal Date, Created At, Activity Time, Verified, and Tags each expose the matching purpose-built body while continuing to serialize into the same condition model.
In the compatible `multiFilterItems` model, relations belong to conditions
within one column. Conditions for the same column can use `and` or `or`, while
different columns combine with AND. The expression editor is also
current-column only. Use `FilterAst` or its grouped editor for cross-column
OR, nested groups, and NOT.
## Configure the showcase building blocks
The showcase combines documented public features rather than introducing another filter API:
- Header controls come from [Filter Header](/guides/data-filter/filter-header/).
- Status and Region use [Selection Filter](/guides/data-filter/filter-types/filter-selection/) with [Selection Cascade](/guides/data-filter/filter-types/filter-selection-cascade/).
- Total combines the [Number Filter](/guides/data-filter/filter-types/number-filter/) and [Slider Filter](/guides/data-filter/filter-types/filter-slider/).
- Order Date uses the [Date Filter](/guides/data-filter/filter-types/date-filter/), while Created At uses [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/).
- The toolbar search uses [Quick Filter](/guides/data-filter/filter-types/quick-filter/).
- Active chips use [Filter Badges](/guides/data-filter/filter-badges/).
- Presets follow [Filter State and Presets](/guides/data-filter/filter-state/).
Default popup controls apply while users edit. Set `disableDynamicFiltering: true` for the ordinary popup's Save/Cancel workflow. The separate [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) always uses Apply/Cancel and also provides mini-filter actions, blanks, and date hierarchies.
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Quick Filter](/guides/data-filter/filter-types/quick-filter/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/)
- [Selection Cascade](/guides/data-filter/filter-types/filter-selection-cascade/)
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Filter Badges](/guides/data-filter/filter-badges/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Current-column expressions](/guides/data-filter/filter-expression/)
- [Canonical local and remote Filter AST](/guides/data-filter/filter-ast/)
- [Clear Filtering demo](https://demo.rv-grid.com/clear-filtering/ts)
---
# Filter State and Presets
URL: https://pro.rv-grid.com/guides/data-filter/filter-state/
Source: src/content/docs/guides/data-filter/filter-state.mdx
Description: Initialize, update, save, restore, clear, and package reusable RevoGrid filter state without losing filter configuration.
Surface: grid
RevoGrid has two filter-state shapes. Choose the smallest one that represents the query you need:
| State | Use it for |
| ----------------- | ------------------------------------------------------------------------------------------ |
| `MultiFilterItem` | Core compatibility, ordinary column controls, and conditions that combine columns with AND |
| `FilterAst` | Pro saved views, remote requests, cross-column OR, nested groups, and NOT |
`MultiFilterItem` is runtime-compatible, but some controls use values such as `Set`. `FilterAst` is the canonical JSON-safe choice for new persistence and transport integrations.
## Set initial column filters
Supply `multiFilterItems` inside the complete grid filter configuration. Each property is a column property, and each array contains that column's conditions:
```ts
import type { ColumnFilterConfig, MultiFilterItem } from '@revolist/revogrid';
const initialFilters: MultiFilterItem = {
status: [{ id: 1, type: 'eq', value: 'Open', relation: 'and' }],
total: [
{ id: 2, type: 'gte', value: 100, relation: 'and' },
{ id: 3, type: 'lte', value: 500, relation: 'and' },
],
};
const filterConfig: ColumnFilterConfig = {
disableDynamicFiltering: false,
multiFilterItems: initialFilters,
};
grid.filter = filterConfig;
```
Conditions within one column can use `and` or `or`. Different columns in `multiFilterItems` always combine with AND. Use `FilterAst` when the query requires cross-column OR, nested groups, or NOT.
## Update applied column state
Call the active filter plugin's `onFilterChange()` method to replace the complete compatibility model without replacing localization, custom operators, selection settings, or other filter configuration:
```ts
import { FilterPlugin, type MultiFilterItem } from '@revolist/revogrid';
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof FilterPlugin,
);
const nextFilters: MultiFilterItem = {
priority: [{ id: 1, type: 'eq', value: 'High', relation: 'and' }],
};
await filterPlugin?.onFilterChange(nextFilters);
```
`AdvanceFilterPlugin` extends Core's `FilterPlugin`, so the same lookup works when Pro filtering is installed. Passing `{}` removes all column conditions.
Use `afterfilterapply` to observe the applied compatibility state:
```ts
import type { MultiFilterItem } from '@revolist/revogrid';
grid.addEventListener('afterfilterapply', (event) => {
const detail = (event as CustomEvent<{ multiFilterItems: MultiFilterItem }>)
.detail;
const appliedFilters = detail.multiFilterItems;
console.log(appliedFilters);
});
```
## Create reusable presets
A preset can be a `MultiFilterItem`, but reassigning `grid.filter` with only that object would drop the rest of the configuration. Keep one factory that recreates every option:
```ts
import type { ColumnFilterConfig, MultiFilterItem } from '@revolist/revogrid';
function createFilterConfig(
multiFilterItems: MultiFilterItem = {},
): ColumnFilterConfig {
return {
allowDuplicateOperators: true,
disableDynamicFiltering: false,
localization: {
captions: {
save: 'Apply',
reset: 'Reset',
},
},
multiFilterItems,
};
}
const presets: Record = {
openHighValue: {
status: [{ id: 101, type: 'eq', value: 'Open', relation: 'and' }],
total: [{ id: 102, type: 'gte', value: 1000, relation: 'and' }],
},
needsReview: {
status: [
{ id: 201, type: 'eq', value: 'Pending', relation: 'or' },
{ id: 202, type: 'eq', value: 'On Hold', relation: 'or' },
],
},
};
function applyPreset(name: keyof typeof presets) {
grid.filter = createFilterConfig(presets[name]);
}
function clearPreset() {
grid.filter = createFilterConfig({});
}
```
When the grid is already initialized, `onFilterChange(presets[name])` is usually simpler because it preserves the active configuration without recreating it.
## Save and restore canonical state
For new saved-view and backend integrations, use Pro's JSON-safe `FilterAst`. Register `AdvanceFilterPlugin`, read a defensive clone with `getFilterAst()`, and restore it atomically with `setFilterAst()`:
```ts
import { AdvanceFilterPlugin, type FilterAst } from '@revolist/revogrid-pro';
grid.plugins = [AdvanceFilterPlugin];
const plugin = (await grid.getPlugins()).find(
(item) => item instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin | undefined;
function saveView(storageKey: string) {
const ast = plugin?.getFilterAst();
if (ast) {
localStorage.setItem(storageKey, JSON.stringify(ast));
} else {
localStorage.removeItem(storageKey);
}
}
async function restoreView(storageKey: string) {
const saved = localStorage.getItem(storageKey);
const ast = saved ? (JSON.parse(saved) as FilterAst) : undefined;
await plugin?.setFilterAst(ast);
}
```
`setFilterAst()` validates the whole tree before applying it. Invalid state rejects without replacing the previous valid filter. Passing `undefined` clears filtering. You can also set initial canonical state with `grid.filter = { filterAst }`; when initial `filterAst`, `multiFilterItems`, and the legacy `collection` are supplied together, `filterAst` takes precedence.
The canonical tree represents conditions, groups, and negation with JSON values. Do not put `Date`, `Set`, functions, cycles, or non-finite numbers in a public AST.
## Compatibility state and JSON
Do not send `MultiFilterItem` directly through `JSON.stringify()` without checking its values. For example, the selection filter stores excluded checklist values in a `Set`, and JSON serializes a `Set` as `{}`.
```ts
const compatibilityState = {
status: [
{
id: 1,
type: 'selection',
value: new Set(['archived']),
relation: 'and',
},
],
};
```
Pro exports compatibility transport helpers that convert values such as `Set` and `Date` to JSON-friendly arrays and ISO strings. They are useful when maintaining an existing `MultiFilterItem` integration, but the compatibility format spans filter-specific runtime contracts. Do not assume those helpers provide a lossless saved-view format for every current or future Pro operator. Prefer `getFilterAst()` for new persistence and remote-filter payloads.
## Clear all filter state
Clear through the plugin so configuration stays installed:
```ts
import { FilterPlugin } from '@revolist/revogrid';
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof FilterPlugin,
);
await filterPlugin?.clearFiltering();
```
With `AdvanceFilterPlugin`, `clearFiltering()` also clears quick-filter and canonical AST state. `await plugin.setFilterAst(undefined)` is the equivalent canonical operation and also clears the quick filter.
## Common mistakes
- Assigning `grid.filter = { multiFilterItems }` after configuring localization, custom filters, selection options, or structured filter types. Use `onFilterChange()` or recreate the complete configuration with a factory.
- Treating `multiFilterItems` as cross-column Boolean logic. Different columns combine with AND; use `FilterAst` for cross-column OR and nested groups.
- Mutating a previously applied object and expecting a reactive update. Create the next state and pass it to `onFilterChange()` or `setFilterAst()`.
- Serializing selection-filter `Set` values directly. Use canonical `FilterAst` for new saved views.
- Restoring untrusted JSON without handling rejection from `setFilterAst()`.
- Clearing state by disabling `grid.filter`. Call `clearFiltering()` to keep filtering installed and configured.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Filter types
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/
Source: src/content/docs/guides/data-filter/filter-types/index.mdx
Description: Choose a RevoGrid Pro structured filter type and open a focused setup, state, and configuration guide for that control.
---
# Array Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/array-filter/
Source: src/content/docs/guides/data-filter/filter-types/array-filter.mdx
Description: Filter array-valued cells with strict empty-array and non-empty-array operators.
The Pro Array Filter adds two valueless operators for array-valued cells. Add
`AdvanceFilterPlugin` and use the `array` family:
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_ARRAY } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'tags',
name: 'Tags',
filter: [FILTER_ARRAY],
},
];
```
## Operators
| Default label | Saved operator ID | Matches |
| ------------------ | ----------------- | -------------------------- |
| Is empty array | `isEmptyArray` | Arrays with `length === 0` |
| Is not empty array | `isNotEmptyArray` | Arrays with `length > 0` |
Non-arrays match neither operator. Sparse arrays use their actual `length`, so
`new Array(3)` is non-empty. The operators need no filter value and remain
visibly active when restored from `multiFilterItems`.
```ts
grid.filter = {
multiFilterItems: {
tags: [
{
id: 0,
type: 'isEmptyArray',
relation: 'and',
},
],
},
};
```
Expressions support `empty array`, `not empty array`, `is empty array`, and `is
not empty array`. See [Blank Values](/guides/data-filter/blank-values/) for the
separate configurable blank policy. With `emptyArray: true`, `[]` explicitly
matches both the Core blank operator and `isEmptyArray`.
## Related guides
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Boolean Filter](/guides/data-filter/filter-types/boolean-filter/)
---
# Array and Tags Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/array-tags/
Source: src/content/docs/guides/data-filter/filter-types/array-tags.mdx
Description: Filter array-valued cells by any, all, none, exact-set, or empty-array semantics.
Surface: grid
Use the array and tags filter for labels, skills, categories, flags, or nested models that resolve to a tag array. Tag identity is typed, so `1`, `'1'`, `true`, and `null` remain distinct values.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_ARRAY_TAGS } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'tags',
name: 'Tags',
filter: ['array', FILTER_ARRAY_TAGS],
},
];
grid.source = [{ tags: ['UI', 'API'] }, { tags: ['Docs'] }, { tags: [] }];
grid.filter = true;
```
The popup flattens provider-backed array values into at most 200 typed tag options. The base `'array'` family also keeps the ordinary empty/not-empty operators available.
When rows store tag IDs and a `ColumnDropdown` editor uses `{ value, label }`
options, keep the IDs in the row and filter model. The filter automatically
reuses the normalized dropdown labels in its popup, header summary, and
condition description.
```ts
const tagOptions = [
{ value: 1, label: 'UI' },
{ value: 2, label: 'API' },
];
grid.columns = [{
prop: 'tags',
columnType: 'dropdown',
filter: ['array', FILTER_ARRAY_TAGS],
dropdown: { source: tagOptions, config: { multiSelect: true, search: true } },
}];
```
For remote labels that are not present in the dropdown source, use
`arrayTags.formatLabel` globally or under `arrayTags.columns[prop]`. An explicit
formatter takes precedence over the dropdown label. Presentation never changes
identity: numeric ID `1` remains distinct from string ID `'1'` during matching
and saved-state restore.
## Operator and saved state
- Structured type: `FILTER_ARRAY_TAGS` (`'arrayTags'`)
- Operator: `ARRAY_TAGS_MATCH` (`'arrayTagsMatch'`)
- Value: `{ mode, values, emptyOnly, exact }`
- Modes: `'any' | 'all' | 'none'`
```ts
grid.filter = {
multiFilterItems: {
tags: [
{
id: 1,
type: 'arrayTagsMatch',
value: {
mode: 'all',
values: ['UI', 'API'],
emptyOnly: false,
exact: false,
},
relation: 'and',
},
],
},
};
```
This JSON-safe object is the programmatic restore shape. `any` matches at least one selected tag, `all` requires every selected tag, and `none` rejects rows containing any selected tag. `exact` compares the complete normalized set and is ignored for `none`. `emptyOnly` takes precedence and matches only an actual empty array.
## Read tags from nested values
Configure an accessor globally or by column property through `filter.arrayTags`. It receives the cell value plus `{ property, column, model }` context and must return the array to treat as tags.
```ts
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'metadata',
name: 'Labels',
filter: ['array', FILTER_ARRAY_TAGS],
},
];
grid.filter = defineAdvancedFilterConfig({
arrayTags: {
columns: {
metadata: {
accessor: (value) => value?.labels,
},
},
},
});
grid.source = [
{ metadata: { labels: ['Priority', 'Customer'] } },
{ metadata: { labels: ['Internal'] } },
];
```
Use `arrayTags.accessor` for one global rule and `arrayTags.columns[prop].accessor` for exceptions. The per-column accessor wins. The same accessor is used to collect popup choices and evaluate rows.
Accessor functions are not serializable. Server-side filtering must implement the same extraction rule. For a high-cardinality or partially loaded remote source, provide complete array values or prepared `{ kind: 'arrayTags', values }` data rather than deriving choices from one page.
## Common mistakes
- Do not restore `type: 'arrayTags'`; saved conditions use `type: 'arrayTagsMatch'`.
- Do not return a scalar from the accessor. It must return the tag array.
- Do not expect string coercion: numeric `1` and string `'1'` are different tags.
- Do not combine `mode: 'none'` with `exact`; normalization disables exact mode for none.
- Do not use `emptyOnly` for `null`, missing, or sparse arrays. It delegates to strict empty-array semantics (`Array.isArray(value) && value.length === 0`).
- Do not forget to reproduce a custom accessor in a remote filter backend.
## Related guides
- [Structured Filter Types](./)
- [Array Filters](/guides/data-filter/filter-types/array-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Boolean Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/boolean-filter/
Source: src/content/docs/guides/data-filter/filter-types/boolean-filter.mdx
Description: Filter boolean columns with dedicated Yes and No operators, with customizable labels for application-specific wording.
The Boolean Filter adds dedicated, valueless operators for columns whose source
values are boolean primitives. By default, users see **Yes** and **No** in the
advanced-filter popup.
## Quick start
Add `AdvanceFilterPlugin`, then assign `FILTER_BOOLEAN` to the column:
```typescript
import { AdvanceFilterPlugin, FILTER_BOOLEAN } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
name: 'Enabled',
prop: 'enabled',
filter: [FILTER_BOOLEAN],
},
];
grid.source = [{ enabled: true }, { enabled: false }];
```
The string form is equivalent:
```typescript
{
name: 'Enabled',
prop: 'enabled',
filter: ['boolean'],
}
```
## Operators
The displayed names and stored operator keys are intentionally separate:
| Default name | Operator key | Matches |
| ------------ | ------------ | ----------------------------- |
| Yes | `isTrue` | The boolean primitive `true` |
| No | `isFalse` | The boolean primitive `false` |
The filter uses strict boolean matching. Values such as `'true'`, `'false'`,
`1`, `0`, `null`, and `undefined` are not coerced into boolean matches.
Configuring blank semantics does not change this typed behavior.
## Customize Yes and No
Use `defineAdvancedFilterConfig` to change the displayed operator names. The
operator keys remain `isTrue` and `isFalse`, so saved filter models and matching
behavior do not change.
```typescript
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
localization: {
filterNames: {
isTrue: 'Enabled',
isFalse: 'Disabled',
},
},
});
```
Labels can use any application-specific wording, such as `Active` / `Inactive`
or localized translations.
## Programmatic filter model
Use the stable operator keys when restoring or sending a filter model:
```typescript
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
enabled: [
{
id: 0,
type: 'isTrue',
relation: 'and',
},
],
},
});
```
## Related guides
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Array Filter](/guides/data-filter/filter-types/array-filter/)
- [Filter Selection](/guides/data-filter/filter-types/filter-selection/)
- [Advanced Filter Showcase](/guides/data-filter/filter-showcase/)
---
# Date range filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/calendar-range/
Source: src/content/docs/guides/data-filter/filter-types/calendar-range.mdx
Description: Filter date columns with an inclusive, accessible calendar range picker.
Use the calendar range for booking periods, reporting windows, delivery dates, and other workflows where users choose a fixed start and end day. It shares RevoGrid Pro's calendar navigation, range preview, keyboard focus, disabled-date behavior, and accessibility semantics.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_CALENDAR_RANGE,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'order', name: 'Order' },
{
prop: 'deliveryDate',
name: 'Delivery',
filter: ['date', FILTER_CALENDAR_RANGE],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ order: 'A-100', deliveryDate: '2026-09-02' },
{ order: 'A-101', deliveryDate: '2026-09-14' },
{ order: 'A-102', deliveryDate: '2026-10-01' },
];
```
## Operator and state
- Type ID: `FILTER_CALENDAR_RANGE` (`'calendarRange'`)
- Operator ID: `CALENDAR_RANGE_BETWEEN` (`'calendarRangeBetween'`)
- Value: inclusive ISO date-only bounds `{ from, to, inclusive: true }`
Both dates use `YYYY-MM-DD`. Partial or invalid ranges are not committed. The lower bound must not be after the upper bound in restored state; the interactive editor normalizes a reversed selection.
## Restore a range programmatically
```ts
import {
CALENDAR_RANGE_BETWEEN,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
deliveryDate: [
{
id: 1,
type: CALENDAR_RANGE_BETWEEN,
value: { from: '2026-09-01', to: '2026-09-30', inclusive: true },
relation: 'and',
},
],
},
});
```
## Calendar configuration
```ts
import {
createCalendarRangeStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const calendar = createCalendarRangeStructuredFilterType({
weekStartsOn: 1,
timeZone: 'Europe/Lisbon',
locale: 'pt-PT',
minDate: '2026-01-01',
maxDate: '2026-12-31',
disabledDates: ['2026-12-25'],
showOperator: true,
showSelectionSummary: true,
showClearButton: true,
showCalendar: true,
calendar: {
views: ['month', 'year'],
defaultView: 'month',
showHeader: true,
showNavigation: true,
showPreviousButton: true,
showNextButton: true,
showMonthLabel: true,
showWeekdays: true,
showOutsideDays: true,
allowOutsideMonthSelection: false,
fixedWeeks: true,
showTodayButton: true,
isMonthDisabled: month => month === '2026-07-01',
dayClassName: date => date.endsWith('-25') ? 'holiday' : undefined,
},
rangeValidation: {
minDays: 2,
maxDays: 31,
allowSameDay: false,
allowDisabledDates: false,
validate: (range, context) =>
range.from.slice(0, 7) === range.to.slice(0, 7)
|| 'Choose dates within one month',
},
onInvalidRange: (range, reason) => {
console.info('Range rejected', range, reason);
},
dateFormat: { day: '2-digit', month: 'short', year: 'numeric' },
formatDate: (date, context) =>
new Intl.DateTimeFormat(context.locale, context).format(
new Date(`${date}T12:00:00Z`),
),
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [calendar],
});
```
`disabledDates` can also be a callback receiving the ISO date and resolved locale/timezone context. When `weekStartsOn` or `timeZone` is omitted, it inherits the resolved date-filter policy for the column. The calendar renders a bounded month view, so a disabled-date callback does not scan grid rows.
### Filter visibility
| Option | Behavior |
| --- | --- |
| `showOperator` | Shows or hides the operator selector. The first configured operator remains active when hidden. |
| `showSelectionSummary` | Shows or hides the selected-range status chip. |
| `showClearButton` | Shows or hides the clear action. |
| `showCalendar` | Shows or hides the complete calendar body. |
| `operators` | Ordered allow-list for between and single-date operators. |
### Range validation
Validation runs before a completed range is committed. An invalid end date keeps the current start date, displays an accessible error, and leaves the previous filter condition unchanged.
| Option | Behavior |
| --- | --- |
| `rangeValidation.minDays` | Minimum inclusive number of selected calendar days. |
| `rangeValidation.maxDays` | Maximum inclusive number of selected calendar days. |
| `rangeValidation.allowSameDay` | Allows a one-day range. Defaults to `true`. |
| `rangeValidation.allowDisabledDates` | Allows unavailable dates inside a range. Defaults to `true` for compatibility. Endpoints must always be selectable. |
| `rangeValidation.validate` | Final application validator. Return `true`, `false`, or a user-facing error string. |
| `onInvalidRange` | Receives the normalized attempted range, reason, and resolved column/locale/timezone context. |
Invalid, zero, or negative `minDays` and `maxDays` values are ignored. Restored conditions remain a data contract: selection UI validation prevents new invalid interactions but does not silently delete saved filters.
## Independent Calendar component
The visual calendar is an independent Pro UI component exported from `@revolist/revogrid-pro`. Date parsing and calendar arithmetic are owned by the internal `@revolist/dates` workspace package. Calendar rendering remains a Pro UI concern.
The filter's nested `calendar` object accepts the reusable component's presentation and interaction options:
| Option | Behavior |
| --- | --- |
| `views` | Available `month` and/or `year` views. |
| `defaultView` | Initial view when it is included in `views`. |
| `showHeader` | Hides the complete heading/navigation row. |
| `showNavigation` | Convenience switch for both navigation actions. |
| `showPreviousButton`, `showNextButton` | Independently control navigation actions. |
| `showMonthLabel` | Hides the month/year heading while preserving navigation geometry. |
| `showWeekdays` | Shows or hides weekday labels. |
| `showOutsideDays` | Renders adjacent-month days. |
| `allowOutsideMonthSelection` | Allows rendered adjacent-month days to be selected. |
| `fixedWeeks` | Uses a stable six-week grid when `true`. |
| `showTodayButton` | Shows the Today shortcut. |
| `isMonthDisabled` | Disables complete months in navigation and year view. |
| `dayClassName`, `renderDay` | Customize day presentation from ISO date and state context. |
| `formatMonth`, `formatWeekday`, `formatDayLabel` | Customize visible and accessible date text. |
| `translations` | Overrides any subset of calendar interface labels. |
| `validateSelection`, `onInvalidSelection` | Validates individual selections before `onSelect`. |
| `onViewChange` | Reports month/year view changes. |
The standalone component is controlled: consumers provide `month`, `focusedDate`, optional `selection`/`preview`, and the corresponding change callbacks. Bounds (`minDate`, `maxDate`) and `isDateDisabled` apply to mouse, keyboard, Today, navigation, and year-view interactions.
The previous flat `showOutsideDays`, `allowOutsideMonthSelection`, `fixedWeeks`, and `showTodayButton` filter options remain compatibility aliases. Nested `calendar` values take precedence.
## Localization
Date text uses the configured `locale`. Interface and accessibility text use the normal advanced-filter localization contract. `CALENDAR_RANGE_LOCALIZATION` exports the complete English fallback and stable keys:
```ts
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
localization: {
filterNames: {
calendarRangeBetween: 'Está no intervalo',
},
captions: {
calendarRangeTitle: 'Intervalo de datas',
calendarRangeBetween: 'entre',
calendarRangeChooseEnd: 'Escolha a data final',
calendarRangeEmpty: 'Nenhum intervalo selecionado',
calendarRangeClear: 'Limpar intervalo de datas',
calendarRangePreviousMonth: 'Mês anterior',
calendarRangeNextMonth: 'Mês seguinte',
calendarRangeCalendar: 'Calendário',
calendarRangeToday: 'Hoje',
calendarRangeInvalid: 'Escolha um intervalo válido',
calendarRangeSameDay: 'Escolha duas datas diferentes',
calendarRangeTooShort: 'Escolha um intervalo maior',
calendarRangeTooLong: 'Escolha um intervalo menor',
calendarRangeContainsDisabledDate: 'O intervalo contém uma data indisponível',
},
},
});
```
The independent `Calendar` also exports `CalendarTranslations` and `DEFAULT_CALENDAR_TRANSLATIONS`; its `translations` property accepts a partial override. Application-provided custom validation messages are displayed as supplied, so localize those strings in the application.
## Common mistakes
- Passing datetimes or locale-formatted strings in restored state; use ISO date-only values.
- Omitting `inclusive: true`; other values are rejected.
- Configuring only `locale` and expecting it to set timezone policy; locale formats text, while `timeZone` decides the civil date.
- Assuming disabled dates change source data or reject every programmatic condition; they primarily constrain calendar selection.
- Replacing `grid.filter` for localization without also retaining a configured `structuredFilterTypes` registration.
## Related guides
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Relative or Rolling Window](/guides/data-filter/filter-types/relative-window/)
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Blank Values](/guides/data-filter/blank-values/)
---
# Chip and Badge Toggle Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/chip-badge-toggles/
Source: src/content/docs/guides/data-filter/filter-types/chip-badge-toggles.mdx
Description: Present a small typed status set as configurable visual toggle chips.
Surface: grid
Use Chip and Badge Toggles when a column has a small, recognizable status set that is faster to scan as colored pills than as a long checklist.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_CHIP_BADGE_TOGGLES,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'status',
name: 'Status',
filter: ['string', FILTER_CHIP_BADGE_TOGGLES],
},
];
grid.filter = true;
```
The built-in derives its choices from parsed unique values. It supports finite numbers, booleans, and strings while keeping their types distinct.
When the column uses `ColumnDropdown` object options, badge labels automatically
reuse the matching option label while saved filter values remain the original
typed IDs. A configured `badge` result takes precedence over the dropdown label.
## Operator and state
| Body ID | Saved operator | Value shape |
| ------------------ | -------------------- | --------------------------------------------------------------------- |
| `chipBadgeToggles` | `chipBadgeSelection` | `{ values: (string \| number \| boolean)[], includeBlanks: boolean }` |
`includeBlanks` uses the grid's configured blank semantics. Selecting every available value and including blanks removes the no-op condition.
## Restore selected badges
```ts
grid.filter = {
multiFilterItems: {
status: [
{
id: 1,
type: 'chipBadgeSelection',
value: {
values: ['Open', 'Blocked'],
includeBlanks: false,
},
relation: 'and',
},
],
},
};
```
## Configure labels, colors, and order
Replace the built-in registration with a configured version. The stable type and operator IDs do not change:
```ts
import {
FILTER_CHIP_BADGE_TOGGLES,
createChipBadgeStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const statusBadges = createChipBadgeStructuredFilterType({
order: ['Blocked', 'Open', 'Done'],
maxVisibleBadges: 20,
badge: ({ value }) => ({
label: String(value),
color: value === 'Blocked' ? '#ef4444' : '#22c55e',
}),
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [statusBadges],
});
grid.columns = [
{ prop: 'status', filter: ['string', FILTER_CHIP_BADGE_TOGGLES] },
];
```
`badge` may return a plain label or safe `{ label, className, color }` metadata. Labels render as text, not HTML. The default visible limit is 200 pills; the complete value set still controls matching when the mounted UI is limited.
## Common mistakes
- Do not use presentation labels as saved values. Filtering uses the original typed scalar value.
- Do not pass `null`, `undefined`, or `''` in `values`; represent blanks with `includeBlanks`.
- Do not create hundreds of status chips without setting a useful `maxVisibleBadges`; use the [Faceted List filter](../faceted-list/) for high-cardinality values.
- Do not replace the registration with a different ID. Keep `FILTER_CHIP_BADGE_TOGGLES` so column configuration and saved state remain compatible.
## Related guides
- [Structured Filter Types](./../)
- [Faceted List Filter](../faceted-list/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Date Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/date-filter/
Source: src/content/docs/guides/data-filter/filter-types/date-filter.mdx
Description: Filter calendar days with absolute comparisons, relative periods, timezones, fiscal calendars, and remote transport.
Surface: grid
Use the Pro date filter when a value represents a calendar day: due dates, business dates, holidays, or reporting periods. Use the [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/) when values represent exact instants such as audit timestamps or scheduled executions.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_DATE } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [{ prop: 'dueDate', name: 'Due date', filter: [FILTER_DATE] }];
grid.filter = true;
grid.source = [
{ dueDate: '2026-08-25' },
{ dueDate: '2026-08-26' },
{ dueDate: null },
];
```
A source `YYYY-MM-DD` keeps its written calendar date. A `Date`, epoch value, or ISO datetime with `Z` or an explicit offset is projected into the resolved timezone before the calendar day is compared.
## Advanced example
Start with browser-local behavior. Add a timezone, week-start, or fiscal-calendar policy only when the application has a defined business requirement.
## Filter a day or range
Date equality includes the complete civil day. A `between` range includes both selected days; internally it is evaluated as a half-open range whose end is the start of the following day.
```ts
grid.filter = {
multiFilterItems: {
dueDate: [
{
id: 1,
type: 'between',
value: {
fromDate: '2026-08-01',
toDate: '2026-08-31',
},
relation: 'and',
},
],
},
};
```
Use ISO calendar strings in persisted state so the intended day remains explicit.
## Configure timezone and calendar policy
```ts
grid.filter = {
date: {
timezoneMode: 'organization',
organizationTimeZone: 'America/New_York',
weekStartsOn: 1, // Monday; Sunday is 0
fiscalYearStart: { month: 2, day: 1 },
// Ordered allowlist used by every date and datetime menu.
operators: ['equals', 'before', 'after', 'between', 'today', 'thisFiscalYear'],
columns: {
accountingDate: {
timezoneMode: 'utc',
// A column entry replaces the global operator list for this field.
operators: ['between', 'thisFiscalYear'],
},
warsawDate: {
timezoneMode: 'explicit',
timeZone: 'Europe/Warsaw',
},
},
},
};
```
Settings resolve field by field: defaults, grid policy, then the partial entry in `date.columns[prop]`.
The `operators` array is an ordered allowlist, so it also controls menu order. A column-level array
replaces the grid-level array for that field. The same date IDs configure datetime menus; the plugin
maps them to the corresponding datetime operators internally.
| Mode | Resolved timezone |
| -------------- | -------------------------------------------------------------- |
| `user` | `userTimeZone`, otherwise the browser's resolved IANA timezone |
| `organization` | Required `organizationTimeZone` |
| `utc` | `UTC` |
| `explicit` | Required `timeZone` |
With no date configuration, behavior remains browser-local and the week starts on Monday. Fiscal
operators are hidden until an effective `fiscalYearStart` is configured globally or for that column.
Once configured, all fiscal quarter and fiscal year operators are available by default; use
`operators` to show only the fiscal choices appropriate for the application. Date calculations still
use January 1 internally when evaluating existing persisted fiscal conditions without an authored
fiscal policy. Initialization rejects unknown operator IDs, invalid IANA names, week indexes outside
`0…6`, missing organization or explicit zones, February 29, and other fiscal dates that do not recur
every year.
The same instant can fall on different calendar days in Warsaw and New York. Calendar strings do not shift: `2026-08-05` remains August 5 under every timezone policy.
## Operators
The date filter provides:
- Comparisons: Equals, Before, After, On or before, On or after, Between, Not equal
- Blank policy: Is blank, Is not blank
- Rolling days: Today, Yesterday, Last 7 days, Next 30 days
- Weeks: This week, Last week, Next week
- Months: This month, Last month
- Calendar periods: This, Previous, and Next quarter or year
- Fiscal periods (when `fiscalYearStart` is set): This, Previous, and Next fiscal quarter or fiscal year
`last7Days` includes today plus the preceding six civil days. `next30Days` includes today plus the following 29 days. Calendar quarters remain January/April/July/October quarters; only fiscal operators use `fiscalYearStart`.
The expression editor uses the same evaluator and captured clock. Examples include `today`, `this week`, `next 30 days`, `this fiscal quarter`, and `between "2026-08-01" and "2026-08-31"`. Expressions still reference only their current column.
## Blank and invalid values
`isEmpty` and `isNotEmpty` use Core's configured blank policy. Invalid nonblank temporal values fail comparisons but are not automatically blank. `false` and `NaN` therefore do not become blank dates.
Read [Blank Values](/guides/data-filter/blank-values/) for policy precedence and missing-property behavior.
## Remote filtering
Each filtering run captures one reference instant. `beforefilterapply` exposes `temporalContext`, and pagination or infinity-scroll `loadData` receives it as the optional seventh argument; the first six arguments are unchanged.
```ts
loadData: async (
skip,
take,
order,
singleConditionFilters,
multiConditionFilters,
quickFilter,
temporalContext,
) =>
fetch('/api/rows', {
method: 'POST',
body: JSON.stringify({
skip,
take,
order,
multiConditionFilters,
quickFilter,
temporalContext,
}),
}).then((response) => response.json());
```
The JSON-safe envelope contains `referenceInstant`, each filtered temporal column's resolved timezone, and condition ranges keyed by `:`. UTC boundaries use `start` and `endExclusive`; an unbounded side is `null`.
## Common mistakes
- Using `date` for timestamps that must retain time-of-day precision; use [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/).
- Assuming browser-local time matches the organization's reporting timezone.
- Treating the end of a date range as exclusive; the selected final day is included.
- Sending a relative operator to a server without the captured `temporalContext`.
- Treating invalid nonblank values as blanks.
## Related guides
- [Fiscal Date Filters](/guides/data-filter/filter-types/fiscal-date-filters/)
- [Datetime Filter](/guides/data-filter/filter-types/datetime-filter/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/#date-hierarchy)
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Infinity Scroll](/guides/infinity-scroll/)
---
# Datetime Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/datetime-filter/
Source: src/content/docs/guides/data-filter/filter-types/datetime-filter.mdx
Description: Filter exact instants with second precision, explicit timezone policy, absolute ranges, relative periods, and programmatic state.
Surface: grid
Use the Pro datetime filter for timestamps that represent exact instants: created times, audit events, log entries, and scheduled executions. Use the [Date Filter](/guides/data-filter/filter-types/date-filter/) instead when the source represents a calendar day without a time.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_DATETIME } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'createdAt',
name: 'Created',
filter: [FILTER_DATETIME],
},
];
grid.filter = true;
grid.source = [
{ createdAt: '2026-08-26T10:30:00Z' },
{ createdAt: new Date('2026-08-26T14:45:00Z') },
{ createdAt: 1787741100000 },
];
```
Accepted source values are `Date`, epoch milliseconds, and ISO datetimes with `Z` or an explicit offset. Date-only and offset-free source strings are invalid for this type.
## Datetime operators
Datetime saved IDs prefix the shared temporal operator name with `datetime`:
| Example saved ID | Meaning |
| -------------------------------------------------------- | -------------------------------------------------- |
| `datetimeEquals` | Exact selected second |
| `datetimeBefore` / `datetimeAfter` | Strict instant comparison |
| `datetimeOnOrBefore` / `datetimeOnOrAfter` | Inclusive comparison |
| `datetimeBetween` | Inclusive selected seconds |
| `datetimeNotEqual` | Not the selected second |
| `datetimeIsEmpty` / `datetimeIsNotEmpty` | Shared blank policy |
| `datetimeToday`, `datetimeLast7Days`, `datetimeThisWeek` | Relative calendar periods in the resolved timezone |
The popup also provides month, quarter, year, and fiscal-period operators. Read the complete list in the [Date Filter operator reference](/guides/data-filter/filter-types/date-filter/#operators).
## Start with a range
```ts
grid.filter = {
multiFilterItems: {
createdAt: [
{
id: 1,
type: 'datetimeBetween',
value: {
fromDate: '2026-08-26T09:00',
toDate: '2026-08-26T17:00',
},
relation: 'and',
},
],
},
};
```
The `datetime-local` values are interpreted in the resolved timezone. Equality selects one second; between includes the final selected second. Internally these become half-open UTC ranges for reliable local and remote evaluation.
## Set an explicit timezone
```ts
grid.filter = {
date: {
timezoneMode: 'organization',
organizationTimeZone: 'America/New_York',
columns: {
createdAt: {
timezoneMode: 'explicit',
timeZone: 'Europe/Lisbon',
},
},
},
};
```
With no date policy, the datetime control uses the browser's local timezone. Configure an IANA timezone when all users must interpret relative periods and wall-clock input consistently.
## Invalid and DST values
- Offset-free source strings such as `2026-08-26T10:30:00` are invalid because they do not identify an instant.
- A wall-clock value inside a daylight-saving gap does not exist and is rejected.
- A repeated wall-clock value is ambiguous and is also rejected.
- Invalid nonblank values fail comparisons; they do not become blank automatically.
## Common mistakes
- Do not use `datetime` for source values such as `2026-08-26`; use `date`.
- Do not restore datetime state with unprefixed date operator IDs.
- Do not assume the browser timezone matches the organization's reporting timezone.
- Use ISO strings in persisted or remote state; JavaScript `Date` objects are not JSON-safe AST input.
## Related guides
- [Fiscal Date Filters](/guides/data-filter/filter-types/fiscal-date-filters/)
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Calendar Range](/guides/data-filter/filter-types/calendar-range/)
- [Timeline Brush](/guides/data-filter/filter-types/timeline-brush/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Faceted List Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/faceted-list/
Source: src/content/docs/guides/data-filter/filter-types/faceted-list.mdx
Description: Filter typed scalar values with searchable all-row and visible-row counts.
Surface: grid
Use the Faceted List filter for a searchable value checklist that also shows how many rows contain each value and how many remain visible after other filters.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_FACETED_LIST,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'region', name: 'Region', filter: ['string', FILTER_FACETED_LIST] },
];
grid.filter = true;
```
The body reads both all-row and visible-row value counts. The two counts make it easier to understand which options remain after other column filters are active.
## Operators and state
| Saved operator | Value shape | Matches |
| -------------------- | ----------------------------------------- | ----------------------------- |
| `facetedListInclude` | `(string \| number \| boolean \| null)[]` | Values in the saved list |
| `facetedListExclude` | `(string \| number \| boolean \| null)[]` | Values outside the saved list |
Typed identity is preserved: `1`, `'1'`, and `true` are different options. `null` is shown as **(Blank)** and `''` as **(Empty)**. Non-finite numbers, arrays, objects, and `undefined` are not facet values.
## Display labels for stored IDs
If the column uses `ColumnDropdown` with object options, the facet editor,
condition description, and header selection automatically show each option's
label while saved conditions continue to contain its typed value.
```ts
import { ColumnDropdown } from '@revolist/revogrid-pro';
const statuses = [
{ value: 10, label: 'Open' },
{ value: 20, label: 'Closed' },
];
grid.columnTypes = { dropdown: ColumnDropdown };
grid.columns = [{
prop: 'statusId',
columnType: 'dropdown',
filter: ['number', FILTER_FACETED_LIST],
dropdown: { source: statuses },
}];
grid.source = [{ statusId: 10 }, { statusId: 20 }];
```
For remote or separately loaded labels, configure `facetedList.formatLabel`
globally or under `facetedList.columns[prop]`. The explicit formatter wins over
the dropdown label and affects presentation only.
```ts
grid.filter = {
facetedList: {
columns: {
statusId: { formatLabel: value => statusLabels.get(value as number) },
},
},
};
```
## Restore selected facets
```ts
grid.filter = {
multiFilterItems: {
region: [
{
id: 1,
type: 'facetedListInclude',
value: ['EMEA', 'APAC'],
relation: 'and',
},
],
},
};
```
The editor may serialize the smaller equivalent include or exclude list. Selecting every available value removes the condition; selecting none is represented as a non-empty exclusion list so local and remote behavior remain consistent.
## Counts and remote data
Facets use the `valueCounts` aggregate in two scopes:
- `all` describes the complete source available to the filter.
- `visible` applies all active filters except the current facet's own condition.
Local grids derive both from provider-backed row state. Pagination and infinite-scroll grids should supply complete structured-filter aggregates when browser rows are only a window of the remote result.
## Common mistakes
- Do not stringify typed values before restoring them unless the source column is also text; numeric `1` and string `'1'` are intentionally distinct.
- Do not put whole option objects in facet conditions. Store their scalar `value`; use dropdown options or `formatLabel` for presentation.
- Do not assume the UI always saves an include condition. It chooses a smaller equivalent include or exclude representation.
- Do not use this type for arbitrary array-valued cells. Use the [Array and Tags filter](../array-tags/) for arrays.
- Do not treat the visible count as the total source count; it reflects other active filters.
## Related guides
- [Structured Filter Types](./../)
- [Token List Filter](../token-list/)
- [Chip and Badge Toggles](../chip-badge-toggles/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Selection Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-selection/
Source: src/content/docs/guides/data-filter/filter-types/filter-selection.mdx
Description: Add searchable value checklists with live updates, custom option sources, templates, grouping, and cascading selection lists.
Selection filters let users include or exclude values through a searchable checkbox list. The list is a virtualized nested `revo-grid`, so large option sets render only the visible rows while preserving sorting, search, and checkbox behavior.
Use the default interaction for immediate checkbox updates. When users should review a staged checklist and choose **Apply**, use the dedicated [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) preset.
## Quick start
Add `AdvanceFilterPlugin`, then include `selection` in the column's filter families:
```typescript
import { AdvanceFilterPlugin, FIlTER_SELECTION } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
name: 'Fruit',
prop: 'name',
filter: ['string', FIlTER_SELECTION],
},
];
grid.filter = true;
grid.source = [{ name: 'Apple' }, { name: 'Banana' }, { name: 'Pear' }];
```
By default, checked values are included and unchecked values are stored as an excluded-value `Set`. Opening and closing a fully selected list does not create a filter.
Add `grid.filter = { selection: { sortDirection: 'asc' } }` when the options should be sorted rather than kept in source order.
- Keep the default mode for immediate checkbox updates and optional live row
filtering while users search. - Use [`selection.excelMode:
'windows'`](/guides/data-filter/filter-types/filter-selection-excel-mode/) for a
transactional checklist with Apply, Cancel, tri-state Select All, blanks, and
date hierarchies.
## Windows Excel mode
`selection.excelMode: 'windows'` changes selection-filter columns into a staged, spreadsheet-style checklist. It keeps the same excluded-value `Set` model but adds Apply, Cancel, tri-state Select All, blanks, search actions, and optional date hierarchies.
See [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/) for the interactive four-framework example and complete behavior reference. This heading remains available for links created before the guide was separated.
## How the option list behaves
The selection popup keeps the search row fixed at the top and renders option rows through an internal `revo-grid`.
- Search still matches normalized option `value`.
- Select-all only affects currently searched and visible option rows.
- Checked state still means the value is included; unchecked values are stored in the selection filter's excluded-value `Set`.
- Opening and closing a fully selected popup does not create an empty selection filter, so `FilterHeaderPlugin` keeps showing `All`.
- Group rows are visual only; option checkboxes still belong to leaf option rows.
The old `.filter-list li` DOM shape is not part of the public API. Use the visible option behavior or `.filter-list-option` when tests need to interact with option rows.
## Control option values and sources
### Parsed cell values
When a column defines `cellParser`, the built-in selection option loader uses the parsed value for option labels, quick search, and row comparison. This lets rows keep identifiers or structured values while the filter presents and compares the normalized value returned by the parser. Context-aware cascading options use the same parsed values.
```typescript
const statusLabels = {
1: 'Active',
2: 'Paused',
};
grid.columns = [
{
prop: 'status',
name: 'Status',
filter: ['selection'],
cellParser: (model, column) => statusLabels[model[column.prop]],
},
];
```
If you provide `selection.getItems`, that custom loader remains the explicit source for option values and labels. See the Core guide on [filtering parsed values](https://rv-grid.com/guide/filters#filter-parsed-values) for the underlying `cellParser` contract.
### Extend source-row eligibility
The built-in selection and slider option loaders emit the cancelable `beforefilteroptionsourcerow` event before a source row contributes a value. Cancel the event to exclude application- or plugin-owned synthetic rows without coupling Advanced Filter to their data shape.
```typescript
import { BEFORE_FILTER_OPTION_SOURCE_ROW_EVENT } from '@revolist/revogrid-pro';
grid.addEventListener(BEFORE_FILTER_OPTION_SOURCE_ROW_EVENT, (event) => {
if (event.detail.row.__summaryRow) {
event.preventDefault();
}
});
```
Grouping rows are excluded before the event is emitted. The `@revolist/pivot` plugin uses this extension automatically for subtotal and grand-total rows. A custom `selection.getItems` loader remains fully application-owned and does not scan or emit events for grid source rows.
## Search and context-aware options
### Quick-search filtering
By default, the selection popup search input does two things:
- narrows the option rows shown inside the popup
- applies a hidden `quickSearch` filter to the grid rows
This preserves the existing behavior for users who expect the grid to narrow while they type. Set `selection.quickSearchFiltering` to `false` when search should only help users find values in the popup and should not change the grid rows until they check or uncheck selection values.
```typescript
grid.filter = {
selection: {
quickSearchFiltering: false,
},
};
```
When selection values are IDs but the popup renders names, use `selection.quickSearchFilter` to match the text users see. The matcher receives normalized `search`, normalized option `value`, display `label`, original `item`, and `columnProp`. If `quickSearchFiltering` remains enabled, grid rows are filtered by the matched option values, so row data can still store IDs.
```typescript
grid.filter = {
selection: {
getItems: {
ownerId: () => [
{ value: 'usr-1', label: 'Ana Silva', email: 'ana@example.com' },
{ value: 'usr-2', label: 'Maks Doe', email: 'maks@example.com' },
],
},
quickSearchFilter: {
ownerId: ({ search, label, item }) =>
`${label} ${item.email}`.toLowerCase().includes(search),
},
},
};
```
### Context-aware cascading options
Enable context-aware selection options with `selection.cascadeOptions.enabled`.
- For column **X**, options are built from rows matching all active filters **except filters for X**.
- This keeps current-column values reversible while still narrowing related column options.
- `optionVisibility` supports `hide`, `disable`, and `show`; `hide` is the default.
- Header totals and tooltips always describe context-valid values, even when the popup shows the complete domain.
- The feature is opt-in, and default behavior is unchanged when omitted.
```typescript
grid.filter = {
selection: {
cascadeOptions: {
enabled: true,
optionVisibility: 'hide',
},
},
};
```
See [Cascading Selection Filters](/guides/data-filter/filter-types/filter-selection-cascade/) for a detailed comparison of all three modes and preserved exclusion behavior.
## Custom option sources
By default, the selection filter builds its checkbox list from the current grid data. Use `filter.selection.getItems` when the list should come from somewhere else, for example:
- server-side or infinite-scroll datasets
- a curated allow-list of accepted values
- normalized values that differ from the rendered cell text
Default lists are rebuilt when the popup opens. If a cell value is edited, rows are removed, or `grid.source` is replaced, the next selection popup reflects the latest column values. Values that no longer exist in the source are not shown unless you provide them through a custom `selection.getItems` loader.
When pinned rows should not contribute checkbox values, keep the default loader and restrict its row stores:
```typescript
grid.filter = {
selection: {
sourceRowTypes: ['rgRow'],
},
};
```
The loader receives the current column `prop` and may return data synchronously or asynchronously.
```typescript
grid.filter = {
selection: {
getItems: async (prop) => {
if (prop !== 'name') {
return [];
}
const response = await fetch('/api/filter-options/names');
const items = await response.json();
return items.map((item: { id: string; title: string }) => ({
value: item.id.toLowerCase(),
label: item.title,
}));
},
},
};
```
If only some columns need custom values, pass a record keyed by column prop:
```typescript
grid.filter = {
selection: {
getItems: {
name: async () => [
{ value: 'apple', label: 'Apple' },
{ value: 'banana', label: 'Banana' },
],
category: () => [
{ value: 'fresh', label: 'Fresh' },
{ value: 'frozen', label: 'Frozen' },
],
},
},
};
```
Keep `value` aligned with the value used during filter comparison. The built-in selection filter compares lower-cased string values, so custom lists should usually provide lower-cased `value` fields.
## Customize option presentation
### Custom option templates
Use `selection.itemTemplate` to render badges, icons, or richer labels inside the selection popup. The checkbox remains controlled by the filter plugin; the template only replaces the content next to it.
To reuse an existing column renderer without defining a second template, enable `selection.syncCellTemplate`. The option item is exposed as a synthetic row model, including metadata returned by `selection.getItems`, and the current column prop is set to the normalized filter value.
```typescript
grid.filter = {
selection: {
syncCellTemplate: {
availability: true,
},
getItems: {
availability: () => [
{ value: 'in stock', label: 'In stock', tone: 'green' },
{ value: 'backorder', label: 'Backorder', tone: 'amber' },
],
},
},
};
```
```typescript
grid.filter = {
selection: {
getItems: {
availability: () => [
{ value: 'in stock', label: 'In stock', tone: 'green' },
{ value: 'backorder', label: 'Backorder', tone: 'amber' },
{ value: 'seasonal', label: 'Seasonal', tone: 'indigo' },
],
},
itemTemplate: {
availability: (h, { item, label, checked }) =>
h(
'span',
{
class: `availability-badge availability-badge--${item.tone}`,
'data-selected': String(checked),
},
label,
),
},
},
};
```
`itemTemplate` receives:
- `columnProp`: Current column property.
- `item`: Original item returned by `selection.getItems` or the default source loader.
- `value`: Normalized value used by selection filtering.
- `label`: Display label.
- `checked`: Whether the option is currently included in the result.
### Option progress
Use `selection.optionProgress` for a responsive distribution bar inside the selection-owned option
row. The label, track, and formatted value share the available width, and unchecked options switch
to the muted visual state automatically. Values read item metadata returned by `selection.getItems`.
```typescript
grid.filter = {
selection: {
getItems: {
status: () => [
{ value: 'processing', label: 'Processing', count: 42 },
{ value: 'shipped', label: 'Shipped', count: 28 },
],
},
optionProgress: {
status: {
valueProp: 'count',
getMax: ({ values }) => Math.max(1, ...values),
formatValue: value => `${value} rows`,
},
},
},
};
```
Use `getValue` instead of `valueProp` for derived values, and `max` or `getMax` to control the range.
`showValue: false` hides the trailing value while keeping the accessible progressbar. `ariaLabel`
accepts a string or formatter.
For secondary metadata that is not progress, `selection.optionColumns` still appends ordinary
read-only RevoGrid columns after the selection-owned checkbox and label column.
### Grouped option rows
Selection option rows can be grouped by fields on the option item. This is useful for large curated lists where users need a hierarchy such as family and color, region and country, or category and status.
```typescript
grid.filter = {
selection: {
grouping: {
name: {
props: ['family', 'color'],
expandedAll: true,
},
},
getItems: {
name: () => [
{ value: 'apple', label: 'Apple', family: 'Tree fruit', color: 'Red' },
{ value: 'pear', label: 'Pear', family: 'Tree fruit', color: 'Green' },
{ value: 'lemon', label: 'Lemon', family: 'Citrus', color: 'Yellow' },
],
},
},
};
```
`selection.grouping` can also be a single `GroupingOptions` object when every selection-filter column should use the same grouping configuration.
### Nested-list plugins and settings
The selection list is a nested grid, and you can pass plugins or grid settings to that nested grid when you need additional rendering behavior.
```typescript
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
class SelectionListPlugin {
constructor(selectionGrid) {
selectionGrid.setAttribute('data-selection-list-plugin', 'mounted');
}
}
grid.plugins = [AdvanceFilterPlugin];
grid.filter = {
selection: {
plugins: {
name: [SelectionListPlugin],
},
gridSettings: {
name: {
theme: 'compact',
rowSize: 32,
frameSize: 2,
hideAttribution: true,
},
},
},
};
```
`selection.plugins` and `selection.gridSettings` accept either a global value or a record keyed by column `prop`.
The filter list owns `source`, `columns`, and `grouping` so filtering, checkbox state, and virtualization stay consistent. Use `selection.grouping` for grouped option rows instead of `gridSettings.grouping`.
## Restore selection state
Selection filters store unchecked values as an excluded-value `Set`. To predefine a selection filter, add a hidden `selection` filter with values that should be excluded.
```typescript
grid.filter = {
multiFilterItems: {
name: [
{
id: 0,
type: 'selection',
value: new Set(['banana', 'lemon']),
relation: 'and',
hidden: true,
},
],
},
};
```
The [Excel-style demo](/guides/data-filter/filter-types/filter-selection-excel-mode/) uses this pattern for the Fruit column, so two fruit values are already unchecked when it loads. See [Filter State and Presets](/guides/data-filter/filter-state/) before persisting this model because a `Set` is not directly JSON-safe.
## Configuration reference
| Option | Purpose |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `sortDirection` | Sort options with `'asc'`, `'desc'`, or `'none'`. |
| `excelMode` | Set to `'windows'` for the dedicated [Excel-style checklist](/guides/data-filter/filter-types/filter-selection-excel-mode/). |
| `quickSearchFiltering` | Choose whether popup search also filters grid rows; defaults to `true`. |
| `quickSearchFilter` | Match search text against labels or custom item metadata. |
| `sourceRowTypes` | Choose which row stores feed the default option loader. |
| `getItems` | Provide synchronous or asynchronous custom options globally or by column. |
| `itemTemplate` / `syncCellTemplate` | Customize option content while the plugin retains checkbox behavior. |
| `optionProgress` | Add selection-owned responsive progress with checked and unchecked visual states. |
| `optionColumns` | Append read-only metadata columns for non-progress option details. |
| `grouping` | Group option rows using fields returned by `getItems`. |
| `plugins` / `gridSettings` | Configure the nested option grid without replacing its owned source, columns, or grouping. |
| `cascadeOptions` | Build options from rows matching filters in other columns. |
Default-mode localization includes `selectionTitle` and `selectionSearchPlaceholder`. Excel mode adds the action and blank captions documented in [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/#localize-the-checklist).
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Excel-Style Filter](/guides/data-filter/filter-types/filter-selection-excel-mode/)
- [Selection Cascade](/guides/data-filter/filter-types/filter-selection-cascade/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Selection Cascade Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-selection-cascade/
Source: src/content/docs/guides/data-filter/filter-types/filter-selection-cascade.mdx
Description: Enable context-aware cascading options for RevoGrid Pro selection filters.
Use `filter.selection.cascadeOptions.enabled` to make selection options context-aware.
In cascade mode, RevoGrid builds each column's selection list from rows matching all active filters **except filters for the current column**. This keeps the current column reversible while still narrowing options in related columns.
Use `optionVisibility` to control how values outside that context appear. A value is **context-valid** when at least one source row containing that value matches the active filters in the other columns.
| Mode | Options in the popup | Context-invalid options | Best suited for |
| --- | --- | --- | --- |
| `hide` | Context-valid values only | Not rendered | Compact, spreadsheet-style cascading filters |
| `disable` | Complete column domain | Visible but disabled | Showing users why a known value is currently unavailable |
| `show` | Complete column domain | Visible and interactive | Workflows where users must edit future filter state across the complete domain |
`hide` is the default when cascading is enabled. The modes change option presentation and interaction only; they do not change how rows are filtered.
## `hide`: show only relevant options
```ts
cascadeOptions: {
enabled: true,
optionVisibility: 'hide',
}
```
Use `hide` for the most direct cascading experience. If the Country filter leaves only Germany, the City popup contains only Berlin and Munich. Paris, Lyon, Warsaw, and Krakow are omitted because they cannot affect the rows in the current country context.
The current column's own filter is still ignored while its option list is built. For example, opening Country continues to show every country allowed by the other active columns, so users can reverse or broaden the Country selection.
## `disable`: reveal unavailable options
```ts
cascadeOptions: {
enabled: true,
optionVisibility: 'disable',
}
```
Use `disable` when users benefit from seeing the complete domain. With Germany active, Berlin and Munich remain interactive while cities from France and Poland remain visible with disabled checkboxes.
- Disabled options remain searchable and follow the configured sort order.
- Clicking a disabled option does not change the filter model.
- **Select All** and tree-selection cascades operate only on context-valid options.
- A custom `itemTemplate` receives `disabled: true` for unavailable options.
This mode is useful when an unavailable value should be discoverable without allowing it to change the current cascade.
## `show`: keep the complete domain interactive
```ts
cascadeOptions: {
enabled: true,
optionVisibility: 'show',
}
```
Use `show` when users must manage selections across the complete domain even when some values cannot currently affect visible rows. With Germany active, every city remains visible and interactive.
Changing Paris while Germany is active updates the stored City exclusion, but it does not immediately change the displayed rows because no German row has City = Paris. The change becomes observable if the Country filter is later broadened to include France. This makes `show` useful for preparing filter state ahead of an upstream change, but it is less compact than `hide` and offers less availability guidance than `disable`.
## Preserved exclusions and header summaries
Selection filters store excluded values. Every visibility mode preserves those exclusions when an option becomes context-invalid; cascading never silently prunes them.
For example, if Paris is excluded and Country is narrowed to Germany:
- `hide` temporarily removes Paris from the popup.
- `disable` keeps Paris visible and disabled.
- `show` keeps Paris visible and interactive.
When France becomes valid again, Paris returns with its previous unchecked state. The FilterHeader count and tooltip always summarize context-valid values, independently of popup mode. Consequently, `disable` and `show` can display more popup options than the denominator shown in the header badge.
Columns with `cellParser` build and compare their cascading options from parsed values, consistent with the Core [filter parsed values](https://rv-grid.com/guide/filters#filter-parsed-values) behavior.
Set `filter.selection.cascadeOptions.showDependencyNumbers` to `true` to show `1`, `2`, and later badges on active header filter icons in the order those filters are applied. The badges are hidden by default; omit this option or set it to `false` to keep them hidden.
- If `country = Germany` is active, opening the **city** filter shows `Berlin` and `Munich`.
- Opening the **country** filter still shows `Germany`, `France`, and `Poland`.
- The example uses `hide`; switch `optionVisibility` to `disable` or `show` to expose the complete City domain.
- The feature is opt-in and does not change behavior when cascading is disabled.
## Configuration
```ts
grid.filter = {
multiFilterItems: {
department: [{ id: 0, type: 'selection', value: ['sales', 'support'], relation: 'and' }],
country: [{ id: 1, type: 'selection', value: ['france', 'poland'], relation: 'and' }],
},
selection: {
cascadeOptions: {
enabled: true,
optionVisibility: 'hide',
showDependencyNumbers: true,
},
},
};
```
## Compatibility with custom getItems
If `selection.getItems` is provided, that custom source is used as-is.
Cascade visibility modes are only applied to the built-in source loader; RevoGrid does not infer contextual availability for custom items.
## Related guides
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Filter Header](/guides/data-filter/filter-header/)
---
# Excel-Style Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-selection-excel-mode/
Source: src/content/docs/guides/data-filter/filter-types/filter-selection-excel-mode.mdx
Description: Configure the Windows Excel-style staged checklist, search actions, blanks, custom values, and date hierarchies.
Surface: grid
Windows Excel mode turns every selection-filter column into a staged checklist modeled after the Windows Excel AutoFilter menu. Users search or check values, review the draft, and then choose **Apply** or **Cancel**.
Excel mode does not read or write XLSX files and does not connect RevoGrid to
Microsoft Excel. It only changes the interaction presented by the Pro
selection filter.
## Quick start
Register `AdvanceFilterPlugin`, enable the selection family on a column, and set the global selection preset to `windows`:
```ts
import { AdvanceFilterPlugin, FIlTER_SELECTION } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ name: 'Status', prop: 'status', filter: [FIlTER_SELECTION] },
{ name: 'Owner', prop: 'owner', filter: ['string'] },
];
grid.filter = {
selection: {
excelMode: 'windows',
sortDirection: 'asc',
},
};
grid.source = [
{ status: 'Open', owner: 'Ana' },
{ status: 'Closed', owner: 'Maks' },
];
```
`selection.excelMode` is grid-wide selection configuration, but it only changes columns that include the `selection` filter. The Owner column above keeps its ordinary text-filter popup.
The example uses the same setup in Vanilla TypeScript, Vue, React, and Angular. Open the Fruit or Availability filter for a flat checklist, or Harvest date for the date hierarchy.
## Apply, cancel, and close
Excel mode is always transactional, even when `disableDynamicFiltering` is `false` or omitted:
- Checking values and typing in the mini-filter only changes the popup draft.
- **Apply** commits the draft and closes the popup.
- **Cancel**, Escape, outside click, the close button, and switching to another popup discard the draft.
- Replacing the grid source or filter configuration while the popup is open also discards its draft.
- Pressing Enter in the mini-filter applies the current search result and closes the popup.
The popup is checklist-only; it does not show the normal operator dropdown. Use the standard [Selection Filter](/guides/data-filter/filter-types/filter-selection/) when changes should apply immediately or users need the ordinary filter-family workflow.
Rows remain unchanged while the checklist is edited and update only after
Apply.
## Search and Select All
Typing a non-empty mini-filter selects the matching results and changes the parent action to **Select All Search Results**. Applying immediately keeps the matches and discards selections hidden by the search.
Enable **Add current selection to filter** when the result should instead be the union of the last applied selection and the current matches. Clearing the search returns the checklist to the last applied state.
**Select All** affects the currently visible or searched options. Parent checkboxes show a mixed state when only some of their leaf values are selected.
Search selects its matches; Add current selection preserves the previously
applied values as well.
### Clearing the condition
Checked values are included. Internally, the selection filter stores unchecked values in an excluded-value `Set`. Applying either of these boundary states removes the selection condition:
- every available value is checked
- every available value is unchecked
This normalization avoids leaving a hidden condition that represents the complete option set.
## Blanks and option sources
Empty strings, `null`, and other normalized blank options share the **(Blanks)** item. It remains searchable and sorts after nonblank options in Excel mode.
By default, options are derived from the current grid rows and account for compatible filters on other columns. An explicit synchronous or asynchronous `selection.getItems` is authoritative:
```ts
grid.filter = {
selection: {
excelMode: 'windows',
getItems: {
status: async () => {
const response = await fetch('/api/filter-options/status');
return response.json(); // [{ value: 'open', label: 'Open' }, ...]
},
},
},
};
```
Keep each `value` aligned with the normalized value used for row comparison. Custom item templates, grouping, nested-grid settings, parsed cell values, and cascading options work as described in the general [Selection Filter guide](/guides/data-filter/filter-types/filter-selection/).
## Date hierarchy
Excel mode creates a local-calendar **Year → Month → Day** tree only when the column provides all three signals:
```ts
grid.columns = [
{
name: 'Created',
prop: 'createdAt',
columnType: 'date',
filter: ['date', 'selection'],
},
];
```
The hierarchy starts collapsed and uses browser-locale month names. Multiple timestamps on the same local day share one day node. Parent and Select All checkboxes derive their mixed state from the raw leaf values. Invalid nonblank values remain selectable as flat root options, followed by **(Blanks)**.
The hierarchy groups raw values by their browser-local calendar day while
preserving each raw leaf for filtering.
Setting only `columnType: 'date'`, or only including `date` in `filter`, is not enough. Without the full configuration, the column uses a flat selection list.
## Restore applied selection
Restore the same state model used by the default selection filter. The `Set` contains normalized values that should be unchecked:
```ts
grid.filter = {
multiFilterItems: {
status: [
{
id: 1,
type: 'selection',
value: new Set(['closed', 'archived']),
relation: 'and',
hidden: true,
},
],
},
selection: {
excelMode: 'windows',
},
};
```
Do not replace the full `filter` object with only `multiFilterItems` when the grid also uses localization or selection settings. Build the complete configuration or use the active plugin APIs described in [Filter State and Presets](/guides/data-filter/filter-state/).
## Localize the checklist
Override only the captions the application needs:
```ts
import { defineAdvancedFilterConfig } from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
localization: {
captions: {
selectionSearchPlaceholder: 'Search values...',
selectionApply: 'Use values',
selectionCancel: 'Discard',
selectionSelectAll: '(All values)',
selectionSelectAllSearchResults: '(All search results)',
selectionAddCurrentSelection: 'Keep the current selection too',
selectionBlanks: '(Empty)',
},
},
selection: {
excelMode: 'windows',
},
});
```
`defineAdvancedFilterConfig` fills the remaining default filter names and captions.
## Common mistakes
- Expecting Excel mode on a column that does not include the `selection` filter.
- Treating the mini-filter as a live grid search; its draft is committed only by Apply or Enter.
- Expecting `disableDynamicFiltering` to remove Apply and Cancel from Excel mode.
- Saving `multiFilterItems` directly as JSON without converting selection `Set` values.
- Configuring only one of the two required date signals and expecting a hierarchy.
- Returning display labels from `getItems` that do not map to the values compared with row data.
## Related guides
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Filter State and Presets](/guides/data-filter/filter-state/)
- [Date Filter](/guides/data-filter/filter-types/date-filter/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Slider Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/filter-slider/
Source: src/content/docs/guides/data-filter/filter-types/filter-slider.mdx
Description: Add a numeric range slider with parsed bounds, editable inputs, formatted values, and optional inline header controls.
The slider filter gives numeric columns a two-handle range control. It derives minimum and maximum bounds from eligible row values and stores the active interval in the advanced filter model.
For a fuller example with formatting and additional slider options:
## Quick start
Add `AdvanceFilterPlugin`, then combine the `number` and `slider` filter families on a numeric column:
```ts
import { AdvanceFilterPlugin, FIlTER_SLIDER } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
name: 'Price',
prop: 'price',
filter: ['number', FIlTER_SLIDER],
},
];
grid.filter = true;
grid.source = [{ price: 25 }, { price: 80 }, { price: 140 }];
```
Returning both handles to the complete data range clears the slider condition. Add `FilterHeaderPlugin` when the same range control should also appear directly in the column header.
Add options only when the default control needs customization:
```ts
grid.filter = {
slider: {
showRangeInputs: true,
formatValue: (value) => `$${value.toFixed(2)}`,
},
};
```
## Parsed cell values and bounds
When the slider column defines `cellParser`, the slider calculates its minimum and maximum bounds from parsed values and applies range filtering to those same values. Use this when the stored value needs normalization before numeric filtering.
Grouping rows and non-finite parsed values are excluded from the bound calculation. The `@revolist/pivot` plugin excludes its total rows through Advanced Filter's cancelable `beforefilteroptionsourcerow` extension event. If no finite values remain, the slider falls back to `0` for both bounds.
```typescript
grid.columns = [
{
name: 'Price',
prop: 'priceInCents',
filter: ['number', 'slider'],
cellParser: (model, column) => Number(model[column.prop] ?? 0) / 100,
},
];
```
See the Core guide on [filtering parsed values](https://rv-grid.com/guide/filters#filter-parsed-values) for the underlying `cellParser` contract.
## Configuration reference
The slider filter can be customized using the following configuration options:
| Option | Type | Default | Description |
| ------------------ | --------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `headerView` | `'slider' \| 'range'` | `'slider'` | With `FilterHeaderPlugin`, shows either the interactive slider or a compact formatted `from–to` range in the column header |
| `showTooltips` | boolean | true | Shows tooltips when hovering over the slider handles |
| `showRangeDisplay` | boolean | true | Displays the current range values above the slider |
| `showRangeInputs` | boolean | false | Shows numeric inputs for editing the selected range |
| `formatValue` | function | `(value) => value.toFixed(2)` | Custom function to format the displayed values |
| `formatInputValue` | function | `(value) => String(value)` | Custom function to format values inside editable range inputs |
| `parseInputValue` | function | `(value) => Number(value.replace(',', '.'))` | Custom function to parse editable range input text |
## Styling
The slider component supports custom styling through CSS variables:
```css
revo-grid {
--slider-color: #c6c6c6; /* Color of the inactive slider track */
--range-color: #0068f0; /* Color of the active slider range */
--tooltip-bg: #333333; /* Tooltip background color */
--tooltip-color: #ffffff; /* Tooltip text color */
}
/* Dark theme support */
revo-grid[theme^='dark'] {
--slider-color: #c6c6c6;
--range-color: #0068f0;
--filter-input-bg: #333333;
--filter-input-color: #ffffff;
--tooltip-bg: #f2f2f6;
--tooltip-color: #333333;
}
```
## Interaction behavior
1. **Range Selection**:
- The slider provides two handles for selecting a range of values
- Values are automatically normalized between the minimum and maximum values in your dataset
- Real-time filtering as users adjust the range
2. **Tooltips**:
- Interactive tooltips show the exact value when hovering over slider handles
- Tooltips can be customized using the `formatValue` function
- Position updates dynamically as the handles move
3. **Range Display**:
- Optional display of current range values above the slider
- Formatted values using the same `formatValue` function
- Clear visual feedback of the selected range
4. **Theme Integration**:
- Automatically adapts to light and dark themes
- Customizable colors through CSS variables
- Consistent look and feel with the rest of your grid
## Combine with other filters
The slider filter can be combined with other filter types for more complex filtering scenarios:
```typescript
grid.columns = [
{
name: 'Price',
prop: 'price',
filter: ['number', 'slider', 'selection'], // Combine with selection filter
},
];
```
## Usage guidance
1. **User experience**:
- Use `showTooltips` for precise value selection
- Implement `formatValue` for meaningful value display
- Enable `showRangeDisplay` for clear range visualization
2. **Accessibility**:
- The handles are native range inputs and remain keyboard-operable.
- Use `showRangeDisplay` or editable range inputs when users also need exact textual values.
## Related guides
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Filter Header](/guides/data-filter/filter-header/)
- [Selection Filter](/guides/data-filter/filter-types/filter-selection/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Fiscal Date Filters
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/fiscal-date-filters/
Source: src/content/docs/guides/data-filter/filter-types/fiscal-date-filters.mdx
Description: Configure fiscal years and quarters for Date, Datetime, Relative Window, expressions, and remote filtering.
Surface: grid
Fiscal filters answer business-calendar questions such as “orders in this fiscal quarter” or “events in the previous fiscal year.” Configure the first day of the fiscal year once, then use dynamic operators that move forward with the current date.
For example, with a fiscal year beginning on February 1, September 3, 2026 belongs to:
- the fiscal year from February 1, 2026 through January 31, 2027;
- the fiscal quarter from August 1 through October 31, 2026.
Calendar operators do not change meaning. “This quarter” in the Date or Datetime filter still uses January, April, July, and October boundaries; “This fiscal quarter” uses the configured fiscal calendar.
## Configure the fiscal year
`month` is one-based. The start must be a real calendar date that occurs every year, so February 29 is not accepted.
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'accountingDate', name: 'Accounting date', filter: 'date' },
{ prop: 'postedAt', name: 'Posted at', filter: 'datetime' },
];
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
timezoneMode: 'organization',
organizationTimeZone: 'Europe/Lisbon',
},
};
```
Once `fiscalYearStart` is configured, Date and Datetime menus include all six fiscal operators by default. The timezone decides which civil date is “today” when the period is resolved.
## Supported filters
| Filter | Fiscal behavior |
| --- | --- |
| [Date Filter](../date-filter/) | Supports This, Previous, and Next fiscal quarter and fiscal year. |
| [Datetime Filter](../datetime-filter/) | Supports the same periods for exact instants, resolved in the column timezone. Saved IDs use the `datetime` prefix. |
| [Relative Window](../relative-window/) | `thisQuarter`, `yearToDate`, and non-rolling custom quarter/year windows align to `fiscalYearStart`. |
| [Filter Expressions](/guides/data-filter/filter-expression/) | Supports phrases such as `this fiscal quarter` and `previous fiscal year` on date-aware columns. |
| [Date range filter](../calendar-range/) | Does not create fiscal periods automatically; users can select the fiscal boundary dates explicitly. |
| [Timeline Brush](../timeline-brush/) | Supports an opt-in `fiscalYear` mode that groups source values at the configured fiscal-year boundary. |
| [Time Matrix](../time-matrix/) | Does not use fiscal periods; it filters weekday and hour combinations. |
`fiscalYearStart` is shared date policy, but only controls that define aligned quarter or year semantics consume it.
## Group a Timeline Brush by fiscal year
Add `fiscalYear` to the Timeline Brush's enabled granularities to show fiscal-year bins. The mode uses the effective `fiscalYearStart` for the column, so a February 1 anchor produces bins from February 1 through January 31. It is opt-in to avoid duplicating the ordinary calendar-year mode.
```ts
import {
FILTER_TIMELINE_BRUSH,
createTimelineBrushStructuredFilterType,
} from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'accountingDate',
name: 'Accounting date',
filter: ['date', FILTER_TIMELINE_BRUSH],
},
];
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
},
structuredFilterTypes: [
createTimelineBrushStructuredFilterType({
granularity: 'fiscalYear',
granularities: ['year', 'fiscalYear'],
}),
],
};
```
Use `format` when you want fiscal-specific labels such as `FY 2026`; the formatter receives the resolved fiscal-year start in its context.
## Choose the available fiscal operators
Use `date.operators` as an ordered allowlist. The same unprefixed IDs configure both Date and Datetime menus; Datetime maps them to its prefixed saved IDs internally.
```ts
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
operators: [
'thisFiscalQuarter',
'previousFiscalQuarter',
'nextFiscalQuarter',
'thisFiscalYear',
'previousFiscalYear',
'nextFiscalYear',
],
},
};
```
| Menu label | Date saved ID | Datetime saved ID |
| --- | --- | --- |
| This fiscal quarter | `thisFiscalQuarter` | `datetimeThisFiscalQuarter` |
| Previous fiscal quarter | `previousFiscalQuarter` | `datetimePreviousFiscalQuarter` |
| Next fiscal quarter | `nextFiscalQuarter` | `datetimeNextFiscalQuarter` |
| This fiscal year | `thisFiscalYear` | `datetimeThisFiscalYear` |
| Previous fiscal year | `previousFiscalYear` | `datetimePreviousFiscalYear` |
| Next fiscal year | `nextFiscalYear` | `datetimeNextFiscalYear` |
These operators need no value. They are resolved from the captured reference instant, timezone, and fiscal-year start every time filtering runs.
## Configure individual columns
Put exceptions in `date.columns`. A column inherits the grid policy, then applies its partial override. A column-level `operators` array replaces the global list for that field.
```ts
grid.filter = {
date: {
fiscalYearStart: { month: 1, day: 1 },
operators: ['thisFiscalQuarter', 'thisFiscalYear'],
columns: {
accountingDate: {
fiscalYearStart: { month: 2, day: 1 },
operators: ['thisFiscalQuarter', 'previousFiscalQuarter'],
},
subscriptionDate: {
fiscalYearStart: { month: 7, day: 1 },
operators: ['thisFiscalYear', 'previousFiscalYear'],
},
},
},
};
```
This is useful when subsidiaries, products, or reporting columns follow different calendars.
## Restore fiscal filter state
Date and Datetime fiscal conditions are value-free:
```ts
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
},
multiFilterItems: {
accountingDate: [
{
id: 1,
type: 'thisFiscalQuarter',
relation: 'and',
},
],
postedAt: [
{
id: 2,
type: 'datetimePreviousFiscalYear',
relation: 'and',
},
],
},
};
```
The canonical Filter AST uses the unprefixed operator with the field's temporal value type:
```ts
{
type: 'condition',
field: 'accountingDate',
operator: 'thisFiscalQuarter',
valueType: 'date',
}
```
Do not save resolved start and end dates for these operators. Keeping the relative operator makes a saved view advance into the next fiscal period automatically.
## Use fiscal Relative Windows
Relative Window uses the fiscal calendar for its quarter and year alignment:
```ts
import {
FILTER_RELATIVE_WINDOW,
createRelativeWindowStructuredFilterType,
} from '@revolist/revogrid-pro';
grid.columns = [
{
prop: 'dueDate',
name: 'Due date',
filter: ['date', FILTER_RELATIVE_WINDOW],
},
];
grid.filter = {
date: {
fiscalYearStart: { month: 2, day: 1 },
},
structuredFilterTypes: [
createRelativeWindowStructuredFilterType({
presets: ['thisQuarter', 'yearToDate'],
customWindow: {
units: ['quarter', 'year'],
showRolling: false,
defaultValue: {
direction: 'last',
amount: 1,
unit: 'quarter',
rolling: false,
},
},
}),
],
};
```
- `thisQuarter` resolves the current fiscal quarter.
- `yearToDate` begins at the current fiscal-year start.
- A custom quarter or year with `rolling: false` follows complete fiscal periods.
- With `rolling: true`, the window is a duration from today and fiscal boundaries do not affect it.
Relative Window saves a versioned expression such as `{ version: 1, mode: 'preset', preset: 'thisQuarter' }`, not the resolved dates.
## Remote filtering
Fiscal periods depend on one reference instant and the effective policy for each column. When filtering is server-side, send the `temporalContext` supplied to `beforefilterapply`, pagination, or infinity-scroll loading. It contains:
- the captured `referenceInstant`;
- each temporal column's resolved timezone and `fiscalYearStart`;
- the resolved UTC `start` and exclusive `endExclusive` boundaries.
Using this envelope prevents the browser and server from resolving “this fiscal quarter” at different times or in different timezones.
## Common mistakes
- Configuring only `operators` without `fiscalYearStart`; fiscal operators stay hidden.
- Using zero-based months; January is `1`, not `0`.
- Expecting `thisQuarter` in the Date filter to become fiscal; use `thisFiscalQuarter`.
- Expecting Calendar Range to generate fiscal periods, or Timeline Brush to do so without enabling `fiscalYear`.
- Persisting resolved dates instead of the relative fiscal operator or Relative Window expression.
- Recalculating the period independently on a remote server instead of using `temporalContext`.
## Related guides
- [Date Filter](../date-filter/)
- [Datetime Filter](../datetime-filter/)
- [Relative or Rolling Window Filter](../relative-window/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Fuzzy Search Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/fuzzy-search/
Source: src/content/docs/guides/data-filter/filter-types/fuzzy-search.mdx
Description: Match scalar text approximately with a configurable similarity threshold and ranked preview.
Surface: grid
Use the Fuzzy Search filter for names, titles, and other text where misspellings or transposed characters should still find a result. It applies deterministic Damerau-Levenshtein similarity and previews the best matching unique values.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_FUZZY } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'customer', name: 'Customer', filter: ['string', FILTER_FUZZY] },
];
grid.filter = true;
```
The popup lets users enter a term and adjust its similarity threshold. Keeping `'string'` also exposes exact text operators.
## Operator and state
| Body ID | Saved operator | Value shape |
| ------- | -------------- | ------------------------------------- |
| `fuzzy` | `fuzzy` | `{ term: string, threshold: number }` |
The term is trimmed. The threshold is clamped to `0..1` and defaults to `0.55` when the saved value is missing or invalid. Matching is case-insensitive; exact and contained text score highly, while word-level similarity also contributes.
## Restore a fuzzy filter
```ts
grid.filter = {
multiFilterItems: {
customer: [
{
id: 1,
type: 'fuzzy',
value: { term: 'Jon Smth', threshold: 0.7 },
relation: 'and',
},
],
},
};
```
An empty or whitespace-only term removes the condition.
## Key behavior
- The preview ranks at most five matches by default and considers at most 2,000 unique candidates.
- The predicate and preview use the same deterministic score.
- `null`, `undefined`, objects, and functions do not match; other scalar values are converted to text.
- The preview reads parsed unique column values. With remote or partially loaded rows, provide complete structured-filter aggregates when the preview must represent the full dataset.
The built-in has no per-column option object. Store the desired threshold in the condition value; create or replace a structured registration only when you need a different editor experience.
## Common mistakes
- Do not use a percentage such as `70`; thresholds use the `0..1` range, so use `0.7`.
- Do not expect the preview to scan an unlimited high-cardinality column; its candidate work is deliberately bounded.
- Do not use fuzzy search for exact identifiers. Use the [Token List filter](../token-list/) instead.
## Related guides
- [Structured Filter Types](./../)
- [Token List Filter](../token-list/)
- [Regular Expression Filter](../regular-expression/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Histogram Brush Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/histogram-brush/
Source: src/content/docs/guides/data-filter/filter-types/histogram-brush.mdx
Description: Filter numeric RevoGrid columns by brushing a range over their value distribution.
The histogram brush is useful for prices, durations, revenue, latency, and other numeric fields where users benefit from seeing the distribution before choosing a range. It reads finite parsed values from the column and commits an inclusive numeric interval.
## Quick start
`AdvanceFilterPlugin` registers the built-in type automatically. Add its stable ID to a numeric column:
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_HISTOGRAM_BRUSH,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'product', name: 'Product' },
{
prop: 'revenue',
name: 'Revenue',
filter: ['number', FILTER_HISTOGRAM_BRUSH],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ product: 'Alpha', revenue: 120 },
{ product: 'Beta', revenue: 480 },
{ product: 'Gamma', revenue: 950 },
];
```
## Operator and state
- Type ID: `FILTER_HISTOGRAM_BRUSH` (`'histogramBrush'`)
- Operator ID: `HISTOGRAM_BRUSH_BETWEEN` (`'histogramBrushBetween'`)
- Value: `{ min: number, max: number, inclusive: true }`
Only finite parsed values participate in the histogram and range match. Returning both handles to the complete numeric domain removes the no-op condition. A logarithmic scale falls back to linear when the data includes zero or negative values.
## Restore a range programmatically
Use the same operator and JSON-safe value in `multiFilterItems`:
```ts
import {
HISTOGRAM_BRUSH_BETWEEN,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
revenue: [
{
id: 1,
type: HISTOGRAM_BRUSH_BETWEEN,
value: { min: 250, max: 800, inclusive: true },
relation: 'and',
},
],
},
});
```
## Configuration
Replace the default registration with a configured instance. Passing only the replacement is enough; the plugin keeps the other built-ins.
```ts
import {
createHistogramBrushStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const histogram = createHistogramBrushStructuredFilterType({
bins: 12, // a positive number or 'auto'
scale: 'log',
formatValue: (value) => `$${value.toLocaleString()}`,
formatMatchCount: (count) => `${count.toLocaleString()} rows`,
chart: {
type: 'line', // 'bar' | 'line' | 'area'; default is 'bar'
showPoints: true,
formatTooltip: (bin, { minLabel, maxLabel, selected }) => ({
title: `${minLabel} – ${maxLabel}`,
details: [`${bin.count} rows`, selected ? 'Selected' : 'Outside selection'],
}),
},
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [histogram],
});
```
`bins: 'auto'` uses a deterministic Sturges-rule count, capped for predictable rendering. A fixed count is normalized to the supported range. Use `cellParser` on the column when stored values need conversion before numeric filtering.
The chart and range slider are separate shared controls. Default per-bin tooltips are keyboard
reachable and localize through `histogramBrushChart`, `histogramBrushItem`, `histogramBrushItems`,
`histogramBrushSelected`, and `histogramBrushOutsideSelection` captions. `formatTooltip` can return
different title/detail content for every bin.
## Common mistakes
- Omitting `AdvanceFilterPlugin`; the Core filter plugin does not render structured bodies.
- Adding the operator ID to `column.filter`; columns opt in with `FILTER_HISTOGRAM_BRUSH`, not `HISTOGRAM_BRUSH_BETWEEN`.
- Restoring strings such as `'250'`; the saved bounds must be finite numbers and `inclusive` must be `true`.
- Expecting `scale: 'log'` with zero or negative data; the editor intentionally uses linear geometry instead.
- Reassigning `grid.filter` with only `multiFilterItems` after configuring a custom registration; include `structuredFilterTypes` again or update the plugin state through its public API.
## Related guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/)
- [Statistical Presets](/guides/data-filter/filter-types/statistical-presets/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Number Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/number-filter/
Source: src/content/docs/guides/data-filter/filter-types/number-filter.mdx
Description: Configure Core numeric equality, comparisons, ranges, blank checks, initial state, and parsed values.
Surface: grid
Use the Core number filter for prices, quantities, percentages, scores, and other numeric fields. It needs no Pro plugin.
## Quick start
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = [
{ prop: 'price', name: 'Price', filter: 'number' },
{ prop: 'quantity', name: 'Quantity', filter: 'number' },
];
grid.filter = true;
grid.source = [
{ price: 120, quantity: 2 },
{ price: 80, quantity: 10 },
];
```
## Operators
| Saved ID | Popup label | Meaning |
| ---------- | ------------ | --------------------------- |
| `eqN` | `=` | Equal |
| `neqN` | `!=` | Not equal |
| `gt` | `>` | Greater than |
| `gte` | `>=` | Greater than or equal |
| `lt` | `<` | Less than |
| `lte` | `<=` | Less than or equal |
| `empty` | Is blank | Uses the blank policy |
| `notEmpty` | Is not blank | Inverse of the blank policy |
Numeric comparisons require the parsed cell value to be a number. Use `cellParser` when source values arrive as formatted strings.
## Start with a numeric range
Use two conditions joined by `and`:
```ts
grid.filter = {
multiFilterItems: {
price: [
{ id: 1, type: 'gte', value: 100, relation: 'and' },
{ id: 2, type: 'lte', value: 500, relation: 'and' },
],
},
};
```
## Parse formatted source values
```ts
grid.columns = [
{
prop: 'price',
name: 'Price',
filter: 'number',
cellParser: (model, column) =>
Number(String(model[column.prop] ?? '').replace(/[$,]/g, '')),
},
];
```
Filtering and bounds-based Pro controls use the parsed number. Blank checks still use the original source value and own-property presence.
## Choose Core conditions or a Pro control
Use Core operators when users know the exact threshold they want. Choose a visual control when the data shape should guide them:
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/) for a simple inclusive interval
- [Histogram Brush](/guides/data-filter/filter-types/histogram-brush/) for a range over a visible distribution
- [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/) for stars, percentages, or scores
- [Statistical Presets](/guides/data-filter/filter-types/statistical-presets/) for relative population rules
## Common mistakes
- Use `eqN` for numeric equality and `eq` for text equality.
- Convert numeric strings with `cellParser`; `gt`, `gte`, `lt`, and `lte` do not treat arbitrary strings as numbers.
- Keep `0` distinct from blank unless your application explicitly changes the blank policy.
- Use two conditions for a range; there is no Core `between` operator in the popup.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Text Filter](/guides/data-filter/filter-types/text-filter/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Slider Filter](/guides/data-filter/filter-types/filter-slider/)
---
# Overview
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/overview/
Source: src/content/docs/guides/data-filter/filter-types/overview.mdx
Description: Choose a Core or Pro filter type and open the focused setup, state, and configuration guide for that control.
Surface: grid
Filter types cover Core condition families, Pro value controls, and global quick filtering. Choose the control that matches the column data and the way users need to narrow it.
## Common filter controls
| Type | Best for |
| -------------------------------------------------------- | ----------------------------------------- |
| [Text](../text-filter/) | Names, labels, codes, and descriptions |
| [Number](../number-filter/) | Equality, comparisons, and numeric ranges |
| [Selection](../filter-selection/) | Searchable checklists of known values |
| [Excel-style Selection](../filter-selection-excel-mode/) | Staged Apply/Cancel checklists |
| [Selection Cascade](../filter-selection-cascade/) | Context-aware dependent value lists |
| [Slider](../filter-slider/) | Visual numeric interval selection |
| [Date](../date-filter/) | Calendar days and relative periods |
| [Datetime](../datetime-filter/) | Exact instants and timezone-aware ranges |
| [Boolean](../boolean-filter/) | Strict Yes/No values |
| [Array](../array-filter/) | Empty and non-empty arrays |
| [Quick Filter](../quick-filter/) | Search words across several columns |
Need reporting periods that do not start in January? Configure [Fiscal Date Filters](../fiscal-date-filters/) for Date, Datetime, and aligned Relative Window conditions.
## Structured control quick start
Structured filter types add purpose-built controls to the Pro filter popup while keeping the standard RevoGrid filter model. `AdvanceFilterPlugin` registers all built-ins, but each column opts into the controls it needs.
```ts
import {
AdvanceFilterPlugin,
FILTER_FACETED_LIST,
FILTER_HISTOGRAM_BRUSH,
FILTER_TOKEN_LIST,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'sku', filter: ['string', FILTER_TOKEN_LIST] },
{ prop: 'region', filter: ['string', FILTER_FACETED_LIST] },
{ prop: 'revenue', filter: ['number', FILTER_HISTOGRAM_BRUSH] },
];
grid.filter = true;
```
The first array entry keeps the ordinary string or number operators available. The structured type adds its specialized body above the ordinary condition list. Conditions still serialize as `multiFilterItems` and compile into the canonical `FilterAst`.
## Structured controls
| Type | Best for | Reads column data |
| ------------------------------------------------------------- | ------------------------------------- | ---------------------------- |
| [Token List](../token-list/) | Pasted exact values | No |
| [Fuzzy Search](../fuzzy-search/) | Approximate text matches | Unique values for preview |
| [Regular Expression](../regular-expression/) | Pattern-based text matching | Unique values for preview |
| [Faceted List](../faceted-list/) | Searchable values with counts | All and visible value counts |
| [Chip and Badge Toggles](../chip-badge-toggles/) | Small visual status sets | Unique values |
| [Histogram Brush](../histogram-brush/) | Numeric distributions and ranges | Numeric values and bounds |
| [Rating or Progress Threshold](../rating-progress-threshold/) | Stars, percentages, or scores | No |
| [Statistical Presets](../statistical-presets/) | Top 10%, average, quartiles, outliers | Numeric values and bounds |
| [Calendar Range](../calendar-range/) | Inclusive date-only ranges | No |
| [Relative Window](../relative-window/) | Rolling date periods | No |
| [Timeline Brush](../timeline-brush/) | Date or datetime density and ranges | Temporal values |
| [Time Matrix](../time-matrix/) | Weekday and hour schedules | No |
| [Tri-state Boolean](../tri-state-boolean/) | All, Yes, No, and optional blanks | Value counts |
| [Array and Tags](../array-tags/) | Any, all, none, exact, or empty tags | Array values |
## Common setup pattern
Every focused guide uses the same three steps:
1. Register `AdvanceFilterPlugin` once on the grid.
2. Put the structured type constant in the matching column's `filter` array.
3. Use `multiFilterItems` only when the application needs an initial or restored condition.
```ts
grid.filter = {
multiFilterItems: {
sku: [
{
id: 1,
type: 'tokenListAnyOf',
value: ['EU-100', 'EU-200'],
relation: 'and',
},
],
},
};
```
The saved `type` is the operator ID, not the structured body ID. Each type guide lists both IDs and the exact JSON-safe value shape.
## Configuration and remote data
Built-in factory options belong to the structured type registration, not to an individual column. Replace a built-in registration when you need custom formatting, limits, locale, colors, or other presentation policy.
For pagination and infinite-scroll datasets, the browser may hold only part of the result. Supply complete server aggregates when a data-derived control must represent the entire remote dataset rather than the loaded page.
Read [Custom Structured Filter Types](/guides/data-filter/structured-filter-types/) for registration replacement, lazy aggregates, remote prepared data, and building a custom body.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
---
# Quick Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/quick-filter/
Source: src/content/docs/guides/data-filter/filter-types/quick-filter.mdx
Description: Add a debounced global search that composes with RevoGrid Pro column filters.
Surface: grid
Quick Filter is RevoGrid Pro's global row search. Use it for a toolbar search box that should look across several columns without creating one filter condition per field.
## Quick start
Register `AdvanceFilterPlugin`, configure the searchable columns, and assign the search text to `grid.quickFilter`:
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'customer', name: 'Customer' },
{ prop: 'city', name: 'City' },
{ prop: 'status', name: 'Status', filter: 'string' },
];
grid.filter = true;
grid.quickFilter = 'Lisbon pending';
```
The direct `quickFilter` property is preferred. The older `additionalData.quickFilter` path remains available for compatibility.
## String and object forms
Use a string for the default 150 ms debounce and all visible regular data columns:
```ts
grid.quickFilter = 'Lisbon pending';
```
Use the object form to choose exact column properties or change the debounce:
```ts
grid.quickFilter = {
text: 'Lisbon pending',
columns: ['city', 'status'],
debounceMs: 250,
};
```
| Field | Default | Behavior |
| ------------ | ---------------------------- | ----------------------------------------- |
| `text` | Required | Trimmed and collapsed to single spaces |
| `columns` | Visible regular data columns | Exact, duplicate-free property list |
| `debounceMs` | `150` | Finite values are clamped to at least `0` |
Omitting `columns` searches visible regular columns. Providing `columns: []` deliberately searches no fields and therefore matches no row while the query contains a token.
## How tokens match
Search is case-insensitive. Normalized text is split on spaces, and every token must appear in at least one searched column. Different tokens may match different columns in the same row.
For example, `Lisbon pending` matches a row when `Lisbon` appears in its city and `pending` appears in its status. This is equivalent to:
```text
(customer contains Lisbon OR city contains Lisbon OR status contains Lisbon)
AND
(customer contains pending OR city contains pending OR status contains pending)
```
Quick Filter reads parsed cell values. A column `cellParser` or Pro value-resolution hook therefore affects both global search and advanced filter option values. Dropdown columns are searched by their visible option labels, including every label in a multi-select value, even when the row stores option IDs through `valueKey`.
## Clear the search
Assign an empty or whitespace-only string:
```ts
grid.quickFilter = '';
```
Whitespace normalization means `' '` also clears the active quick filter.
## Observe or intercept changes
Quick-filter changes emit a cancelable event before filtering and a completion event afterward:
```ts
grid.addEventListener('beforequickfilterapply', (event) => {
console.log('Applying', event.detail.quickFilter);
});
grid.addEventListener('afterquickfilterapply', async (event) => {
const visibleRows = await grid.getVisibleSource();
console.log('Applied', event.detail.quickFilter, visibleRows.length);
});
```
Both event details contain the normalized `quickFilter`, current `source`, and columns. Clearing emits the same events with `quickFilter` set to `undefined`. If `beforequickfilterapply` is prevented, local filtering and `afterquickfilterapply` do not run.
## Combine with column filters
Quick Filter is ANDed with active column conditions and the canonical Filter AST:
```ts
grid.filter = {
multiFilterItems: {
amount: [{ id: 1, type: 'gte', value: 200, relation: 'and' }],
},
};
grid.quickFilter = 'Lisbon pending';
```
A row must satisfy the amount condition and every quick-filter token. Quick Filter does not add a synthetic column entry to `multiFilterItems`; Pro composes it into the effective `FilterAst` with `quickContains` conditions.
## Remote data
The Pro Pagination, Infinity Scroll, and Server-Side Grouping plugins intercept `beforequickfilterapply`, prevent local filtering, and pass the normalized quick filter to their remote loaders. Pagination and Infinity Scroll receive it as the optional sixth `loadData` argument. They emit `afterquickfilterapply` only after the winning remote request updates the source.
For a custom remote adapter, prevent `beforequickfilterapply`, cancel or ignore stale requests, send `event.detail.quickFilter` to the backend, replace `grid.source`, and emit a completion signal only for the latest response.
## Common mistakes
- Do not expect `columns: []` to mean all columns; omit `columns` for the default search set.
- Do not split the query yourself. RevoGrid normalizes whitespace and applies its AND-across-tokens semantics.
- Do not expect each token to match the same column; tokens may match different fields in one row.
- Do not read `event.detail.source` as the filtered result. Use `getVisibleSource()` after local completion.
- Do not use the deprecated `additionalData.quickFilter` path in new code.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Filter Events](/guides/data-filter/filter-events/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Rating or Progress Threshold Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/rating-progress-threshold/
Source: src/content/docs/guides/data-filter/filter-types/rating-progress-threshold.mdx
Description: Filter ratings, percentages, and bounded scores with an accessible threshold control.
Use this filter for star ratings, completion percentages, health scores, and other bounded numeric values. Users choose greater-than-or-equal, equal, or less-than-or-equal and then set one threshold.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_RATING_PROGRESS_THRESHOLD,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'task', name: 'Task' },
{
prop: 'rating',
name: 'Rating',
filter: ['number', FILTER_RATING_PROGRESS_THRESHOLD],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ task: 'Design', rating: 4.5 },
{ task: 'Build', rating: 3 },
{ task: 'Review', rating: 5 },
];
```
## Operators and state
| Operator | Stable ID | Meaning |
| -------- | --------------------- | ----------------------------------------------- |
| At least | `RATING_PROGRESS_GTE` | Value is greater than or equal to the threshold |
| Exactly | `RATING_PROGRESS_EQ` | Value equals the threshold |
| At most | `RATING_PROGRESS_LTE` | Value is less than or equal to the threshold |
The condition value is one finite number. The type ID is `FILTER_RATING_PROGRESS_THRESHOLD` (`'ratingProgressThreshold'`). Values are clamped to `0…max` and aligned to the configured step.
## Restore a threshold programmatically
```ts
import {
RATING_PROGRESS_GTE,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
rating: [
{
id: 1,
type: RATING_PROGRESS_GTE,
value: 4,
relation: 'and',
},
],
},
});
```
## Configuration
The default presentation is stars with `max: 5` and `step: 1`. Percent and score presentations default to a maximum of `100`.
```ts
import {
createRatingProgressThresholdStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const progress = createRatingProgressThresholdStructuredFilterType({
unit: 'percent', // 'stars', 'percent', or 'score'
max: 100,
step: 5,
formatValue: (value) => `${value}%`,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [progress],
});
```
`max` and `step` must be positive. A step larger than the maximum is normalized to the maximum. `formatValue` changes the displayed value text; it does not transform source values.
## Common mistakes
- Using `FILTER_RATING_PROGRESS_THRESHOLD` as the condition operator; saved conditions use one of the three `RATING_PROGRESS_*` operator IDs.
- Supplying a string threshold such as `'80'`; restore a finite number.
- Using `formatValue` to convert a `0…1` source into a percentage. Use `cellParser` for conversion, then configure the visual domain.
- Choosing `RATING_PROGRESS_EQ` with fractional data while using a step that cannot select those fractions.
- Omitting the configured registration when replacing the complete `grid.filter` object later.
## Related guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Histogram Brush](/guides/data-filter/filter-types/histogram-brush/)
- [Statistical Presets](/guides/data-filter/filter-types/statistical-presets/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Regular Expression Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/regular-expression/
Source: src/content/docs/guides/data-filter/filter-types/regular-expression.mdx
Description: Match or exclude scalar text with a validated regular expression and bounded preview.
Surface: grid
Use the Regular Expression filter when users need an explicit text pattern, such as an invoice prefix, an email domain, or a controlled identifier format.
## Quick start
```ts
import { AdvanceFilterPlugin, FILTER_REGEX } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'invoiceId', name: 'Invoice ID', filter: ['string', FILTER_REGEX] },
];
grid.filter = true;
```
The popup validates the pattern before committing it and previews matching unique values.
## Operators and state
| UI mode | Saved operator | Value shape |
| ---------------------- | ----------------- | ------------------------------------ |
| Matches pattern | `regexMatches` | `{ pattern: string, flags: string }` |
| Does not match pattern | `regexNotMatches` | `{ pattern: string, flags: string }` |
Supported flags are `i` for case-insensitive matching and `m` for multiline anchors. Flags are normalized into stable `im` order and duplicates are removed.
## Restore a regular expression
```ts
grid.filter = {
multiFilterItems: {
invoiceId: [
{
id: 1,
type: 'regexMatches',
value: { pattern: '^INV-\\d{6}$', flags: 'i' },
relation: 'and',
},
],
},
};
```
An empty pattern removes the condition. Invalid patterns remain in the editor without replacing the last valid condition.
## Validation and preview
RevoGrid rejects invalid expressions and common nested-quantifier patterns that can cause catastrophic backtracking. The preview shows two matching unique values by default and scans at most 10,000 unique scalar values. Matching uses parsed scalar values; `null`, `undefined`, objects, and functions do not match either operator.
For remote or partially loaded rows, provide complete unique-value aggregates when the preview must describe the full result set. The saved pattern and flags remain the remote execution contract.
## Common mistakes
- Escape backslashes for JavaScript strings. A regex digit class is written as `\\d` inside the saved string.
- Do not add unsupported flags such as `g`, `s`, or `u`; only `i` and `m` are retained.
- Do not expect an invalid edit to clear a working filter; the last valid condition stays active.
- Do not use arbitrary user-supplied patterns without considering backend regex compatibility when filtering remotely.
## Related guides
- [Structured Filter Types](./../)
- [Fuzzy Search Filter](../fuzzy-search/)
- [Token List Filter](../token-list/)
- [Remote aggregates and custom structured filters](/guides/data-filter/structured-filter-types/)
---
# Relative or Rolling Window Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/relative-window/
Source: src/content/docs/guides/data-filter/filter-types/relative-window.mdx
Description: Filter dates with saved expressions such as last seven days, this quarter, or a custom rolling window.
Use relative windows for dashboards, queues, renewals, overdue work, and saved views that should move with the current date. Unlike a fixed calendar range, the saved state is an expression that resolves against the filter run's captured reference date.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_RELATIVE_WINDOW,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'task', name: 'Task' },
{ prop: 'dueDate', name: 'Due', filter: ['date', FILTER_RELATIVE_WINDOW] },
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ task: 'Renew license', dueDate: '2026-08-24' },
{ task: 'Send report', dueDate: '2026-08-26' },
{ task: 'Plan launch', dueDate: '2026-09-10' },
];
```
## Operator and state
Both the type and operator use the stable ID `'relativeWindow'`, exported as `FILTER_RELATIVE_WINDOW` and `RELATIVE_WINDOW_OPERATOR`.
Saved values are versioned expressions:
```ts
{ version: 1, mode: 'preset', preset: 'last7Days' }
{
version: 1,
mode: 'custom',
direction: 'next',
amount: 2,
unit: 'week',
rolling: false,
}
```
Preset IDs are `last7Days`, `today`, `thisMonth`, `thisQuarter`, `yearToDate`, and `overdue`. Custom directions are `last` and `next`; units are `day`, `week`, `month`, `quarter`, and `year`.
## Restore a relative window
```ts
import {
RELATIVE_WINDOW_OPERATOR,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
dueDate: [
{
id: 1,
type: RELATIVE_WINDOW_OPERATOR,
value: { version: 1, mode: 'preset', preset: 'last7Days' },
relation: 'and',
},
],
},
});
```
The expression remains relative. Each filtering run captures one reference instant, so every row and date-aware condition in that run uses the same “today.”
## Configuration
The shortcut row and custom-window controls are independently configurable. Built-in preset IDs can be hidden, reordered, or mixed with application-specific shortcuts:
```ts
import {
createRelativeWindowStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const relativeWindow = createRelativeWindowStructuredFilterType({
locale: 'en-GB',
dateFormat: { day: '2-digit', month: 'short', year: 'numeric' },
presets: [
'today',
'thisMonth',
{
id: 'next-fortnight',
label: 'Next 14 days',
title: 'Rolling from today',
expression: {
version: 1,
mode: 'custom',
direction: 'next',
amount: 14,
unit: 'day',
rolling: true,
},
},
'overdue',
],
customWindow: {
directions: ['last', 'next'],
units: ['day', 'week', 'month'],
showRolling: true,
defaultValue: {
direction: 'next',
amount: 2,
unit: 'week',
rolling: false,
},
},
showHeading: true,
showPreview: true,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [relativeWindow],
date: {
timezoneMode: 'organization',
organizationTimeZone: 'Europe/Lisbon',
weekStartsOn: 1,
fiscalYearStart: { month: 2, day: 1 },
},
});
```
Custom shortcuts do not extend or bypass the date-expression model. Each button is backed by a normal validated `RelativeDateWindowExpression`, and selecting it persists only that expression. Its `id` identifies the button; `label`, `ariaLabel`, `title`, and `disabled` control its presentation. A matching custom label is also used in the compact condition summary.
### Visibility and allowed values
| Option | Behavior |
| --- | --- |
| `presets` | Ordered built-in IDs and custom shortcuts. Use `false` or `[]` to hide the shortcut row. |
| `customWindow` | Custom editor settings. Use `false` to hide the whole Last/Next, amount, unit, and rolling section. |
| `customWindow.directions` | Allowed direction choices: `last`, `next`, or both. |
| `customWindow.units` | Allowed unit choices: `day`, `week`, `month`, `quarter`, and/or `year`. |
| `customWindow.showRolling` | Set to `false` to hide the rolling switch and keep the configured/default rolling policy. |
| `customWindow.defaultValue` | Initial direction, amount, unit, and rolling policy when no custom expression is saved. |
| `showHeading` | Set to `false` to hide the in-body title and description while retaining an accessible section label. |
| `showPreview` | Set to `false` to hide the resolved absolute-date preview. |
| `locale`, `dateFormat` | Format preview text only. |
For example, a compact preset-only editor can omit every other section:
```ts
createRelativeWindowStructuredFilterType({
presets: ['today', 'last7Days', 'overdue'],
customWindow: false,
showHeading: false,
showPreview: false,
});
```
The default remains unchanged when these options are omitted: all six built-ins, the complete custom editor, rolling switch, heading, and preview are shown. Duplicate shortcut IDs or expressions and invalid custom expressions are ignored. An empty direction or unit list hides the custom editor because it cannot produce a valid expression.
`locale` and `dateFormat` affect preview text only. Timezone decides the reference civil date, `weekStartsOn` controls calendar-week windows, and `fiscalYearStart` controls quarter and year policy where applicable.
For a custom expression, `amount` must be an integer from `1` through `10,000`. `rolling: true` uses a rolling duration; `rolling: false` resolves complete calendar units.
## Common mistakes
- Saving the preview's absolute `{ from, to }` range instead of the versioned expression; that turns a moving saved view into a fixed range.
- Omitting `version: 1` or using unsupported preset, direction, or unit strings.
- Treating `last7Days` as seven 24-hour durations; it is a civil-date window resolved in the column timezone.
- Using an offset-free datetime source for date filtering without first deciding the intended timezone and source contract.
- Assuming `locale` changes week start, fiscal policy, or timezone; configure those under `filter.date`.
## Related guides
- [Fiscal Date Filters](/guides/data-filter/filter-types/fiscal-date-filters/)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Calendar Range](/guides/data-filter/filter-types/calendar-range/)
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Statistical Presets Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/statistical-presets/
Source: src/content/docs/guides/data-filter/filter-types/statistical-presets.mdx
Description: Filter numeric columns with stable top-percentile, average, quartile, outlier, and negative-value presets.
Statistical presets turn common analytical questions into one-click filters. Use them for metrics where users frequently ask for the top 10%, values above average, the bottom quartile, two-sigma outliers, or negative values.
## Quick start
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import {
AdvanceFilterPlugin,
FILTER_STATISTICAL_PRESETS,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'team', name: 'Team' },
{
prop: 'margin',
name: 'Margin',
filter: ['number', FILTER_STATISTICAL_PRESETS],
},
];
grid.filter = true;
document.querySelector('#app')!.append(grid);
grid.source = [
{ team: 'North', margin: 18 },
{ team: 'South', margin: -4 },
{ team: 'West', margin: 31 },
{ team: 'East', margin: 9 },
];
```
## Operator and state
- Type ID: `FILTER_STATISTICAL_PRESETS` (`'statisticalPresets'`)
- Operator ID: `STATISTICAL_PRESET_OPERATOR` (`'statisticalPreset'`)
- Presets: `top10Percent`, `aboveAverage`, `bottomQuartile`, `outliersTwoSigma`, and `negativeOnly`
- Value: `{ preset, lower, upper, lowerInclusive, upperInclusive, outside }`
The popup computes statistics from the current finite parsed values. When a preset is selected, the resolved numeric boundaries are serialized into the condition. Those boundaries stay stable when the source later changes; reopen and select the preset again to recompute it.
## Restore a preset programmatically
Restore the resolved boundaries, not only the preset name:
```ts
import {
STATISTICAL_PRESET_OPERATOR,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.filter = defineAdvancedFilterConfig({
multiFilterItems: {
margin: [
{
id: 1,
type: STATISTICAL_PRESET_OPERATOR,
value: {
preset: 'negativeOnly',
lower: null,
upper: 0,
lowerInclusive: false,
upperInclusive: false,
outside: false,
},
relation: 'and',
},
],
},
});
```
`null` represents an unbounded side. `outside: true` matches values outside the serialized lower and upper boundaries and is used by the two-sigma outlier preset.
## Configuration
```ts
import {
createStatisticalPresetsStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const presets = createStatisticalPresetsStructuredFilterType({
formatValue: (value) => `${value.toFixed(1)}%`,
formatMatchCount: (count) => `${count.toLocaleString()} matches`,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [presets],
});
```
Both callbacks change presentation only. Use `cellParser` when the stored value must be normalized before statistics and comparisons are calculated.
## Common mistakes
- Saving only `{ preset: 'top10Percent' }`; all boundary and inclusion fields are required for a valid restored condition.
- Expecting an applied preset to move automatically as rows change; its serialized boundaries are deliberately stable.
- Including `NaN`, `Infinity`, or nonnumeric strings in expected statistics; only finite parsed values participate.
- Treating `negativeOnly` as “zero or below”; its upper boundary is exclusive, so zero does not match.
- Recomputing boundaries independently with different quantile or variance rules when the application can save the condition emitted by the grid.
## Related guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Histogram Brush](/guides/data-filter/filter-types/histogram-brush/)
- [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
---
# Text Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/text-filter/
Source: src/content/docs/guides/data-filter/filter-types/text-filter.mdx
Description: Configure Core text filtering with contains, begins-with, equality, blank checks, initial state, and parsed values.
Surface: grid
Use the Core text filter for names, labels, codes, descriptions, and other values that users should match by typing. It needs no Pro plugin.
## Quick start
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = [
{ prop: 'name', name: 'Name', filter: 'string' },
{ prop: 'status', name: 'Status', filter: 'string' },
];
grid.filter = true;
grid.source = [
{ name: 'Anna Silva', status: 'Open' },
{ name: 'John Smith', status: 'Pending' },
];
```
Open a column's filter button, choose **Contains**, enter text, and apply. Text equality, begins-with, and contains matching are case-insensitive.
## Operators
| Saved ID | Popup label | Needs a value |
| ------------- | ---------------- | ------------- |
| `contains` | Contains | Yes |
| `notContains` | Does not contain | Yes |
| `begins` | Begins with | Yes |
| `eq` | Equal | Yes |
| `notEq` | Not equal | Yes |
| `empty` | Is blank | No |
| `notEmpty` | Is not blank | No |
An empty text input is a no-op. Blank operators use the grid's source-aware [blank policy](/guides/data-filter/blank-values/), not a display-string conversion.
## Start with a condition
```ts
grid.filter = {
multiFilterItems: {
status: [{ id: 1, type: 'eq', value: 'Open', relation: 'and' }],
},
};
```
Combine alternatives within one column with `or`:
```ts
grid.filter = {
multiFilterItems: {
status: [
{ id: 1, type: 'eq', value: 'Open', relation: 'or' },
{ id: 2, type: 'eq', value: 'Pending', relation: 'and' },
],
},
};
```
Different columns always combine with AND in `multiFilterItems`. Use [Filter AST](/guides/data-filter/filter-ast/) for cross-column OR.
## Filter a normalized value
Conditions receive `cellParser` output when the stored model needs normalization:
```ts
grid.columns = [
{
prop: 'customerCode',
name: 'Customer code',
filter: 'string',
cellParser: (model, column) => String(model[column.prop] ?? '').trim(),
},
];
```
## Common mistakes
- Use `eq`, not the numeric `eqN`, for a text column.
- Do not expect text conditions on different columns to combine with OR.
- Use [Selection Filter](/guides/data-filter/filter-types/filter-selection/) when users should choose from repeated known values instead of typing.
- Use [Token List](/guides/data-filter/filter-types/token-list/) when users paste several exact values at once.
## Related guides
- [Filtering Quick Start](/guides/data-filter/filter-basics/)
- [Number Filter](/guides/data-filter/filter-types/number-filter/)
- [Blank Values](/guides/data-filter/blank-values/)
- [Filter Header](/guides/data-filter/filter-header/)
---
# Time Matrix Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/time-matrix/
Source: src/content/docs/guides/data-filter/filter-types/time-matrix.mdx
Description: Filter timestamp columns by selected weekday and whole-hour combinations in a resolved timezone.
Surface: grid
Use the time matrix for activity schedules, support coverage, usage windows, or any timestamp question expressed as weekday plus hour. Users can select individual cells or start from Business hours, Weekends, and Nights presets.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_TIME_MATRIX,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'occurredAt',
name: 'Activity time',
filter: ['datetime', FILTER_TIME_MATRIX],
},
];
grid.source = [
{ occurredAt: '2026-08-24T09:30:00Z' },
{ occurredAt: '2026-08-25T22:15:00Z' },
];
grid.filter = true;
```
Row values must resolve to instants (`Date`, an ISO timestamp, or a numeric timestamp). The filter converts each instant to weekday and hour in the condition's timezone.
## Operator and saved state
- Structured type: `FILTER_TIME_MATRIX` (`'timeMatrix'`)
- Operator: `TIME_MATRIX_OPERATOR` (`'timeMatrix'`)
- Value: `{ version: 1, timeZone, ranges }`
Each range uses Sunday-based `weekday` (`0` is Sunday), an inclusive `startHour`, and an exclusive `endHour`.
```ts
grid.filter = {
multiFilterItems: {
occurredAt: [
{
id: 1,
type: 'timeMatrix',
value: {
version: 1,
timeZone: 'Europe/Lisbon',
ranges: [
{ weekday: 1, startHour: 9, endHour: 17 },
{ weekday: 2, startHour: 9, endHour: 17 },
],
},
relation: 'and',
},
],
},
};
```
Use that JSON-safe value for programmatic restore. Adjacent selected hours are compacted into ranges. Selecting no cells or every hour of every day removes the no-op condition.
## Configure schedule presentation
```ts
import {
createTimeMatrixStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const activityTime = createTimeMatrixStructuredFilterType({
timeZone: 'Europe/Lisbon',
locale: 'en-GB',
weekStartsOn: 1,
useAmPm: false,
// Context shading in the matrix.
workingHours: { startHour: 8, endHour: 18 },
weekendDays: [5, 6],
// Shortcut buttons are configured independently.
presets: {
businessHours: {
days: [1, 2, 3, 4],
startHour: 9,
endHour: 17,
label: 'Core hours',
description: 'Monday–Thursday, 09:00–17:00',
},
weekends: {}, // Inherits weekendDays ([5, 6]).
nights: {
days: [0, 1, 2, 3, 4, 5, 6],
startHour: 20,
endHour: 6, // An end before the start creates an overnight window.
},
},
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [activityTime],
});
```
`workingHours` and `weekendDays` shade schedule context; selected cells alone determine filtering. Defaults are 06:00–22:00 and Sunday/Saturday. If `timeZone` or `weekStartsOn` is omitted, it inherits the column's resolved date-filter settings.
The built-in shortcut defaults remain Business hours (Monday–Friday, 08:00–20:00), Weekends (the configured `weekendDays`, all day), and Nights (every day, 22:00–06:00).
| Option | Purpose |
| --- | --- |
| `workingHours` | Start/end boundaries for outside-working-hours shading. |
| `weekendDays` | Sunday-based days used for weekend shading and, by default, the Weekends shortcut. |
| `workingHours: false` | Disable outside-working-hours shading and remove that legend item. |
| `weekendDays: []` | Disable weekend shading and remove that legend item. |
| `showLegend: false` | Hide the complete schedule-context legend. |
| `presets.businessHours` | Configure `days`, `startHour`, `endHour`, `label`, and `description`, or use `false` to hide it. |
| `presets.weekends` | Configure `days`, `label`, and `description`, or use `false` to hide it. An empty object inherits `weekendDays`. |
| `presets.nights` | Configure `days`, an overnight-capable hour window, `label`, and `description`, or use `false` to hide it. |
| `presets: false` | Hide the complete shortcut row while retaining direct matrix selection. |
All days use Sunday-based numbering (`0`–`6`), and all hour boundaries are whole hours (`0`–`24`). A preset window whose `endHour` is earlier than `startHour` wraps across midnight. Invalid days or hour windows fall back to that preset's defaults.
For example, keep Business hours and hide the other shortcuts:
```ts
const activityTime = createTimeMatrixStructuredFilterType({
presets: {
weekends: false,
nights: false,
},
});
```
## Localization
`TIME_MATRIX_LOCALIZATION` exports the complete English fallback and stable keys. Override the operator through `filterNames` and authored UI/accessibility text through `captions`.
```ts
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [activityTime],
localization: {
filterNames: {
timeMatrix: 'Matches schedule',
},
captions: {
timeMatrixTitle: 'Coverage schedule',
timeMatrixDescription: 'Select included weekday and hour combinations.',
timeMatrixClear: 'Clear schedule',
'timeMatrixPreset.businessHours': 'Office hours',
'timeMatrixPresetDescription.businessHours':
'Monday–Friday office coverage',
},
},
});
```
Other stable captions cover the grid and legend, selected/unselected cell states, summaries, and all preset labels/descriptions. Per-instance `label` and `description` values on a preset take precedence over localization captions. Dynamic messages use named placeholders including `{day}`, `{start}`, `{end}`, `{state}`, `{hours}`, `{days}`, and `{timeZone}`. Weekday and clock text comes from `locale` and `useAmPm`, not fixed translated strings.
## Common mistakes
- Do not store local clock text such as `'09:30'`; the predicate needs an instant.
- Do not treat `endHour` as inclusive. `{ startHour: 9, endHour: 17 }` covers 09:00 through 16:59.
- Do not use locale weekday numbering in saved state; persisted weekday is always Sunday-based.
- Do not expect `workingHours` or `weekendDays` shading to add selected cells. Configure shortcut selections through `presets`.
- Use a valid IANA timezone. Invalid `timeZone` configuration throws.
## Related guides
- [Structured Filter Types](./)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Timeline Brush Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/timeline-brush/
Source: src/content/docs/guides/data-filter/filter-types/timeline-brush.mdx
Description: Filter date and datetime columns by brushing an inclusive range over a compact temporal distribution.
Surface: grid
Use the timeline brush when users need to see where date or datetime values are concentrated before choosing a range. It bins parsed column values by day, week, or month and keeps the committed condition independent from the chart UI.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_TIMELINE_BRUSH,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'createdAt',
name: 'Created',
filter: ['datetime', FILTER_TIMELINE_BRUSH],
},
];
grid.source = [
{ createdAt: '2026-08-20T09:30:00Z' },
{ createdAt: '2026-08-24T16:15:00Z' },
];
grid.filter = true;
```
Use `'date'` instead of `'datetime'` for ISO date-only values such as `2026-08-20`. Selecting the complete displayed domain is a no-op and removes the timeline condition.
## Operator and saved state
- Structured type: `FILTER_TIMELINE_BRUSH` (`'timelineBrush'`)
- Operator: `TIMELINE_BETWEEN` (`'timelineBetween'`)
- Value: `{ from, to, inclusive: true }`
Date columns store inclusive ISO dates. Datetime columns store inclusive ISO instants.
```ts
grid.filter = {
multiFilterItems: {
createdAt: [
{
id: 1,
type: 'timelineBetween',
value: {
from: '2026-08-20T00:00:00.000Z',
to: '2026-08-24T23:59:59.999Z',
inclusive: true,
},
relation: 'and',
},
],
},
};
```
This is also the programmatic restore shape. Keep the operator ID in `type`; the structured type ID belongs in the column definition.
## Configure the timeline
Replace the built-in registration with a configured instance. The stable ID stays the same, so every column using `FILTER_TIMELINE_BRUSH` receives these options.
```ts
import {
createTimelineBrushStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const timeline = createTimelineBrushStructuredFilterType({
granularity: 'week', // 'day' | 'week' | 'month'
binCap: 48,
timeZone: 'Europe/Lisbon',
locale: 'en-GB',
format: { day: '2-digit', month: 'short', year: 'numeric' },
chart: {
type: 'area', // 'bar' | 'line' | 'area'; default is 'bar'
height: 72,
showPoints: true,
formatTooltip: (bin, { fromLabel, toLabel, selected }) => ({
title: fromLabel === toLabel ? fromLabel : `${fromLabel} – ${toLabel}`,
details: [`${bin.count} events`, selected ? 'Selected' : 'Outside selection'],
}),
},
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [timeline],
date: { weekStartsOn: 1 },
});
```
`timeZone` and `weekStartsOn` inherit the column's resolved `filter.date` settings when the factory does not override them. Dense spans are deterministically coarsened to stay within `binCap`.
The chart is a reusable presentation control; the dual-range slider remains independent. Its default
tooltips are keyboard reachable and localize through `timelineBrushChart`, `timelineBrushItem`,
`timelineBrushItems`, `timelineBrushSelected`, and `timelineBrushOutsideSelection` captions. Use
`formatTooltip` for domain-specific rows or terminology.
For a partially loaded remote source, provide complete temporal aggregates or exported `TimelineBrushPreparedData`; otherwise the distribution can only describe rows loaded in the browser.
## Common mistakes
- Do not restore `type: 'timelineBrush'`; conditions use `type: 'timelineBetween'`.
- Do not mix date-only and datetime values in one column. Declare the base family explicitly.
- Do not pass display-formatted dates unless a `cellParser` converts them into valid date or instant values.
- Do not expect a full-domain selection to remain in state; it intentionally clears the no-op condition.
- For remote pagination, do not present page-local bins as the complete dataset distribution.
## Related guides
- [Structured Filter Types](./)
- [Date and Datetime Filters](/guides/data-filter/filter-types/date-filter/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Token List Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/token-list/
Source: src/content/docs/guides/data-filter/filter-types/token-list.mdx
Description: Filter scalar values against an exact list pasted or entered as tokens.
Surface: grid
Use the Token List filter when users already have an exact list of identifiers, SKUs, email addresses, or other scalar values. They can paste comma-, newline-, or tab-separated text instead of selecting values from the column.
## Quick start
`AdvanceFilterPlugin` registers the built-in body. Add `FILTER_TOKEN_LIST` to the column's filter families:
```ts
import { AdvanceFilterPlugin, FILTER_TOKEN_LIST } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'sku', name: 'SKU', filter: ['string', FILTER_TOKEN_LIST] },
];
grid.filter = true;
```
Keeping `'string'` in the array also exposes the ordinary text operators. Use only `FILTER_TOKEN_LIST` when the popup should contain only the token workflow.
## Operators and state
| UI mode | Saved operator | Value shape | Matches |
| --------- | ----------------- | ----------- | --------------------------------------------------------- |
| Is any of | `tokenListAnyOf` | `string[]` | A non-null scalar whose string value is one of the tokens |
| None of | `tokenListNoneOf` | `string[]` | Any value that does not match the **Is any of** rule |
Input is trimmed, empty tokens are removed, duplicates keep their first position, and comparisons are exact and case-sensitive. Objects and functions never match **Is any of**. The body does not scan source rows, so it is also suitable when the complete value list comes from outside the grid.
## Restore a token list
Use the operator ID—not `FILTER_TOKEN_LIST`—inside `multiFilterItems`:
```ts
grid.filter = {
multiFilterItems: {
sku: [
{
id: 1,
type: 'tokenListAnyOf',
value: ['EU-100', 'EU-200', 'US-500'],
relation: 'and',
},
],
},
};
```
Assign an empty `multiFilterItems` object to clear restored filters:
```ts
grid.filter = { multiFilterItems: {} };
```
## Key behavior
- Pasting `EU-100, EU-200\nUS-500` creates three tokens.
- Numeric and boolean cell values are compared through their string representation.
- The condition value is a JSON-safe array and can be transported with the canonical filter AST.
- Removing every token removes the owned condition instead of saving an empty no-op filter.
## Common mistakes
- Do not save `type: 'tokenList'`; that is the body ID. Save `tokenListAnyOf` or `tokenListNoneOf`.
- Do not expect fuzzy or case-insensitive matching. Use the [Fuzzy Search filter](../fuzzy-search/) for approximate text.
- Do not pass one comma-containing value when commas are meaningful inside the value; pasted commas are delimiters.
## Related guides
- [Structured Filter Types](./../)
- [Faceted List Filter](../faceted-list/)
- [Regular Expression Filter](../regular-expression/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
---
# Tri-state Boolean Filter
URL: https://pro.rv-grid.com/guides/data-filter/filter-types/tri-state-boolean/
Source: src/content/docs/guides/data-filter/filter-types/tri-state-boolean.mdx
Description: Filter strict boolean values with All, Yes, No, and an optional blanks-as-No policy.
Surface: grid
Use the tri-state boolean filter when a boolean column needs one compact control for **All**, **Yes**, and **No**. It is especially useful when missing values may optionally belong to the No result.
## Quick start
```ts
import {
AdvanceFilterPlugin,
FILTER_TRI_STATE_BOOLEAN,
} from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{
prop: 'approved',
name: 'Approved',
filter: ['boolean', FILTER_TRI_STATE_BOOLEAN],
},
];
grid.source = [{ approved: true }, { approved: false }, { approved: null }];
grid.filter = true;
```
The editor reports Yes, No, and blank counts from provider-backed `valueCounts`. Choosing All removes its condition.
## Operator and saved state
- Structured type: `FILTER_TRI_STATE_BOOLEAN` (`'triStateBoolean'`)
- Operator: `TRI_STATE_BOOLEAN_OPERATOR` (`'triStateBoolean'`)
- Value: `{ state: 'yes' | 'no', coerceBlank: boolean }`
Yes matches strict `true`. No matches strict `false`; with `coerceBlank: true`, it also matches values classified as blank by the active Core blank policy.
```ts
grid.filter = {
multiFilterItems: {
approved: [
{
id: 1,
type: 'triStateBoolean',
value: { state: 'no', coerceBlank: true },
relation: 'and',
},
],
},
};
```
Use the same condition for programmatic restore. There is no saved `{ state: 'all' }` condition because All means no filtering.
## Configure blank behavior
The blank toggle delegates to the shared Core blank-semantics policy. Configure that policy at grid level and override individual columns only when necessary.
```ts
grid.filter = {
blankSemantics: {
null: true,
undefined: true,
emptyString: true,
whitespaceOnlyString: false,
missingProperty: true,
},
};
grid.columns = [
{
prop: 'approved',
filter: ['boolean', FILTER_TRI_STATE_BOOLEAN],
blankSemantics: {
emptyString: false,
},
},
];
```
The structured body is already built in and has no presentation factory options. Customize its operator label with `localization.filterNames.triStateBoolean` and body captions through the standard advanced-filter localization config.
## Common mistakes
- Do not use strings such as `'true'` and `'false'`; Yes and No match boolean primitives strictly.
- Do not persist All as an active condition; remove the column's condition instead.
- Do not assume `coerceBlank` means every falsy value. It uses configured blank semantics, not JavaScript truthiness.
- Do not duplicate blank rules in application code; set grid- or column-level `blankSemantics`.
- For remote data, provide complete `valueCounts` if the browser holds only a page of rows.
## Related guides
- [Structured Filter Types](./)
- [Boolean Filters](/guides/data-filter/filter-types/boolean-filter/)
- [Blank Value Semantics](/guides/data-filter/blank-values/)
- [Custom and Remote Structured Filters](/guides/data-filter/structured-filter-types/)
---
# Custom Structured Filter Types
URL: https://pro.rv-grid.com/guides/data-filter/structured-filter-types/
Source: src/content/docs/guides/data-filter/structured-filter-types.mdx
Description: Configure the structured filter registry, lazy and remote aggregates, built-in replacements, and custom popup bodies.
Surface: grid
Structured filter types add purpose-built controls to the Pro column-filter popup without replacing RevoGrid's filter model. A type is one **popup body plus the operator IDs it owns**. The grid still owns the popup shell, header, active-state icon, close behavior, **Groups** launcher, ordinary condition list, and optional **Expression** editor.
Start with the [Structured Filter Types overview](/guides/data-filter/filter-types/overview/) when choosing or configuring a built-in. This guide covers the shared registry contract, aggregate access, remote data, built-in replacement, and application-defined types.
The [`Advanced Filtering: Order Explorer`](https://demo.rv-grid.com/filter-showcase/ts) exposes every built-in on a dedicated order column and lets you switch among the TypeScript, React, Vue, and Angular implementations.
## Register and select a type
`AdvanceFilterPlugin` registers all 14 built-ins. Registration alone does not change a column: opt in through the column's existing `filter` field. A column can combine its base family with one or more structured types.
```ts
import {
AdvanceFilterPlugin,
FILTER_FACETED_LIST,
FILTER_HISTOGRAM_BRUSH,
FILTER_TOKEN_LIST,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = [
{ prop: 'project', filter: ['string', FILTER_TOKEN_LIST] },
{ prop: 'region', filter: ['string', FILTER_FACETED_LIST] },
{ prop: 'revenue', filter: ['number', FILTER_HISTOGRAM_BRUSH] },
];
grid.filter = defineAdvancedFilterConfig({
allowDuplicateOperators: false,
});
```
When a structured type is selected, its body appears above the ordinary condition list. Its owned operators are omitted from that list so the popup does not expose duplicate controls, while operators from the column's base family remain available. Multiple structured IDs can be placed in the same `filter` array when their bodies should appear together.
## State, AST, and expression ownership
Every editor reads and commits ordinary `MultiFilterItem` conditions. A condition has the same stable fields used elsewhere by filtering: `id`, `type`, `value`, and `relation`. The registry limits a body to its declared `operatorIds`, preserves unrelated conditions, reuses condition IDs where possible, and removes the owned condition when the control returns to its no-op state.
`AdvanceFilterPlugin` converts those conditions into canonical `FilterAst` condition nodes. Structured values are JSON-safe, so operator IDs and values survive AST and saved-view serialization without an opaque UI-only state layer. The current-column Expression editor and the grouped AST editor remain grid-owned surfaces; a custom structured body does not need to recreate either one.
Operator IDs must be unique across registered structured types. Registering a
second type that claims an operator owned by a different type throws.
Registering the same stable type ID replaces that type, which is how a
configured built-in overrides its default presentation.
## Lazy aggregate access
A body declares only the provider-backed projections it may use through `aggregateNeeds`:
| Need | Result |
| -------------- | -------------------------------------------------- |
| `values` | Parsed column values in row order |
| `uniqueValues` | First-seen unique parsed values |
| `valueCounts` | `{ value, count }` entries |
| `numericRange` | Finite numeric `values`, `count`, `min`, and `max` |
Registering a type and creating its aggregate accessor do not scan rows. The first `context.aggregates.get(need, scope)` call performs the scan and caches it for that popup-body render; built-in bodies that need a projection call it as they render. The default scope is `'all'`; pass `'visible'` to use the provider's currently visible item indexes. Both scopes exclude synthetic rows and resolve values through the grid's parsed-value path.
```ts
const allCounts = context.aggregates?.get('valueCounts');
const visibleCounts = context.aggregates?.get('valueCounts', 'visible');
```
Asking for an undeclared need throws, which keeps data dependencies explicit.
### Complete aggregates for remote rows
Pagination and infinite-scroll grids only keep a window of rows in the browser. Supply server-produced aggregates through `structuredFilterAggregates` so counts, previews, histograms, and presets describe the complete remote result set instead of the loaded page. The callback is lazy and cached once per need, scope, type, and open popup. Return `undefined` when a request should use the local-row fallback.
```ts
import type { StructuredFilterAggregateProvider } from '@revolist/revogrid-pro';
const remoteAggregates: StructuredFilterAggregateProvider = {
complete: true,
get({ typeId, column, need, scope }) {
if (
typeId === FILTER_FACETED_LIST &&
column.prop === 'region' &&
need === 'valueCounts'
) {
return scope === 'visible'
? response.visibleRegionCounts
: response.allRegionCounts;
}
if (
typeId === FILTER_HISTOGRAM_BRUSH &&
column.prop === 'revenue' &&
need === 'numericRange'
) {
return response.revenueRange;
}
return undefined;
},
};
grid.filter = defineAdvancedFilterConfig({
structuredFilterAggregates: remoteAggregates,
});
```
Set `complete: true` for a partially loaded remote row source. A missing aggregate then fails explicitly instead of silently deriving an incomplete result from the loaded page. For very large numeric, timeline, or array columns, implement `getPrepared()` and return the exported compact `HistogramPreparedData`, `StatisticalPresetsPreparedData`, `TimelineBrushPreparedData`, or `{ kind: 'arrayTags', values }` shape. Those bodies use server bins, resolved preset boundaries/counts, timeline buckets, or unique tags directly and do not request row-level arrays.
The filter condition itself remains the remote execution contract: stable operator IDs plus JSON-safe values are forwarded in `multiConditionFilters`. Date-aware filters also include resolved half-open UTC ranges, the captured reference instant, timezone, week start, and fiscal-year anchor in `temporalContext`.
Canonical `filterAst` and its `executionContext` are forwarded by pagination, infinite scroll, and server-side grouping. The execution context includes each column's resolved JSON-safe blank policy. If a column uses a JavaScript `isBlank` callback or an array/tags accessor function, configure the equivalent rule in the backend; functions are intentionally never serialized.
## Built-in guides
Each built-in now has a focused setup, saved-state, configuration, and troubleshooting guide:
| Built-in | Focused guide | Aggregate need |
| ---------------------------- | ------------------------------------------------------------------------------------------- | -------------- |
| Token list | [Token List Filter](/guides/data-filter/filter-types/token-list/) | None |
| Fuzzy search | [Fuzzy Search Filter](/guides/data-filter/filter-types/fuzzy-search/) | `uniqueValues` |
| Regular expression | [Regular Expression Filter](/guides/data-filter/filter-types/regular-expression/) | `uniqueValues` |
| Faceted list | [Faceted List Filter](/guides/data-filter/filter-types/faceted-list/) | `valueCounts` |
| Chip and badge toggles | [Chip and Badge Toggle Filter](/guides/data-filter/filter-types/chip-badge-toggles/) | `uniqueValues` |
| Histogram brush | [Histogram Brush Filter](/guides/data-filter/filter-types/histogram-brush/) | `numericRange` |
| Rating or progress threshold | [Rating or Progress Threshold](/guides/data-filter/filter-types/rating-progress-threshold/) | None |
| Statistical presets | [Statistical Presets Filter](/guides/data-filter/filter-types/statistical-presets/) | `numericRange` |
| Calendar range | [Calendar Range Filter](/guides/data-filter/filter-types/calendar-range/) | None |
| Relative window | [Relative Window Filter](/guides/data-filter/filter-types/relative-window/) | None |
| Timeline brush | [Timeline Brush Filter](/guides/data-filter/filter-types/timeline-brush/) | `values` |
| Time matrix | [Time Matrix Filter](/guides/data-filter/filter-types/time-matrix/) | None |
| Tri-state boolean | [Tri-state Boolean Filter](/guides/data-filter/filter-types/tri-state-boolean/) | `valueCounts` |
| Array and tags | [Array and Tags Filter](/guides/data-filter/filter-types/array-tags/) | `values` |
## Configure built-ins by replacing registrations
Factory options belong to a structured type registration, not to the column. Replace the matching built-in in `structuredFilterTypes`, then opt columns into the same stable ID.
```ts
import {
BUILT_IN_STRUCTURED_FILTER_TYPES,
FILTER_HISTOGRAM_BRUSH,
createHistogramBrushStructuredFilterType,
defineAdvancedFilterConfig,
} from '@revolist/revogrid-pro';
const histogram = createHistogramBrushStructuredFilterType({
bins: 16,
formatValue: (value) => `${value.toFixed(1)} ms`,
});
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: BUILT_IN_STRUCTURED_FILTER_TYPES.map((type) =>
type.id === FILTER_HISTOGRAM_BRUSH ? histogram : type,
),
});
```
You can also pass only the replacement in `structuredFilterTypes`; `AdvanceFilterPlugin` registers the built-ins first and then replaces the matching stable ID.
## Create a custom structured type
A custom type needs two coordinated registrations:
1. `StructuredFilterType` defines the popup body and the operator IDs it may commit.
2. `customFilters` defines the predicate for each operator and maps it back to the same `columnFilterType` ID.
```ts
import {
AdvanceFilterPlugin,
defineAdvancedFilterConfig,
structuredFilterButton,
structuredFilterSection,
type StructuredFilterType,
} from '@revolist/revogrid-pro';
const PRIORITY_PICKER = 'priorityPicker';
const PRIORITY_IS = 'priorityIs';
const priorityPicker: StructuredFilterType = {
id: PRIORITY_PICKER,
operatorIds: [PRIORITY_IS],
headerControl: {
kind: 'popup',
template: (h, { presentation }) => h(
'span',
{ class: 'priority-header-badge' },
presentation.summary,
),
},
render(context) {
const selected = context.conditions[0]?.value;
return structuredFilterSection(
'Priority',
['low', 'medium', 'high'].map((value) =>
structuredFilterButton({
label: value,
pressed: selected === value,
onClick: () =>
void context.commit(
selected === value ? [] : [{ type: PRIORITY_IS, value }],
),
}),
),
'Choose one priority.',
);
},
};
grid.plugins = [AdvanceFilterPlugin];
grid.filter = defineAdvancedFilterConfig({
structuredFilterTypes: [priorityPicker],
customFilters: {
[PRIORITY_IS]: {
columnFilterType: PRIORITY_PICKER,
name: 'Is priority',
func: (cellValue, expected) => cellValue === expected,
},
},
localization: {
filterNames: { [PRIORITY_IS]: 'Is priority' },
},
});
grid.columns = [{ prop: 'priority', filter: ['string', PRIORITY_PICKER] }];
```
Declare `aggregateNeeds` only if the body derives choices or summaries from grid data, and call `context.aggregates.get()` only for those declared needs. Use `context.labels.operator()` and `context.labels.caption()` for localized text, and use `context.commit()` (or its equivalent alias `replaceConditions()`) rather than mutating filter state directly. `headerControl` is optional: a popup control supplies non-interactive trigger content, while a resolver may return an inline interactive control using the same controlled context.
The built-in types also implement the optional `mount(host, context)` adapter. It mounts the same controlled editor inside a grouped-filter rule, with the same aggregates, remote `preparedData`, date settings, labels, and condition contract as the regular popup. Custom structured types should implement `mount` when their JSON value needs an interactive grouped editor; without it, grouped mode preserves the value and shows an explicit unavailable-editor state instead of coercing an object to text.
Selection rules are handled similarly by the grouped builder itself: `in` / `notIn` use the existing provider-backed option source as an autocomplete with removable tags. Values are never parsed by commas, so a value such as `"North, America"` remains one value.
The plugin also exposes `registerStructuredFilterType()`, `unregisterStructuredFilterType()`, and `getStructuredFilterType()` on an `AdvanceFilterPlugin` instance for runtime registration. Configuration is usually simpler because `initConfig()` can rebuild the complete registry consistently.
## Related filtering guides
- [Structured Filter Types](/guides/data-filter/filter-types/overview/)
- [Custom Filter Operators](/guides/data-filter/custom-filter-operators/)
- [Advanced Filtering Overview](/guides/data-filter/filter-showcase/)
- [Canonical Filter AST](/guides/data-filter/filter-ast/)
- [Filter Expressions](/guides/data-filter/filter-expression/)
- [Blank Values](/guides/data-filter/blank-values/)
---
# What Is a Filter AST?
URL: https://pro.rv-grid.com/guides/data-filter/what-is-filter-ast/
Source: src/content/docs/guides/data-filter/what-is-filter-ast.mdx
Description: Understand Filter AST in plain language, why teams use it, and when it is the right filtering model.
Surface: grid
A Filter AST is a portable description of a filter. It records **what the filter means** as data, without tying that meaning to one filter popup, framework, database, or API.
You do not need compiler knowledge to use it. If you can read a sentence such as “show open, high-priority tickets or any ticket from a VIP customer,” you already understand the idea.
## AST in plain language
AST stands for **Abstract Syntax Tree**:
- **Abstract** means it keeps the meaning and leaves out presentation details such as buttons, labels, and popup layout.
- **Syntax** means it follows a small set of structural rules.
- **Tree** means a rule can contain smaller rules, like branches containing more branches and leaves.
In a RevoGrid Filter AST, the leaves are field conditions and the branches join them with `and`, `or`, or `not`.
Consider this business rule:
> Show a ticket when it is both open and high priority, or when the customer is a VIP.
Its tree reads like this:
```text
OR
├─ AND
│ ├─ status equals "Open"
│ └─ priority is at least 3
└─ customerTier equals "VIP"
```
The same rule as a RevoGrid `FilterAst` is ordinary JSON-shaped data:
```ts
import type { FilterAst } from '@revolist/revogrid-pro';
const ticketQueue: FilterAst = {
type: 'group',
operator: 'or',
children: [
{
type: 'group',
operator: 'and',
children: [
{
type: 'condition',
field: 'status',
operator: 'equal',
valueType: 'string',
value: 'Open',
},
{
type: 'condition',
field: 'priority',
operator: 'greaterThanOrEqual',
valueType: 'number',
value: 3,
},
],
},
{
type: 'condition',
field: 'customerTier',
operator: 'equal',
valueType: 'string',
value: 'VIP',
},
],
};
```
The AST describes the question. It does not contain the rows, the matching results, or database-specific instructions.
## What “canonical” means
Applications often create filters in several places: column menus, a visual rule builder, quick search, saved views, and backend APIs. Without a shared format, each feature can describe the same rule differently.
RevoGrid's canonical `FilterAst` is the common contract between those paths. It is:
- **Meaningful:** nested Boolean logic is explicit rather than implied by UI state.
- **JSON-safe:** it can be serialized, stored, logged, and sent in a request.
- **Validated:** malformed trees are rejected before they replace the active filter.
- **Portable:** the same tree can drive local rows or be interpreted by your backend.
- **UI-independent:** a filter can survive a redesign or move between different applications.
“Canonical” does not mean RevoGrid automatically converts the tree to SQL, REST parameters, or GraphQL. Your backend owns that translation and must allow-list fields and operators before building a query.
## The business case
Filter AST becomes valuable when a filter is more than a temporary choice in one column menu.
| Need | Business outcome |
| ------------------------------------------- | --------------------------------------------------------------------------------------------- |
| Saved views and reusable segments | Users can return to the same definition instead of rebuilding it. |
| Shared rules across screens | A queue, dashboard, and export can use one filter contract. |
| Remote or virtual data | The backend can evaluate the complete dataset instead of only the loaded page. |
| Cross-column `OR`, nested groups, and `NOT` | Business rules are represented without flattening or changing their meaning. |
| Visual and programmatic editing | A person can use the grouped editor while an application can generate the same structure. |
| Validation and diagnostics | Invalid saved or generated rules fail as one atomic update, leaving the previous view active. |
| Auditing and troubleshooting | A JSON filter is easier to inspect, compare, version, and log than transient UI state. |
Typical examples include support queues, approval routing, fraud-review segments, inventory alerts, compliance views, and report definitions.
## When to use it
Use `FilterAst` when at least one of these is true:
- The rule needs `OR` across different fields, nested groups, or `NOT`.
- Users save, share, or reopen complex views.
- Filtering is executed by a remote data service.
- A visual rule builder and application code must exchange the same filter.
- You need a stable, JSON-safe filtering contract.
Use ordinary `multiFilterItems` instead when users only filter individual columns and all columns combine with `AND`. It is simpler and works with existing Core column controls. `FilterAst` is a Pro capability and requires `AdvanceFilterPlugin` for canonical execution.
## The three building blocks
Every tree is made from only three node types:
| Node | What it says | Everyday example |
| ----------- | ------------------------------------------------ | --------------------------- |
| `condition` | Test one field with one operator. | Status equals Open. |
| `group` | Join one or more child rules with `and` or `or`. | Open **and** high priority. |
| `not` | Reverse the result of one child rule or group. | **Not** archived. |
A condition names the row field, operator, and operand type:
```ts
const condition: FilterAst = {
type: 'condition',
field: 'amount',
operator: 'greaterThanOrEqual',
valueType: 'number',
value: 1000,
};
```
`valueType` describes the operand; it does not convert the values in your rows. For example, `valueType: 'number'` expects a real number rather than the string `'1000'`.
## Use a Filter AST in RevoGrid
Register `AdvanceFilterPlugin`, get its runtime instance, and apply the complete tree:
```ts
import { AdvanceFilterPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
grid.plugins = [AdvanceFilterPlugin];
grid.columns = columns;
grid.source = rows;
const filterPlugin = (await grid.getPlugins()).find(
(plugin) => plugin instanceof AdvanceFilterPlugin,
) as AdvanceFilterPlugin;
await filterPlugin.setFilterAst(ticketQueue);
```
Read the effective filter when you need to save or send it:
```ts
const currentFilter = filterPlugin.getFilterAst();
if (currentFilter) {
localStorage.setItem('ticket-queue', JSON.stringify(currentFilter));
}
```
Restore the saved tree by parsing it and applying it again:
```ts
const savedFilter = localStorage.getItem('ticket-queue');
if (savedFilter) {
await filterPlugin.setFilterAst(JSON.parse(savedFilter));
}
```
Clear it with `undefined`:
```ts
await filterPlugin.setFilterAst(undefined);
```
`setFilterAst()` replaces the tree as one atomic operation. `getFilterAst()` returns a defensive copy, so changing that returned object does not change the grid. Build the next tree and call `setFilterAst()` again.
## Let users build the tree visually
The AST is the data model, not a required user interface. A familiar filter rail, active chips, search field, ranges, and toggles can all compile into the same tree. This lets people filter in business language while the application gets a validated, portable definition.
[Open the Filter AST Side Panel article](/guides/data-filter/filter-ast-side-panel/). It mounts the same canonical grouped editor used by the filter dialog and exposes Vanilla TypeScript, Vue, React, Angular, shared grid setup, and styling sources.
Each interaction in the example rebuilds and applies the complete AST. Removing a chip removes its branch; **Clear all** applies `undefined`. The side panel owns presentation, while `AdvanceFilterPlugin` owns validation and execution.
For unrestricted rule creation, enable the grouped editor so users can create nested rules without writing JSON:
```ts
grid.filter = {
groupedFilter: {},
};
```
The editor and the API work with the same canonical tree. A user can create a rule visually, your application can read it with `getFilterAst()`, and the saved result can be restored later with `setFilterAst()`.
## Send the same meaning to a backend
Listen for `filterastchange` to receive the effective AST and its execution context. The context fixes time-sensitive details such as the instant and timezone used for relative date rules.
For a remote data source, cancel local application in `beforefilterapply`, send the AST and execution context to your service, and replace the grid rows with the server result. The backend must evaluate the rule against the full dataset, not only the page loaded in the browser.
The [Canonical Filter AST reference](/guides/data-filter/filter-ast/) has the complete event example, remote-preview flow, operator table, value rules, and diagnostics.
## A safe implementation checklist
1. Start with `multiFilterItems` unless the product needs canonical or nested logic.
2. Treat the AST as untrusted input when it comes from storage, a URL, or another service.
3. Apply it through `setFilterAst()` so RevoGrid validates it atomically.
4. Store or transport the tree returned by `getFilterAst()`.
5. Send `executionContext` with remote requests, especially for relative dates and blank-value policy.
6. On the server, allow-list fields and operators and use parameterized database queries.
7. Test the same saved rule locally and remotely to confirm that both sides return the same rows.
## Common questions
### Is a Filter AST a string expression?
No. It is structured data. A string such as `status = "Open" AND priority >= 3` can be convenient for people, but an AST makes the grouping and types explicit for software.
### Does it replace column filters?
No. Simple column filtering remains the easiest choice for ordinary grid interactions. The AST is the shared model for cases that need richer logic, persistence, or transport.
### Can I store it in a database or URL?
Yes, if the surrounding storage or URL encoding is suitable for your size and security requirements. The AST is JSON-safe, but you should still validate restored state and avoid treating client-provided fields or operators as trusted database instructions.
### What does `projectable` mean?
Some ASTs can be shown again as independent column filters; those are projectable. A cross-column `OR` or `NOT` cannot be represented faithfully by ordinary column controls, so it is canonical-only. It is still a valid filter and can be edited in the grouped editor.
### Where is the complete technical contract?
Continue to [Canonical Filter AST](/guides/data-filter/filter-ast/) for every supported operator, exact value shapes, quick-filter composition, events, remote ownership, validation codes, and custom evaluators. See [Filter State and Presets](/guides/data-filter/filter-state/) for persistence patterns and [Filtering Quick Start](/guides/data-filter/filter-basics/) for simpler column filtering.
---
# Data Grid Formatting
URL: https://pro.rv-grid.com/guides/data-grid/context-menu/
Source: src/content/docs/guides/data-grid/context-menu.mdx
Description: Add spreadsheet-style context-menu defaults for cells, rows, groups, and columns while preserving application menu contributions.
`DataGridContextMenuPlugin` provides a complete, task-oriented menu without requiring every grid to define copy, edit, inspection, row, filter, formatting, sizing, grouping, column-management, and export actions from scratch. It auto-installs `ContextMenuPlugin`, `ColumnDialogPlugin`, Column Hide support, the cell inspector, and the spreadsheet formatting runtime. Add `AutoFillPlugin` and `EventManagerPlugin` explicitly when dragging a range handle should also stretch advanced formats such as Rating together with the values.
[Open the four-framework standalone example](https://example.rv-grid.com/data-grid-context-menu/)
or browse its source in
[`revogrid-demos/pro-data-grid-context-menu`](https://github.com/revolist/revogrid-demos/tree/main/pro-data-grid-context-menu).
## Enable the preset
```ts
import { DataGridContextMenuPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridContextMenuPlugin];
```
Installing the plugin enables its defaults. Set the property to `false` to keep the plugin registered while suppressing the preset:
```ts
grid.dataGridContextMenu = false;
```
The preset is a prepended context-menu contribution. Existing `rowContextMenu`, `columnContextMenu`, and independent plugin contributions remain after it by default.
## Customize by stable item ID
Hide or disable one generated command without rebuilding its parent submenu:
```ts
grid.dataGridContextMenu = {
hiddenItems: {
'row.delete': true,
},
disabledItems: {
'row.duplicate': context => context.rows.length > 20,
},
};
```
Resolvers receive a semantic `DataGridContextMenuContext` with the active `surface`, target rows, range, leaf columns, column group, and low-level menu context.
The surfaces are `cell`, `rowHeader`, `rowGroup`, `columnHeader`, and `columnGroupHeader`. Disable a whole surface with `areas`:
```ts
grid.dataGridContextMenu = {
areas: { rowGroup: false },
};
```
## Extend or replace generated items
Use `items` for a simple extension. Use `getItems` when a surface needs complete replacement or transformation.
```ts
grid.dataGridContextMenu = {
items: context => [{
id: 'app.audit',
name: 'Open audit history',
action: () => openAuditHistory(context.rows[0]?.model),
}],
getItems: (context, defaults) =>
context.surface === 'columnGroupHeader'
? [{ id: 'app.groupSummary', name: 'Open group summary' }]
: defaults,
};
```
Set `includeHostItems: false` to suppress the application-provided base menu on enabled preset surfaces. Other plugin contributions still compose independently.
## Command lifecycle and remote data
Every built-in command first emits a cancelable `datagrid-context-menu-command` event:
```ts
grid.addEventListener('datagrid-context-menu-command', event => {
if (event.detail.id === 'row.delete' && usesRemoteRows) {
event.preventDefault();
void deleteRowsOnServer(event.detail.context.rows);
}
});
```
A `commandHandlers` entry also replaces local behavior:
```ts
grid.dataGridContextMenu = {
commandHandlers: {
'row.delete': context => api.deleteRows(
context.rows.map(row => row.model.id),
),
},
};
```
Local row mutation is hidden for remote/server-grouped runtimes until a handler is provided. Clipboard permission and export failures emit `datagrid-context-menu-error`; Cut clears cells only after the clipboard write succeeds.
## Undo and redo with History
Add `HistoryPlugin` to make built-in mutating context-menu commands participate
in the same chronological undo/redo stack as direct edits and row ordering:
```ts
import {
DataGridContextMenuPlugin,
HistoryPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [HistoryPlugin, DataGridContextMenuPlugin];
```
Each command is one atomic history step, including multi-cell Cut, Paste, cell
Clear, and row Clear. Structural tracking covers row insert, duplicate, delete,
pin, and unpin commands, plus column insert, duplicate, delete, pin, unpin, and
move commands. Undo and redo restore the complete affected row sources or
authored column tree.
Copy and Copy with headers are intentionally excluded because they do not
mutate grid state. Canceled, unavailable, and no-op commands create no entry.
Commands replaced through `commandHandlers` are application-owned and are not
snapshotted automatically, so remote side effects remain under application
control.
In-memory history preserves row objects and function-valued column definitions,
including nested groups, renderers, and editors. Persistent History adapters
require serializable row and column definitions; JSON storage cannot recreate
functions. See [History Undo/Redo](../data-manage/history/) for stack,
persistence, and replay details.
## Optional capabilities
Commands appear only when their runtime exists. Add the matching plugin to expose filtering, Excel export, row selection, column auto-size, column collapse, tree, or server-grouping actions. Set `unavailableItems: 'disable'` to show unavailable commands in a disabled state instead.
```ts
grid.dataGridContextMenu = {
unavailableItems: 'disable',
rowPinning: true,
};
```
Row pinning is opt-in. Column insertion, duplication, and deletion are also opt-in and require an application factory so every new column receives a unique schema key:
```ts
grid.dataGridContextMenu = {
columnSchema: {
createColumn({ action, sourceColumn }) {
return {
...sourceColumn,
prop: `${String(sourceColumn.prop)}-${action}-${crypto.randomUUID()}`,
};
},
},
};
```
## Spreadsheet-style formatting
Cell and column-header menus include a type-aware **Format** submenu. Native
numeric values expose Number, Currency, Accounting, Percentage, and Scientific
presets; native `Date` values expose Date, Date & time, and Time. **More
formats…** opens a separate Format Cells dialog for locale, decimal and date
styles, font and fill, alignment, wrapping, borders, and a live preview.
See [Format Cells](./format-cells/) for the complete popup guide, including
selection behavior, physical coordinates, render-time application, direct plugin
usage, and runtime persistence limits.
Formatting changes presentation only. Raw source values used by editors,
sorting, filtering, and formulas are not rewritten. Cell commands target the
active range; column-header commands create a true column formatting default.
Map application column types explicitly when their values do not carry native
number or `Date` types:
```ts
grid.dataGridContextMenu = {
formatting: {
locale: 'en-GB',
currencies: ['GBP', 'EUR', 'USD'],
columnTypeKinds: {
money: 'number',
applicationDate: 'date',
},
},
};
```
Set `formatting: false` to remove the whole formatting submenu. Formatting
commands respect grid and per-cell readonly rules. Value presets preserve
authored custom cell templates; the rich dialog can still compose appearance
through `cellProperties` without replacing interactive content.
## Inspect raw cell and column data
**Inspect cell** opens a compact read-only popup for the exact invoked cell. It
shows the column label and property, row dimension and authored source index,
plus the raw value and its JavaScript type. The bounded serializer displays
dates, `undefined`, `bigint`, symbols, functions, accessors, and circular
references as selectable JSON-like inspection text without invoking getters or
`toJSON`.
Full enumerable row snapshots are opt-in because source models can contain
hidden or internal fields. **Inspect column** is available from leaf column
headers and shows the property, pinned dimension, visible and physical indexes,
type, readonly mode, sort order, filter configuration, rendered width, and
authored width bounds. The complete resolved column definition is also opt-in
because schema objects can contain private application metadata.
Limits and an application redaction hook are available when complete row or
column inspection is appropriate:
```ts
grid.dataGridContextMenu = {
inspection: {
includeRowData: true,
includeColumnData: true,
serialization: { maxDepth: 5, maxEntries: 100, maxOutputLength: 20_000 },
redact(value, { kind }) {
return kind === 'row' || kind === 'column'
? redactPrivateFields(value)
: value;
},
localeText: { title: 'Cell details' },
},
};
```
The limits bound recursive traversal and rendered output after JavaScript has
discovered an object's own keys. They are not a sandbox for hostile Proxy traps
or extremely wide objects; use `redact` to replace untrusted complex values.
Both inspectors remain available for readonly cells and readonly grids. Hide
them like any other built-in item with `hiddenItems: { 'cell.inspect': true,
'column.inspect': true }`, or replace their behavior through the matching
`commandHandlers` entry.
Grid readonly mode blocks all local data and schema mutations. Static and
functional column readonly rules hide Edit for the invoked cell; mixed Clear,
Cut, and Paste targets remain usable but skip every readonly cell. Copy and
non-mutating view commands remain available. Synthetic row-group records never
receive destructive row commands.
---
# Format Cells
URL: https://pro.rv-grid.com/guides/data-grid/format-cells/
Source: src/content/docs/guides/data-grid/format-cells.mdx
Description: Configure declarative and interactive spreadsheet-style value, visual presentation, appearance, and editor behavior for RevoGrid cells, rows, and columns.
The **Format Cells** dialog provides spreadsheet-style presentation controls
without changing the values stored in the grid source. Use it for number,
currency, date, and time display as well as typography, colors, alignment,
wrapping, and borders.
The dialog and the quick **Format** submenu are powered by
`DataGridFormattingPlugin`. Registering it also installs
`DataGridContextMenuPlugin`, which supplies the selection opened from cell and
column-header menus. Registering `DataGridContextMenuPlugin` as the entry point
continues to work and installs the same formatting runtime.
Formatting created by the **Format Cells** dialog is runtime presentation
metadata. The plugin does not write it into source row objects, change the raw
cell value, or store an Excel-style format string beside the value.
Declarative formatting is covered first below.
## Declarative cell, row, and column formatting
Register `DataGridFormattingPlugin`, then use either of these declarative
inputs:
- `column.dataGridFormat` accepts either a format for the whole column or a
function that chooses a format for each cell.
- `grid.dataGridFormatting` defines reactive whole-column, whole-row, and cell
presets. Reassigning this property replaces the declarative preset state and
refreshes the grid.
This complete example formats positive amounts as GBP currency and negative
amounts as EUR accounting values. The resolver receives the current row and
cell value, so no external lookup function is needed. The example does not
retrieve the plugin or call `apply()`:
```ts
import type { ColumnRegular } from '@revolist/revogrid';
import {
DataGridFormattingPlugin,
type DataGridCellFormat,
} from '@revolist/revogrid-pro';
type InvoiceRow = {
id: string;
customer: string;
amount: number;
};
const gbpFormat: DataGridCellFormat = {
// Controls how the raw cell value is displayed.
value: {
// Uses the built-in currency formatter.
kind: 'preset', preset: 'currency',
// Applies UK number and currency conventions.
locale: 'en-GB',
// Displays the value as British pounds.
currency: 'GBP',
// Always displays two digits after the decimal point.
decimalPlaces: 2,
// Adds thousands separators, for example £1,200.00.
useGrouping: true,
},
// Controls the visual styling of the formatted cell.
appearance: {
// Aligns the displayed value to the right of the cell.
horizontal: 'right',
},
};
const euroWarningFormat: DataGridCellFormat = {
value: {
kind: 'preset', preset: 'accounting',
locale: 'en-GB',
currency: 'EUR',
decimalPlaces: 2,
},
appearance: {
bold: true,
textColor: '#b91c1c',
fillColor: '#fef2f2',
horizontal: 'right',
},
};
const columns: ColumnRegular[] = [
{ prop: 'customer', name: 'Customer' },
{
prop: 'amount',
name: 'Amount',
// Called independently for every cell in this column.
dataGridFormat: ({ model }) =>
model.amount < 0 ? euroWarningFormat : gbpFormat,
},
];
const source: InvoiceRow[] = [
{ id: 'invoice-1', customer: 'Acme Ltd', amount: 1200 },
{ id: 'invoice-2', customer: 'Northwind', amount: -350.5 },
];
// Installs formatting, the Format Cells dialog, and its context-menu commands.
grid.plugins = [DataGridFormattingPlugin];
grid.columns = columns;
grid.source = source;
```
The resolver argument contains `{ model, value, prop, rowType }`. Return a
`DataGridCellFormat` to format the cell, or `undefined` when the column does not
define a format for that cell. For one format across the entire column, assign
the object directly instead:
```ts
dataGridFormat: gbpFormat;
```
When formatting is application state rather than column configuration, keep the
same presets in the reactive grid property:
```ts
grid.dataGridFormatting = {
columns: [{ column: 1, format: gbpFormat }],
rows: [{ row: 0, format: { appearance: { bold: true } } }],
cells: [{
range: { start: { row: 1, column: 1 } },
format: euroWarningFormat,
}],
};
```
Coordinates are zero-based physical indexes in the row and column source
arrays. A row preset applies to every current and future column at that row
coordinate. A column preset applies to every current and future row at that
column coordinate. Presets use regular body sources by default. Set `rowType`
or `colType` when targeting a pinned source.
The declarative precedence is cell preset, then row preset, then grid column
preset, then `column.dataGridFormat`. Runtime operations through
`DataGridFormattingPlugin.apply()`, `applyValueFormat()`, or `clear()` take
precedence over every declarative source. The raw values remain `1200` and
`-350.5` for editing, sorting, filtering, and application code.
Framework wrappers use the same grid property. In React and Vue, bind
`dataGridFormatting` directly. If a generated Angular wrapper version does not
yet declare that plugin property as an `@Input`, set
`gridElement.dataGridFormatting = formattingState` on the underlying
`HTMLRevoGridElement`. Do not put formatting state in `additionalData`; that
compatibility path is deprecated.
## Format a whole column, whole row, or individual cells
The mutation API uses the same zero-based physical coordinates as declarative
state. Complete-row and complete-column targets are explicit, so their intent
is not lost when the grid currently has no rows or columns:
```ts
const formatting = (await grid
.getPlugins())
.find(plugin => plugin instanceof DataGridFormattingPlugin);
if (!formatting) throw new Error('Formatting plugin is not installed.');
// The entire Amount column, including rows added later.
formatting.apply(
{ scope: 'columns', ranges: { start: 1 } },
gbpFormat,
);
// The entire first body row, including columns added later.
formatting.apply(
{ scope: 'rows', ranges: { start: 0 } },
{ appearance: { bold: true, fillColor: '#fef3c7' } },
);
// One cell at physical row 1, physical column 1.
formatting.apply(
{ start: { row: 1, column: 1 } },
euroWarningFormat,
);
```
`end` is inclusive and may be before `start`; the plugin normalizes reversed
ranges. Supply an array to format multiple independent ranges. Row targets may
set `rowType`, column targets may set `colType`, and cell ranges may set both.
The same targets work with `applyValueFormat()`, `patch()`, `clear()`,
`openDialog()`, and `inferValueKind()`.
## Enable Format Cells
Register the formatting plugin to make formatting available from cell and
column-header menus. It installs the context-menu integration and dialog
automatically:
```ts
import { DataGridFormattingPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin];
```
The one-line formatting panel is installed with the formatting runtime but is
disabled by default. Enable or disable it reactively with its dedicated grid
property:
```ts
grid.dataGridFormattingPanel = true;
// Hide it again without removing the formatting plugin.
grid.dataGridFormattingPanel = false;
```
Assign a `DataGridFormattingPanelConfig` object instead of `true` when the
toolbar also needs localized labels.
Do not register `DataGridContextMenuPlugin` separately. If your grid already
uses it as its main menu plugin, keep that setup; it installs
`DataGridFormattingPlugin` in the opposite direction and reuses one instance of
each plugin.
Right-click a writable cell or column header and open **Format**. The submenu
offers type-aware quick presets and two general actions:
- **More formats…** opens the complete Format Cells dialog.
- **Clear formatting** removes both value formatting and appearance formatting
from the selected target.
For the complete context-menu configuration and command customization contract,
see [Data Grid Formatting](./context-menu/).
## Configure formatting defaults
Formatting options belong under `grid.dataGridContextMenu.formatting`:
```ts
type InvoiceRow = {
id: string;
customer: string;
amount: number;
issuedAt: string;
};
grid.dataGridContextMenu = {
formatting: {
autoAlignNumericValues: true,
locale: 'en-GB',
locales: ['en-GB', 'de-DE', 'fr-FR'],
currencies: ['GBP', 'EUR', 'USD'],
fontFamilies: ['Inter', 'Georgia', 'IBM Plex Mono'],
columnTypeKinds: {
money: 'number',
applicationDate: 'date',
},
// Keep this column's active column-level format authoritative.
protectedColumnProps: ['amount'],
resolveStructuralRowIdentity: (row) => row.id,
},
};
```
| Option | Purpose |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `autoAlignNumericValues` | Right-aligns finite number and bigint values, plus Number, Currency, Accounting, Percentage, and Scientific formats, when horizontal alignment is Automatic. Effective advanced presentations keep their own layout. Defaults to `true`; set to `false` to disable this derived alignment. |
| `locale` | Initial locale used by `Intl.NumberFormat` and `Intl.DateTimeFormat`. The browser locale is used when omitted. |
| `locales` | Locale choices shown in the dialog. A built-in list is used when omitted. |
| `currencies` | Currency choices shown by Currency and Accounting formats. The first entry is used by the dialog and quick presets. |
| `fontFamilies` | Application font choices shown in Typography. |
| `columnTypeKinds` | Maps application column types to `number`, `date`, `text`, `boolean`, `mixed`, or `unknown` for command availability. It does not convert values. |
| `resolveValueKind` | Overrides value-kind inference for the complete selected target. It does not convert values. |
| `resolveStructuralRowIdentity` | Retains formatting across length-changing immutable source updates by matching old and new row objects with an application-owned stable identity. Object identity is used when omitted. |
| `scopes` | Limits formatting to `cell`, `columnHeader`, or both. |
| `protectedColumnProps` | Column `prop` values whose active column-level format masks cell and row formats. The masked state is retained and becomes effective again if protection is removed. |
| `advancedFormats` | Configures compatible visual formats, individual format availability, custom definitions, and optional editor references. Set it to `false` to disable advanced formats. |
| `localeText` | Replaces dialog, preset, option, palette, and advanced-format registry labels for localization. |
### Protect a column format from overrides
Use `protectedColumnProps` when a column's value, appearance, or presentation
must remain uniform even when users or application code apply formats to a row
or individual cell. The setting is selected by stable column `prop`, not by a
physical column index:
```ts
grid.dataGridContextMenu = {
formatting: {
protectedColumnProps: ['amount'],
},
};
```
Protection applies only while the column has an active column-owned format
from `column.dataGridFormat`, `dataGridFormatting.columns`, or a whole-column
runtime operation. Cell and row formats are still retained in formatting state;
they become effective again if the property is removed from the configuration.
Clearing the column format restores the normal newest-runtime-operation
precedence.
### Physical coordinates and source replacement
Formatting belongs to a physical source position, not to a row object or
business key:
```ts
grid.dataGridFormatting = {
cells: [{
range: {
start: { row: 1, column: 1 },
end: { row: 3, column: 2 },
},
format: euroWarningFormat,
}],
};
// New row objects at the same source positions receive the same formatting.
grid.source = invoices.map((invoice) => ({ ...invoice }));
```
Replacing every record without changing source length preserves formats at the
same coordinates. Structural source changes retain formats with surviving row
objects, remove formats owned by deleted rows, and shift or pin retained
formats with their rows. If an application recreates row objects while also
inserting or deleting records, configure `resolveStructuralRowIdentity` with a
stable unique business key so the old and new rows can be matched:
```ts
grid.dataGridContextMenu = {
formatting: {
resolveStructuralRowIdentity: (row) => row.id,
},
};
```
Filtering only changes virtual order and does not change physical formatting
addresses.
## Accessibility and localization
Format Cells uses the shared modal focus trap and restores focus to the grid
when it closes. Every native input and select has a programmatic label;
icon-only actions expose a localized accessible name, pressed or expanded
state, and a tooltip on both hover and keyboard focus. Escape dismisses an open
control tooltip before a second Escape closes the dialog. Preset lists,
segmented controls, color palettes, and expandable sections support their
native or ARIA keyboard patterns, visible focus, forced-colors mode, and reduced
motion preferences.
Dialog labels, actions, tooltips, validation feedback, the plain-text example,
and palette color names come from `formatting.localeText`. Section templates
replace `{section}` with the localized section name:
```ts
grid.dataGridContextMenu = {
formatting: {
localeText: {
title: 'Formatear celdas',
close: 'Cerrar formato de celdas',
bold: 'Negrita',
expandSection: 'Abrir {section}',
collapseSection: 'Cerrar {section}',
invalidColor: 'Introduce un color hexadecimal válido.',
textExample: 'Ejemplo de texto',
colorLabels: {
'#be185d': 'Rosa',
'#2563eb': 'Azul',
},
advancedGroupLabels: {
Indicators: 'Indicadores',
},
advancedFormatLabels: {
heatmap: 'Mapa térmico',
},
advancedControlLabels: {
'heatmap.midValue': 'Punto medio',
'heatmap.highColor': 'Color alto',
},
},
},
};
```
`localeText` is partial. Unspecified strings and palette names retain the
built-in English defaults. Every label map is merged by key, including
`presetLabels`, `optionLabels`, `colorLabels`, `advancedGroupLabels`,
`advancedFormatLabels`, `advancedFormatDescriptions`,
`advancedControlLabels`, and `advancedOptionLabels`.
Advanced controls use `.` keys. Select options append
the option value: `..`. These same stable
keys localize application-defined formats; their registry-authored label or
description remains the fallback when a translation is omitted.
Set `formatting: false` to remove the formatting submenu while keeping the rest
of the context menu:
```ts
grid.dataGridContextMenu = {
formatting: false,
};
```
This hides formatting commands; it does not remove declarative formats already
provided through `dataGridFormat` or `dataGridFormatting`.
## Value formatting
Value formats change only the string rendered in a cell. They do not replace
the raw value used by editing, sorting, filtering, formulas, or application
code.
The available presets are:
| Preset | Display behavior |
| ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| Automatic | Leaves value display to the column's existing renderer. |
| Number | Locale-aware numeric output with decimal and grouping controls. |
| Currency | Locale-aware currency output using the selected ISO currency. |
| Accounting | Currency output with accounting-style negative values. |
| Percentage | Locale-aware percentage output. With the default two decimal places, a raw value of `0.25` displays as `25.00%` in `en-US`. |
| Scientific | Locale-aware scientific notation. |
| Date | Date output with short, medium, long, or full date style. |
| Time | Time output with short, medium, or long time style. |
| Date & time | Combined date and time output. |
| Plain text | Displays the value as text. |
Numeric formats support decimal places, thousands grouping, and negative-number
styles. Currency formats also expose the configured currency list. Date and
time formats support style controls and use the configured locale.
Quick menu commands are type-aware. Native JavaScript numbers and `bigint`
values expose numeric commands, while native `Date` values expose date and time
commands. Use `columnTypeKinds` or `resolveValueKind` to classify custom column
types and parseable date strings:
```ts
const issuedAtColumn = {
prop: 'issuedAt',
name: 'Issued',
columnType: 'applicationDate',
};
grid.columns = [
{ prop: 'customer', name: 'Customer' },
{ prop: 'amount', name: 'Amount', columnType: 'money' },
issuedAtColumn,
];
grid.dataGridContextMenu = {
formatting: {
columnTypeKinds: {
money: 'number',
applicationDate: 'date',
},
},
};
```
Type classification controls which quick commands are available; it does not
coerce the source value. Numeric presets format only `number` and `bigint`
values, so convert numeric strings to numbers before formatting them. Date
presets accept valid `Date` objects, parseable date strings, and non-negative
Excel serial-date numbers.
If a value cannot be converted by the selected formatter, the renderer falls
back to the original value's string representation instead of throwing.
Selecting `preset: 'currency'` is sufficient to activate the built-in
`Intl.NumberFormat` currency renderer. You do not install a separate currency
formatter. `currency`, `locale`, `decimalPlaces`, and `useGrouping` configure
that renderer.
## Exact Excel-compatible format codes
Choose **Custom** in Format Cells to edit an Excel-compatible number-format
code with a live preview. Invalid codes disable Apply. The exact code is stored
as a separate discriminated variant, so it cannot conflict with preset options:
```ts
const exactExcelFormat: DataGridCellFormat = {
value: {
kind: 'code',
formatCode: '[Red][<0]#,##0.00;#,##0.00;"-";@',
locale: 'en-GB',
},
};
```
The internal formatter supports positive/negative/zero/text sections,
conditions and colors, locale/currency tokens, dates and elapsed time,
fractions, scientific notation, percentages, scaling, grouping, text
placeholders, quoted/escaped literals, and Excel spacing/fill instructions.
Raw source values are never replaced by their display text. Excel serial dates
use the shared dates module, including serial 60, while application `Date`
values remain `Date` values.
## Advanced visual formats
**Advanced format** is a category in the existing Value formatting list. Select
it to reveal a grouped icon list of compatible visual formats and the options
declared by the selected format. Every row remains visible in the details pane;
built-ins use Font Awesome SVG icons, and custom definitions without an icon
receive the standard formatting icon. The category includes only formats
compatible with every non-null value in the current selection. Choosing one
stores only its stable ID and serializable options in
`DataGridCellFormat.presentation`; source values remain unchanged. The
appearance inspector stays available, so inherited text color, fill,
typography, alignment, and borders can still be adjusted.
You can predefine the same format on a column or choose it per cell:
```ts
const columns: ColumnRegular[] = [
{
prop: 'completion',
name: 'Completion',
dataGridFormat: {
presentation: {
id: 'progress-line',
options: { minValue: 0, maxValue: 100 },
},
},
},
{
prop: 'metric',
name: 'Metric',
dataGridFormat: ({ model }) =>
model.metricKind === 'history'
? { presentation: { id: 'sparkline' } }
: {
presentation: {
id: 'heatmap',
options: { minValue: 0, maxValue: 100 },
},
},
},
];
```
Built-in formats and their required source shapes are:
| Value shape | Format IDs |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Finite number | `progress-line`, `progress-line-value`, `circular-progress`, `heatmap`, `rating`, `change`, `threshold` |
| Boolean | `boolean`, `thumbs` |
| String, number, or boolean | `badge`, `avatar`, `avatar-with-text` |
| Non-empty number array | `sparkline`, `bar`, `pie` |
| Non-empty `{ value, color?, name? }[]` | `pie` |
| Non-empty `{ start, end, label? }[]` | `timeline` |
| Non-empty record of numeric values | `summary-percentage`, `summary-aggregate` |
The chart-only column-header renderer is not a cell format and is intentionally
excluded. Mixed selections show a format only when every selected non-null
value matches its shape.
When edited, Sparkline and Bar chart cells accept comma-separated numbers or a
JSON number array. Timeline opens as compact JSON and saves a validated
`{ start, end, label? }[]` value, so structured data never becomes
`[object Object]` or a display string.
The built-in `boolean` format displays `Yes`/`No`, while `thumbs` is its visual
alternative. Both open the same typed `Yes`/`No`/`Not set` dropdown on edit.
Their dropdown values and options reuse the active cell template, so switching
one cell between Boolean and Thumbs changes both its grid presentation and its
editor presentation without adding a column type.
### Configure or disable formats
Compatible built-ins are enabled by default. Disable one format, change its
application defaults, or disable the Advanced category under the
context-menu formatting config:
```ts
grid.dataGridContextMenu = {
formatting: {
advancedFormats: {
formats: {
pie: false,
heatmap: {
defaults: { minValue: -20, maxValue: 50, colorMap: 'coldmap' },
},
},
},
},
};
// Removes the Advanced category but keeps value and appearance formats.
grid.dataGridContextMenu = {
formatting: { advancedFormats: false },
};
```
Application-owned data such as badge styles, threshold definitions, dropdown
choices, and timeline bounds belongs in `defaults` or the column. The dialog
shows only controls declared by the format definition.
For the built-in Badge format, **Fill color** controls the badge background and
**Text color** controls the badge label. These colors do not paint the complete
cell. A matching `badgeStyles` entry supplies the fill fallback, while the label
inherits ordinary theme text until the user chooses an explicit **Text color**.
### Register an application format
Custom formats use the same registry and dialog controls as built-ins:
```ts
grid.dataGridContextMenu = {
formatting: {
advancedFormats: {
customFormats: [
{
id: 'risk-meter',
label: 'Risk meter',
group: 'Application',
valueKind: 'number',
defaults: { maximum: 10, color: '#dc2626' },
controls: [
{ type: 'number', key: 'maximum', label: 'Maximum', min: 1 },
{ type: 'color', key: 'color', label: 'Color' },
],
cellTemplate: (h, { value, column }) =>
h('meter', {
min: 0,
max: column.maximum,
value,
style: { accentColor: column.color },
}),
},
],
},
},
};
```
Use `isCompatible({ value, model, column, rowType })` instead of `valueKind`
when application data needs a more specific predicate.
## Editors for formatted cells
Formatting can select an editor for the actual cell being edited. The source
value preset can supply an editor, and an advanced-format definition can expose
its own `editor` reference. Editors remain optional dependencies supplied by
the application:
```ts
import DateColumnType from '@revolist/revogrid-column-date';
import { badgeRenderer, ColumnDropdown } from '@revolist/revogrid-pro';
const dateType = new DateColumnType();
grid.dataGridContextMenu = {
formatting: {
advancedFormats: {
presetEditors: {
date: dateType.editor,
datetime: dateType.editor,
time: dateType.editor,
},
customFormats: [
{
id: 'status-badge',
label: 'Status badge',
valueKind: 'scalar',
cellTemplate: badgeRenderer,
editor: ColumnDropdown.editor,
},
],
},
},
};
```
Editor resolution happens for each cell, so two cells in one column can use
different editors when `dataGridFormat` returns different value presets or
presentations. If a configured editor reference cannot be resolved, the plugin
uses the column's original editor and then the grid's text editor.
An application-authored `editor` is protected by default. Set
`replaceAuthoredEditors: true` only when formatted cells should take ownership
globally. Advanced presentation renderers also protect an authored
`cellTemplate` by default; configure `replaceAuthoredTemplates` globally or
`replaceAuthoredTemplate` on one definition when that advanced renderer should
replace it. Ordinary value categories are an explicit presentation choice: a
non-Automatic category replaces the authored visual template, while Automatic
restores it.
## Appearance formatting
Numeric cells use spreadsheet-style right alignment by default while the
formatting plugin is installed. This applies to finite number and bigint source
values even when they have no stored format, and to Number, Currency,
Accounting, Percentage, and Scientific value formats. An explicit **Left**,
**Center**, or **Right** appearance choice overrides the default.
Advanced presentations such as Progress line and Rating stars retain their
renderer-owned layout while alignment is Automatic; an explicit appearance
alignment still overrides that layout.
To retain the grid's existing alignment instead, disable automatic numeric
alignment in the formatting configuration:
```ts
grid.dataGridContextMenu = {
formatting: {
autoAlignNumericValues: false,
},
};
```
The appearance side of the dialog contains a live preview and these groups:
- **Typography:** font family, font size, bold, italic, underline, and
strikethrough.
- **Colors:** text color and fill color, including predefined and custom colors.
- **Alignment:** horizontal alignment, vertical alignment, and text wrapping.
- **Borders:** border style and border color.
Appearance is composed with the column's existing `cellProperties`. Apart from
the enabled-by-default numeric alignment described above, formatting adds or
overrides only explicitly selected style values, so unrelated authored classes
and styles remain in place.
Columns with an authored `cellTemplate`, including selection-style columns,
keep the complete Format Cells category list. Choosing a concrete value format
switches the cell to that plain value presentation; choosing Automatic restores
the authored template. Row-drag content remains appearance-only. For an
advanced format supplied declaratively or through the plugin API, set
`replaceAuthoredTemplates` globally, or `replaceAuthoredTemplate` on that
definition, only when its renderer should explicitly own those cells.
## Copy and paste formatting
`DataGridFormattingPlugin` installs one `ClipboardPlugin` automatically. The
formatting plugin remains the owner of the format throughout copy and paste:
1. Copy asks the formatting runtime for the effective `DataGridCellFormat` at
every selected physical cell.
2. RevoGrid-to-RevoGrid copy stores that canonical format in the versioned rich
clipboard payload, together with typed values, formulas, range geometry,
merges, and explicit dimensions.
3. Excel HTML import converts allowlisted Excel declarations into the same
`DataGridCellFormat` model. Clipboard code never installs raw Excel CSS on a
cell. Excel CSS escapes and named formats such as `Short Date` are normalized
by the formatting plugin.
4. Paste sends values and formulas through the regular grid edit, readonly, and
validation pipeline. Only accepted destinations receive formatting, applied
as one formatting batch.
5. Copy back to Excel converts the canonical format into Excel HTML declarations
and number-format codes without changing the source row value.
This keeps formatting created at every supported scope in sync: individual
cells, rectangular or independent ranges, complete rows, and complete columns.
The effective resolved format is copied, including inherited row or column
formatting; the destination receives a cell-level snapshot of that appearance.
Date handling follows the same non-destructive rule. Excel serial dates stay
typed numbers with a date/time format after import. When copying an application
`Date` or valid date-like value to Excel, only the outbound HTML representation
is converted to an Excel 1900-system serial. RevoGrid source data is not
rewritten.
Some Excel Desktop versions, including Excel for macOS, omit the typed `x:num`
attribute and expose only formatted display text. When that text is accompanied
by a recognized Accounting, Percentage, or date format, the formatting plugin
recovers the typed numeric or serial-date value before applying the canonical
format. It does not infer a type from unformatted visual text.
The same conversion covers the full Excel number-format family: Number,
Currency, Accounting, Percentage, Fraction, Scientific, Date, Time, combined
date-time, elapsed time, Special, and numeric/date/time Custom codes. General
and Text remain text without an explicit typed clipboard value. Conditions,
four-section positive/negative/zero/text rules, locale tokens, scaling commas,
literal affixes, spacing, and fill are compiled by the same formatting engine
used for cell rendering. See the
[display-only category matrix](/guides/grid-utilities/clipboard-json/#display-only-excel-category-coverage)
for the exact fallback policy.
The [native Excel interoperability matrix](/guides/grid-utilities/clipboard-json/#native-excel-interoperability-matrix)
shows the real Excel-to-RevoGrid rendering for the full tested number-format
family and representative appearance options. The formats shown there are
stored and rendered by this formatting plugin; clipboard code does not attach
Excel CSS to cells.
For the exact directional support matrix, custom clipboard handlers, rich MIME
contract, formulas, merges, dimensions, and the browser/native Excel boundary,
see [Smart Excel-Compatible Clipboard](/guides/grid-utilities/clipboard-json/).
## Range fill formatting
Register `AutoFillPlugin` and `EventManagerPlugin` alongside
`DataGridFormattingPlugin` to stretch the source range's effective formatting
together with the value sequence selected by AutoFill. A single Rating cell fills
Rating cells; a two-row or two-column source repeats its formats as a tile over the
extended range.
```ts
import {
AutoFillPlugin,
DataGridFormattingPlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin, AutoFillPlugin, EventManagerPlugin];
```
Formatting remains owned by this plugin rather than being converted into cell CSS.
The source format is resolved from the same cell, row, column, and authored-column
precedence used during rendering. Destination formats are written as cell-level
runtime operations in one batch after the range edit is accepted. A source slot
with no effective format explicitly clears the corresponding destination slot, so
old destination formatting does not leak into the filled result.
See the [Autofill guide](/guides/data-manage/autofill/#filling-formatted-cells)
for value strategies, preview behavior, and range-fill setup.
## Excel export
Add `ExportExcelPlugin` when Format Cells presentation should be retained in an
`.xlsx` workbook. The Excel plugin discovers the formatting runtime during
export:
```ts
import {
DataGridFormattingPlugin,
ExportExcelPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin, ExportExcelPlugin];
```
The integration exports the current formatting automatically:
- Number, Currency, Accounting, Percentage, Scientific, Date, Time, Date &
time, and Plain text presets become native Excel number-format codes.
- Numeric and date cells remain typed workbook values rather than preformatted
strings, so Excel formulas, editing, and sorting continue to work.
- Font family and size, emphasis, text and fill colors, alignment, wrapping,
and borders become workbook cell styles.
- Group rows become merged, indented workbook headings. Export follows the
visible grouped projection, so collapsed descendants are not included.
Advanced visual formats are not converted into Excel chart drawings. A format
definition can provide `exportValue` to convert its structured data to a
workbook-safe scalar while leaving the grid source unchanged. The built-in
Timeline format does this automatically: multiple events export as readable
text such as `Build: 8–12; Review: 14–18` instead of `[object Object]`.
Advanced formats without `exportValue` continue to export their raw cell value,
ordinary value format, and appearance.
Authored `column.excelExport` properties remain the base export configuration.
An explicitly selected Format Cells value or appearance setting overrides the
corresponding workbook field for that target without replacing unrelated
column-owned export properties.
## Selection scope
The menu surface decides which formatting scope is created:
- From a **cell**, formatting applies to the active writable range. If the
multi-range plugin owns several selected ranges and the menu opens inside
them, all resolved writable cells in those ranges are targeted.
- From a **column header**, formatting creates a column-wide default at the
physical column address. It applies to body and pinned rows rendered for that
column.
Readonly behavior is resolved before the command runs:
- Grid-level readonly mode prevents formatting changes.
- Readonly cells are skipped in mixed cell selections.
- A formatting command is unavailable when no writable target remains.
- Synthetic grouping rows are not assigned cell formatting.
## Apply, Cancel, and Clear
The dialog edits a temporary draft:
- **Apply** commits the complete value, advanced presentation, and appearance
draft to the selected cells or columns, closes the dialog, and refreshes the
grid.
- **Cancel**, the close button, backdrop dismissal, and Escape close the dialog
without committing the draft.
- **Clear formatting** commits an explicit clear operation for the selected
target and refreshes the grid.
Clear formatting does not clear the source value. It removes only the rendered
format. An explicit clear also participates in cell-versus-column precedence,
which is important when a cell override and a column default both exist.
## Where formatting is stored
Formatting can come from declarative configuration or runtime operations:
- `column.dataGridFormat` remains part of the column definition.
- `grid.dataGridFormatting` is application-owned grid state. Reassigning it
replaces all declarative grid presets.
- Formats created through the dialog or plugin API are held privately by the
current `DataGridFormattingPlugin` instance; source rows are not modified.
The effective precedence is:
1. A runtime format or explicit clear created through `apply()`,
`applyValueFormat()`, `clear()`, or the context menu.
2. A physical range preset in `grid.dataGridFormatting.cells`.
3. A row preset in `grid.dataGridFormatting.rows`.
4. A column preset in `grid.dataGridFormatting.columns`.
5. The column's static or function-based `dataGridFormat`.
Within runtime state, each apply or clear operation receives a new revision.
When matching runtime cell, row, and column operations overlap, the newest
revision wins. For example:
1. Format the `amount` column as Number.
2. Format one `amount` cell as Currency; that newer cell override wins.
3. Clear the entire `amount` column; the newer column clear wins over the older
cell override.
4. Format the individual cell again; the newest cell format wins.
A whole-row operation follows the same rule. It is stored once for the physical
row, rather than expanded into the currently visible cells, so it also formats
columns added later.
A `DataGridCellFormat` used by any of these sources has up to three independent
layers: ordinary value formatting, an advanced visual presentation, and
appearance. Omit any layer that the cell does not need:
```ts
const format = {
value: {
kind: 'preset', preset: 'currency',
locale: 'en-GB',
currency: 'GBP',
decimalPlaces: 2,
useGrouping: true,
negativeStyle: 'minus',
},
appearance: {
bold: true,
textColor: '#0f172a',
fillColor: '#fef3c7',
horizontal: 'right',
wrap: false,
borderStyle: 'solid',
borderColor: '#f59e0b',
},
presentation: {
id: 'progress-line',
options: { minValue: 0, maxValue: 100 },
},
} as const;
```
No `format` property is added to the example source row. Reading
`row.amount` still returns the original value.
## How formatting reaches rendered cells
After Apply, Clear, or a `grid.dataGridFormatting` assignment, the plugin asks
RevoGrid to refresh all rendered cells. For each non-grouping cell, it resolves
the effective format during `beforecellrender`:
1. Runtime and declarative sources are resolved using the precedence described
above.
2. Appearance settings are composed into `cellProperties`.
3. A compatible advanced presentation renderer is supplied when one is
configured. An authored `cellTemplate` or row-drag renderer remains protected
unless replacement was explicitly enabled.
4. When there is no advanced renderer, a value-format renderer is supplied for
a non-Automatic preset. This explicit choice replaces an authored visual
template; Automatic leaves the authored template in place.
5. The underlying row model and raw value remain unchanged.
Because lookup happens during rendering, formatting follows virtualization,
scrolling, sorting, filtering, pinning, and ordinary grid refreshes. The plugin
does not inspect rendered DOM to reconstruct state.
## Save and restore formatting
Storage belongs to the application. After every runtime formatting change, the
plugin emits `datagridformattingchange` with the complete versioned state. Save
`event.detail.state` wherever the application already stores user preferences,
then pass it back through `grid.dataGridFormatting.state` when creating the
grid.
For example, use `localStorage` without adding storage behavior to the plugin:
```ts
const storageKey = 'invoice-grid-formatting';
const savedState = localStorage.getItem(storageKey);
grid.plugins = [DataGridFormattingPlugin];
grid.dataGridFormatting = {
// Restore runtime formats once; omit state when nothing has been saved.
state: savedState ? JSON.parse(savedState) : undefined,
};
grid.addEventListener('datagridformattingchange', ({ detail }) => {
// detail.state is the complete snapshot; no merge with older state is needed.
localStorage.setItem(storageKey, JSON.stringify(detail.state));
});
```
The event fires after dialog **Apply**, **Clear formatting**, `apply()`,
`applyValueFormat()`, `clear()`, `setState()`, undo, and redo. Assigning
`dataGridFormatting.state` restores state without emitting another event, so it
does not create a save loop or a history entry.
Declarative `columns`, `rows`, and `cells` remain application defaults. The runtime
snapshot contains only user or API operations that override them, including
explicit clears and cell/row/column operation order. Source rows and raw values
remain unchanged.
### Save to a server or application store
Load state first, assign it to the grid, and save the same complete event
payload. Wrap it with your dataset version (or another stable layout token) so
the server can reject stale coordinates:
```ts
const saved = await formattingApi.load(documentId);
grid.dataGridFormatting = {
state: saved?.formatting,
};
let documentVersion = saved?.version;
let pendingSave = Promise.resolve();
grid.addEventListener('datagridformattingchange', ({ detail }) => {
// Serialize writes so an older response cannot overwrite a newer edit.
pendingSave = pendingSave.then(async () => {
const result = await formattingApi.save(documentId, {
version: documentVersion,
datasetVersion: currentDatasetVersion,
formatting: detail.state,
});
documentVersion = result.version;
});
});
```
`detail.state` is already a complete JSON-serializable snapshot: save it as a
replacement, not as a partial patch. On reload, restoring the snapshot does not
emit another persistence event. The application owns request batching,
authentication, retries, error messages, and conflict handling; the formatting
plugin only reports state changes.
Formatting addresses are physical indexes in the corresponding body or pinned
source arrays. They remain valid when row objects are replaced, edited, or
loaded again in the same order. If server sorting, filtering, pagination, or a
new dataset version changes that physical order, do not apply the old snapshot
blindly: reject it by `datasetVersion`, or migrate its coordinates before
assigning `grid.dataGridFormatting.state`. No row key callback is required or
consulted by the formatting runtime.
### Undo and redo
Add History when formatting changes should use the same undo and redo stack as
data edits. `HistoryPlugin` installs Event Manager automatically:
```ts
import {
DataGridFormattingPlugin,
HistoryPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin, HistoryPlugin];
```
Each Apply or Clear operation is one history entry. Undo and redo restore the
complete previous or next formatting state and emit
`datagridformattingchange`, so the application saves the replayed state through
the same listener. No additional history configuration is required.
### Manual state control
The plugin instance also exposes `getState()` and `setState()`:
```ts
const saved = formatting.getState();
formatting.setState(saved);
```
State version 2 includes runtime row, column, and physical-cell operations,
explicit clears, dimension types, and revision order. It is JSON-safe when custom
presentation options are JSON-safe.
## Programmatic formatting API
`DataGridFormattingPlugin` also provides methods for application-driven
formatting. It still installs the context menu and dialog; you only need to
retrieve the instance when application code must apply, inspect, clear, or open
formatting programmatically. Use the grid's plugin collection rather than
attaching custom methods to the grid element:
```ts
import {
DataGridFormattingPlugin,
type DataGridFormattingSelection,
} from '@revolist/revogrid-pro';
type InvoiceRow = { id: string; amount: number };
const invoice: InvoiceRow = { id: 'invoice-1', amount: 1200 };
grid.columns = [{ prop: 'amount', name: 'Amount' }];
grid.plugins = [DataGridFormattingPlugin];
grid.source = [invoice];
const formatting = (await grid.getPlugins()).find(
(plugin) => plugin instanceof DataGridFormattingPlugin,
) as DataGridFormattingPlugin | undefined;
formatting?.configure({
locale: 'en-GB',
currencies: ['GBP', 'EUR'],
});
// Zero-based physical indexes into the rgRow and rgCol source arrays.
const selection: DataGridFormattingSelection = {
start: { row: 0, column: 0 },
};
formatting?.apply(selection, {
value: {
kind: 'preset', preset: 'currency',
locale: 'en-GB',
currency: 'GBP',
decimalPlaces: 2,
},
appearance: {
bold: true,
horizontal: 'right',
},
});
```
`start` identifies the first physical source cell. The optional inclusive
`end` defaults to `start`, so one coordinate formats one cell. `rowType` and
`colType` default to `rgRow` and `rgCol`.
```ts
// Entire physical row across four columns.
formatting?.patch({
start: { row: 3, column: 0 },
end: { row: 3, column: 3 },
}, {
appearance: { bold: true },
});
// Three rows by two columns.
formatting?.apply({
start: { row: 3, column: 1 },
end: { row: 5, column: 2 },
}, {
appearance: { fillColor: '#fef3c7' },
});
// Independent or overlapping ranges are accepted as an array.
formatting?.clear([
{ start: { row: 0, column: 0 }, end: { row: 1, column: 1 } },
{ start: { row: 8, column: 4 } },
]);
```
Physical indexes address the unfiltered `source` arrays directly. Sorting,
filtering, and viewport order do not change which source models are targeted.
The plugin stores those coordinates directly and resolves the current source
row and column only when it needs rendering or callback context.
The public methods are:
| Method | Purpose |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `configure(config)` | Sets dialog and rendering options such as locale, currencies, fonts, and localized labels. |
| `apply(selection, format)` | Replaces the selected target's complete value, advanced presentation, and appearance format. |
| `applyValueFormat(selection, valueFormat)` | Selects an ordinary value presentation, retains appearance, and replaces an active advanced visualization. |
| `clear(selection)` | Applies an explicit clear that suppresses lower-priority declarative formats for the selected target. |
| `getFormat(address)` | Resolves the effective format at a physical cell address. |
| `getColumnFormat(address)` | Resolves the effective format for a complete physical column. |
| `getState()` | Returns a portable, versioned snapshot of runtime Apply and Clear operations. |
| `setState(state)` | Replaces runtime operations, refreshes the grid, and emits `datagridformattingchange`. |
| `openDialog(selection, valueKind, valueFormatting?)` | Opens Format Cells for an application-provided selection. Pass `false` as the third argument for appearance-only editing. |
For example, open the same dialog programmatically:
```ts
formatting?.openDialog(selection, 'number');
```
Or inspect and clear the known target:
```ts
const current = formatting?.getFormat({ row: 0, column: 0 });
if (current?.value?.preset === 'currency') {
formatting?.clear(selection);
}
```
## Practical recommendations
- Use physical coordinates for persisted formatting. Replacing source objects
at the same positions preserves formatting; moving records does not move it.
- Keep source values typed and unformatted; let the renderer own presentation.
- Use `columnTypeKinds` to control type-aware command availability for custom
column types and parseable date strings; it does not coerce numeric strings.
- Use column-header formatting for broad defaults and cell formatting for
exceptions.
- Choose Automatic to restore a custom interactive template after displaying a
plain value format.
- Listen to `datagridformattingchange` and persist `detail.state` in the
application-owned browser store, state manager, or server API.
---
# Cell and Row Notes
URL: https://pro.rv-grid.com/guides/data-grid/notes/
Source: src/content/docs/guides/data-grid/notes.mdx
Description: Add versioned cell and row notes with severity, mentions, filtering, offline drafts, and XLSX export.
`GridNotesPlugin` adds metadata notes without changing source rows. Every note is addressed by a stable host row ID and, for cell notes, a column prop. Sorting, filtering, pinning, grouping, clipboard values, and CSV output therefore remain independent from note content.
## Configure the plugin
```ts
import {
GridNotesPlugin,
InMemoryGridNoteAdapter,
} from '@revolist/revogrid-pro';
const adapter = new InMemoryGridNoteAdapter();
grid.plugins = [GridNotesPlugin];
grid.gridNotes = {
adapter,
getRowId: row => row.id,
getRowLabel: row => row.status,
getCurrentUser: () => ({
id: session.user.id,
name: session.user.name,
avatarUrl: session.user.avatarUrl,
}),
mentions: {
search: (query, signal) => directory.searchUsers(query, { signal }),
},
can: ({ action, target, user }) => permissions.can(action, target, user),
excel: { includeNotesSheet: true },
};
```
`getRowId` is required. It must be pure, stable, and globally unique across body and pinned row stores. A visual row index is not a valid ID. Duplicate visual presentations of the same authored row should resolve to the same ID; synthetic group and subtotal rows should not receive an authored ID.
`getRowLabel` is optional and controls the human-readable value displayed in a note title. Use it to bind a field such as `status`; the stable `getRowId` value is never shown in the popover.
The bundled in-memory adapter is a reference implementation for demos and tests. Production applications should implement `GridNoteStorageAdapter` against their API. Bulk `loadSummaries()` responses contain marker metadata and a 120-character excerpt only. Full bodies come from `getNote()`, while version bodies are paged through `listVersions()`.
Set `GridNoteUser.avatarUrl` on authors and users returned by the mention directory to show their image beside their name in note metadata and mention suggestions. If an image cannot load, the plugin retains an accessible initials avatar, using `GridNoteUser.color` when supplied.
## Interaction
- Open the row context menu for **Add cell note** or **Add row note**.
- Press Shift+F2 on the focused cell.
- Select a coloured corner marker without entering cell edit mode.
- Select the row-gutter bar or dot to open the row note and the other cell notes on that row.
- Save with Ctrl+Enter or Command+Enter.
The portalled dialog closes on Escape, outside pointer interaction, or when its virtualized anchor disappears. Unsaved text is retained in local storage and dirty close attempts use `confirmDiscard`.
Selecting **Delete** opens a confirmation panel inside that same dialog. It does not call the browser-native confirmation API.
## Adapter concurrency
`upsert`, `delete`, and `restoreVersion` receive an idempotent mutation ID and expected version. An adapter should throw `GridNoteConflictError` when the current version changed. Restore creates a new current version with `restoredFromVersionId`; it never rewinds or mutates the append-only log.
Empty text is treated as a confirmed delete. Transient saves retry with jittered exponential backoff, pause while the browser is offline, and clear the persisted draft only after success.
## Severity filter and export
The context menu includes a multi-select **Filter by note severity** submenu. Matching is row-wide: a row remains when its row note or any cell note has a selected severity. The plugin applies its own trimmed-state category, so ordinary filters keep their state.
When `ExportExcelPlugin` is also installed and `excel.includeNotesSheet` is true, XLSX output gains a Notes worksheet. It contains one row per current note, including target identity, severity, body, mentions, author, timestamps, and version count. Clipboard and CSV values are unchanged.
## Events
Listen for `gridnotechange`, `gridnotemention`, `gridnoteconflict`, `gridnoteerror`, and `gridnotefilterchange`. Mention events contain only users introduced relative to the previous version, including a restore that reintroduces a mention.
---
# Autofill
URL: https://pro.rv-grid.com/guides/data-manage/autofill/
Source: src/content/docs/guides/data-manage/autofill.mdx
Description: Fill ranges with spreadsheet-like copy and sequence behavior.
The `AutoFillPlugin` adds spreadsheet-style drag fill to RevoGrid. Users select one
or more cells, drag the fill handle, and RevoGrid fills the target range with the
best matching strategy.
For a richer spreadsheet experience, pair it with `AutoFillPreviewPlugin`. The
preview plugin shows ghost values while the user is dragging, but it does not write
anything to the data store until the drop-time autofill is accepted.
Select one or more seed cells in the demo below, then drag the fill handle to
preview the values before releasing the mouse to commit them.
## Usage
```ts
import { AutoFillPlugin, AutoFillPreviewPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin, AutoFillPreviewPlugin];
```
Use only `AutoFillPlugin` when you want values to appear only after mouse release.
Add `AutoFillPreviewPlugin` when users should see the predicted values before they
drop the mouse.
### Formula autofill ownership
`AutoFillPlugin` remains the single owner of fill-handle interaction, strategy
selection, preview matrices, and committed range edits. When `FormulaPlugin` is
used, it automatically installs or reuses that same `AutoFillPlugin` and adds a
result transformer for raw `=...` cells. It does not implement a second fill
pipeline. A host-provided AutoFill instance, including its custom strategies, is
therefore preserved.
The fill handle still requires range selection:
```ts
import {
AutoFillPreviewPlugin,
FormulaPlugin,
} from '@revolist/revogrid-pro';
grid.range = true;
grid.plugins = [FormulaPlugin, AutoFillPreviewPlugin];
```
`AutoFillPreviewPlugin` is optional. When present, preview and commit pass through
the same Formula transformer, so the formula shown during the drag is the formula
stored after release.
One row down:
| Seed formula | Filled formula |
| --- | --- |
| `=A1` | `=A2` |
| `=$A$1` | `=$A$1` |
| `=A$1` | `=A$1` |
| `=$A1` | `=$A2` |
| `=SUM(A1:B2,$C1:D$4)` | `=SUM(A2:B3,$C2:D$4)` |
One column right:
| Seed formula | Filled formula |
| --- | --- |
| `=A1` | `=B1` |
| `=$A$1` | `=$A$1` |
| `=A$1` | `=B$1` |
| `=$A1` | `=$A1` |
| `=SUM(A1:B2,$C1:D$4)` | `=SUM(B1:C2,$C1:E$4)` |
Each endpoint of a range moves independently. Quoted and unquoted sheet
qualifiers are retained, so copying `='Product List'!A1:B2` down produces
`='Product List'!A2:B3`. Multi-cell selections tile from the corresponding seed
cell in every direction. Moving a relative coordinate before row 1 or column A
produces `#REF!`.
## Filling formatted cells
Register AutoFill and Event Manager with the formatting plugin when the fill handle
should stretch both values and formatting. Event Manager confirms the accepted range
edit before formatting is applied:
```ts
import {
AutoFillPlugin,
AutoFillPreviewPlugin,
DataGridFormattingPlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
DataGridFormattingPlugin,
AutoFillPlugin,
EventManagerPlugin,
AutoFillPreviewPlugin,
];
```
The formatting plugin resolves the effective format of every source cell,
including cell-, row-, and column-level formatting, and repeats that format as a
two-dimensional tile over the added cells. Advanced presentations such as Rating,
Progress, Badge, fonts, fills, borders, alignment, and number formats therefore
follow the same fill-handle gesture as the values. Existing source cells are not
rewritten, and formatting is applied only when the unified range edit is accepted.
See [Format Cells: range fill and clipboard](/guides/data-grid/format-cells/#range-fill-formatting)
for formatting ownership and persistence details.
### Crossing Pinned And Main Viewports
Add `MultiRangeSelectionPlugin` when users should be able to drag the autofill
handle from a pinned row or column into the main viewport, or from the main
viewport into a pinned section. Cross-viewport autofill is enabled by default
with multi-range selection.
```ts
import {
AutoFillPlugin,
MultiRangeSelectionPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [AutoFillPlugin, MultiRangeSelectionPlugin];
```
Cross-viewport autofill is scoped to visible viewport segments. If the main
viewport is scrolled, the plugin does not treat the pinned section and main data
as one hidden continuous range, and it does not write rows or columns behind the
scroll gap. The final selection is represented as multiple ranges so each visible
source and target segment remains explicit.
Disable the cross-viewport drag integration while keeping multi-range selection
enabled with:
```ts
grid.multiRangeSelection = { crossViewportAutofill: false };
```
## What Users See
1. Select a source cell or range.
2. Drag the small autofill handle from the selection corner.
3. Ghost values appear in the temporary target range when preview is enabled.
4. Release the mouse to commit the same values that were previewed.
5. Pressing escape, clearing the temporary range, or changing the source clears the preview.
Existing selected source cells are not visually replaced by preview values. The
preview only appears in the autofill target range.
## Built-In Strategies
AutoFill strategies are checked from most specific to most general. If no sequence
strategy can safely infer a pattern, RevoGrid repeats the selected values with copy
fallback.
| Strategy | User behavior | Example |
| --- | --- | --- |
| Linear numeric sequence | Continues a selected numeric interval. | `1, 3` -> `5, 7` |
| Single numeric step | Extends one selected number by `+1`. | `10` -> `11, 12` |
| Number in text | Continues labels with embedded numbers and preserves padding. | `INV-099` -> `INV-100`; `1st Floor` -> `2nd Floor` |
| Weekday names | Continues English weekday labels and wraps the week. | `Mon, Tue` -> `Wed` |
| Month names | Continues English month labels and wraps the year. | `Jan, Feb` -> `Mar` |
| Date unit sequence | Continues daily, weekly, monthly, quarterly, and yearly date intervals. | `2024-12-04, 2024-12-05` -> `2024-12-06` |
| Time sequence | Continues `HH:mm` and `HH:mm:ss` time intervals. | `09:00, 09:30` -> `10:00` |
| Boolean cycle | Repeats boolean-like toggle pairs. | `Yes, No` -> `Yes, No` |
| Text suffix sequence | Keeps compatibility with existing `Book 1`, `Book 2` fills. | `Book 1, Book 2` -> `Book 3` |
| Copy fallback | Repeats selected values when the pattern is plain or ambiguous. | `Draft, Approved` -> `Draft, Approved` |
## Strategy Notes
### Numeric Values
Select two or more numeric cells to preserve an interval. For example, selecting
`100` and `110` continues with `120`, `130`, and so on. Selecting one numeric cell
uses a simple `+1` sequence.
### Text With Numbers
AutoFill understands numbers inside labels, not only numbers at the end. It keeps
the visible text shape stable when the selected values are consistent.
```txt
Item 001 -> Item 002
INV-099 -> INV-100
1st Floor, 2nd Floor -> 3rd Floor
```
If labels use different prefixes, suffixes, or number formats, RevoGrid falls back
to copying the selected values instead of guessing.
### Dates And Times
Dates preserve the selected visible format when possible:
```txt
2024-12-04 -> 2024-12-05
2024/12/04 -> 2024/12/05
12/04/2024 -> 12/05/2024
```
Monthly, quarterly, and yearly fills are inferred from consistent month steps. Time
fills preserve `HH:mm` or `HH:mm:ss` and wrap across midnight.
### Weekdays And Months
Weekday and month strategies support English full names and three-letter labels.
The output format follows the source format:
```txt
Monday, Tuesday -> Wednesday
Mon, Tue -> Wed
January, February -> March
Jan, Feb -> Mar
```
### Boolean-Like Values
Boolean cycle supports `true/false`, `yes/no`, and `on/off`. String pairs require
both values in the selected range, so a single `Yes` cell is copied instead of
turning into an inferred toggle.
### Copy Fallback
Copy fallback is intentional. It handles plain text, mixed values, and unsupported
patterns by repeating the selected values exactly. This keeps AutoFill predictable
for status columns and other business data where guessing a sequence would be
surprising.
## Custom Strategies
Developers can register custom strategies for domain-specific patterns. Custom
strategies run before copy fallback, so they can override generic copy behavior.
```ts
import type { AutoFillStrategy } from '@revolist/revogrid-pro';
const priorityStrategy: AutoFillStrategy = (selectedData, direction, targetRange, context) => {
// Return a matrix when your strategy supports the selection.
// Return null to let the next strategy try.
return null;
};
AutoFillPlugin.registerStrategy(priorityStrategy);
```
Return `null` when a strategy is not confident. Preview and final apply share the
same strategy list, so a custom strategy automatically affects both behaviors.
---
# Collaborative Editing
URL: https://pro.rv-grid.com/guides/data-manage/collaborative-editing/
Source: src/content/docs/guides/data-manage/collaborative-editing.mdx
Description: Learn how to add shared cell editing, presence, pending review, permissions, server commits, conflicts, and audit attribution to RevoGrid Pro.
Collaborative Editing is separately distributed to keep Yjs out of applications
that do not need it. Trial users should install
`@revolist/revogrid-collaborative-editing-trial` alongside
`@revolist/rv-pro-trial`. Collaborative Presence remains available from the
main Pro trial package.
`CollaborativeEditingPlugin` lets several users work on the same grid data at the same time. One user edits a cell, the edit is written into a shared Yjs document, and the other connected grids can receive the same change.
You do not need to know Yjs before using the plugin. Think of Yjs as the shared notebook where the browser stores collaborative changes. RevoGrid still renders the grid, your app still owns users and business rules, and your backend still decides what is finally saved.
Install collaborative editing as a separate package so Yjs is only loaded by applications that need realtime editing:
```sh
pnpm add @revolist/revogrid-collaborative-editing @revolist/revogrid-pro
```
Trial equivalent:
```sh
pnpm add @revolist/revogrid-collaborative-editing-trial @revolist/rv-pro-trial
```
## Live Demo: Split Collaborative Editing
The demo catalog includes a complete split-view example with two users editing the same shared document. Open it when you want to see the moving parts together instead of reading isolated snippets.
[Open the Collaborative Editing demo](https://demo.rv-grid.com/collaborative-editing/ts)
The demo shows:
- two grids on the same page using one collaborative document;
- Avery and Grace rendered with matching presence colors and avatars;
- live remote focus and selection markers through `CollaborativePresencePlugin`;
- the first grid with the `Task` column pinned while the second grid keeps it unpinned;
- `EventManagerPlugin` with `applyEventsToSource: false`;
- an in-memory presence transport that stands in for a realtime provider;
- a mock `commitAdapter` that logs server commits so the backend boundary is visible.
## The Mental Model
Collaborative editing has a few moving parts. The names are technical, but the ideas are simple.
A user changes a cell. `EventManagerPlugin` normalizes that edit so collaborative editing sees cell edits, paste, range edits, undo, and redo through one pipeline.
Presence means "where are the other users looking or editing?" Add `CollaborativePresencePlugin` when you want remote cursors, ranges, colors, and labels.
In review mode, edits stay pending until your UI commits or discards them. This is useful when a manager, workflow, or server must approve changes.
A commit is the point where your app asks the backend to accept changes. The plugin calls your `commitAdapter`; your backend can accept, rewrite, or reject each edit.
A conflict happens when users edit the same row and column concurrently. The default policy keeps the change pending so your UI can show a decision.
Audit records explain who changed what and when. Add `AuditHistoryPlugin` when committed collaborative edits should be logged.
The plugin coordinates grid edits, shared Yjs state, pending changes, conflicts, presence bridging, and audit integration. Your app still owns authentication, user identity, backend persistence, authorization, and the review UI.
## Beginner Setup
Start with the smallest local setup, then add the pieces your product needs.
### 1. Minimal Local Grid
This example needs no server and no transport. It is useful for learning the plugin and verifying that local cell edits enter the collaborative pipeline.
1. Import the base plugins.
```ts
import {
EventManagerPlugin,
} from '@revolist/revogrid-pro';
import {
CollaborativeEditingPlugin,
type CollaborativeEditingConfig,
} from '@revolist/revogrid-collaborative-editing';
```
2. Give every row a stable id.
```ts
const rows = [
{ id: 'task-1', task: 'Revenue forecast', owner: 'Avery', status: 'Draft' },
{ id: 'task-2', task: 'Vendor renewal', owner: 'Mina', status: 'Review' },
{ id: 'task-3', task: 'Headcount plan', owner: 'Jon', status: 'Approved' },
];
const columns = [
{ prop: 'task', name: 'Task' },
{ prop: 'owner', name: 'Owner' },
{ prop: 'status', name: 'Status' },
];
```
3. Register plugins and bind configuration.
```ts
const grid = document.querySelector('revo-grid')!;
grid.columns = columns;
grid.source = rows;
grid.plugins = [
EventManagerPlugin,
CollaborativeEditingPlugin,
];
grid.eventManager = { applyEventsToSource: false };
const collaborativeEditing: CollaborativeEditingConfig = {
documentId: 'local-learning-session',
user: {
id: 'avery',
name: 'Avery Stone',
color: '#2563eb',
permissionLevel: 'editor',
},
rowIdProp: 'id',
};
grid.collaborativeEditing = collaborativeEditing;
```
`grid.eventManager = { applyEventsToSource: false }` is important. It lets collaborative editing own how edits are applied, reviewed, committed, or rolled back instead of letting each raw grid edit immediately mutate source data twice.
### 2. Two Grids On The Same Page
Use a shared Yjs document when two grids on one page should represent the same collaborative session. This is the easiest way to understand how two users share edits before connecting a real transport.
```ts
import * as Y from 'yjs';
import {
CollaborativePresencePlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
import {
CollaborativeEditingPlugin,
} from '@revolist/revogrid-collaborative-editing';
const doc = new Y.Doc();
const plugins = [
EventManagerPlugin,
CollaborativePresencePlugin,
CollaborativeEditingPlugin,
];
function configureGrid(grid: HTMLRevoGridElement, user: { id: string; name: string; color: string }) {
grid.columns = columns;
grid.source = rows.map(row => ({ ...row }));
grid.plugins = plugins;
grid.eventManager = { applyEventsToSource: false };
grid.collaborativeEditing = {
documentId: 'same-page-demo',
user,
rowIdProp: 'id',
doc,
presence: {
enabled: true,
showLabels: true,
staleAfterMs: 15_000,
},
};
}
configureGrid(gridA, { id: 'avery', name: 'Avery Stone', color: '#2563eb' });
configureGrid(gridB, { id: 'grace', name: 'Grace Hopper', color: '#16a34a' });
```
Both grids use the same `documentId` and the same `doc`, so they are intentionally part of the same collaborative document.
`CollaborativeEditingPlugin` maps awareness state into presence users. `CollaborativePresencePlugin` renders those users as colored focus and range markers. For deeper cursor and range behavior, read [Collaborative Presence](/guides/data-manage/collaborative-presence/).
### 3. Review Mode With Pending Changes
Use review mode when users should make changes locally, inspect the pending list, then commit or discard those changes from your UI.
```ts
grid.collaborativeEditing = {
documentId: `budget:${budgetId}`,
user: currentUser,
rowIdProp: 'id',
mode: 'review',
conflictPolicy: 'manual',
};
```
Read the plugin instance when your UI needs pending changes or review actions.
```ts
const plugins = await grid.getPlugins();
const collaborative = plugins.find(
plugin => plugin instanceof CollaborativeEditingPlugin,
) as CollaborativeEditingPlugin | undefined;
const pending = collaborative?.getPendingChanges() ?? [];
await collaborative?.commitPendingChanges(pending.map(change => change.id));
collaborative?.discardPendingChanges();
```
Review mode is a good default for finance, procurement, admin, support, planning, and any workflow where users should not silently overwrite each other.
### 4. Commit Changes To A Server
Use `commitAdapter` when your backend must validate, persist, approve, reject, version, or rewrite edits.
```ts
grid.collaborativeEditing = {
documentId: `forecast:${forecastId}`,
user: currentUser,
rowIdProp: 'id',
mode: 'autoCommit',
commitAdapter: {
async commit(changes, context) {
const response = await fetch(`/api/forecasts/${forecastId}/changes`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
documentId: context.documentId,
user: context.user,
changes,
}),
});
return response.json();
},
},
};
```
The response should describe which changes were committed and which were rejected. Rejected changes are rolled back to their previous local value.
```ts
[
{ id: 'change-1', status: 'committed' },
{ id: 'change-2', status: 'rejected', reason: 'Budget is locked' },
]
```
### 5. Add Business Permissions
Use `permissions.canEditCell` for user, role, row, tenant, or workflow-specific edit rules.
```ts
grid.collaborativeEditing = {
documentId: `pricing:${priceBookId}`,
user: {
id: currentUser.id,
name: currentUser.name,
roles: currentUser.roles,
permissionLevel: currentUser.permissionLevel,
},
rowIdProp: 'sku',
permissions: {
canEditCell({ user, row, prop, newValue }) {
if (row?.locked) return false;
if (prop === 'approvedPrice') return user.roles?.includes('pricing-admin') ?? false;
if (prop === 'discount' && Number(newValue) > 0.2) return user.permissionLevel === 'manager';
return user.permissionLevel === 'editor';
},
},
};
```
Client permissions improve UX and prevent obvious invalid edits early. They are not a security boundary; repeat important checks in your backend through `commitAdapter`.
## Connect More Than One Browser
A shared local `Y.Doc` is enough for demos and same-page tests. For real users in different browsers, add a transport.
```ts
import {
createYjsWebSocketTransport,
} from '@revolist/revogrid-collaborative-editing';
grid.collaborativeEditing = {
documentId: `project:${projectId}:tasks`,
user: currentUser,
rowIdProp: 'id',
transport: createYjsWebSocketTransport({
url: 'wss://collaboration.example.com',
params: { token: sessionToken },
}),
};
```
Use WebSocket when your product needs a controlled server, authentication, observability, and enterprise networking. Use WebRTC for peer-to-peer collaboration where your deployment can support signaling and peer connectivity. Use a custom transport when your application already wraps Yjs providers.
## Listen For Conflicts
The default `conflictPolicy` is `manual`. When two users edit the same row and column concurrently, the plugin emits `collaborativeconflict` and keeps the local change pending.
```ts
grid.addEventListener('collaborativeconflict', (event) => {
const conflict = event.detail;
showConflictDialog({
rowId: conflict.local.rowId,
column: conflict.local.prop,
localValue: conflict.local.newValue,
remoteValue: conflict.remoteValue,
user: conflict.remoteUser,
});
});
```
Set `conflictPolicy: 'lastWriteWins'` only for low-risk cells such as notes, draft comments, or transient planning values where silent overwrite is acceptable.
## Configuration Reference
### Business-Critical Properties
| Property | Type | Default | First-time guidance |
|:--|:--|:--|:--|
| `documentId` | `string` | `'revogrid-collaborative-document'` | The shared session name. Use the same value for every user editing the same business object, such as `budget:2026` or `project:alpha:tasks`. |
| `user` | `CollaborativeEditingUser` | `undefined` | The current editor. Provide at least `id` and `name`; without a user, local edits are ignored because edits must be attributable. |
| `rowIdProp` | `string` | `'id'` | The row field that uniquely identifies the business record. Use your real primary key, such as `invoiceId`, `sku`, `taskId`, or `employeeId`. |
| `commitAdapter` | `CollaborativeEditingCommitAdapter` | `undefined` | Your backend boundary. Add it when changes must be saved, validated, approved, rejected, versioned, or audited by a server. |
| `permissions` | `CollaborativeEditingPermissions` | `undefined` | Client-side business rules. Use this for role checks, locked rows, approval status, tenant rules, or field-level permissions. |
| `audit` | `CollaborativeEditingAuditConfig \| boolean` | enabled | Audit integration. Keep it enabled for operational, financial, admin, support, or regulated workflows. |
| `conflictPolicy` | `'manual' \| 'lastWriteWins'` | `'manual'` | How same-cell concurrent edits are handled. Keep `manual` for business data. |
### Runtime And Collaboration Properties
| Property | Type | Default | First-time guidance |
|:--|:--|:--|:--|
| `enabled` | `boolean` | `true` | Turns collaboration on or off without changing the plugin list. Useful for feature flags or read-only screens. |
| `doc` | `Y.Doc` | new isolated document | Pass this when your app owns the Yjs document or when several grids should share one local document. |
| `mode` | `'autoCommit' \| 'review'` | `'autoCommit'` | Use `autoCommit` for immediate server reconciliation. Use `review` when users need commit and discard buttons. |
| `transport` | `CollaborativeEditingTransport` | `undefined` | Connects different browsers. Without transport, collaboration is local to the current page or shared `doc`. |
| `presence` | `{ enabled, showLabels, staleAfterMs }` | enabled, labels shown | Controls the bridge into `CollaborativePresencePlugin`. Use it when users should see who is focused or editing. |
### `user`
`user` tells the plugin who is editing. It is used for presence labels, permission checks, commit requests, audit records, and conflict metadata.
```ts
const user = {
id: 'u-123',
name: 'Avery Stone',
email: 'avery@example.com',
initials: 'AS',
color: '#2563eb',
roles: ['planner', 'budget-editor'],
permissionLevel: 'editor',
};
```
Use a stable application user id. Avoid session ids or display names as `id`, because audit history and server reconciliation need durable identity.
### `documentId`
`documentId` means "which shared document is this grid editing?"
```ts
documentId: `forecast:${forecastId}`
documentId: `project:${projectId}:schedule`
documentId: `tenant:${tenantId}:price-book:${priceBookId}`
```
Use the same `documentId` for every client editing the same dataset. Use a different `documentId` when two grids should not share edits or presence.
### `rowIdProp`
Collaborative editing stores cell changes by stable row identity. If `rowIdProp` is wrong, edits may apply to the wrong row after sorting or filtering.
```ts
const rows = [
{ invoiceId: 'INV-1001', status: 'Draft', total: 1200 },
];
grid.collaborativeEditing = {
documentId: 'invoices:open',
user,
rowIdProp: 'invoiceId',
};
```
Use `id` only when every row really has a durable `id`. For generated, aggregated, or temporary rows, provide a stable key before enabling collaboration.
### `commitAdapter`
`commitAdapter` means "ask my backend whether these edits are accepted."
```ts
commitAdapter: {
async commit(changes, context) {
const response = await fetch('/api/reconcile-grid-changes', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
documentId: context.documentId,
user: context.user,
changes,
}),
});
return response.json() as Promise;
}>>;
},
}
```
Return `committed` when the server accepts a change. Return `rejected` when validation, permissions, workflow status, version checks, or business rules fail.
### `permissions`
`permissions.canEditCell` runs before a local edit is accepted. It complements RevoGrid's `readonly` support:
- use column `readonly` for static UI-level rules
- use `permissions.canEditCell` for user, row, tenant, or workflow-specific rules
```ts
permissions: {
canEditCell({ user, row, prop, newValue }) {
if (row?.approvalStatus === 'Approved') return false;
if (prop === 'budgetLimit') return user.roles?.includes('finance-admin') ?? false;
if (prop === 'discount' && Number(newValue) > 0.2) return user.permissionLevel === 'manager';
return user.permissionLevel === 'editor';
},
}
```
### `audit`
When `AuditHistoryPlugin` is registered, collaboration commits are recorded as audit events by default.
```ts
audit: {
enabled: true,
actionType: 'collaborative-price-change',
source: 'api',
}
```
Disable audit only for temporary or non-business data:
```ts
audit: false
```
## Commit Modes
Use `mode: 'autoCommit'` when the server should accept or reject each edit as soon as it is made.
Use `mode: 'review'` when users need an explicit commit/discard workflow. In review mode, edits are optimistic locally and remain in `getPendingChanges()` until the host commits or discards them through the plugin instance.
## Audit Trail
When `AuditHistoryPlugin` is registered and `audit.enabled !== false`, committed collaborative changes call `AuditHistoryPlugin.recordEvent()` with:
- `type: 'collaborative-edit'`
- current collaborative user
- transaction id
- row id, row index, column, old value, and new value
- collaborative metadata and document id
Use the Audit History storage hooks to persist those records to your backend.
## Common First-Time Mistakes
| Mistake | What happens | Fix |
|:--|:--|:--|
| Missing `EventManagerPlugin` | Edits do not enter the normalized collaborative edit pipeline. | Register `EventManagerPlugin` before `CollaborativeEditingPlugin`. |
| Missing `user` | Local edits are ignored because the plugin cannot attribute them. | Pass a stable `user` with at least `id` and `name`. |
| Missing or unstable `rowIdProp` | Edits can target the wrong row after sorting, filtering, or reordering. | Use a durable business id field. |
| Different `documentId` values | Users do not share the same collaborative document. | Use one stable `documentId` for every client in the same session. |
| Expecting automatic persistence | Local/Yjs state changes, but your backend is not updated. | Add a `commitAdapter`. |
| Expecting visible labels without presence plugin | Editing works, but remote cursors and ranges do not render. | Add `CollaborativePresencePlugin` and enable `presence`. |
## What To Read Next
- [Collaborative Presence](/guides/data-manage/collaborative-presence/) for remote cursors, selections, labels, stale users, and pinned-area behavior.
- [Audit Trail History](/guides/audit-trail/) for persisted change logs and restore workflows.
- [Event Manager](/guides/data-manage/event-manager-explanation/) for the edit event pipeline collaborative editing uses.
---
# Collaborative Presence
URL: https://pro.rv-grid.com/guides/data-manage/collaborative-presence/
Source: src/content/docs/guides/data-manage/collaborative-presence.mdx
Description: Render remote collaborator focus cells, selected ranges, labels, colors, stale-user cleanup, and source-index row patches in RevoGrid Pro.
`CollaborativePresencePlugin` shows where other users are looking or editing inside the grid. It renders colored focus rectangles, selected ranges, avatars, and user labels without changing the local user's focus, selection, editor, data history, or keyboard state.
Use it by itself when your app already has presence data, or use it with [Collaborative Editing](/guides/data-manage/collaborative-editing/) when Yjs awareness should drive remote cursors and ranges automatically.
## What It Does
Show the single cell another user is focused on.
Show another user's selected or active range.
Render initials, names, and stable colors so users can identify each other quickly.
Resolve marker positions from RevoGrid viewport state, including pinned columns, instead of querying physical cell DOM nodes.
Presence markers are overlays. They do not move local focus, create local selections, open editors, or persist data by themselves.
## Basic Setup
1. Import and register the plugin.
```ts
import {
CollaborativePresencePlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
CollaborativePresencePlugin,
];
```
2. Provide users through `grid.collaborativePresence`.
```ts
grid.collaborativePresence = {
users: [
{
id: 'avery',
name: 'Avery Stone',
initials: 'AS',
color: '#2563eb',
activity: 'editing',
focus: { x: 0, y: 1 },
range: { x: 0, y: 1, x1: 2, y1: 3 },
lastActiveAt: Date.now(),
},
{
id: 'grace',
name: 'Grace Hopper',
initials: 'GH',
color: '#16a34a',
activity: 'viewing',
focus: { x: 1, y: 0 },
lastActiveAt: Date.now(),
},
],
};
```
The plugin observes `grid.collaborativePresence`, `grid['collaborative-presence']`, and `additionalData.collaborativePresence`.
## Cell And Range Coordinates
Presence coordinates use grid source indexes:
- `x` is the source column index in the target column area.
- `y` is the source row index in the target row area.
- `x1` and `y1` are the opposite range corner.
- `colType` defaults to `'rgCol'`.
- `rowType` defaults to `'rgRow'`.
```ts
grid.collaborativePresence = {
users: [
{
id: 'avery',
name: 'Avery Stone',
color: '#2563eb',
focus: { x: 0, y: 2 },
range: { x: 0, y: 2, x1: 3, y1: 4 },
},
],
};
```
For pinned columns, pass the matching column type. This keeps the marker in the pinned viewport instead of the main viewport.
```ts
grid.collaborativePresence = {
users: [
{
id: 'grace',
name: 'Grace Hopper',
color: '#16a34a',
focus: {
x: 0,
y: 1,
colType: 'colPinStart',
rowType: 'rgRow',
},
},
],
};
```
The plugin resolves source indexes through RevoGrid stores and viewport structures. This is important when sorting, filtering, pinning, or virtualization changes which physical cells are mounted.
## Configuration Reference
| Property | Type | Default | What it controls |
|:--|:--|:--|:--|
| `enabled` | `boolean` | `true` | Turns all presence markers on or off without removing the plugin. |
| `showLabels` | `boolean` | `true` | Shows or hides user name labels. Focus and range colors still render when labels are hidden. |
| `staleAfterMs` | `number` | `undefined` | Hides users whose `lastActiveAt` is older than the configured timeout. |
| `users` | `CollaborativePresenceUser[]` | `[]` | The remote users to render. Each user can provide `focus`, `range`, or both. |
| `remoteEdits` | `CollaborativePresenceRowPatch[]` | `[]` | Optional source-row patches for remote data changes that should update grid rows without resetting focus or selection. |
## User Shape
```ts
type CollaborativePresenceUser = {
/**
* Stable user id used to distinguish one collaborator from another.
* Use your application user id, not a temporary session id or display name.
*/
id: string;
/**
* Human-readable name shown in the presence label when `showLabels` is true.
*/
name: string;
/**
* Optional short avatar text. If omitted, the label can derive initials from `name`.
*/
initials?: string;
/**
* Optional marker color used for focus borders, range overlays, avatar, and label.
* Keep this stable per user so collaborators are easy to recognize.
*/
color?: string;
/**
* Optional activity state for your own presence model.
* The plugin accepts viewing, editing, and idle states.
*/
activity?: 'viewing' | 'editing' | 'idle';
/**
* Optional focused cell for this user.
* `x` and `y` are source indexes in the specified column and row areas.
* Set this to `null` when the user has no active focus to render.
*/
focus?: {
/** Source column index inside `colType`. */
x: number;
/** Source row index inside `rowType`. */
y: number;
/**
* Column area that owns the focused cell.
* Defaults to `rgCol`; use pinned column types for pinned cells.
*/
colType?: 'colPinStart' | 'rgCol' | 'colPinEnd';
/**
* Row area that owns the focused cell.
* Defaults to `rgRow`; use pinned row types for pinned rows.
*/
rowType?: 'rgRow' | 'rowPinStart' | 'rowPinEnd';
} | null;
/**
* Optional selected or active range for this user.
* `x`, `y` and `x1`, `y1` are opposite corners in source indexes.
* Set this to `null` when the user has no range to render.
*/
range?: {
/** Source column index for one range corner. */
x: number;
/** Source row index for one range corner. */
y: number;
/** Source column index for the opposite range corner. */
x1: number;
/** Source row index for the opposite range corner. */
y1: number;
/**
* Column area that owns the range.
* Defaults to `rgCol`; use pinned column types for pinned ranges.
*/
colType?: 'colPinStart' | 'rgCol' | 'colPinEnd';
/**
* Row area that owns the range.
* Defaults to `rgRow`; use pinned row types for pinned ranges.
*/
rowType?: 'rgRow' | 'rowPinStart' | 'rowPinEnd';
} | null;
/**
* Last known activity time for stale-user cleanup.
* Accepts a timestamp, date string, or Date object.
* Used only when `staleAfterMs` is configured.
*/
lastActiveAt?: number | string | Date;
};
```
Use stable `id` values and consistent colors so the same person looks the same across grids and sessions.
## Labels And Stale Users
Use `showLabels: false` for dense grids where full labels would cover too much content.
```ts
grid.collaborativePresence = {
showLabels: false,
users,
};
```
Use `staleAfterMs` to hide users who disconnect or stop sending presence updates.
```ts
grid.collaborativePresence = {
staleAfterMs: 15_000,
users: remoteUsers.map(user => ({
...user,
lastActiveAt: user.lastSeenAt,
})),
};
```
## Remote Row Patches
`remoteEdits` can apply remote row updates directly into the RevoGrid data store without resetting the full source or disturbing the local editor state.
```ts
grid.collaborativePresence = {
remoteEdits: [
{
rowIndex: 2,
rowType: 'rgRow',
data: {
status: 'Approved',
owner: 'Grace',
},
},
],
users,
};
```
`rowIndex` is a source row index, not the currently visible row position. That keeps patches stable when the grid is sorted or filtered.
## With Collaborative Editing
When `CollaborativeEditingPlugin` has presence enabled, it maps Yjs awareness state into `CollaborativePresencePlugin`.
```ts
grid.plugins = [
EventManagerPlugin,
CollaborativePresencePlugin,
CollaborativeEditingPlugin,
];
grid.collaborativeEditing = {
documentId: `budget:${budgetId}`,
user: currentUser,
rowIdProp: 'id',
presence: {
enabled: true,
showLabels: true,
staleAfterMs: 15_000,
},
};
```
Use direct `grid.collaborativePresence` updates when your app owns the realtime presence source. Use collaborative editing presence when Yjs awareness owns the realtime presence source.
---
# Event Manager
URL: https://pro.rv-grid.com/guides/data-manage/event-manager-explanation/
Source: src/content/docs/guides/data-manage/event-manager-explanation.mdx
Description: Get comprehensive documentation on the event management.
Learn how to handle, customize, and optimize events to create complex interactions and workflows with the Event Manager Plugin for RevoGrid. The main purpose of this plugin is to collect and dispatch edit events in one centralized location, simplifying event management.
The Event Manager Plugin aggregates various editing events into a single event, `gridedit`. This approach reduces the complexity of handling multiple event types individually, such as `clipboardrangepaste`, `beforeedit`, and `beforerangeedit`. By using the Event Manager Plugin, you can catch all these events through one unified event, making it a robust starting point for building your custom event management system.
## Key Features
- **Centralized Event Handling:** Collects multiple edit events into a single `gridedit` event.
- **Simplified Event Management:** Reduces the need to manage numerous event types individually.
- **Customizable Workflows:** Provides a foundation to create complex interactions and workflows.
## Unified payload
`data`, `previousData`, and `models` always use RevoGrid's normal numeric-row
and field-patch format. Feature plugins can add semantic information through
the ordered `domainChanges` array without changing that grid-data contract.
Replay-only feature state is kept separately in keyed `history` snapshots, so
internal undo data never appears as a synthetic grid field.
For example, a Kanban move can expose a normal `status` patch in `data`, identify
the operation as a card move in `domainChanges`, and keep its compact undo state
in `history`.
```ts
grid.addEventListener('gridedit', (event) => {
const { data, previousData, domainChanges = [], eventTypes } = event.detail;
// Common RevoGrid patches, regardless of which feature produced the edit.
saveGridChanges({ data, previousData });
// Optional feature semantics. One transaction may contain several entries.
for (const change of domainChanges) {
if (change.type === 'kanban-card') {
const { action, cardId, previousCard, card } = change.detail;
saveCardChange({ action, cardId, previousCard, card });
}
}
if (eventTypes.includes('kanban-card-change')) {
refreshKanbanSummary();
}
});
```
The currently exported domain types and tags are:
| Feature | Domain type | `eventTypes` tag |
| --- | --- | --- |
| Gantt task | `gantt-task` | `gantt-task-change` |
| Gantt dependency | `gantt-dependency` | `gantt-dependency-change` |
| Gantt assignment | `gantt-assignment` | `gantt-assignment-change` |
| Kanban card | `kanban-card` | `kanban-card-change` |
| Scheduler event | `event-scheduler-event` | `event-scheduler-event-change` |
Domain changes are ordered. Bulk operations and scheduling cascades include all
affected entities, not only the item directly manipulated. Structural changes
can have no safe numeric row patch, so `data` may be empty while
`domainChanges` still fully describes the committed operation.
Use feature-specific types when you only consume one producer:
```ts
import type { KanbanEventManagerEvent } from '@revolist/kanban';
interface MyCard {
id: string;
status: string;
}
grid.addEventListener('gridedit', (event) => {
const detail = event.detail as KanbanEventManagerEvent;
detail.domainChanges?.forEach(({ detail: cardChange }) => {
console.log(cardChange.action, cardChange.cardId);
});
});
```
### Cancellation and source ownership
For ordinary grid edits, EventManager owns applying `data`; canceling
`gridedit` prevents that write. A feature event with
`sourceMutation: 'producer'` was already committed by Gantt, Kanban, or
Scheduler. Its `gridedit` is therefore observational and canceling it does not
roll the mutation back. Use the producer's cancelable `before` event for
validation instead.
When collection is enabled, consecutive compatible changes become one ordered
`gridedit` transaction and one undo step:
```ts
grid.eventManager = {
collectEvents: true,
collectEventsDelay: 100,
};
```
The transaction preserves the earliest previous state and latest committed
state for each entity. Do not rely on exact equality of `eventTypes`; use
`eventTypes.includes(...)` because a transaction may contain several tags.
Plugins that submit producer-owned history must also handle `beforehistoryedit`.
After successfully restoring their state, they call
`markEventManagerHistoryReplayHandled(detail, ownedHistoryKeys)` and cancel
that replay event.
This keeps malformed or unavailable producer history on its current undo/redo
stack instead of silently consuming it. Applications normally do not need to
read `history`; it is replay state owned by `HistoryPlugin` and the producer.
## Implementation
Here's a brief overview of how to implement the Event Manager Plugin in your RevoGrid setup.
### Step-by-Step Guide
1. **Import Required Modules:**
Import the necessary modules from the RevoGrid library to start setting up the Event Manager Plugin.
2. **Define Edit Details Type:**
Create a type for edit details that will help structure the event data.
3. **Create EventManagerPlugin Class:**
Develop the `EventManagerPlugin` class to handle and dispatch edit events. This class listens for various edit events and dispatches a unified `gridedit` event.
- **Collect Edit Events:** Listens for `clipboardrangepaste`, `beforeedit`, and `beforerangeedit` events.
- **Dispatch Unified Event:** Dispatches the `gridedit` event after collecting details from the various edit events.
4. **Add Plugin to RevoGrid:**
Attach the Event Manager Plugin to your RevoGrid instance and start listening for the `gridedit` event.
```javascript
...
grid.plugins = [EventManagerPlugin];
...
```
For detailed implementation, please refer to the full code below.
The Event Manager Plugin is a powerful tool for managing edit events.
By consolidating multiple event types into a single, manageable event, it simplifies event handling and provides a solid foundation for developing custom workflows and interactions.
---
# Grouping with Aggregation
URL: https://pro.rv-grid.com/guides/data-manage/grouping-aggregation/
Source: src/content/docs/guides/data-manage/grouping-aggregation.mdx
Description: Learn how to implement data grouping with custom aggregation functions in RevoGrid
## Overview
The Grouping with Aggregation feature allows you to organize your data into hierarchical groups and display custom aggregated values for each group. This is particularly useful for displaying summary statistics, totals, averages, or custom calculations for grouped data.
## Key Features
- **Hierarchical Grouping**: Group data by multiple columns in a tree-like structure
- **Custom Aggregations**: Define custom aggregation functions for different data types
- **Visual Indicators**: Expandable/collapsible groups with aggregation values displayed
- **Flexible Templates**: Customize how group headers and aggregations are displayed
## Basic Usage
## Implementation
### 1. Import Required Components
```typescript
import { groupingAggregation } from '@revolist/revogrid-pro';
```
### 2. Define Aggregation Functions
Create custom aggregation functions for different columns. **Important**: Aggregation functions receive an array of complete data objects, not just the column values. You need to extract the specific property you want to aggregate:
```typescript
const aggregations = {
price: (values: any[]) => {
const prices = values.map(item => item.price);
const sum = prices.reduce((acc, val) => acc + val, 0);
const avg = sum / prices.length;
return `Avg: $${avg.toFixed(2)}`;
},
quantity: (values: any[]) => {
const quantities = values.map(item => item.quantity);
const total = quantities.reduce((acc, val) => acc + val, 0);
return `Total: ${total}`;
},
product: (values: any[]) => {
return `${values.length} products`;
}
};
```
### 3. Create Group Template
Use the `groupingAggregation` function to create a template that displays aggregations:
```typescript
const groupTemplate = (h: any, props: any) => {
return groupingAggregation(h, props, aggregations);
};
```
### 4. Configure Columns
Apply the group template to columns that will be used for grouping:
```typescript
const columns = [
{ name: '🆔 ID', prop: 'id', size: 60 },
{ name: '📂 Category', prop: 'category', size: 120, template: groupTemplate },
{ name: '📁 Subcategory', prop: 'subcategory', size: 120, template: groupTemplate },
{ name: '📦 Product', prop: 'product', size: 150 },
{ name: '💰 Price', prop: 'price', size: 100 },
{ name: '📊 Quantity', prop: 'quantity', size: 100 },
{ name: '📅 Order Date', prop: 'orderDate', size: 120 },
{ name: '🚚 Delivery Date', prop: 'deliveryDate', size: 120 },
{ name: '👤 Customer', prop: 'customer', size: 150 },
{ name: '🌍 Region', prop: 'region', size: 100 },
{ name: '📋 Status', prop: 'status', size: 100 },
];
```
### 5. Set Up Grouping Configuration
Define which columns should be used for grouping:
```typescript
const grouping = {
props: ['orderDate', 'category', 'subcategory']
};
```
### 6. Initialize the Grid
```typescript
const grid = document.createElement('revo-grid');
grid.source = yourData;
grid.columns = columns;
grid.grouping = grouping;
// Grouping is a built-in feature, no plugin needed
```
## Aggregation Function Examples
### Numeric Aggregations
```typescript
// Average
price: (values: any[]) => {
const prices = values.map(item => item.price);
const avg = prices.reduce((acc, val) => acc + val, 0) / prices.length;
return `Avg: $${avg.toFixed(2)}`;
}
// Sum
quantity: (values: any[]) => {
const quantities = values.map(item => item.quantity);
const total = quantities.reduce((acc, val) => acc + val, 0);
return `Total: ${total}`;
}
// Min/Max
price: (values: any[]) => {
const prices = values.map(item => item.price);
const min = Math.min(...prices);
const max = Math.max(...prices);
return `Range: $${min} - $${max}`;
}
```
### String Aggregations
```typescript
// Count
product: (values: any[]) => {
return `${values.length} products`;
}
// Concatenation
names: (values: any[]) => {
const names = values.map(item => item.name);
return names.join(', ');
}
// Unique count
categories: (values: any[]) => {
const categories = values.map(item => item.category);
const unique = new Set(categories);
return `${unique.size} unique categories`;
}
```
### Date Aggregations
```typescript
// Date range
orderDate: (values: any[]) => {
const dates = values.map(item => new Date(item.orderDate));
const sorted = dates.sort((a, b) => a.getTime() - b.getTime());
const earliest = sorted[0].toLocaleDateString();
const latest = sorted[sorted.length - 1].toLocaleDateString();
return `${earliest} - ${latest}`;
}
```
## Advanced Configuration
### Custom Group Header Styling
You can customize the appearance of group headers by modifying the template function:
```typescript
const customGroupTemplate = (h: any, props: any) => {
const aggregationValue = getAggregationValue(props);
return h('div', {
style: {
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '4px 8px',
backgroundColor: '#f0f0f0',
borderRadius: '4px'
}
}, [
h('span', { style: { fontWeight: 'bold' } }, props.name),
h('span', { style: { fontSize: '12px', color: '#666' } }, `(${aggregationValue})`)
]);
};
```
### Conditional Aggregations
You can apply different aggregation logic based on the column or group:
```typescript
const conditionalAggregations = {
price: (values: any[]) => {
const prices = values.map(item => item.price);
const sum = prices.reduce((a, b) => a + b, 0);
const avg = sum / prices.length;
return `Avg: $${avg.toFixed(2)}`;
}
};
```
## Best Practices
1. **Performance**: Keep aggregation functions lightweight, especially with large datasets
2. **User Experience**: Provide clear, meaningful aggregation labels
3. **Data Types**: Ensure aggregation functions handle the correct data types
4. **Error Handling**: Add validation for edge cases (empty arrays, null values)
5. **Accessibility**: Use descriptive text for screen readers
## Common Use Cases
- **Sales Reports**: Group by region/category with revenue totals
- **Inventory Management**: Group by department with stock quantities
- **Financial Data**: Group by account type with balance summaries
- **Project Management**: Group by team with task counts and completion rates
---
# History Undo/Redo
URL: https://pro.rv-grid.com/guides/data-manage/history/
Source: src/content/docs/guides/data-manage/history.mdx
Description: History and undo and redo capabilities in data grid, making it easier to manage user edits
The History Plugin brings powerful undo and redo capabilities to your data grid, making it easier to manage user edits and maintain data integrity.
Keyboard shortcuts like `Ctrl+Z` (or `Cmd+Z` on macOS) for undo and `Ctrl+Y` (or `Cmd+Y` on macOS) / or `Ctrl+Shift+Z` (or `Cmd+Shift+Z` on macOS) for redo are supported out of the box.
Shortcuts work from document keydown events by default, even when the grid does not currently have a focused cell. Set `keyboardShortcutsWithoutFocus: false` to require grid focus.
You can customize its behavior by using the BEFORE_UNDO_EVENT and BEFORE_REDO_EVENT hooks.
- Undo/Redo Management: Automatically tracks user changes with the ability to undo/redo actions using keyboard shortcuts.
- Configurable Stack Size: Tracks up to 200 changes by default, and accepts partial `history` config when you only need to override one option.
- Provider Persistence: Can load and save stack state through local cache and custom server providers for same-dataset sessions.
- Custom Event Hooks: Supports custom behavior via beforeundo and beforeredo events, allowing full control over the undo/redo process.
- Pro Editing Coverage: Tracks regular cell edits, Pro editor templates, textarea editors, range edits, clipboard paste, autofill, row-edit saves, and audit restore replays. `HistoryPlugin` installs its Event Manager dependency automatically.
- Context-menu Transactions: Tracks each built-in Cut, Paste, Clear, row mutation, or column-schema mutation as one chronological undo/redo action. Copy remains non-mutating and is not recorded.
### Additional details
Register `HistoryPlugin` to enable undo/redo. It automatically installs
`EventManagerPlugin` when an Event Manager instance is not already present, so
normal edit events enter the undo stack without a second undocumented plugin.
```ts
import {
HistoryPlugin,
createLocalStorageHistoryAdapter,
} from '@revolist/revogrid-pro';
plugins = [HistoryPlugin];
```
Undo, redo, and public replay operations emit `beforehistedit`. The Event
Manager installed by History handles that event and emits the normal `gridedit`
flow. If your application already registers `EventManagerPlugin`, History
reuses that instance rather than creating a duplicate.
`AuditHistoryPlugin` automatically reuses or installs `HistoryPlugin` for restore replay, keyboard shortcuts, and existing History integrations. Keep History configuration on `grid.history`; Audit History does not define a nested History config.
### Context-menu history
When `HistoryPlugin` and `DataGridContextMenuPlugin` are installed together,
each built-in mutating command is atomic: a multi-cell Cut, Paste, cell Clear,
or row Clear creates one history entry, and undo restores all affected cells in
reverse edit order. Redo reapplies them in forward order.
History also snapshots the affected authored structure for these commands:
- Rows: insert above/below, duplicate, delete, pin top/bottom, and unpin.
- Columns: insert left/right, duplicate, delete, pin left/right, unpin, and move
left/right/start/end.
Plain Copy and Copy with headers do not create entries because they do not
mutate the grid. A `commandHandlers` override is application-owned and is also
excluded: if a custom handler changes remote or application state, record and
restore that state in the application instead. Canceled, unavailable, and no-op
commands leave both stacks unchanged.
In-memory structural entries retain row object identity, nested column groups,
renderers, editors, and other function-valued column fields. Persisted entries
should use serializable row and column definitions; storage formats such as
JSON cannot restore functions. Context-menu replay temporarily bypasses
`clearOnSourceChange` only for its own row-source replacements. Unrelated
application source replacements continue to use the configured clearing policy.
`grid.history` is optional. Add it only when you need custom stacks, a smaller
or larger stack limit, or an initially disabled plugin.
```ts
grid.history = {
maxStackSize: 100,
keyboardShortcutsWithoutFocus: false,
};
```
### Persisting History
Undo and redo stacks are index-based, so persisted history is safe only when the user returns to the same dataset, row order, and compatible column schema. Structural context-menu entries additionally require serializable row and column definitions. For long-term recovery across changing data, use `AuditHistoryPlugin`, which restores by stable row identity.
Use `sourceId` to reject stale server states. With multiple providers, the plugin loads the first provider immediately, then applies later providers only when their `updatedAt` state is newer and their `sourceId` is compatible.
When storage is configured, `clearOnSourceChange` defaults to `false` so the initial data load does not erase the restored stack. Set it to `true` only when your app wants every source replacement to clear persisted undo/redo state.
```ts
grid.history = {
sourceId: `invoices:${workspaceId}:${datasetVersion}`,
storageProviders: [
createLocalStorageHistoryAdapter('invoices:history'),
{
async load() {
const response = await fetch('/api/grid-history');
return response.json();
},
async save(state) {
await fetch('/api/grid-history', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(state),
});
},
async clear() {
await fetch('/api/grid-history', { method: 'DELETE' });
},
},
],
};
```
Listen to `historyerror` when you want to surface provider failures or skipped states:
```ts
grid.addEventListener('historyerror', (event) => {
console.warn(event.detail.phase, event.detail.error);
});
```
To call `undo()`, `redo()`, `clear()`, `disable()`, or state helpers, get plugin instances
from the grid and find the `HistoryPlugin` instance.
```ts
async undo() {
// get plugins from the grid
const plugins = await this.gridRefNativeElement.getPlugins();
const history = plugins.find((plugin) => plugin instanceof HistoryPlugin);
if (history) {
if (history.canUndo()) {
history.undo();
}
}
}
```
For custom controls, listen to `historychanged` instead of reading plugin
internals. The event detail includes `undoStackSize`, `redoStackSize`,
`canUndo`, `canRedo`, and `disabled`.
### Row reorder history
When `HistoryPlugin` and `RowOrderPlugin` are installed together, every
successful row drop is one undo/redo action by default. This includes selected
row blocks and grouped drops that update group fields.
```ts
grid.history = {
rowOrderHistory: 'track', // default
};
```
Use `rowOrderHistory: 'clear'` to discard existing history after a committed
drop, or `rowOrderHistory: 'preserve'` when another integration records a richer
domain snapshot. Gantt uses `preserve` and records canonical tasks and
dependencies together.
### Troubleshooting
- Undo/redo does not record changes: ensure `HistoryPlugin` is present in the
grid's `plugins` array. Event Manager setup and keyboard shortcuts are
automatic.
- Restore flashes are missing in Audit History: install `EventManagerPlugin` and `CellFlashPlugin`; Audit restore replay flows through `beforehistedit` and the normal edit event path.
- Calling plugin methods does not work: call `getPlugins()` first, then find
`HistoryPlugin`.
- History clears after sort, filter, or source replacement: this is intentional
because those operations can change visible row indexes. Successful row
reorders are tracked unless `rowOrderHistory` is `clear` or `preserve`.
- Persisted history was skipped: check the `historyerror` event for a
`storage-source-mismatch` phase and update the configured `sourceId`.
- Need a known-good reference: see code in:
`revogrid-pro/src/components/history/**.**`.
### Key methods
The History Plugin provides four key methods to enhance control over `undo/redo` functionality:
- `clear()`: Resets both the undo and redo stacks, clearing all recorded changes.
- `disable(disable = true)`: Temporarily disables the plugin, preventing changes from being recorded or undo/redo operations from being triggered.
- `undo()`: Reverts the last change recorded in the undo stack and moves it to the redo stack for potential reapplication.
- `redo()`: Reapplies the most recent change from the redo stack, moving it back to the undo stack for further management.
- `canUndo()` / `canRedo()`: Returns whether undo or redo is currently available.
- `getUndoStackSize()` / `getRedoStackSize()`: Returns stack counts for custom controls.
- `isDisabled()`: Returns whether history is disabled.
These methods give developers flexibility in managing user actions and tailoring the plugin’s behavior to specific application requirements.
### Behind the Scenes
The plugin listens to edit events (onEditEvent) and tracks changes in undo and redo stacks. Keyboard shortcuts like Ctrl+Z for undo and Ctrl+Y (or Ctrl+Shift+Z) for redo are supported out of the box. You can customize its behavior by using the BEFORE_UNDO_EVENT and BEFORE_REDO_EVENT hooks.
Here’s a quick snippet showing how the plugin processes undo actions:
```ts
undo() {
if (this.undoStack.length === 0) return;
const lastChange = this.undoStack.pop();
if (lastChange) {
const event = this.emit(BEFORE_UNDO_EVENT, {
data: lastChange.previousData,
type: lastChange.type,
lastChange,
});
if (!event.defaultPrevented) {
const data = event.detail.data || lastChange.previousData;
this.replayChange({
data,
type: event.detail.type || lastChange.type,
models: lastChange.models,
previousData: lastChange.data,
});
this.redoStack.push(lastChange);
}
}
}
```
`replayChange()` emits `beforehistedit` first. If no integration handles that event, History falls back to `providers.data.setRangeData(...)`.
Try it Out!
Add the History Plugin to your RevoGrid instance and experience seamless undo/redo functionality. This plugin is especially useful in applications that handle complex data operations, ensuring users can easily revert or reapply changes as needed.
```javascript
// fixing render for multiframework
```
---
# Nested Grid
URL: https://pro.rv-grid.com/guides/data-manage/nested-grid/
Source: src/content/docs/guides/data-manage/nested-grid.mdx
Description: Learn how to use the Nested Grid to display hierarchical data within RevoGrid cells.
The Nested Grid allows you to display hierarchical data by embedding RevoGrids inside cells. This is particularly useful when you need to show detailed, structured data within a cell, such as project details, task lists, or any other nested data structure.
## Row Expansion
The Nested Grid combined with the Row Expansion plugin to provide a more interactive experience. Row expansion allows users to expand a row to reveal additional details or nested grids.
### Enabling Row Expansion
To enable row expansion, you can configure the grid as follows:
```typescript
import { RowExpandPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowExpandPlugin];
```
### Example Configuration
Here's an example of how to set up row expansion with nested grids:
```typescript
grid.columns = [
{ prop: 'id', name: 'ID', expand: true }, // Enable row expansion for this column
{ prop: 'name', name: 'Name' },
{
prop: 'details',
name: 'Details',
}
];
// Additional configuration for row expansion
grid.additionalData = {
rowExpand: {
expandedRows: new Set([1]), // Example of expanded rows
expandedRowHeight: 200, // Height for the expanded row
// Hide expand toggle based on row data
hideExpand: (rowIndex, model) => {
// Example: Hide expand toggle for rows without tasks
return !model.tasks || model.tasks.length === 0;
},
template: (h, props) => {
return h('revo-grid', {
key: 'nested-grid',
class: 'mt-1',
style: {
height: '100%',
maxHeight: '90%',
maxWidth: '95%',
},
theme: isDark() ? 'darkCompact' : 'compact',
resize: true,
readonly: true,
columns: [
{ prop: 'id', name: '#', size: 50 },
{ prop: 'task', name: 'Task', size: 220 },
{
prop: 'status',
name: 'Status',
size: 150,
cellProperties: ({ value }) => ({
style: {
color: '#fff',
backgroundColor: value === 'Complete' ? '#00c874' : value === 'In Progress' ? '#fdaa3d' : '#e2435c',
},
}),
},
],
source: props.model.tasks, // Source data for the nested grid
hideAttribution: true,
});
},
},
};
```
## Configuration Options
The nested grid configuration supports all standard RevoGrid properties. Here are some commonly used options:
- `columns`: Array of column definitions for the nested grid
- `source`: Function that returns the data array for the nested grid
- `theme`: Theme to apply to nested grids
- `readonly`: Whether the nested grid should be read-only
- `rowSize`: Height of rows in the nested grid
- `resize`: Enable column resizing in nested grid
- Any other RevoGrid properties you want to apply to nested grids
### Row Expansion Options
The row expansion configuration supports several options to control expansion behavior:
- `expandedRows`: Set of row IDs that should be expanded by default
- `expandedRowHeight`: Height for expanded rows (in pixels)
- `hideExpand`: Controls visibility of expand toggle buttons, can be:
- A boolean: `true` to hide all expand toggles
- A function: `(rowIndex: number, model: any) => boolean` for conditional control
Example of conditional expand toggle visibility:
```typescript
grid.additionalData = {
rowExpand: {
// Hide expand toggle for rows that don't have nested data
hideExpand: (rowIndex, model) => {
return !model.hasNestedData || model.tasks?.length === 0;
},
// ... other configuration options
}
};
```
## Best Practices
1. **Data Structure**: Organize your data hierarchically with clear parent-child relationships.
2. **Performance**: Consider the following for optimal performance:
- Limit the number of nested grids visible at once
- Keep nested data sets reasonably sized
- Use read-only mode for nested grids if editing isn't required
3. **Responsive Design**: Ensure columns with nested grids have sufficient width.
4. **User Experience**: Use row expansion judiciously to enhance user interaction without overwhelming them with too much information at once.
## Advanced Usage
### Custom Cell Templates
You can combine nested grids with custom cell templates for rich data visualization:
```typescript
{
prop: 'progress',
name: 'Progress',
cellTemplate: (h, props) => {
const progress = props.model[props.prop] || 0;
return h('div', {
style: {
width: '100%',
height: '100%'
}
}, [
h('div', {
style: {
width: `${progress}%`,
backgroundColor: progress === 100 ? '#4CAF50' : '#2196F3'
}
})
]);
}
}
```
---
# Row Expand Plugin
URL: https://pro.rv-grid.com/guides/data-manage/row-expand/
Source: src/content/docs/guides/data-manage/row-expand.mdx
Description: Learn how to use RowExpandPlugin for inline expandable row details.
The `RowExpandPlugin` adds inline expandable detail rows to RevoGrid. It is useful when a row needs a compact
secondary area for a form, nested grid, notes, preview content, or any row-local custom template.
Unlike the [Master Rows](/guides/data-manage/row-master/) feature, `RowExpandPlugin` does not render a
master-detail overlay. It inserts an expanded row into the regular row viewport and renders your
`rowExpand.template` inside that row.
Use `RowExpandPlugin` for inline detail rows that should stay in the normal grid flow. Use
[`MasterRowPlugin`](/guides/data-manage/row-master/) when the detail area must span the grid, stay aligned
through overlay rendering, or work as a full master-detail view across pinned areas.
## Basic Setup
Import the plugin and add it to the grid:
```ts
import { RowExpandPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [RowExpandPlugin];
```
Add `expand: true` to the column that should render the expand toggle:
```ts
grid.columns = [
{ prop: 'name', name: 'Name', expand: true },
{ prop: 'status', name: 'Status' },
];
```
Configure expanded row behavior with `grid.rowExpand`:
```ts
grid.rowExpand = {
expandedRows: new Set([0]),
expandedRowHeight: 220,
template: (h, props) =>
h('div', { class: 'row-detail' }, [
h('strong', null, props.model.name),
h('p', null, props.model.description),
]),
};
```
## Configuration
| Option | Type | Description |
| ------ | ---- | ----------- |
| `expandedRows` | `Set` | Physical row indexes that should be expanded by default. |
| `expandedRowHeight` | `number` | Height, in pixels, for each expanded detail row. |
| `hideExpand` | `boolean \| ((rowIndex: number, model: any) => boolean)` | Hides the expand toggle for all rows or for specific rows. Return `true` to hide the toggle. |
| `template` | `(h: any, props: any) => VNode` | Renders the expanded row content. The template receives the row model and render context. |
`additionalData.rowExpand` is still supported for compatibility, but prefer the direct `grid.rowExpand`
property.
## Programmatic Expansion
The plugin listens for row expansion events:
```ts
import {
ROW_COLLAPSE,
ROW_COLLAPSE_ALL,
ROW_EXPAND,
ROW_EXPAND_ALL,
} from '@revolist/revogrid-pro';
grid.dispatchEvent(new CustomEvent(ROW_EXPAND, { detail: { rowIndex: 0 } }));
grid.dispatchEvent(new CustomEvent(ROW_COLLAPSE, { detail: { rowIndex: 0 } }));
grid.dispatchEvent(new CustomEvent(ROW_EXPAND_ALL));
grid.dispatchEvent(new CustomEvent(ROW_COLLAPSE_ALL));
```
The `rowIndex` value is the virtual row index at the time the event is dispatched.
## Difference From Master Rows
`RowExpandPlugin` and `MasterRowPlugin` both expose expandable row experiences, but their rendering models are
different:
| Capability | RowExpandPlugin | MasterRowPlugin |
| ---------- | --------------- | --------------- |
| Rendering model | Inline expanded row in the regular row viewport | Overlay-rendered master-detail row |
| Required plugins | `RowExpandPlugin` | `MasterRowPlugin` (`OverlayPlugin` is auto-installed) |
| Toggle setup | Set `expand: true` on a column | Use `EXPAND_COLUMN` or dispatch `ROW_MASTER` |
| Best for | Nested grids, row forms, compact detail rows | Full-width master-detail content and pinned-area spanning layouts |
| Layout scope | Constrained by regular grid row and columns | Can span across the grid through overlay rendering |
For the overlay-based master-detail implementation, read the
[Master Rows guide](/guides/data-manage/row-master/) or the [Row Master API](/api/row-master/).
For `MasterRowPlugin` source refreshes, see [Preserve or Reset Detail State](/guides/data-manage/row-master/#preserve-or-reset-detail-state).
That guide covers keeping expanded details and draft inputs, or recreating details from updated data.
`preserveExpandedOnSourceChange` belongs to `grid.masterRow`; it is not a `RowExpandPlugin` option.
## Example Use Cases
- Inline edit forms, as shown in the [Form Edit example](/guides/form-edit/).
- Nested grids inside expanded rows, as shown in the [Nested Grid guide](/guides/data-manage/nested-grid/).
- Compact row detail panels that do not need to span pinned columns or the full grid.
---
# Master Rows
URL: https://pro.rv-grid.com/guides/data-manage/row-master/
Source: src/content/docs/guides/data-manage/row-master.mdx
Description: A guide to using the Master Row Plugin.
The **Master Row Plugin** enhances the [RevoGrid](https://rv-grid.com/) grid component by enabling the creation and management of master-detail rows.
It is an extremely **complex and advanced feature**, despite its seemingly simple nature. Read more below.
1. Dynamic Overlay Synchronization: The Master Row Plugin creates a dynamic overlay that tracks grid scrolling.
This ensures that master-detail information remains perfectly aligned and visible as you navigate through large datasets.
2. Expansive Master Detail Context: Unlike tree view, group view, or merge cell approaches, this plugin uniquely expands the master row context into the pinned area, allowing it to fill the entire grid. This capability provides a richer, more immersive data presentation and is exceptionally challenging to achieve.
3. Viewport Synchronization: Detail overlays track virtual row positions. Expanded details remain mounted but hidden while offscreen, preserving their local state until they are collapsed or removed.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Setup](#basic-setup)
- [Configuration](#configuration)
- [Custom Templates](#custom-templates)
- [Programmatic Expand and Collapse](#programmatic-expand-and-collapse)
- [Preserve or Reset Detail State](#preserve-or-reset-detail-state)
- [API Reference](#api-reference)
---
## Features
- **Expandable Rows:** Easily expand rows to reveal additional details.
- **Custom Templates:** Define how master rows are rendered using customizable templates.
---
## Basic Setup
1. **Import Necessary Plugins and Utilities:**
```javascript
import { EXPAND_COLUMN, MasterRowPlugin, CellColumnFocusVerifyPlugin } from '@revolist/revogrid-pro';
import type { RowMasterConfig } from '@revolist/revogrid-pro';
import './row-master.style.css';
```
2. **Define Columns:**
```javascript
const columns: HTMLRevoGridElement['columns'] = [
{
prop: 'expand',
...EXPAND_COLUMN,
},
// ...
];
```
3. **Initialize the Grid with Plugins:**
```javascript
const grid = document.querySelector('revo-grid');
grid.source = makeData(10, 20);
grid.columns = columns;
// Define plugin
grid.plugins = [
MasterRowPlugin,
];
const masterRow: RowMasterConfig = {
rowHeight: 100,
template: (h, data) => {
return data.model.subRows?.map((subRow: Person) =>
h('span', { class: { 'master-row': true } }, [
h('img', { src: subRow.avatar, width: '20', height: '20' }),
`${subRow.firstName} ${subRow.lastName} `,
]),
);
},
};
grid.masterRow = masterRow;
}
```
### Configuration
The Master Row Plugin can be customized by assigning a `RowMasterConfig` object to `grid.masterRow`. The primary configuration option is the `template` function, which defines how the master row content is rendered.
#### RowMasterConfig
| Property | Type | Description |
| -------- | ---- | ----------- |
| `template` | `(h: Function, data: any) => JSX.Element` | A function that returns the JSX structure for the master row. It receives a rendering function `h` and the data context `data`. |
| `preserveExpandedOnSourceChange` | `boolean` | Defaults to `false`. Retain expanded details and local state after source replacement, using `rowKey`, the Tree Data ID field, or physical source positions. |
| `rowKey` | `string` | Optional unique ID field. Overrides the automatic Tree Data ID field; ordinary grids otherwise use physical source positions. |
| `allowDetailEvents` | `readonly string[]` | Event names from the master detail area that are allowed to bubble to the parent grid. All RevoGrid events inside the detail area are stopped by default. |
### Custom Templates
Customize the master row by defining a `template` function that returns the desired JSX structure. This allows for flexible and dynamic rendering based on the row data.
```javascript
const masterRow: RowMasterConfig = {
template: (h, data) => {
return data.model.subRows?.map((subRow: Person) =>
h('div', { class: { 'custom-master-row': true } }, [
h('img', { src: subRow.avatar, width: '30', height: '30' }),
`${subRow.firstName} ${subRow.lastName}`,
]),
);
},
};
```
To let selected events from nested detail content reach the parent grid, add them by name:
```javascript
const masterRow: RowMasterConfig = {
allowDetailEvents: ['afteredit'],
template: (h, data) => h('revo-grid', { source: data.model.details }),
};
```
### Programmatic Expand and Collapse
`EXPAND_COLUMN` toggles a single master row by dispatching the `ROW_MASTER` event with the current cell template payload. You can dispatch the same event directly when a toolbar, external control, or custom cell template needs to control master-row state.
For one row, pass the same cell template payload shape used by cell templates. The plugin treats this as a toggle:
```ts
import { ROW_MASTER } from '@revolist/revogrid-pro';
grid.dispatchEvent(new CustomEvent(ROW_MASTER, {
detail: cellTemplateData,
}));
```
For multiple rows, pass a batch payload with a `rows` array. Each row target declares its desired final state with `expanded: true` or `expanded: false`, so one event can open and close different rows at the same time:
```ts
import { ROW_MASTER } from '@revolist/revogrid-pro';
import type { RowMasterBatchEvent } from '@revolist/revogrid-pro';
const detail: RowMasterBatchEvent = {
rows: [
{ rowType: 'rgRow', rowIndex: 1, expanded: true },
{ rowType: 'rgRow', rowIndex: 3, expanded: false },
{ rowType: 'rowPinStart', rowIndex: 0, expanded: true },
],
};
grid.dispatchEvent(new CustomEvent(ROW_MASTER, { detail }));
```
`rowIndex` is the current virtual row index for the given `rowType`. The plugin converts every target to a stable internal row reference before applying the batch, so one event can safely mix opened and closed rows without later changes shifting earlier targets. If multiple targets resolve to the same row, the last entry wins.
### Preserve or Reset Detail State
Choose whether a source update should keep the current detail view or start a fresh one.
These options apply to `MasterRowPlugin` and `grid.masterRow`.
#### Keep expanded rows and local input state
Enable `preserveExpandedOnSourceChange` to keep expanded details and local input state when you
replace the source. For ordinary grids, details stay at the same source positions within each row type.
Tree Data automatically follows its ID field (`tree.idField`, or `id` by default):
```ts
grid.masterRow = {
rowHeight: 180,
preserveExpandedOnSourceChange: true,
template: (h, data) => h('div', null, [
h('strong', null, data.model.name),
h('input', {
type: 'text',
'aria-label': 'Draft note',
placeholder: 'Type a draft note',
}),
]),
};
// Refresh with new row objects.
grid.source = grid.source.map(row => ({ ...row }));
```
Open a detail, type a note, then refresh the source. The detail at that position stays expanded and
its uncontrolled input keeps its value. Scrolling out of view and back also retains local state.
With positional matching, inserting or reordering records keeps the draft at its source position, so it may
belong to a different record. Sorting the displayed rows does not change their physical source indexes.
#### Follow records using an ID field
Set `rowKey` when expanded details should follow their records across source reordering or API refreshes:
```ts
grid.masterRow = {
...grid.masterRow,
preserveExpandedOnSourceChange: true,
rowKey: 'id',
};
// New objects in a different order; details follow matching IDs.
grid.source = [...grid.source].reverse().map(row => ({ ...row }));
```
IDs must be unique strings or finite numbers in each row type. Missing, invalid, or duplicate IDs
are not preserved; they do not fall back to source positions. Removed or hidden records lose their details.
Tree Data uses its ID field automatically, including when activated by a tree column. An explicit
`rowKey` takes precedence. Without either a custom field or Tree Data, matching uses physical source indexes.
Preserving detail state does not save input text into your data model. Store drafts in application
state if they must survive explicit collapse, removal of a record, grid destruction, or a page reload.
For controlled inputs and nested components, their own value/prop update rules still apply.
#### Recreate details from updated data
Leave `preserveExpandedOnSourceChange` unset, or set it to `false`, to collapse expanded rows and
remove their detail DOM whenever the source is replaced. Opening a row again renders its template
with the updated model and starts a fresh detail view. Do not cancel source cleanup in a
`BEFORE_ROW_MASTER_COLLAPSE` listener when you want this reset behavior:
```ts
grid.masterRow = {
...grid.masterRow,
preserveExpandedOnSourceChange: false,
};
// New records, for example the result of a server refresh.
grid.source = grid.source.map(row => ({ ...row, status: 'Updated' }));
```
If you want a fresh detail to open automatically, dispatch `ROW_MASTER` after the source update.
This example opens the first row of an ordinary, nonempty, unfiltered source:
```ts
import { ROW_MASTER } from '@revolist/revogrid-pro';
grid.addEventListener('afteranysource', () => {
grid.dispatchEvent(new CustomEvent(ROW_MASTER, {
detail: {
rows: [{ rowType: 'rgRow', rowIndex: 0, expanded: true }],
},
}));
}, { once: true });
grid.source = grid.source.map(row => ({ ...row, status: 'Updated' }));
```
Use the target row's current virtual index when sorting, filtering, or Tree Data changes its position.
To reset just one detail without replacing the source, explicitly collapse it and expand it again
using the [programmatic controls](#programmatic-expand-and-collapse).
### Automatic Collapse
Expanded master rows are cleared when the grid is about to change row visibility or row structure in a way that can invalidate the detail overlay position.
The plugin automatically cleans up expanded rows when:
- the grid source is replaced, unless `preserveExpandedOnSourceChange` is enabled
- filtering is about to trim rows through `beforefiltertrimmed`
- row dragging starts
During that cleanup, expanded master rows are collapsed, detail overlay nodes are removed, duplicated detail rows are removed from the row item lists, and custom detail row heights are cleared. This prevents a filtered or replaced row from leaving stale detail height behind after the row itself is no longer visible.
For source updates, prefer `preserveExpandedOnSourceChange` to retain details after a refresh. The same positional or `rowKey` matching rules apply when source cleanup is canceled through the event. To cancel automatic cleanup through an event instead, listen for `BEFORE_ROW_MASTER_COLLAPSE` and call `preventDefault()`:
```ts
import { BEFORE_ROW_MASTER_COLLAPSE } from '@revolist/revogrid-pro';
grid.addEventListener(BEFORE_ROW_MASTER_COLLAPSE, (event) => {
if (event.detail.reason === 'filter') {
event.preventDefault();
}
});
```
When `preserveExpandedOnSourceChange` is enabled, source updates use preservation directly and do not emit this collapse event. The event detail includes `reason` (`'source'`, `'filter'`, or `'drag'`) and `rows`, the expanded master rows that are about to be cleared.
Preventing the default cleanup means your application is responsible for handling any stale detail overlays or custom row heights if the related source row is no longer visible.
---
## API Reference
### MasterRowPlugin
A plugin class that manages master-detail rows within the RevoGrid component.
#### Methods
- **constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders)**
Initializes the plugin with the RevoGrid instance and plugin providers.
- **expandRow(type: DimensionRows, virtRowIndex: number)**
Expands a specific row to show its master details.
- **collapseRow(type: DimensionRows, rowVIndexes: number[])**
Collapses one or more rows, hiding their master details.
- **destroy()**
Cleans up event listeners and other resources when the plugin is destroyed.
#### Events
The plugin listens to various RevoGrid events to manage the state of master rows, including:
- `AFTER_GRID_RENDER_EVENT`
- `BEFORE_ROW_RENDER_EVENT`
- `BEFORE_ROW_MASTER_COLLAPSE` - cancelable event fired before automatic master-row cleanup collapses expanded rows.
- `ROW_MASTER` - toggles one row from a cell template payload, or applies a batch of desired row states with `{ rows: [{ rowType, rowIndex, expanded }] }`.
- `OVERLAY_NODE`
- ...and more.
---
# Tree Data Grid
URL: https://pro.rv-grid.com/guides/data-manage/tree/
Source: src/content/docs/guides/data-manage/tree.mdx
Description: Enhance your RevoGrid with hierarchical data visualization using the TreeData Plugin.
The **TreeData Plugin** transforms parent-child relationships data into a hierarchical tree structure and provides the ability to group rows by `parentId` and is optimized for the best performance, making it suitable for large datasets.
It enables features such as expandable rows, and level indicators, making it ideal for applications that handle nested data.
## Tree Plugin vs Key Grouping
RevoGrid has two different ways to show hierarchical rows, and they solve different problems:
- **Key grouping** uses the grid's built-in `grouping` configuration, for example `grid.grouping = { props: ['country', 'city'] }`. The grid reads one or more column values, creates synthetic group header rows for each matching key, and places source rows under those generated groups. Use it when the hierarchy should be calculated from repeated values such as category, region, status, or date.
- **TreeDataPlugin** uses explicit row relationships from your data, normally `id` and `parentId`. The plugin keeps your original rows as the tree nodes, computes tree metadata such as level, expanded state, and visibility, and trims collapsed descendants from the viewport. Use it when the hierarchy already exists in the data, such as folders, tasks and subtasks, organization charts, bill of materials, or nested records.
In short, key grouping answers "which rows share the same values?", while the tree plugin answers "which row is the parent of this row?". Key grouping is value-driven and creates group rows; tree data is relationship-driven and renders your existing rows as parent and child nodes.
It is an extremely **complex and advanced feature**, despite its seemingly simple nature.
1. **Group Rows by Parent ID**: build hierarchical structures without modifying the original data, ensuring smooth performance even with large datasets.
2. **Complicated Drag-and-Drop**: The plugin supports **drag-and-drop between parent nodes** and automatically updates the `parentId` of the moved rows.
3. **Advanced Proxy System**: The plugin uses an advanced **proxy system** to handle sorting and filtering while keeping track of hierarchical relationships. This ensures that **all other plugins** remain unaffected by tree view manipulations, allowing for seamless integration.
4. **Metadata Computation**: The plugin computes tree metadata (e.g., level, expanded state, visibility) without modifying the underlying data. It uses a **recursive approach** to compute tree levels and visibility based on the parent-child relationships, and it leverages **row trimming** to optimize performance without altering the source data.
5. **Crucial for Other Plugins**: Beyond its core functionality, the Tree View plugin plays a **crucial role** in supporting other RevoGrid plugins. It facilitates **data transfer** between different components, ensuring compatibility with features like filtering, sorting, and row selection.
- **Hierarchical Data Support**: Automatically organizes flat data into a tree structure based on customizable `id`, `parentId`, and `level` fields.
- **Expandable Rows**: Expand and collapse rows dynamically to reveal or hide child rows.
- **Customizable Templates**: Supports tree-specific cell templates for better customization.
- **Root Parent Support**: Define custom root parent identifiers for flexibility in tree structure.
## Data Structure
To build the hierarchical tree structure, each data item must include the following fields:
- **`id`**: A unique identifier for each row.
- **`parentId`**: Links the item to its parent row. Root-level rows should use the `rootParentId` value (default: `'root'`).
Without these fields, the plugin cannot establish the necessary parent-child relationships for the tree structure.
```javascript
const data = [
{ id: '1', parentId: 'root', name: 'Parent 1' },
{ id: '2', parentId: '1', name: 'Child 1.1' },
{ id: '3', parentId: '1', name: 'Child 1.2' },
{ id: '4', parentId: 'root', name: 'Parent 2' },
];
```
## Additional Configuration Options
The plugin supports the following options for customization:
- idField: Defines the field representing unique row identifiers. Default: 'id'.
- parentIdField: Specifies the field indicating parent row IDs. Default: 'parentId'.
- levelField: Sets the field to store hierarchy levels. Default: 'level'.
- rootParentId: Defines the identifier for root-level rows. Default: 'root'.
- expandedRowIds: A Set of row IDs to predefine expanded rows.
### Example Configuration
You can provide these options via revogrid.additionalData?.tree to customize the plugin’s behavior:
```javascript
grid.additionalData = {
tree: {
idField: 'customId',
parentIdField: 'customParentId',
levelField: 'depth',
rootParentId: null,
expandedRowIds: new Set(['row1', 'row2']), // Pre-expanded rows
},
};
```
By setting these options, you can adapt the plugin to different data structures and define default states for the tree.
### Expand Multiple Rows
Use `expandRowsById()` to reveal several branches in one tree update. It preserves existing expanded rows and skips updates when all supplied IDs are already expanded.
```ts
import { TreeDataPlugin } from '@revolist/revogrid-pro';
const tree = (await grid.getPlugins()).find(
plugin => plugin instanceof TreeDataPlugin,
);
tree?.expandRowsById(['phase-1', 'phase-2']);
```
### Animated Collapse and Expand
Tree rows can animate while they are trimmed during collapse and restored during expand. Enable `tree.animation`; TreeDataPlugin registers `DimensionAnimationPlugin` automatically when it is not already present.
```javascript
import { TreeDataPlugin } from '@revolist/revogrid-pro';
grid.plugins = [TreeDataPlugin];
grid.tree = {
animation: true,
};
grid.dimensionAnimation = {
duration: 180,
};
```
When `tree.animation` is not enabled, tree collapse and expand use the existing immediate trim behavior.
---
# Checkbox Editor
URL: https://pro.rv-grid.com/guides/editors/editor-checkbox/
Source: src/content/docs/guides/editors/editor-checkbox.mdx
The `editorCheckbox` is a custom cell editor for RevoGrid that provides a checkbox input
to edit boolean values directly within the grid cells.
* **Features**:
- Renders a checkbox input element for cells, allowing users to toggle boolean values (`true/false`)
with a simple click.
- Automatically dispatches a `celledit` event upon change, updating the grid's data model with
the new value.
- Ensures seamless integration with RevoGrid by providing row and column details in the event payload.
- The demo combines checkbox editing with `RowOddPlugin` zebra striping and the
`material`/`darkMaterial` themes.
---
# Counter Editor
URL: https://pro.rv-grid.com/guides/editors/editor-counter/
Source: src/content/docs/guides/editors/editor-counter.mdx
Description: A plus/minus counter editor for numeric values
The `editorCounter` is a custom cell editor for RevoGrid that provides plus and minus buttons
to increment/decrement numeric values within a specified range directly within the grid cells.
**Features**:
- Renders plus and minus buttons for easy value adjustment
- Supports customizable minimum and maximum values through column properties
- Configurable step size for increments/decrements
- Shows current value with optional display toggle
- Automatically dispatches a `celledit` event upon change
- Seamless integration with RevoGrid's data model
- Modern, responsive design with customizable theming
## Usage
Import the `editorCounter` and assign it to the `cellTemplate` property of a column in the RevoGrid:
```typescript
import { editorCounter } from '@revolist/revogrid-pro'
grid.columns = [
{
prop: 'quantity',
name: 'Quantity',
cellTemplate: editorCounter,
min: 0, // Minimum value (default: 0)
max: 100, // Maximum value (default: 100)
step: 5, // Step size for increments/decrements (default: 1)
hideValue: false, // Set to true to hide the value display
},
];
```
## Configuration Options
The counter editor supports several configuration options through column properties:
- `min` (number): The minimum allowed value (default: 0)
- `max` (number): The maximum allowed value (default: 100)
- `step` (number): The increment/decrement step size (default: 1)
- `hideValue` (boolean): Whether to hide the numeric value display (default: false)
## Event Handling
The counter editor dispatches a `celledit` event whenever the value changes. The event detail contains:
- `rgCol`: The column index of the edited cell
- `rgRow`: The row index of the edited cell
- `type`: The type of the cell
- `prop`: The property of the cell being edited
- `val`: The new numeric value after the change
- `preventFocus`: A flag to control grid focus behavior (default: true)
## Best Practices
1. **Step Size**: Choose a step size that makes sense for your data. For example:
- Use 1 for integer values like quantities
- Use 0.1 or 0.01 for decimal values like prices
- Use 5 or 10 for larger ranges
2. **Value Range**: Set appropriate min and max values to prevent invalid data:
- For quantities: min = 0, max = maximum stock level
- For percentages: min = 0, max = 100
- For ratings: min = 1, max = 5 (or your rating scale)
3. **Visual Feedback**: The editor provides visual feedback through:
- Disabled buttons when at min/max values
- Hover effects on buttons
- Clear value display (unless hidden)
4. **Accessibility**: The editor supports keyboard interaction:
- Tab navigation between buttons
- Button states clearly indicated through styling
- Numeric value visible by default
---
# Dropdown Editor
URL: https://pro.rv-grid.com/guides/editors/editor-dropdown/
Source: src/content/docs/guides/editors/editor-dropdown.mdx
The `ColumnDropdown` column type renders the Pro dropdown editor inside a grid cell. Use it for single-select values, or enable `config.multiSelect` when the cell value is an array.
Register `ColumnDropdown` in `columnTypes`, set `columnType: 'dropdown'` on a column, and provide a `dropdown.source` list with `{ value, label }` options.
**Features**:
- Supports single-select cells with scalar values
- Supports multi-select cells with array values through `config.multiSelect: true`
- Can enable search with `config.search: true`
- Saves changes through RevoGrid cell edit events, so the grid data model stays in sync
- Supports custom option and selected-value templates through the dropdown render callbacks
- Can mirror the column cell template in options and selected values with `dropdown.syncCellTemplate`
- Can disable the dropdown per cell and disable individual options
### Disable a dropdown for every cell
Set `dropdown.disabled: true` to keep the selected value visible while blocking the dropdown. A disabled dropdown never opens through a click, double-click, Enter, F2, typing, or `config.autoOpen`. It cannot change a selection or remove a multiselect chip.
```typescript
import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
const columns: ColumnRegular[] = [{
prop: 'status',
name: 'Status',
columnType: 'dropdown',
dropdown: {
disabled: true,
source: [
{ value: 'active', label: 'Active' },
{ value: 'archived', label: 'Archived' },
],
},
}];
grid.columns = columns;
grid.source = [{ status: 'active' }];
```
### Disable dropdowns based on the row
Use a synchronous callback returning a boolean. It receives the current `ColumnDataSchemaModel`: `model`, `value`, `column`, `prop`, `rowIndex`, `colIndex`, `type`, `colType`, and `data`. The `value` is the stored cell value, even when `cellParser` supplies a display label. Keep this callback free of side effects; do not change grid state while rendering. Promise-returning callbacks are unsupported.
```typescript
import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
const columns: ColumnRegular[] = [{
prop: 'status',
name: 'Status',
columnType: 'dropdown',
dropdown: {
disabled: ({ model }) => model.locked === true,
source: [
{ value: 'active', label: 'Active' },
{ value: 'archived', label: 'Archived' },
],
},
}];
grid.columns = columns;
grid.source = [
{ status: 'active', locked: false },
{ status: 'archived', locked: true },
];
```
The callback is resolved separately for each cell and reevaluated on grid updates. Update rows through the grid source/data APIs so the rendered state refreshes. Disabling an open dropdown closes it without saving a new value. Re-enabling restores interaction and does not automatically reopen the popup. The Formula Bar dropdown also respects the resolved disabled state.
### Disable individual items
Set `disabled: true` on an option to keep it visible while preventing selection. Other items remain available. Disabled options have disabled styling and `aria-disabled="true"`; initial focus and Arrow Up/Down navigation skip them. Click, Enter, and Space cannot select or toggle them. Search, sorting, and custom option renderers preserve these restrictions. A custom renderer should also make the disabled state visually clear.
```typescript
import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
const statusOptions = [
{ value: 'active', label: 'Active' },
{ value: 'paused', label: 'Paused' },
{ value: 'archived', label: 'Archived', disabled: true },
];
const columns: ColumnRegular[] = [
{
prop: 'status',
name: 'Status',
columnType: 'dropdown',
dropdown: { source: statusOptions },
},
{
prop: 'tags',
name: 'Tags',
columnType: 'dropdown',
dropdown: {
source: statusOptions,
config: { multiSelect: true, search: true, sortDirection: 'asc' },
},
},
];
grid.columns = columns;
grid.source = [{ status: 'archived', tags: ['archived', 'active'] }];
```
An already-selected disabled value remains displayed and stored. For single-select cells, an enabled option can replace that value. For multiselect cells, disabled selected items cannot be toggled or removed, and their remove controls are omitted; enabled selected items can still change. The dropdown never silently removes stored values when options become disabled.
If all visible items are disabled, an enabled dropdown can still open to show them, but has no active option and cannot select an item. This also applies when a search matches only disabled items. Option disabling defaults to `false` and does not disable the whole dropdown.
### Dropdown disabling versus readonly
| Setting | Effect |
| --- | --- |
| `dropdown.disabled` | Blocks dropdown opening, selection, and chip removal; does not make the entire grid cell readonly. |
| `column.readonly` | Blocks dropdown interaction and Core cell edits such as paste and Delete; accepts a boolean or a cell-context callback. |
| `grid.readonly` | Blocks dropdown interaction and editing throughout the grid. |
| `dropdown.source[i].disabled` | Blocks interaction with that option while keeping enabled options available. |
Grid and column readonly rules always win, even when `dropdown.disabled` returns `false`. Dropdown columns are editable by default. Readonly cells preserve their passive cell rendering without an interactive dropdown trigger. Disabled dropdown triggers expose `aria-disabled="true"` and are removed from tab order.
See the [Editor Dropdown API](/api/editor-dropdown/) for the column contract and the [Dropdown API](/api/dropdown/) for standalone properties and option types. Standalone `DropdownProps.disabled` accepts a boolean; cell callbacks belong to `DropdownColumnProps.disabled`.
### Keep cell and dropdown templates in sync
Set `dropdown.syncCellTemplate` to `true` to reuse the column `cellTemplate` for both option rows and selected values. Object option fields are merged into the synthetic template model, so the same renderer can use metadata such as a status tone, avatar URL, or icon. Explicit `renderOption` and `renderValue` callbacks remain authoritative.
```typescript
import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const columns: ColumnRegular[] = [{
prop: 'status',
columnType: 'dropdown',
cellTemplate: (h, { model, value }) => h('span', {
class: `status-badge status-badge--${model.tone}`,
}, model.label ?? value),
dropdown: {
syncCellTemplate: true,
source: [
{ value: 'active', label: 'Active', tone: 'green' },
{ value: 'paused', label: 'Paused', tone: 'amber' },
],
},
}];
const grid = document.querySelector('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
grid.columns = columns;
```
---
# Row Editor
URL: https://pro.rv-grid.com/guides/editors/editor-row/
Source: src/content/docs/guides/editors/editor-row.mdx
The `RowEditPlugin` is a custom plugin for RevoGrid that provides row-level editing capabilities,
allowing users to edit entire rows inline with dedicated action buttons for saving or canceling changes.
* **Features**:
- **Row Editing Mode:** Enables editing of entire rows by rendering editors for all cells in the selected row.
- **Inline Editors:** Supports inline editing of multiple cells in a row simultaneously.
- **Action Buttons:** Provides intuitive buttons for `Save` and `Cancel` actions:
- `Save`: Dispatches `CELL_EDIT_SAVE_EVENT` to persist changes.
- `Cancel`: Dispatches `CELL_EDIT_CANCEL_EVENT` to discard changes.
- **Event Handling:** Listens for key events to manage row editing:
- `CELL_EDIT_EVENT`: Initiates row editing mode.
- `CELL_EDIT_SAVE_EVENT`: Saves edited data and updates the grid model.
- `CELL_EDIT_CANCEL_EVENT`: Cancels editing and restores original data.
- `BEFORE_CELL_RENDER_EVENT`: Ensures cells in the editing row render with editors.
---
# Slider Editor
URL: https://pro.rv-grid.com/guides/editors/editor-slider/
Source: src/content/docs/guides/editors/editor-slider.mdx
The `editorSlider` is a custom cell editor for RevoGrid that provides a slider input
to edit numeric values within a specified range directly within the grid cells.
**Features**:
- Renders a slider input element for cells, allowing users to select numeric values by dragging a slider handle
- Supports customizable minimum and maximum values through column properties
- Automatically dispatches a `celledit` event upon change, updating the grid's data model with the new value
- Ensures seamless integration with RevoGrid by providing row and column details in the event payload
---
# Textarea Editor
URL: https://pro.rv-grid.com/guides/editors/editor-textarea/
Source: src/content/docs/guides/editors/editor-textarea.mdx
This editor is a custom cell editor for RevoGrid that allows users to handle multi-line text input, making it suitable for applications requiring
rich text editing capabilities within grid cells.
To move to the next line within the `TextAreaEditor`, the user should press `Shift + Enter`.
---
# Timeline Editor
URL: https://pro.rv-grid.com/guides/editors/editor-timeline/
Source: src/content/docs/guides/editors/editor-timeline.mdx
Description: A custom cell editor for managing date ranges with a visual timeline representation
The `editorTimeline` is a custom cell editor for RevoGrid that provides a visual timeline interface
for managing date ranges directly within grid cells.
**Features**:
- Visual timeline bar with progress indicator
- Date range selection with native date pickers
- Progress tracking based on current date
- Responsive design that fits within grid cells
---
# Customization
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/
Source: src/content/docs/guides/event-scheduler/customization/index.mdx
Description: Customize scheduler labels, renderers, properties, styles, visual hooks, and states.
Event Scheduler can be customized at several levels. Start with labels, formatters, CSS variables, and classes when you only need to change text or visual styling. Use `eventScheduler.customization` when a product needs RevoGrid-style render hooks for generated scheduler headers, cells, event sections, or create previews.
The scheduler still owns projection, selection, drag/drop, resize, keyboard behavior, permission checks, conflicts, and mutation events. Custom render hooks should describe what a section looks like, while the scheduler continues to manage how that section behaves.
## Customization layers
| Layer | Use it for |
| --- | --- |
| `labels` | Rename built-in UI text such as resource labels, editor fields, action labels, and validation messages. |
| Formatters | Change displayed text without replacing markup, for example resource labels, time labels, day headers, hover labels, create-range labels, and tooltips. |
| Direct properties hooks | Add classes, inline styles, attributes, data fields, CSS variables, status colors, or simple renderers to scheduler surfaces. |
| `customization` | Replace or annotate scheduler headers, cells, event sub-sections, resize handles, badges, and create previews. |
| Conflict and state hooks | Customize conflict badges/tooltips and empty/loading/error overlay states. |
| CSS variables/classes | Theme the scheduler and target built-in state classes such as selected, conflict, locked, status, type, weekend, today, or non-working time. |
## Customization areas
| Area | Use it for |
| --- | --- |
| [Labels and formatters](./labels-formatters/) | Text-only labels, date/time display, resource names, hover labels, and tooltips. |
| [Event rendering](./events/) | Event button properties, content templates, badges, resize handles, tooltips, and status colors. |
| [Layout, headers, and cells](./layout/) | Column sizes, generated headers, time rows, day cells, resource rows, and timeline cells. |
| [Slots, hours, and markers](./slots-hours/) | Create previews, empty slots, closed hours, availability styling, and current-time markers. |
| [Conflicts and states](./conflicts-states/) | Conflict indicators, warning/error treatment, remote loading, empty, and error overlays. |
| [Context menus](./context-menu/) | Event, slot, day-header, and background commands. |
| [Styling and themes](./styling/) | CSS variables, built-in state classes, and white-label themes. |
## Choosing the right hook
- Use `labels` or formatters when the result is plain text.
- Use CSS variables when the built-in structure is correct and the change is visual.
- Use `eventProperties` or `statusColorResolver` when styling depends on event data.
- Use `customization.*Properties` to add classes, styles, ARIA labels, or data attributes without replacing content.
- Use `customization.*Template` only when the visible markup for that section needs to change.
Template hooks replace only the section they target. Scheduler-owned interaction handlers, selection state, permission attributes, drag/resize behavior, generated data attributes, conflict classes, and core accessibility attributes remain managed by the plugin.
For interaction callbacks, cancelable DOM events, selection, clipboard, keyboard shortcuts, and custom editor workflows, see [Editing and Interaction](../event-editing/).
For compact copyable examples of each major customization surface, see [Examples](../examples/). That page includes basic weekly setup, event templates, day headers, slot context menus, current-time markers, closed hours, read-only mode, cancelable selection, and theme snippets.
---
# Conflicts and States
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/conflicts-states/
Source: src/content/docs/guides/event-scheduler/customization/conflicts-states.mdx
Description: Customize conflict warnings, empty overlays, loading overlays, and error states.
## Conflict display
Conflict customization is separate from general event rendering so products can apply a consistent warning/error treatment without replacing event content.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
conflicts: {
className: ({ severity }) => `booking-conflict booking-conflict--${severity}`,
properties: ({ conflictTypes }) => ({
'data-conflict-types': conflictTypes.join(' '),
}),
indicator: (h, { severity }) =>
h('span', { class: 'booking-conflict__badge' }, severity === 'error' ? 'Blocked' : 'Review'),
tooltip: ({ label }) => `Planning issue: ${label}`,
},
},
};
```
The scheduler still adds built-in conflict classes, data attributes, and severity attributes to event blocks. Custom conflict hooks run only for conflicted segments.
## Empty, loading, and error states
Remote mode and local empty states can use product-specific wording and markup through `labels`, `emptyStateText`, and `customization.emptyState`.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
emptyStateText: 'No resources match this view.',
labels: {
remoteLoading: 'Loading bookings...',
remoteError: 'Bookings could not be loaded.',
remoteEmpty: 'No bookings in this range.',
},
customization: {
emptyState: {
template: ({ kind, label }) =>
kind === 'error' ? `Action needed: ${label}` : label,
properties: ({ kind }) => ({
class: `booking-scheduler-state booking-scheduler-state--${kind}`,
}),
},
},
};
```
The template can return a string or a DOM node. Property hooks can add classes, style variables, ARIA attributes, and data attributes to the overlay element.
---
# Context Menus
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/context-menu/
Source: src/content/docs/guides/event-scheduler/customization/context-menu.mdx
Description: Customize scheduler context-menu actions for events, slots, headers, and background surfaces.
## Context menu actions
Use scheduler context-menu hooks when product actions depend on the clicked event, empty slot, resource, or scheduler surface. The default event and slot menus are generic conveniences; products can replace them, append shared actions, or keep built-ins and filter individual commands.
| Config | Purpose |
| --- | --- |
| `getSlotContextMenuItems` | Replaces the empty-slot menu for the clicked slot. |
| `getEventContextMenuItems` | Replaces the event-bar menu for the clicked event segment. |
| `getDayHeaderContextMenuItems` | Defines optional day-header menu commands for products that expose header actions. |
| `getBackgroundContextMenuItems` | Defines background/empty-scheduler-space commands. Slot backgrounds use this when no slot factory is supplied. |
| `contextMenuTemplate` | Replaces the rendered content for scheduler-specific menu items. |
| `onContextMenuAction` | Receives every scheduler-specific item selection with target context. |
| `hiddenItems` / `disabledItems` | Hide or disable built-in scheduler actions by id. |
| `items` / `itemFilter` | Compatibility hooks for appending raw Pro context-menu items or transforming built-ins. |
Scheduler-specific menu items support `id`, `label`, `icon`, `shortcut`, `disabled`, `hidden`, `danger`, `separator`, `group`, and `action`. Use `danger` for destructive actions, `separator` for visual breaks, and `disabled` / `hidden` functions when the command depends on event or slot state.
Menu item visibility and disabled state are resolved when the menu opens. If visibility depends on remote permissions or capacity checks, keep that state in application data and return it synchronously from `hidden` or `disabled`. Time-row context menus are optional; products that need them can use the underlying Pro row context-menu configuration until a dedicated scheduler time-row trigger is required.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
contextMenu: {
enabled: true,
hiddenItems: { confirm: true },
getEventContextMenuItems: ({ segment }) => [
{
id: 'open-record',
label: 'Open record',
icon: 'fa-solid fa-arrow-up-right-from-square',
shortcut: 'O',
action: () => openPlanningRecord(segment?.event.id),
},
{ id: 'event-edit-separator', separator: true },
{
id: 'duplicate-event',
label: 'Duplicate',
shortcut: 'Cmd+D',
disabled: ({ segment }) => segment?.event.locked === true,
action: ({ segment }) => duplicateBooking(segment?.event.id),
},
{
id: 'delete-event',
label: 'Delete',
danger: true,
disabled: ({ segment }) => segment?.event.readonly === true,
action: ({ segment }) => deleteBooking(segment?.event.id),
},
],
getSlotContextMenuItems: ({ slot }) => [
{
id: 'create-booking',
label: 'Create booking here',
icon: 'fa-solid fa-plus',
shortcut: 'C',
action: () => createBookingAt(slot?.startDateTime),
},
{
id: 'block-slot',
label: 'Block this time',
group: 'availability',
action: ({ slot }) => createHoldAt(slot?.startDateTime),
},
],
getBackgroundContextMenuItems: () => [
{ id: 'refresh-availability', label: 'Refresh availability' },
],
contextMenuTemplate: (h, item) =>
item.separator
? null
: h('span', { class: 'planner-menu-item' }, [
item.icon ? h('span', { class: item.icon }) : null,
h('span', null, item.label),
item.shortcut ? h('kbd', null, item.shortcut) : null,
]),
onContextMenuAction: ({ itemId, target, segment, slot }) => {
trackSchedulerMenuAction({ itemId, target, eventId: segment?.event.id, slot });
},
},
};
```
Built-in context-menu actions continue to respect scheduler permissions, locked resources, read-only mode, disabled/non-selectable slots, and conflict rules. The default built-in labels can be changed through `labels`, and all built-ins can be removed with `hiddenItems`, transformed with `itemFilter`, or replaced entirely with target-specific factories.
Built-in menu labels are generic by default: `createEventHereAction`, `assignOpenEventAction`, `editEventAction`, `confirmEventAction`, and `markPlannedAction`. Integrations should use these generic event labels or replace the menus with target-specific factories.
---
# Event Rendering
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/events/
Source: src/content/docs/guides/event-scheduler/customization/events.mdx
Description: Customize event colors, properties, content, badges, resize handles, tooltips, and accessible labels.
## Event branding
Event blocks support a lightweight styling layer before you use the grouped `customization.events` API.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
statusColorResolver: (event) => {
if (event.status === 'confirmed') return '#2563eb';
if (event.status === 'planned') return '#d97706';
if (event.status === 'blocked') return '#dc2626';
},
eventProperties: ({ event }) => ({
class: `scheduler-event--${event.type ?? 'default'}`,
style: {
'--shift-accent': event.status === 'blocked' ? '#dc2626' : '#2563eb',
},
'data-shift-status': event.status,
}),
eventTemplate: (h, { event }) =>
h('span', { class: 'shift-event-title' }, event.title),
};
```
`statusColorResolver` resolves the scheduler event accent color when the event does not already provide `event.color` and the status does not map to a built-in status class. `eventProperties` is merged onto the event button and can return `class`, `style`, attributes, data fields, or event handlers. `eventTemplate` replaces the default event content only; it does not replace the outer interactive event shell.
## Event section templates
Use direct event hooks when a product needs a simple public API for event bars in week and resource-timeline views.
| Config | Purpose |
| --- | --- |
| `eventTemplate` | Legacy top-level event content renderer. |
| `eventContentTemplate` | Replaces the default title, time, badge, and resource content block. |
| `eventProperties` | Adds classes, styles, attributes, data fields, or event handlers to the event button. |
| `eventTooltipTemplate` | Overrides the event `title` tooltip with custom details. |
| `eventBadgeTemplate` | Replaces individual status, type, category, or required-role badges. |
| `eventResizeHandleTemplate` | Replaces the visual content inside start/end resize handles. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
eventProperties: (context) => ({
class: [
'event-card',
context.isSelected ? 'event-card--selected' : '',
context.isLocked ? 'event-card--locked' : '',
context.hasConflict ? 'event-card--conflict' : '',
context.isShortEvent ? 'event-card--short' : '',
].filter(Boolean).join(' '),
style: {
'--event-accent': context.hasConflict ? '#dc2626' : context.event.color ?? '#2563eb',
},
'data-event-duration': context.duration,
}),
eventContentTemplate: (h, { event, resource, start, end, isFromPreviousDay, isContinuedToNextDay }) =>
h('span', { class: 'event-card__content' }, [
h('strong', { class: 'event-card__title' }, event.title),
h('span', { class: 'event-card__time' }, `${start.slice(11, 16)}-${end.slice(11, 16)}`),
resource ? h('span', { class: 'event-card__resource' }, resource.name) : null,
isFromPreviousDay ? h('span', { class: 'event-card__continuation' }, 'From previous day') : null,
isContinuedToNextDay ? h('span', { class: 'event-card__continuation' }, 'Continues') : null,
]),
eventBadgeTemplate: (h, { kind, label, isLocked }) =>
h('span', { class: `event-card__badge event-card__badge--${kind}` }, isLocked ? 'Locked' : label),
eventResizeHandleTemplate: (h, { edge }) =>
h('span', { class: `event-card__resize event-card__resize--${edge}` }),
eventTooltipTemplate: ({ event, resource, duration, hasConflict }) =>
`${event.title} | ${resource?.name ?? 'Unassigned'} | ${duration} minutes${hasConflict ? ' | Conflict' : ''}`,
};
```
Event render contexts include `event`, `resource`, the first real projected `segment`, the ordered `segments` represented by the bar, aggregate `start`, `end`, and `duration`, plus `day`, `status`, `type`, `isSelected`, `isDragging`, `isResizing`, `isLocked`, `hasConflict`, `isReadonly`, `isFromPreviousDay`, `isContinuedToNextDay`, and `isShortEvent`. Dragging and resizing state are currently reserved and remain `false` until the scheduler exposes public pointer-state rendering.
The legacy `first`, `last`, and `sliceSlot` fields remain available for source compatibility but are deprecated. Because hooks now run once per continuous bar, `first` and `last` are always `true`, while `sliceSlot` is the primary segment's `startSlot`. New code should use `segment.startSlot`, `segments`, and the aggregate range fields instead.
`customization.events` provides the grouped form of the same customization surface and layers on top of direct event properties where both are supplied. It customizes the event button and its internal sections. It applies to both week and resource timeline render modes.
When `customization.events.content` is configured, it is the authoritative content hook. Returning `null` or `undefined` restores the built-in event content rather than invoking `eventContentTemplate` or legacy `eventTemplate`. Without the grouped hook, Scheduler tries `eventContentTemplate`, then `eventTemplate`, and finally the built-in content.
| Hook | What it customizes |
| --- | --- |
| `className` | Adds one or more classes to the event button. |
| `style` | Adds inline styles or CSS custom properties to the event button. |
| `properties` | Adds attributes, classes, styles, or data attributes to the event button. |
| `content` | Replaces the default title, time, badge, and resource content block. |
| `continuationLabels` | Replaces previous/next-day continuation labels. |
| `badgeTray` | Replaces the tray that holds badges for multi-slot events. |
| `badge` | Replaces individual status, type, category, or required-role badges. |
| `resizeHandle` | Replaces the visual content inside start/end resize handles. |
| `tooltip` | Overrides the event `title` tooltip. |
| `ariaLabel` | Overrides the event button accessible label. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
events: {
className: ({ event }) => `event-card event-card--${event.status ?? 'default'}`,
style: ({ event }) => ({
'--event-border-color': event.status === 'blocked' ? '#dc2626' : '#2563eb',
}),
properties: ({ event, segment }) => ({
'data-event-id': event.id,
'data-event-start-slot': segment.startSlot,
}),
content: (h, { event, resource, segment }) =>
h('span', { class: 'event-card__content' }, [
h('strong', { class: 'event-card__title' }, event.title),
h('span', { class: 'event-card__resource' }, resource?.name ?? 'Unassigned'),
segment.conflict ? h('span', { class: 'event-card__warning' }, 'Conflict') : null,
]),
badge: (h, { kind, label }) =>
h('span', { class: `event-card__badge event-card__badge--${kind}` }, label),
tooltip: ({ event, resource }) => `${event.title} - ${resource?.name ?? 'Unassigned'}`,
ariaLabel: ({ event }) => `Scheduled event ${event.title}`,
},
},
};
```
The outer event element remains a scheduler-managed button. It keeps click, double-click, pointer, selection, disabled move/resize state, conflict state, and accessibility wiring. Custom event content should avoid interactive controls inside the event button unless the product has tested the keyboard and pointer behavior carefully.
## Rendering content at the bottom of a bar
Each event assignment is rendered as one continuous visual bar. In vertical views the bar is anchored from the day's first slot cell; in resource timeline it is attached to the resource row. Event customization hooks therefore run once per visible bar rather than once per grid cell.
Use `context.segment` for the first real projected segment and `context.segments` when you need every day segment represented by a multi-day timeline bar. The aggregate `context.start`, `context.end`, and `context.duration` fields describe the complete visible bar.
To render content pinned to the bottom of a multi-slot bar — such as a status badge, category chip, or summary — use `badgeTray`. Its return value is placed directly inside the event button.
Include the `event-scheduler-event__badge-tray` wrapper class in the returned element to get the built-in absolute bottom positioning:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
events: {
content: (h, context) =>
h('span', { class: 'my-event' }, [
h('strong', { class: 'my-event__title' }, context.event.title),
h('span', { class: 'my-event__time' }, `${context.start.slice(11, 16)}-${context.end.slice(11, 16)}`),
h('span', { class: 'my-event__badge' }, String(context.event.status ?? '')),
]),
badgeTray: (h, context) => {
// Runs once for a multi-slot bar whose visible end is in this view.
const status = String(context.event.status ?? '');
if (!status) return null;
return h('span', { class: 'event-scheduler-event__badge-tray' }, [
h('span', { class: 'my-event__badge' }, status),
]);
},
},
},
};
```
`badgeTray` does not fire for single-slot bars or for a bar whose visible end continues beyond the current view (`isContinuedToNextDay`). When `badgeTray` returns `null` or `undefined`, the default badge tray — which displays type, category, and required-role chips — is rendered instead.
---
# Labels and Formatters
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/labels-formatters/
Source: src/content/docs/guides/event-scheduler/customization/labels-formatters.mdx
Description: Customize scheduler text, dates, resource labels, hover labels, and tooltips.
## Labels and formatters
Use labels and formatters for text-level customization. This keeps the built-in DOM, accessibility attributes, selection, tooltips, and editing behavior intact.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
locale: 'en-US',
timeZone: 'America/New_York',
labels: {
resourceColumn: 'Crew',
unassignedResource: 'Open demand',
newEvent: 'New assignment',
createEvent: 'Create shift',
editEvent: 'Edit shift',
},
dayHeaderFormatter: (date) =>
date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
timeZone: 'America/New_York',
}),
timeLabelFormatter: (minutes) => `${Math.floor(minutes / 60)}:00`,
timelineHeaderFormatter: ({ startDateTime, endDateTime }) =>
`${startDateTime.slice(11, 16)}-${endDateTime.slice(11, 16)}`,
hoverTimeFormatter: (minutes) => `Create at ${String(Math.floor(minutes / 60)).padStart(2, '0')}:00`,
resourceLabelFormatter: (resource) => resource.name,
resourceMetaFormatter: (resource) =>
[resource.role, resource.metadata?.location].filter(Boolean).join(' - '),
tooltipFormatter: ({ event, resource }) =>
`${event.title}\n${resource?.name ?? 'Unassigned'}\n${event.startDateTime}`,
};
```
Built-in day, range, and timeline date labels use `locale` and `timeZone`. Formatter hooks take precedence when you need product-specific date or time strings, including date-only labels that should not shift when displayed in a non-UTC timezone.
Formatters are the best first choice when the content is still a string. Move to templates only when you need nested markup, custom badges, custom attributes, or section-specific replacement.
---
# Layout, Headers, and Cells
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/layout/
Source: src/content/docs/guides/event-scheduler/customization/layout.mdx
Description: Customize scheduler column sizing, generated headers, time rows, day cells, resource rows, and timeline cells.
## Grouped customization API
Use the grouped `customization` API when you need precise control over generated scheduler surfaces.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
columnSizes: {
week: {
timeColumnSize: 96,
dayColumnSize: 220,
},
resourceTimeline: {
resourceColumnSize: 260,
timelineColumnSize: 144,
},
},
headers: {
dayTemplate: (h, context) =>
h('span', { class: 'scheduler-day-header' }, context.defaultLabel),
dayProperties: (context) => ({
class: context.today ? 'scheduler-day-header--brand-today' : '',
'data-scheduler-date': context.date,
}),
groupTemplate: (h, context) =>
h('strong', { class: 'scheduler-range-header' }, context.defaultLabel),
},
cells: {
resourceProperties: ({ resource }) => ({
'data-resource-role': resource?.role,
}),
timelineProperties: ({ date, slotIndex }) => ({
class: 'scheduler-timeline-slot',
'data-scheduler-slot-key': `${date}:${slotIndex}`,
}),
},
events: {
properties: ({ event }) => ({
'data-event-status': event.status,
}),
content: (h, { event, resource }) =>
h('span', { class: 'scheduler-event-title' }, [
h('strong', {}, event.title),
h('small', {}, resource?.name ?? 'Unassigned'),
]),
badge: (h, context) =>
h('span', {
class: `scheduler-event-chip scheduler-event-chip--${context.kind}`,
}, context.label),
resizeHandle: (h, context) =>
h('span', {
class: `scheduler-event-resize scheduler-event-resize--${context.edge}`,
}),
},
createPreview: {
createRangeTemplate: (context) =>
`${context.title}: ${context.startLabel}-${context.endLabel}`,
createRangeProperties: () => ({ class: 'scheduler-create-preview' }),
},
conflicts: {
className: ({ severity }) => `scheduler-conflict scheduler-conflict--${severity}`,
indicator: (h, context) =>
h('span', { class: 'scheduler-conflict-badge' }, context.severity),
tooltip: ({ label }) => `Review before saving: ${label}`,
},
emptyState: {
template: ({ kind, label }) => `${kind}: ${label}`,
properties: ({ kind }) => ({
class: `scheduler-state scheduler-state--${kind}`,
}),
},
},
};
```
Template hooks receive RevoGrid's `h` helper and a scheduler context object. Return a string, a VNode, `null`, or `undefined`. Returning `null` or `undefined` keeps the built-in section. Property hooks return attributes, `class`, `style`, or data attributes for the target element.
Custom properties are merged with built-in scheduler properties. Built-in interaction handlers and scheduler data attributes remain in place, so custom hooks should not attach drag, resize, selection, or mutation behavior directly.
## Column sizes
`customization.columnSizes` controls scheduler-owned column widths by view. These values are resolved before legacy top-level size props such as `timeColumnSize`, `dayColumnSize`, `resourceColumnSize`, and `timelineColumnSize`.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
columnSizes: {
week: {
timeColumnSize: 88,
dayColumnSize: 180,
},
resourceTimeline: {
resourceColumnSize: 280,
timelineColumnSize: 120,
},
},
},
};
```
Use week sizes for `day`, `week`, and `month` style calendar grids. Use resource timeline sizes when the first column is the pinned resource column and the remaining columns are timeline slots.
## Resource panel resizing and scrolling
Resource timelines automatically provide a draggable divider between the resource grid and timeline. Dragging previews the divider and commits the panel width on release. Resource columns keep their own widths; each panel has an independent horizontal scrollbar when its contents overflow. Vertical row scrolling stays synchronized.
The resource panel initially uses the combined resource-column width, capped to leave room for the timeline. Set `resourcePanel.initialWidth` in pixels or as a percentage of grid width:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
resourcePanel: { initialWidth: '30%' },
};
```
Use `resourcePanel: false` to disable the divider and panel scrollbars. The chosen width survives refreshes and temporary view switches within the Scheduler instance. It is not saved across page reloads. Calendar time-label columns are unchanged.
The last visible resource column stretches to fill extra panel space after resizing. When the panel narrows, it returns to its configured or manually resized width and scrolls horizontally when needed. Columns with `autoSize` keep their own sizing, and `maxSize` limits stretching. Gantt task tables use the same shared behavior.
Gantt and Scheduler use `PanelResizePlugin` from `@revolist/revogrid-pro`. Its `setPanelWidth(width)` and `getPanelWidth()` methods operate on the configured pinned panel; `panel-resize` reports `{ width }`. The `--rv-panel-accent` CSS variable controls the divider color.
## Header templates and properties
Header customization targets the generated scheduler columns. For day headers, use the direct week-scheduler hooks when you only need day-header customization. Use `customization.headers` when you need to customize every generated header type.
Direct day-header hooks:
| Hook | Surface |
| --- | --- |
| `dayHeaderFormatter` | Formats the built-in day header label. |
| `dayHeaderTemplate` | Replaces the day header content. |
| `dayHeaderProperties` | Adds classes, styles, attributes, data fields, or event handlers to day headers. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
dayHeaderFormatter: (date) =>
date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
}),
dayHeaderTemplate: (h, context) =>
h('span', { class: 'planner-day-header' }, [
h('span', { class: 'planner-day-header__label' }, context.defaultLabel),
context.today ? h('span', { class: 'planner-day-header__badge' }, 'Today') : null,
context.holiday ? h('span', { class: 'planner-day-header__holiday' }, context.holidayLabel) : null,
h('span', { class: 'planner-day-header__count' }, `${context.eventCount ?? 0} events`),
]),
dayHeaderProperties: (context) => ({
class: [
context.today ? 'planner-day-header--today' : '',
context.weekend ? 'planner-day-header--weekend' : '',
context.holiday ? 'planner-day-header--holiday' : '',
].filter(Boolean).join(' '),
style: {
'--planner-day-event-count': context.eventCount ?? 0,
...(context.holiday ? { '--planner-day-state': 'holiday' } : {}),
},
'data-planner-day': context.date,
}),
};
```
Day header context includes `defaultLabel`, `date`, `dayIndex`, `eventCount`, `today`, `weekend`, `holiday`, and `holidayLabel`. Holiday state is date-level: it comes from the primary scheduler calendar or global `kind: 'holiday'` availability entries. Resource-specific holidays stay on the scheduler body cells.
`customization.headers.dayTemplate` takes precedence over `dayHeaderTemplate` when both are provided. This lets applications keep older grouped customization config while adding direct day-header hooks where convenient.
Grouped header customization has one template hook and one matching properties hook per generated header section.
| Hook | Surface |
| --- | --- |
| `timeTemplate` / `timeProperties` | Pinned time header in day/week/month style views. |
| `resourceTemplate` / `resourceProperties` | Pinned resource header in resource timeline views. |
| `dayTemplate` / `dayProperties` | Day header cells. |
| `timelineTemplate` / `timelineProperties` | Resource timeline slot headers. |
| `groupTemplate` / `groupProperties` | Grouped range/day/timeline headers when column grouping is enabled. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
customization: {
headers: {
dayTemplate: (h, context) =>
h('span', { class: 'brand-day-header' }, [
h('strong', {}, context.defaultLabel),
context.today ? h('small', {}, 'Today') : null,
context.holiday ? h('small', {}, context.holidayLabel) : null,
context.eventCount ? h('small', {}, `${context.eventCount} events`) : null,
]),
dayProperties: (context) => ({
class: [
context.today ? 'brand-day-header--today' : '',
context.weekend ? 'brand-day-header--weekend' : '',
context.holiday ? 'brand-day-header--holiday' : '',
],
'data-brand-date': context.date,
}),
},
},
};
```
Header contexts include the active view, the default label, date or slot fields where relevant, event counts, today/weekend/holiday flags, holiday labels, and group kind for grouped headers.
## Cell templates and properties
Cell customization targets the scheduler body cells. Use property hooks when you only need classes, styles, or test/data attributes. Use templates when the default cell body should be replaced.
Use the direct time-axis hooks when the product only needs to control the pinned time column in day, week, or month-style views.
| Config | Purpose |
| --- | --- |
| `timeLabelFormatter` | Formats built-in time text such as `06:00`, `6 AM`, or `6:00`. |
| `timeLabelTemplate` | Replaces the pinned time-label cell content. |
| `timeRowProperties` | Adds classes, styles, attributes, data fields, or event handlers to time-label cells. |
| `timeRange` | Sets the visible start/end time and hides time outside that range. |
| `slotMinutes` | Sets row granularity with a 15-minute minimum, for example `15`, `30`, or `60`. When `snapMinutes` is omitted, interaction snapping follows this active value as zoom changes. |
| `rowSize` | Controls row density in pixels. |
| `nonWorkingTime` | Styles closed or outside-business-hours cells in the scheduler body. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
timeRange: { start: '06:00', end: '22:00' },
slotMinutes: 30,
rowSize: 40,
timeLabelFormatter: (minutes) => {
const hour = Math.floor(minutes / 60);
const minute = minutes % 60;
return `${hour % 12 || 12}:${String(minute).padStart(2, '0')} ${hour < 12 ? 'AM' : 'PM'}`;
},
timeLabelTemplate: (h, context) =>
h('span', { class: 'planner-time-label' }, context.timeLabel),
timeRowProperties: (context) => ({
class: context.currentTimeLabel ? 'planner-time-row--current' : 'planner-time-row',
style: {
'--planner-time-start': context.startMinutes,
},
'data-planner-time': context.timeLabel,
}),
nonWorkingTime: {
enabled: true,
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
className: 'planner-closed-hours',
},
};
```
`customization.cells.timeTemplate` and `customization.cells.timeProperties` layer over the direct `timeLabelTemplate` and `timeRowProperties` hooks when both are provided. Use `rowSize` for compact, dense, or comfortable density presets, and use `timeRange` when night hours should be hidden from the grid instead of only styled as closed.
| Hook | Surface |
| --- | --- |
| `timeTemplate` / `timeProperties` | Time-label rows in week-style grids. |
| `dayTemplate` / `dayProperties` | Day/time slots in week-style grids. |
| `resourceTemplate` / `resourceProperties` | Resource rows in resource timeline grids. |
| `resourceGroupTemplate` / `resourceGroupProperties` | Group rows when resource grouping is enabled. |
| `timelineTemplate` / `timelineProperties` | Timeline cells in resource timeline grids. |
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
cells: {
resourceProperties: ({ resource, unassigned, depth }) => ({
class: [
resource?.role ? `resource-row--${resource.role.toLowerCase()}` : '',
unassigned ? 'resource-row--open-demand' : '',
],
style: { '--resource-depth': depth },
'data-resource-role': resource?.role,
}),
timelineProperties: ({ date, slotIndex, segmentCount }) => ({
class: segmentCount > 0 ? 'timeline-slot--busy' : 'timeline-slot--open',
'data-slot-key': `${date}:${slotIndex}`,
}),
},
},
};
```
When a cell template is provided, the scheduler renders that template instead of the default cell content for that slot. Continuous event bars are rendered by the Scheduler's bar layer and remain independent of slot-cell content, so prefer cell properties when the goal is only styling or annotation.
---
# Slots, Hours, and Markers
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/slots-hours/
Source: src/content/docs/guides/event-scheduler/customization/slots-hours.mdx
Description: Customize create previews, empty slots, closed hours, availability states, and current-time markers.
## Create previews
Create preview customization affects empty-slot hover labels and the drag-create range preview.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
customization: {
createPreview: {
hoverTimeProperties: ({ startDateTime }) => ({
class: 'create-hover-slot',
'data-hover-start': startDateTime,
}),
createRangeTemplate: ({ title, startLabel, endLabel, durationLabel, resource }) =>
`${title} for ${resource?.name ?? 'Unassigned'}: ${startLabel}-${endLabel} (${durationLabel})`,
createRangeProperties: ({ durationMinutes }) => ({
class: durationMinutes >= 240 ? 'create-range--long' : 'create-range--short',
}),
},
},
};
```
Use `hoverTimeProperties` for attributes and styling on the slot while the pointer is over a creatable time. Use `createRangeTemplate` and `createRangeProperties` for the range ghost shown during drag-create interactions.
## Empty slot customization
Empty slots are a primary extension point for booking, staffing, room planning, maintenance windows, pricing, capacity, and availability workflows. Use direct slot hooks when a product needs custom empty-cell content or behavior without replacing event bars.
| Hook | Purpose |
| --- | --- |
| `slotTemplate` | Renders custom content inside empty week or resource-timeline slots. Occupied slots keep their event bars. |
| `slotProperties` | Adds classes, styles, attributes, data fields, or event handlers to scheduler slot cells. |
| `slotTooltip` | Overrides the native tooltip for disabled, closed, priced, or capacity-based slots. |
| `onSlotClick` | Runs custom click behavior before built-in editor-open behavior. |
| `onSlotDoubleClick` | Runs custom double-click behavior for immediate create or custom workflows. |
| `onSlotContextMenu` | Receives the resolved slot when the scheduler context menu opens. |
| `isSlotDisabled` | Marks slots as disabled and blocks built-in create/editor behavior. |
| `isSlotSelectable` | Marks slots as non-selectable for create/selection UX. |
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
slotTemplate: (h, context) =>
h('span', { class: 'booking-slot-capacity' }, [
context.disabled ? 'Closed' : `${getCapacity(context)} seats`,
]),
slotProperties: (context) => ({
class: [
context.disabled ? 'booking-slot--closed' : '',
!context.selectable ? 'booking-slot--invalid' : '',
context.segmentCount > 0 ? 'booking-slot--busy' : 'booking-slot--open',
].filter(Boolean).join(' '),
style: {
'--booking-slot-capacity': getCapacity(context),
},
'data-booking-slot': context.startDateTime,
}),
slotTooltip: (context) =>
context.disabled ? 'Outside working hours' : `Available from ${context.startDateTime}`,
isSlotDisabled: (context) => isClosedForBusiness(context),
isSlotSelectable: (context) => !hasBlockedAvailability(context),
onSlotClick: (context, event) => {
if (context.disabled) {
event.preventDefault();
showClosedSlotPopover(context);
}
},
onSlotDoubleClick: (context) => {
if (!context.disabled) createBookingImmediately(context);
},
onSlotContextMenu: (context) => {
trackSlotMenuOpened(context);
},
contextMenu: {
enabled: true,
hiddenItems: {
create: true,
'assign-open': true,
},
items: ({ target, slot }) => target === 'slot' && slot
? [
{ name: 'Create booking', action: () => createBookingImmediately(slot) },
{ name: 'Block this time', action: () => blockSlot(slot) },
]
: [],
},
};
```
`slotTemplate` is intentionally limited to empty cells so products can customize available space without removing visible event bars. Use `customization.cells.dayTemplate` or `customization.cells.timelineTemplate` only when the whole scheduler body cell, including event rendering, should be replaced. Use `isSlotDisabled` and `isSlotSelectable` with `allowCreate` or conflict rules when closed hours should block create, drag, paste, and editor flows.
## Working and closed hours
Use working and closed-hour APIs when availability is part of the scheduler contract: business hours, weekends, holidays, lunch breaks, maintenance windows, or capacity holds. These hooks build on the existing `nonWorkingTime`, calendars, availability data, and conflict rules.
| Config | Purpose |
| --- | --- |
| `workingHours` | Defines global or per-weekday working ranges. Use `false` in `byDay` to close a weekday. |
| `closedHours` | Defines closed ranges such as lunch, maintenance, holidays, or blocked planning windows. |
| `availabilityRules` | Applies product-specific availability rules from slot context. |
| `closedSlotTemplate` | Renders custom content inside closed slots. |
| `closedSlotProperties` | Adds classes, styles, attributes, data fields, or event handlers to closed slots. |
| `closedSlotTooltip` | Overrides the native tooltip/title for closed slots. |
| `isTimeSlotAvailable` | Blocks built-in create/editor behavior when it returns `false`. |
Use `workingHours` for global business hours, `workingHours.byDay` for weekday-specific hours or closed weekends, `closedHours` for explicit closed ranges such as lunch or maintenance, and date-specific `closedHours` entries for holidays. Closed slots can be styled with `closedSlotProperties`, replaced with `closedSlotTemplate`, and explained through `closedSlotTooltip`.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
workingHours: {
byDay: {
1: { start: '08:00', end: '18:00' },
2: { start: '08:00', end: '18:00' },
3: { start: '08:00', end: '18:00' },
4: { start: '08:00', end: '18:00' },
5: { start: '08:00', end: '16:00' },
0: false,
6: false,
},
},
closedHours: [
{
id: 'lunch',
days: [1, 2, 3, 4, 5],
ranges: { start: '12:00', end: '13:00' },
title: 'Closed',
reason: 'Lunch break',
className: 'planner-slot--lunch',
},
{
id: 'maintenance',
dates: ['2026-06-10'],
ranges: { start: '15:00', end: '17:00' },
reason: 'Maintenance window',
className: 'planner-slot--maintenance',
},
],
availabilityRules: ({ date, startMinutes }) =>
date === '2026-06-12' && startMinutes >= 14 * 60
? { available: false, reason: 'Capacity hold', className: 'planner-slot--held' }
: undefined,
isTimeSlotAvailable: ({ date, slotIndex }) =>
!isCompanyHoliday(date) && slotIndex !== 0,
closedSlotProperties: ({ availabilityKind, reason }) => ({
class: `planner-slot--closed planner-slot--${availabilityKind}`,
style: {
'--planner-closed-opacity': reason === 'Lunch break' ? 0.6 : 0.85,
},
}),
closedSlotTooltip: ({ title, reason }) =>
[title, reason].filter(Boolean).join(' - '),
closedSlotTemplate: (h, { reason }) =>
h('span', { class: 'planner-slot__closed-label' }, reason ?? 'Closed'),
};
```
Closed-slot context extends normal slot context with `available`, `availabilityKind`, `availabilityId`, `title`, and `reason`. Built-in create/editor behavior is blocked when `isTimeSlotAvailable` returns `false`, when `closedHours`/`availabilityRules` mark the slot unavailable, or when `isSlotDisabled` / `isSlotSelectable` deny the slot. For resource-specific calendars, remote availability, and holidays, continue using `calendars` and `eventSchedulerAvailability`; the closed-slot hooks style those states too.
## Current time marker
The current-time marker can use the built-in red line and badge, or it can be replaced by product-specific marker content. Week-style views render a horizontal marker on the active date. Resource timeline views render a vertical marker in the active timeline slot.
| Config | Purpose |
| --- | --- |
| `showCurrentTimeMarker` | Simple show/hide alias. |
| `currentTimeMarker` | Detailed marker config, including fixed `dateTime` for deterministic demos/tests. |
| `currentTimeUpdateInterval` | Live refresh interval in milliseconds. Defaults to `60000`; use `0` to disable automatic live refresh. |
| `currentTimeLabelFormatter` | Formats the marker label, such as `12:26`, `Now`, or a localized time. |
| `currentTimeMarkerTemplate` | Replaces marker content in week and resource timeline cells. |
| `currentTimeMarkerProperties` | Adds classes, styles, attributes, data fields, or event handlers to the marker cell. |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
showCurrentTimeMarker: true,
currentTimeUpdateInterval: 60_000,
currentTimeLabelFormatter: ({ dateTime }) =>
new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
}).format(new Date(dateTime)),
currentTimeMarkerProperties: ({ orientation }) => ({
class: orientation === 'horizontal'
? 'planner-now-marker planner-now-marker--horizontal'
: 'planner-now-marker planner-now-marker--vertical',
style: {
'--event-scheduler-current-time-color': '#dc2626',
'--planner-now-marker-thickness': '2px',
},
}),
currentTimeMarkerTemplate: (h, context) =>
h('span', { class: 'planner-now-marker__label' }, context.label),
};
```
Marker context includes `view`, `date`, `dayIndex`, `slotIndex`, `startMinutes`, `endMinutes`, `dateTime`, `label`, `position`, `orientation`, and, when available, `row`, `resourceId`, and `resource`. The marker is hidden when disabled, outside the visible date range, or outside the visible time range. Use `currentTimeMarker: { dateTime }` when the marker should point at a fixed instant rather than live `now`.
---
# Styling and Themes
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/styling/
Source: src/content/docs/guides/event-scheduler/customization/styling.mdx
Description: Theme Event Scheduler with CSS variables, built-in classes, state selectors, and white-label styles.
## CSS variables and classes
Use CSS when the scheduler structure is correct and only the theme needs to change. The scheduler exposes classes and CSS variables for common states, including today, weekend, current time, conflict, selected, locked, unassigned, status, type, category, non-working time, hover time, ghost ranges, create ranges, editor surfaces, and context menus.
```css
.event-scheduler-plugin {
--event-scheduler-accent: #0f766e;
--event-scheduler-accent-soft: #ccfbf1;
--event-scheduler-surface: #ffffff;
--event-scheduler-surface-sunken: #f8fafc;
--event-scheduler-grid-line: #d8dee6;
--event-scheduler-current-time-color: #dc2626;
--event-scheduler-weekend-bg: rgba(15, 118, 110, 0.04);
--event-scheduler-hover-time-bg: rgba(15, 118, 110, 0.12);
--event-scheduler-create-range-bg: rgba(15, 118, 110, 0.16);
}
.event-scheduler-event--status-confirmed {
--event-scheduler-event-color: #2563eb;
}
.event-scheduler-event--type-maintenance {
--event-scheduler-event-bg: #fff7ed;
--event-scheduler-event-color: #c2410c;
}
.event-scheduler-day-cell__slot--non-working {
opacity: 0.8;
}
```
Prefer CSS variables for broad theme changes. Prefer `eventProperties` and `customization.*Properties` when classes or attributes depend on event, resource, date, slot, conflict, or projection context.
## White-label themes
Scheduler styles are designed around CSS custom properties. Apply theme variables at the grid, plugin host, product shell, or global stylesheet level:
```css
.booking-scheduler revo-grid {
--event-scheduler-accent: #315f8c;
--event-scheduler-accent-soft: #e8f1f8;
--event-scheduler-grid-line: #d8dee6;
--event-scheduler-surface: #ffffff;
--event-scheduler-surface-sunken: #f6f8fb;
--event-scheduler-event-bg: #eef7f3;
--event-scheduler-event-color: #16825c;
--event-scheduler-conflict-warning-bg: #fff7ed;
--event-scheduler-conflict-warning-color: #c2410c;
--event-scheduler-remote-state-bg: rgba(255, 255, 255, 0.96);
}
```
Prefer CSS variables for broad white-label theming, and use render hooks only when the product needs different markup or data-driven content.
---
# Editing and Interaction
URL: https://pro.rv-grid.com/guides/event-scheduler/event-editing/
Source: src/content/docs/guides/event-scheduler/event-editing.mdx
Description: Create, move, resize, delete, validate, select, copy, paste, and handle interactions with the RevoGrid Scheduler lifecycle.
Enable editing with the master `editable` flag and the specific `allow*` permissions your product needs.
Editing is checked in layers. The scheduler first applies `editable`, `allowCreate`, `allowMove`, `allowResize`, `allowDelete`, event/resource locks, date locks, and permission hooks. It then projects the next schedule and rejects the mutation when conflicts resolve to `severity: 'error'`. Use this to block edits outside working hours.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
allowDelete: true,
conflicts: {
enabled: true,
rules: { 'outside-availability': 'error' },
},
createEventDraft: (context) => ({
id: `event-${crypto.randomUUID()}`,
resourceId: context.resourceId,
title: 'New booking',
startDateTime: context.startDateTime,
endDateTime: context.endDateTime,
status: 'draft',
}),
validateMutation: (detail) => {
if (detail.event?.locked) {
return 'Locked events cannot be changed.';
}
},
};
```
To enforce working hours, define them with `calendars` or `eventSchedulerAvailability`; then block the `outside-availability` conflict as shown above. `nonWorkingTime` can make closed cells visible, but it does not by itself reject create, move, resize, or editor changes.
Conflicts are evaluated against the projected next event array before the local mutation is committed. This means move, resize, create, edit, delete, bulk, template, paste, and recurrence actions can all be stopped by the same conflict configuration. Use `conflicts.policy: 'prevent'` when every detected conflict should block by default, or keep `policy: 'mark'` and promote only selected rules such as `overlap`, `outside-availability`, or `blocked-time` to `error`.
The before-change detail includes the projected `conflicts` array. Use it when the UI needs custom messages or business-specific decisions on top of configured conflict rules.
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
const blockingConflict = event.detail.conflicts?.find((conflict) =>
conflict.severity === 'error' &&
event.detail.eventId !== null &&
conflict.eventIds.includes(event.detail.eventId)
);
if (blockingConflict) {
event.preventDefault();
}
});
```
For one-off business rules that are not calendar rules, use `permissions.canCreate`, `permissions.canMutate`, or `validateMutation`. These hooks are useful for role checks, approval states, custom capacity rules, or backend-specific constraints.
If you prefer event-based validation, listen for `event-scheduler-before-event-change` and call `event.preventDefault()`. This is the same cancelable-before-event pattern used by RevoGrid editing events. Do not use `event-scheduler-event-changed` for cancellation; it fires after the scheduler has already accepted and applied the change.
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
if (event.detail.action === 'move' && event.detail.event?.locked) {
event.preventDefault();
}
});
```
## Unified committed changes
Every successful create, edit, move, resize, delete, paste, duplicate,
recurrence, or bulk mutation also reaches EventManager's shared `gridedit`
stream. Inspect `event-scheduler-event-change` in `eventTypes` and the typed,
ordered `domainChanges` entries:
```ts
grid.addEventListener('gridedit', (event) => {
if (!event.detail.eventTypes.includes('event-scheduler-event-change')) return;
for (const change of event.detail.domainChanges ?? []) {
if (change.type !== 'event-scheduler-event') continue;
saveEventChange(change.detail);
}
});
```
Each entry contains the Scheduler action, event ID, previous event, and
committed event. Creates have a null previous event; deletes have a null
committed event; bulk and server-canonical mutations include every affected
event. These snapshots are detached from Scheduler state.
`data`, `previousData`, and `models` contain normal row/field patches only when
an event keeps a stable canonical source index. Creates, deletes, and unstable
positions remain in `domainChanges`. The complete event collection used for
undo/redo is stored separately in `history`, never as a synthetic grid field.
Cancellation remains on `event-scheduler-before-event-change` and
`validateMutation`. Scheduler `gridedit` details have
`sourceMutation: 'producer'`, so they are observational: `preventDefault()`
does not roll back the committed schedule. With remote mutation hooks enabled,
Scheduler publishes only after ordered server commitment; rejected remote
mutations publish nothing.
When EventManager collection is enabled, consecutive Scheduler mutations merge
into one ordered transaction and one history step. Undo restores the earliest
snapshot and redo restores the latest committed snapshot.
## Undo and redo
`EventSchedulerPlugin` installs the shared `HistoryPlugin` automatically. Every
accepted create, edit, move, resize, delete, paste, duplicate, recurrence, or
bulk mutation is stored as one scheduler-owned event snapshot. Undo and redo
restore the complete event collection and rebuild the normal scheduler
projection, including conflicts, coverage, utilization, and event segments.
```ts
const plugins = await grid.getPlugins();
const history = plugins.find((plugin) =>
plugin.constructor?.name === 'HistoryPlugin'
);
history.undo();
history.redo();
```
The normal History keyboard shortcuts, stack limits, persistence adapters,
`beforeundo`, `beforeredo`, `historychanged`, and `historyerror` behavior remain
available. Canceled or rejected scheduler mutations do not create entries, and
multi-event operations remain atomic.
Scheduler rows and columns are derived projection data, so history stores event
entities rather than visible grid cells. If the host persists every state
transition, listen to `historychanged` as well as the scheduler's final mutation
events and read `scheduler.getProjection()?.events` after undo or redo.
## Data ownership and save flows
Event Scheduler does not require one persistence model. Choose the data ownership flow that matches your app: upload all events as host state, save the final projected schedule, save every accepted mutation, or let remote hooks load and commit data.
Use `source` to upload host-owned data into the scheduler:
```ts
grid.eventSchedulerResources = resources;
grid.source = events;
```
The scheduler updates its accepted event list and `grid.source` after permissions, before hooks, `validateMutation`, and conflict rules pass. Final events such as `event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire afterward, so use them for persistence and framework state sync. Use before hooks or `validateMutation` for cancellation.
### Save the full projection after work
Use this flow when the user can make several local edits and your app saves the whole schedule later, for example from a toolbar Save button. Read `grid.source`; generated scheduler rows, columns, and segments remain internal render artifacts.
```ts
const plugins = await grid.getPlugins();
const scheduler = plugins.find((plugin) =>
plugin.constructor?.name === 'EventSchedulerPlugin'
);
scheduler.createEvent({
id: `event-${crypto.randomUUID()}`,
resourceId: 'room-a',
title: 'Design review',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T10:00:00.000Z',
});
scheduler.editEvent('booking-1', {
resourceId: 'room-b',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T11:00:00.000Z',
});
scheduler.deleteEvent('hold-1');
const savedEvents = grid.source;
await api.saveSchedule({ events: savedEvents });
// Later, or after clearing the scheduler, upload the saved data back.
grid.source = savedEvents;
```
### Save incrementally while work happens
Use this flow when the host app owns canonical state and wants every accepted scheduler change to update that state immediately. Each final event carries `detail.events`, the full next accepted event list after the mutation.
```ts
let persistedEvents = [...initialEvents];
function persistSchedulerEvents(event: CustomEvent) {
persistedEvents = event.detail.events;
void api.saveEvents(persistedEvents);
}
grid.addEventListener('event-scheduler-event-created', persistSchedulerEvents);
grid.addEventListener('event-scheduler-event-changed', persistSchedulerEvents);
grid.addEventListener('event-scheduler-event-deleted', persistSchedulerEvents);
// Later, upload the persisted host state into a clean scheduler.
grid.source = persistedEvents;
```
The final event detail also includes action-specific context such as `eventId`, `event`, `previousEvent`, `action`, and `conflicts`. Use those fields for audit logs or targeted API calls, but keep `detail.events` as the simplest full-list state sync contract.
### Save through remote APIs
Use remote mode when the server owns the visible range or when loading the whole schedule up front is too expensive. Remote loaders upload visible data into the scheduler. `commitMutation` receives the accepted projected mutation request and can return canonical server events.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
remote: {
enabled: true,
mode: 'remote',
loadResources: ({ signal }) =>
api.loadResources({ signal }),
loadEvents: ({ dateRange, resourceIds, signal }) =>
api.loadEvents({ dateRange, resourceIds, signal }),
validateMutation: (request) =>
api.validateScheduleChange(request),
commitMutation: async (request) => {
const result = await api.saveScheduleChange({
action: request.action,
eventId: request.eventId,
event: request.event,
previousEvent: request.previousEvent,
changes: request.changes,
events: request.events,
conflicts: request.conflicts,
});
return {
accepted: true,
events: result.events,
};
},
},
};
const plugins = await grid.getPlugins();
const scheduler = plugins.find((plugin) =>
plugin.constructor?.name === 'EventSchedulerPlugin'
);
// Reload the current visible range into a clean scheduler state.
await scheduler.refreshVisibleRange();
```
Remote `commitMutation` runs after the local mutation has been accepted. If the backend rejects the change, return `{ accepted: false, message }` or throw; with the default rollback behavior the scheduler restores the previous event list. See [Remote Data](/guides/event-scheduler/remote-data/) for paging, state events, errors, and refresh controls.
`validateMutation` is the config-level equivalent of the cancelable before-change event. It receives the same detail shape and can return `false` or a string to reject.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
validateMutation: ({ action, event, changes }) => {
if (action === 'delete' && event?.status === 'completed') {
return 'Completed events cannot be deleted.';
}
if (changes?.resourceId === 'locked-room') {
return 'That room is locked.';
}
},
};
```
## Built-in editor
The built-in create/edit dialog supports unassigned, single-resource, and
multi-resource events. It keeps the compact `resourceId` shape for one selected
resource and writes `resourceIds` when two or more resources are selected:
```ts
{
id: 'shared-maintenance',
resourceIds: ['crew-a', 'crew-b'],
title: 'Shared maintenance',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T10:00:00.000Z',
}
```
Multi-resource assignment is enabled by default. Disable it when each event
must belong to at most one resource:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
allowMultiResourceAssignment: false,
};
```
With this option disabled, selecting another resource replaces the current
selection, and the built-in editor saves the assignment through `resourceId`.
The option only constrains the built-in editor; custom editors and
host-provided `resourceIds` remain application-owned.
The resource selector edits the event's inline assignment fields. If your
backend owns relationships through `eventSchedulerAssignments`, provide a
custom editor and update that assignment collection in the host application.
## Custom editor
Set `eventEditor: false` to disable the built-in editor, or provide a custom editor hook when the host app owns a drawer, modal, or approval flow:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
eventEditor: (context) => {
openProductDrawer({
mode: context.mode,
event: context.event,
resources: context.resources,
readOnly: context.readonly,
validationMessage: context.validationMessage,
onSave: context.submit,
onDelete: context.delete,
onClose: context.close,
});
return true;
},
};
```
The editor helpers still route changes through permissions, conflicts, `event-scheduler-before-event-change`, local mutation, post-commit events, and remote mutation hooks.
## Interaction callbacks
Use named before hooks to validate or cancel user actions before local scheduler state changes. Return `false` to cancel with the default message, or return a string to cancel with a product-specific validation message.
| Before hook | Action |
| --- | --- |
| `beforeEventCreate` | Create from slot, editor create, template create, recurrence create |
| `beforeEventUpdate` | Editor/API updates and status changes |
| `beforeEventMove` | Drag move and bulk move |
| `beforeEventResize` | Drag resize |
| `beforeEventDelete` | Delete and bulk delete |
| `beforeEventDuplicate` | Duplicate selected events |
| `beforeEventLock` | Lock or unlock changes |
| `beforeSlotSelect` | Empty-slot selection/click before create behavior |
Final callbacks fire after the scheduler has committed local state and include enough context to update app state or persist to a backend.
| Final callback | Action |
| --- | --- |
| `onEventCreate` | Event created, including paste/duplicate/template-created events |
| `onEventUpdate` | Event updated or status changed |
| `onEventMove` | Event moved |
| `onEventResize` | Event resized |
| `onEventDelete` | Event deleted |
| `onEventDuplicate` | Duplicated event created |
| `onEventLock` | Event locked or unlocked |
| `onEventCopy` | Event copied into the scheduler clipboard |
| `onEventPaste` | Pasted event created |
| `onSelectionChange` | Event selection changed |
| `onSlotSelect` | Empty slot selected/clicked |
| `contextMenu.onContextMenuAction` | Scheduler context-menu item selected |
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
beforeEventMove: ({ event, changes }) => {
if (event?.locked) return 'Locked events cannot be moved.';
return validateMoveOnServerCache(event, changes);
},
beforeEventResize: ({ event, changes }) =>
isOutsidePolicyWindow(event, changes) ? 'Outside policy window.' : undefined,
beforeEventDelete: ({ event }) =>
event?.metadata?.protected ? 'Protected events cannot be deleted.' : undefined,
beforeSlotSelect: ({ date, startDateTime }) =>
isClosedForBooking(date, startDateTime) ? 'This slot is closed.' : undefined,
onEventMove: ({ event, previousEvent }) =>
saveEventMove({ event, previousEvent }),
onEventResize: ({ event, previousEvent }) =>
saveEventResize({ event, previousEvent }),
onEventDelete: ({ event }) =>
archiveDeletedEvent(event),
onSelectionChange: ({ eventIds }) =>
setSelectedPlannerEvents(eventIds),
contextMenu: {
onContextMenuAction: ({ itemId, target, segment, slot }) =>
trackSchedulerCommand({ itemId, target, eventId: segment?.event.id, slot }),
},
};
```
The lower-level `event-scheduler-before-event-change`, `event-scheduler-before-event-select`, `event-scheduler-before-slot-select`, `event-scheduler-event-created`, `event-scheduler-event-changed`, `event-scheduler-event-deleted`, and `event-scheduler-event-selected` DOM events remain available for integrations that prefer event listeners. Call `preventDefault()` on the before events to cancel the pending scheduler action. `validateMutation` still runs in the same mutation pipeline for shared validation logic.
## Selection, clipboard, and keyboard
Scheduler selection supports selected events, selected slots, selected time ranges, multi-select, range select, copy, paste, duplicate, and keyboard-driven commands. Use the cancelable `event-scheduler-before-event-select` event when the host application owns selected event state: prevent the pending selection, update application state, then pass the accepted ids back through `selection.selectedEventIds`.
```ts
let selectedEventIds: readonly string[] = [];
grid.addEventListener('event-scheduler-before-event-select', (event) => {
event.preventDefault();
selectedEventIds = event.detail.eventIds;
renderScheduler();
});
function renderScheduler() {
grid.eventScheduler = {
...grid.eventScheduler,
selectionMode: 'multiple',
selection: {
...grid.eventScheduler?.selection,
selectedEventIds,
selectedSlot,
selectedRange,
clipboard: true,
copyOffsetDays: 7,
onChange: ({ eventIds }) => setSelectedEventIds(eventIds),
},
onSelectionChange: ({ eventIds, events }) =>
syncInspectorSelection({ eventIds, events }),
onSlotSelect: (slot) =>
setSelectedSlot(slot),
};
}
```
Accepted selections still emit `event-scheduler-event-selected`, `onSelectionChange`, and `selection.onChange`. A prevented selection does not emit final selection callbacks; the host re-applies its accepted state through the config.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
beforeSlotSelect: ({ date }) =>
isClosedForBooking(date) ? 'This slot is closed.' : undefined,
};
grid.addEventListener('event-scheduler-before-slot-select', (event) => {
if (isReadOnlyResource(event.detail.resourceId)) {
event.preventDefault();
}
});
```
Use `clipboardAdapter` when copy/paste must integrate with product state, permissions, or a custom clipboard store. Paste remains disabled when there is no internal payload and `clipboardAdapter.hasPayload()` is false.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
clipboardAdapter: {
hasPayload: () => appClipboard.has('scheduler-events'),
read: () => appClipboard.get('scheduler-events'),
write: (payload) => appClipboard.set('scheduler-events', payload),
clear: () => appClipboard.delete('scheduler-events'),
},
onCopy: ({ eventIds }) => trackCopy(eventIds),
onPaste: ({ event }) => persistCreatedEvent(event),
onDuplicate: ({ event }) => persistCreatedEvent(event),
};
```
Keyboard shortcuts can be disabled globally, disabled per command, or rebound. Use `Mod` for Cmd on macOS and Ctrl on Windows/Linux.
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
keyboardShortcuts: {
enabled: true,
helpPanel: true,
shortcuts: {
copy: 'Mod+c',
paste: 'Mod+v',
duplicate: 'Mod+Shift+d',
delete: false,
lock: false,
today: ['t', 'Home'],
},
},
};
```
Available keyboard commands are `escape`, `help`, `today`, `search`, `previous`, `next`, `cyclePrevious`, `cycleNext`, `open`, `create`, `edit`, `lock`, `delete`, `copy`, `paste`, `duplicate`, and `viewWeek`.
---
# Examples
URL: https://pro.rv-grid.com/guides/event-scheduler/examples/
Source: src/content/docs/guides/event-scheduler/examples.mdx
Description: Working Event Scheduler examples for setup, templates, menus, availability, read-only mode, cancelable selection, and custom themes.
This page collects small, product-neutral Event Scheduler examples. The same APIs work for shifts, bookings, appointments, staffing, rooms, maintenance, tasks, equipment, and team planning.
The runnable demo catalog includes these complete scheduler stories:
| Demo | Use it for |
| --- | --- |
| Event Staff Scheduler | Resource timeline, coverage, conflicts, templates, saved views, and remote-mode controls. |
| Employee Shift Planner | Week view with days on the x-axis, time on the y-axis, drag-create, move, resize, delete, and closed hours. |
| Room Booking Scheduler | Resource booking with availability windows, blocked time, conflicts, and grouped resources. |
| Equipment / Machine Scheduler | Maintenance planning, utilization, capacity rows, and exportable workload data. |
## Basic weekly scheduler
```ts
import {
EventSchedulerPlugin,
type EventSchedulerConfig,
type EventSchedulerEventEntity,
} from '@revolist/scheduler';
const events: EventSchedulerEventEntity[] = [
{
id: 'booking-1',
title: 'Customer onboarding',
resourceId: 'room-a',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T11:00:00.000Z',
type: 'booking',
status: 'confirmed',
},
];
const eventScheduler: EventSchedulerConfig = {
view: 'week',
weekStartDate: '2026-06-08',
weekStartsOn: 1,
visibleDays: [1, 2, 3, 4, 5],
timeRange: { start: '06:00', end: '22:00' },
slotMinutes: 30,
locale: 'en-US',
timeZone: 'UTC',
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
allowDelete: true,
todayHighlight: true,
showCurrentTimeMarker: true,
};
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = eventScheduler;
grid.source = events;
```
## Custom event template
```ts
grid.eventScheduler = {
...grid.eventScheduler,
eventProperties: ({ event, isSelected, hasConflict, isLocked }) => ({
class: [
'planner-event',
isSelected ? 'planner-event--selected' : '',
hasConflict ? 'planner-event--conflict' : '',
isLocked ? 'planner-event--locked' : '',
].filter(Boolean).join(' '),
style: {
'--planner-event-accent': event.color ?? '#2563eb',
},
}),
eventContentTemplate: (h, { event, resource, start, end, isShortEvent }) =>
h('span', { class: 'planner-event__body' }, [
h('strong', { class: 'planner-event__title' }, event.title),
isShortEvent ? null : h('span', { class: 'planner-event__time' }, `${start.slice(11, 16)}-${end.slice(11, 16)}`),
resource ? h('small', { class: 'planner-event__resource' }, resource.name) : null,
]),
eventTooltipTemplate: ({ event, resource, duration }) =>
`${event.title}\n${resource?.name ?? 'Unassigned'}\n${duration} minutes`,
};
```
## Custom day header
```ts
grid.eventScheduler = {
...grid.eventScheduler,
dayHeaderFormatter: (date) =>
new Intl.DateTimeFormat('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
}).format(date),
dayHeaderTemplate: (h, context) =>
h('span', { class: 'planner-day-header' }, [
h('strong', null, context.defaultLabel),
context.today ? h('span', { class: 'planner-day-header__badge' }, 'Today') : null,
context.holiday ? h('span', { class: 'planner-day-header__holiday' }, context.holidayLabel) : null,
h('small', null, `${context.eventCount ?? 0} events`),
]),
dayHeaderProperties: ({ today, weekend, holiday }) => ({
class: [
today ? 'planner-day-header--today' : '',
weekend ? 'planner-day-header--weekend' : '',
holiday ? 'planner-day-header--holiday' : '',
].filter(Boolean).join(' '),
}),
};
```
## Custom slot context menu
```ts
grid.eventScheduler = {
...grid.eventScheduler,
contextMenu: {
enabled: true,
getSlotContextMenuItems: ({ slot }) => slot
? [
{
id: 'create-booking',
label: 'Create booking here',
shortcut: 'C',
action: () => createBooking(slot),
},
{
id: 'hold-slot',
label: 'Block this time',
group: 'availability',
action: () => createHold(slot),
},
]
: [],
onContextMenuAction: ({ itemId, target, slot }) =>
analytics.track('scheduler_menu_action', { itemId, target, slot }),
},
};
```
## Custom current time marker
```ts
grid.eventScheduler = {
...grid.eventScheduler,
showCurrentTimeMarker: true,
currentTimeUpdateInterval: 60_000,
currentTimeLabelFormatter: ({ dateTime }) =>
new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
}).format(new Date(dateTime)),
currentTimeMarkerProperties: ({ orientation }) => ({
class: `planner-now planner-now--${orientation}`,
style: {
'--event-scheduler-current-time-color': '#dc2626',
},
}),
currentTimeMarkerTemplate: (h, { label }) =>
h('span', { class: 'planner-now__label' }, label),
};
```
## Closed hours and availability
```ts
grid.eventScheduler = {
...grid.eventScheduler,
workingHours: {
byDay: {
1: { start: '08:00', end: '18:00' },
2: { start: '08:00', end: '18:00' },
3: { start: '08:00', end: '18:00' },
4: { start: '08:00', end: '18:00' },
5: { start: '08:00', end: '16:00' },
0: false,
6: false,
},
},
closedHours: [
{
id: 'lunch',
days: [1, 2, 3, 4, 5],
ranges: { start: '12:00', end: '13:00' },
title: 'Closed',
reason: 'Lunch break',
},
],
closedSlotTooltip: ({ title, reason }) =>
[title, reason].filter(Boolean).join(' - '),
closedSlotTemplate: (h, { reason }) =>
h('span', { class: 'planner-slot__closed' }, reason ?? 'Closed'),
isTimeSlotAvailable: ({ date, startMinutes }) =>
date !== '2026-06-19' && startMinutes >= 8 * 60,
};
```
## Read-only mode
```ts
grid.eventScheduler = {
...grid.eventScheduler,
editable: false,
allowCreate: false,
allowMove: false,
allowResize: false,
allowDelete: false,
keyboardShortcuts: {
enabled: true,
shortcuts: {
copy: 'Mod+c',
paste: false,
duplicate: false,
delete: false,
lock: false,
},
},
};
```
Read-only mode keeps rendering, selection, tooltips, context menus, and custom templates active while blocking scheduler mutations.
## Cancelable selection state
```ts
let selectedEventIds: readonly string[] = [];
grid.addEventListener('event-scheduler-before-event-select', (event) => {
event.preventDefault();
selectedEventIds = event.detail.eventIds.map(String);
renderScheduler();
});
function renderScheduler() {
grid.eventScheduler = {
...grid.eventScheduler,
selectionMode: 'multiple',
selection: {
selectedEventIds,
clipboard: true,
onChange: ({ eventIds }) => {
selectedEventIds = eventIds.map(String);
renderScheduler();
},
},
onSelectionChange: ({ events }) =>
inspector.show(events),
onEventMove: ({ events }) => {
grid.source = [...events];
void saveEvents(events);
},
};
}
```
Use the before-select event when selection lives in application state. Prevent the pending scheduler selection, update host state, then pass the accepted ids back through `selection.selectedEventIds`. Event arrays, visible ranges, and filters follow the same ownership model: the scheduler emits callbacks and the host updates `eventScheduler`, `source`, and related props.
## Custom styling and theme
```css
.booking-scheduler revo-grid {
--event-scheduler-accent: #0f766e;
--event-scheduler-accent-soft: #ccfbf1;
--event-scheduler-grid-line: #d8dee6;
--event-scheduler-current-time-color: #dc2626;
--event-scheduler-weekend-bg: rgba(15, 118, 110, 0.05);
--event-scheduler-event-bg: #eef7f3;
--event-scheduler-event-color: #0f766e;
}
.planner-event--conflict {
--event-scheduler-event-color: #b45309;
--event-scheduler-event-bg: #fff7ed;
}
.planner-day-header--today {
box-shadow: inset 0 -2px 0 var(--event-scheduler-accent);
}
```
Use CSS variables for broad theme changes. Use `eventProperties`, `dayHeaderProperties`, `slotProperties`, `closedSlotProperties`, and `currentTimeMarkerProperties` when styling depends on scheduler state.
## Related guides
- [Week View](../week-view/) covers day/time configuration, visible range, week start, locale, timezone, and edit ownership.
- [Customization](../customization/) documents the full visual customization category. Use [Event Rendering](../customization/events/) for templates and tooltips, [Slots, Hours, and Markers](../customization/slots-hours/) for slot and current-time examples, [Context Menus](../customization/context-menu/) for commands, and [Styling and Themes](../customization/styling/) for CSS hooks.
- [Availability](../scheduling-rules/availability/) covers calendars, working hours, blocked time, breaks, holidays, and outside-hours styling.
- [Editing and Interaction](../event-editing/) covers read-only mode, permissions, cancelable hooks, mutation events, selection, clipboard, keyboard shortcuts, custom editors, and persistence.
---
# Export
URL: https://pro.rv-grid.com/guides/event-scheduler/export/
Source: src/content/docs/guides/event-scheduler/export.mdx
Description: Export scheduler events, workload, conflicts, coverage gaps, visible schedules, and print views.
The event scheduler exports data from the current projection, so filters, visible resources, conflicts, coverage, and utilization match what the user is reviewing.
```ts
const scheduler = (await grid.getPlugins()).find(
(plugin) => plugin.constructor?.name === 'EventSchedulerPlugin',
);
const projection = scheduler.getProjection();
const eventsCsv = scheduler.exportEvents({ format: 'csv' });
const workloadJson = scheduler.exportResourceWorkload({ format: 'json' });
const conflictsCsv = scheduler.exportConflicts({ format: 'csv' });
const coverageCsv = scheduler.exportCoverage({ format: 'csv', gapsOnly: true });
const visibleCsv = scheduler.exportVisibleSchedule({ format: 'csv' });
download(eventsCsv.content, eventsCsv.fileName, eventsCsv.mimeType);
```
For printable schedules, create an HTML view from the same projection.
```ts
const print = scheduler.createPrintView({
title: 'Weekly Staff Schedule',
includeEvents: true,
includeWorkload: true,
includeConflicts: true,
includeCoverage: true,
});
const printWindow = window.open('', '_blank');
printWindow?.document.write(print.html);
printWindow?.print();
```
## Production Checklist
- Use stable ids for events, resources, assignments, templates, and coverage requirements.
- Store scheduler datetimes as ISO strings and define a product time-zone policy before persisting user edits.
- Keep `source` controlled by app state and persist every created, changed, and deleted event array.
- Enable `allowCreate` intentionally. It defaults to false.
- Add permission rules for locked resources, read-only events, completed statuses, and protected date ranges.
- Choose conflict policy deliberately. Use `prevent` or error rules for hard booking constraints.
- Use availability for real scheduling constraints and `nonWorkingTime` for visual closed-time guidance.
- Use `resourceGrouping`, `filters`, and `savedViews` for planner navigation instead of mutating source arrays.
- Use `coverage` and `utilization` only when the resource metadata needed for planning is present.
- Add remote validation and rollback for server-backed schedulers.
- Keep keyboard shortcuts enabled unless the host app owns all scheduler keyboard behavior.
- Expose export or print workflows from the plugin projection when planners need to share schedules.
- Test drag-create, move, resize, delete, selection, keyboard shortcuts, conflicts, and remote failure paths in the target framework.
## Graphical Resource Timeline export
Graphical export is additive: CSV/JSON and `createPrintView()` keep their existing contracts. The graphical methods wait for pending saves, dispatch/reconciliation and persisted history replay, then capture all matching logical rows in the current native sort/filter/collapse order, including unmounted rows. No source, selection, scroll, history or live view is changed.
```ts
import { EventSchedulerPlugin, ExportSchedulerPlugin } from '@revolist/scheduler';
grid.plugins = [EventSchedulerPlugin, ExportSchedulerPlugin];
const exporter = (await grid.getPlugins()).find(
plugin => plugin instanceof ExportSchedulerPlugin,
)!;
const images = await exporter.exportImages({
format: 'svg', // or 'png'; PNG supports pixelRatio: 1 | 2
title: 'Client schedule',
});
if (images.ok) {
// Each tile is a separate file. Present individual links rather than assuming
// the browser permits several automatic downloads.
for (const tile of images.tiles) {
const url = URL.createObjectURL(tile.blob);
// Use url in a preview/download link; revoke it when replaced or disposed.
}
} else {
console.error(images.error.code, images.error.message);
}
const pdf = await exporter.exportPdf({
paper: 'a4', // 'letter' also supported
orientation: 'landscape',
title: 'Client schedule',
});
// pdf.ok => pdf.blob is application/pdf, with fileName, pageCount and metadata.
// No print dialog or application-supplied PDF encoder is required.
```
For a ready-made preview with explicit download links and cancellation:
```ts
import { mountScheduleExportPreview } from '@revolist/scheduler';
const dispose = mountScheduleExportPreview(container, exporter);
// Call dispose() on unmount to cancel work and revoke owned preview URLs.
```
The [equipment and machine example](https://demo.rv-grid.com/equipment-machine-scheduler/ts) includes the controls and portable equivalents for its custom content in Vue, TypeScript, React and Angular.
### Scope and timezones
The default includes current visible resource columns, their retained order/width, filters, collapsed groups, authored event colors, progress and conflict/availability markers. An explicit `resourceColumns` subset can omit visible columns; it cannot expose hidden columns. `resourceIds` intersects the visible resource scope and preserves its order. Unknown resource ids fail. Collapsed child payloads are excluded. There is no automatic privacy redaction: applications control authorization.
An optional `range: { startDateTime, endDateTime }` uses absolute ISO instants and an exclusive end. Export can project another date range from supplied canonical data without changing the grid; the existing visible-day/time-window policy still applies. The exporting browser's IANA timezone is captured once. Axis windows are split at browser midnight and offset transitions, and repeated hours are labeled with offsets. Event instants, elapsed durations, membership, schedule-timezone calendars and conflict policy remain unchanged. Each page/tile labels its timezone and converted range.
### Custom content and supplied images
Custom event, resource, header, group or timeline-cell presentation requires its matching `templates` renderer. Export blocks with `missing-export-template` for a rendered instance without one; it does not fall back to incomplete built-in text. Templates receive immutable context and return portable `text`, `rect`, `line` or `image` nodes in the provided box. No HTML, raw SVG, external CSS, scripts or network URLs are accepted. Export-only renderers must include the information your custom UI makes visible.
```ts
import type { ScheduleExportOptions } from '@revolist/scheduler';
const exportOptions: ScheduleExportOptions = {
templates: {
event: ({ width, height, text }) => width < 12 ? [] : [
{ kind: 'rect', x: 0, y: 0, width, height, fill: '#2563eb' },
{ kind: 'text', x: 4, y: 16, text, fill: '#ffffff' },
],
},
};
```
Images must be explicitly supplied in `assets` as keyed `{ blob, width, height }` PNG/JPEG records, then referenced by `image` nodes through `asset`. Bytes and dimensions are decoded and checked before any successful artifact set is returned. Existing avatar URLs are never fetched implicitly; provide a portable resource renderer and approved image assets where applicable.
System fonts are used. Unicode and emoji appearance depends on the platform's installed system fonts. SVG is self-contained, with concrete styles and embedded supplied images; system typography can vary when opened on another OS. PNG and PDF preserve the rendering browser's appearance. PDF pages are rasterized at 192 DPI; searchable/selectable text and tagged PDF accessibility are not promised.
### Complete snapshots, limits and errors
Remote or lazy data requires `loadSnapshot(request)`. The request contains captured configuration (including native collapse), resource-column filters/sorting, instant range, schedule/export timezones, request token and cancellation signal. `resourceIds` is undefined for the full filtered scope; `visibleResourceIds` identifies currently loaded visible resources. An explicit `resourceIds` subset remains intersected with that scope. The application translates its filters and enforces authorization; it must return an isolated schedule-timezone `projection` and explicit `coverage: { startDateTime, endDateTime, resourceIds, complete: true, requestToken, allMatchingResources: true }`, with an optional server `revision`. Build that projection using `createEventSchedulerProjection` and the captured schedule settings with all authorized overlapping events, assignments and availability. The adapter must apply the captured native filter/sort contract to its returned rows, including matching resources not yet loaded by the grid. `allMatchingResources: true` is required for default scope, and `requestToken` must echo the captured request. Canonical projection/collapse rules are reapplied before adapter row membership/order is accepted, so collapsed child payloads cannot appear in the output. Missing or partial coverage blocks export; no partial-success option exists. Data is never inserted into live stores. Cross-page database consistency is the adapter's responsibility.
The default preparation timeout is 30 seconds (`preparationTimeoutMs` overrides it). Changed state during loading retries twice, then returns `stale-snapshot`. `signal` cancels preparation/encoding; destroying the Scheduler cancels its exports. Error results include an actionable code/message, including `incomplete-data`, `settle-timeout`, `invalid-template`, `asset-unavailable`, `export-too-large`, `encoding-failed` and `aborted`.
Images default to 1600 × 1000 CSS-pixel tiles (`tileWidth`/`tileHeight` override); PDF uses A4/Letter portrait/landscape with 32 CSS-pixel margins (`margin` overrides). Resource columns and time headers repeat; dense rows continue across lane slices. Geometry uses 96 CSS pixels/inch; PNG pixel ratio and PDF DPI change resolution, not layout. Export rejects pages too narrow for their resource columns rather than silently dropping columns or shrinking text.
Limits per export: 200 pages/tiles, 250,000 scene nodes, 32 MiB per SVG tile and 128 MiB aggregate artifacts. PNG tiles are limited to 16,384 pixels per side and 64 megapixels after scaling. Encoding is sequential. Reduce the range, selected resources, visible column subset or image quality when a budget is exceeded. Other Scheduler layouts and layout-preserving XLSX export are outside this graphical API.
The PDF runtime (pdfmake, MIT license) is included in the Scheduler bundle. Export itself creates no download, popup or print dialog; applications own those actions and their object URL lifecycle.
A local production-build comparison with the exporter removed from the same source measured the UMD bundle at 1,097,163 → 2,117,568 bytes, or 301,232 → 661,100 bytes with Node gzip defaults: an increase of 359,868 bytes (about 351 KiB gzip). The sum of emitted ESM modules increased by 1,335,364 bytes (385,813 bytes when each module is gzipped separately). These measurements include the PDF runtime and exporter UI; emitted-module totals are not a tree-shaken application's download size. No PDF font VFS is bundled because these PDF pages contain raster images.
The separate `ExportSchedulerPlugin` follows the grid exporter convention: register it in `grid.plugins`, retrieve its instance, and call its export methods. `exportBlob(options)` provides the Core PDF-style convenience (PDF Blob, `null` on cancellation, rejection on error). `getDocumentDefinition(options)` exposes the same raster-page pdfmake definition used by `exportPdf()` for inspection or customization. The cancelable `event-scheduler-before-export` event receives the format, captured metadata/options and, for PDF, the document definition. It runs before artifact delivery; call `preventDefault()` to cancel. No exporter methods or PDF engine are installed on the Scheduler instance or grid element.
---
# Grouping
URL: https://pro.rv-grid.com/guides/event-scheduler/grouping/
Source: src/content/docs/guides/event-scheduler/grouping.mdx
Description: Group scheduler resources by team, department, location, parent tree, or custom business rules.
Use `resourceGrouping` for team, department, location, room-building, machine-line, or sprint-team grouping. Parent-child resources can render as a tree.
```ts
grid.eventSchedulerResources = [
{ id: 'north', name: 'North Campus', role: 'Location' },
{ id: 'room-a', parentId: 'north', name: 'Room A', role: 'Room', metadata: { floor: '1' } },
{ id: 'room-b', parentId: 'north', name: 'Room B', role: 'Room', metadata: { floor: '2' } },
];
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
resourceGrouping: {
enabled: true,
tree: true,
groupBy: (resource) => String(resource.metadata?.floor ?? resource.group ?? 'Other'),
groupSummaryFormatter: ({ summary }) =>
`${summary.eventCount} events - ${summary.totalHours}h`,
},
};
```
Group rows aggregate workload, coverage, and utilization summaries when those features are enabled.
## Resource Planner Patterns
Use `resourceTimeline` when resources are the main rows and time moves horizontally.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-14' },
resourceColumnSize: 220,
timelineColumnSize: 72,
resourceGrouping: {
enabled: true,
groupBy: (resource) => resource.metadata?.location as string,
tree: true,
collapsed: false,
groupSummaryFormatter: ({ summary }) =>
`${summary.eventCount} events - ${summary.totalHours}h`,
},
resourceMetaFormatter: (resource) =>
[resource.role, resource.metadata?.location].filter(Boolean).join(' - '),
};
```
Events without `resourceId`, `resourceIds`, or side-channel assignments render in the generated unassigned row when `unassigned.enabled` is not false.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
unassigned: {
enabled: true,
showCount: true,
requiredRoleField: 'metadata.requiredRole',
filter: { status: ['draft', 'planned'], role: 'Technician' },
counterFormatter: (summary) => `${summary.visibleCount} open requests`,
requiredRoleFormatter: ({ role }) => `Need ${role}`,
assignmentValidator: ({ event, resource }) => {
const required = event.metadata?.requiredRole;
const skills = Array.isArray(resource.metadata?.skills)
? resource.metadata.skills
: [];
return !required || skills.includes(required) || `${resource.name} cannot fill ${required}.`;
},
},
};
```
Coverage compares required capacity with assigned event capacity in the visible range. Utilization calculates scheduled workload by resource.
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
coverage: {
enabled: true,
capacityField: 'capacity',
countEvent: ({ event, resource }) =>
event.status === 'cancelled' ? false : resource.capacity ?? 1,
},
utilization: {
enabled: true,
minHoursField: 'metadata.minHoursPerWeek',
targetHoursField: 'metadata.targetHoursPerWeek',
maxHoursField: 'metadata.maxHoursPerWeek',
excludeStatuses: ['cancelled', 'blocked'],
},
};
```
Use `filters` and `savedViews` to reduce projected data without mutating source arrays:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
filters: {
search: 'security',
resourceRoles: 'Security',
eventStatuses: ['planned', 'confirmed'],
conflictsOnly: false,
unassignedOnly: false,
},
activeSavedViewId: 'security-week',
savedViews: {
views: [
{ id: 'conflicts', label: 'Conflicts only', filters: { conflictsOnly: true } },
{ id: 'unassigned', label: 'Unassigned', filters: { unassignedOnly: true } },
{
id: 'security-week',
label: 'Security team',
dateRange: { start: '2026-06-08', end: '2026-06-12' },
filters: { resourceRoles: 'Security' },
},
],
},
};
```
The projection exposes `filterSummary` with visible and hidden resource/event counts.
## Resource columns
Group sections use a compact neutral row with the resource count beside the name
and the existing summary aligned on the right of the timeline. Customize the count
with `labels.resourceGroupCountLabel`; `groupSummaryFormatter` continues to own the
summary text. Resource avatars use the shared Pro avatar renderer.
Resource Timeline accepts normal RevoGrid column definitions in `grid.columns`.
Omitted or empty columns use the default Resource column. Reassign `grid.columns`
to update the resource fields while Scheduler is active.
Their values come from `eventSchedulerResources`; the grid's `source` still owns
scheduled events. Use `prop` for a resource field or a nested field path. The
legacy `resourceLabel` prop displays the existing identity template and edits
`resource.name`.
```ts
grid.columns = [
{ prop: 'resourceLabel', name: 'Resource', size: 220 },
{ prop: 'role', name: 'Role', size: 140, tree: true },
{ prop: 'metadata.location', name: 'Location', size: 140 },
{ prop: 'capacity', name: 'Capacity', size: 100, filter: 'number' },
];
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
resourceGrouping: { enabled: true, tree: true },
};
```
Registered grid `columnTypes` are supported, including their editors and readonly defaults. Use ordinary `editor`, `readonly`, `cellTemplate`, `cellParser`, `cellCompare`,
`filter`, `sortable`, `size`, `minSize`, and `maxSize` settings. Templates and
parsers receive the projected grid row, including its `resource` entity and raw
column values. Synthetic groups, the unassigned row, and empty rows cannot be
edited. Real parent resources can be edited. Resource IDs remain immutable.
The default Resource column is editable. Set `editable: false` for Scheduler or
`readonly: true` on a column to disable editing. Resource `locked`, `readonly`,
and `disabled` flags also prevent resource-cell edits.
Identity can be moved or hidden (`hidden: true`). Choose the hierarchy control
column with `tree: true`; if omitted, the first visible resource column is used.
Resource columns remain pinned together on the left; header dragging reorders
that area. On narrow layouts, hide optional columns to leave room for time slots.
Scheduler reuses TreeDataPlugin and native grid sort/filter controls, including
Shift-click additive sorting. Tree filters follow that plugin's existing
positive/exclusion filter semantics and expansion behavior. They search **loaded
resources only**. They do not fetch unloaded children, change event membership,
redefine group summary totals, or remove hidden demand from conflict checks.
For advanced resource filters, register `AdvanceFilterPlugin` alongside `EventSchedulerPlugin`. Use `filter: ['selection']` for checkbox value lists, such as resource names, roles, groups, and locations, and `filter: ['number']` for numeric capacity conditions. Synthetic section headings do not appear in the value lists. Filtering continues to use the shared grid and tree plugins.
### Resource edits and application saves
Resource edits replace `eventSchedulerResources` immutably and recalculate
scheduling conflicts. Proposed conflicts cover all loaded bookings, including dates and events hidden by the current view or filters. They participate in the existing grid undo/redo history.
By default, edits that create conflicts are accepted and the conflicts displayed.
An application can reject a proposed edit using the cancelable event:
```ts
grid.addEventListener('event-scheduler-before-resource-change', event => {
if (event.detail.conflicts.length) event.preventDefault();
});
grid.addEventListener('event-scheduler-resource-changed', event => {
// Save event.detail.resources in your application's persistence layer.
// origin is 'edit', 'undo', or 'redo'.
});
```
Computed columns use `valueGetter(resource)`. They are read-only unless supplied
with `valueSetter(resource, value)`, which must return a new resource with the
same ID. Nested field edits preserve unrelated metadata.
```ts
const doubledCapacity = {
prop: 'doubleCapacity',
name: 'Double capacity',
valueGetter: resource => (resource.capacity ?? 0) * 2,
valueSetter: (resource, value) => ({ ...resource, capacity: Number(value) / 2 }),
};
```
### Column layout state
Column widths and dragged order survive refreshes and temporary view switches.
Explicit configuration changes override retained state. Use the Scheduler plugin's
`getResourceColumnState()` to inspect column props, widths, visibility, order,
sorting, and filters, and `resetResourceColumnState(prop?)` to reset all columns or
one column. `event-scheduler-resource-column-state-changed` reports native
presentation changes. Applications own persistence across page loads.
When grouping is active, the plugin projection retains loaded descendant rows.
TreeDataPlugin owns their visible order and collapse/filter trimming through grid
providers; collapsing a branch does not remove its rows or events from the
underlying projection.
---
# Event Scheduler
URL: https://pro.rv-grid.com/guides/event-scheduler/introduction/
Source: src/content/docs/guides/event-scheduler/introduction/index.mdx
Description: Set up the standalone RevoGrid Event Scheduler for staff scheduling, shift planning, room booking, equipment planning, and team calendars.
Plan bookings. Move events. See conflicts.
Try moving or resizing a booking, or double-click one to edit its details. Changes stay in this demo until you reload the page.
## Install
`EventSchedulerPlugin` from `@revolist/scheduler` turns RevoGrid into an editable calendar or resource timeline for room bookings, staff rosters, equipment plans, and team schedules.
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/scheduler
```
Import the Pro and Scheduler styles once in your application entry:
```ts
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/scheduler/styles.css';
```
Create an empty grid and provide scheduler data through side-channel props. The scheduler owns the generated grid projection; your app owns persistence.
## Minimal Astro setup
Create a grid, register `EventSchedulerPlugin`, and provide the configuration and event source shown below. Use the room-booking preview above to try the interactions.
The only required event fields are `id`, `startDateTime`, and `endDateTime`. Add `title`, `resourceId`, `status`, `type`, `metadata`, and resources when your product needs labels, capacity rows, filtering, styling, or persistence fields.
## Minimal TypeScript setup
If the grid already exists in your app, set the same properties directly:
```ts
import { EventSchedulerPlugin } from '@revolist/scheduler';
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
slotMinutes: 60,
timeRange: { start: '08:00', end: '18:00' },
};
grid.source = [
{
id: 'booking-1',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T12:00:00.000Z',
},
];
```
## What Event Scheduler does
Event Scheduler uses RevoGrid as the rendering and interaction surface for time-based planning. Instead of building ordinary data rows and columns yourself, you give the grid scheduler configuration, events, resources, and optional planning datasets. The plugin projects that data into timeline columns, resource rows, event blocks, drag handles, conflict indicators, non-working time, and editor interactions.
Use it when the main question is "who or what is booked, when, and against which capacity?" Common use cases include:
- staff and employee shift scheduling
- room, desk, vehicle, or equipment booking
- machine and production line planning
- field crew, support queue, and service dispatch calendars
- sprint, delivery, or team-capacity calendars
## Core concepts
- `events` are the scheduled blocks. Each event needs an `id`, `startDateTime`, and `endDateTime`. Add `title`, `resourceId`, `resourceIds`, or `assignmentIds` when the event needs a label or belongs to a person, room, machine, team, or other capacity.
- `resources` are the schedulable rows in resource planning. A resource can be a person, room, vehicle, machine, location, team, department, or parent group.
- `views` decide how time is shown. Use `day`, `week`, or `month` for calendar-style time grids, and `resourceTimeline` when each resource needs its own row across a visible date or time range.
- `projection` is the derived scheduler model. The plugin combines config, events, resources, assignments, availability, templates, coverage requirements, and filters into visible rows, columns, event lanes, conflict markers, group summaries, and overlays.
- `source` is the host-owned event input. The scheduler emits authored `gridedit` changes after accepted local, history, and remote mutations so the application can update its source state without triggering source lifecycle events for Scheduler-internal rendering.
## Data ownership
Use the scheduler props as the public data boundary:
- **Events**: Every event needs `id`, `startDateTime`, and `endDateTime`. `title`, `resourceId`, `resourceIds`, `status`, and `type` are optional. Use resource fields for resource timelines or leave them out for unassigned demand.
- **Resources**: Use resources for people, rooms, vehicles, machines, teams, locations, or parent groups. `calendarId`, `capacity`, `group`, `role`, and `metadata` feed calendars, grouping, coverage, utilization, and filters.
- **Assignments**: Use assignments when your system stores event-to-resource links separately from the event record. They are merged with `resourceId` and `resourceIds` during projection.
- **Availability**: Use dated `working`, `blocked`, `holiday`, and `break` windows for operational exceptions such as PTO, maintenance, temporary opening hours, or one-off closures.
- **Templates and coverage**: Use templates for repeated event creation and coverage requirements for required capacity/headcount checks.
`locked` and `readonly` events or resources reject normal edit flows. `color`, `className`, `status`, `type`, `category`, and `metadata` let products style, classify, filter, or persist scheduler records without replacing the scheduler model.
## Main props
- **`eventScheduler`**: Main `EventSchedulerConfig`. Controls the active view, visible date range, slot size, visible hours, editing permissions, event layout, conflicts, calendars, filters, labels, formatters, templates, remote loading, and customization hooks.
- **`source`**: The event blocks to render. Events contain stable ids and start/end datetimes, plus optional titles, status/type metadata, and resource or assignment links.
- **`eventSchedulerResources`**: The schedulable resources shown in resource-timeline flows. Use it for people, rooms, machines, teams, locations, vehicles, or parent groups.
- **`eventSchedulerAssignments`**: Optional assignment link data when events and resources are connected by a separate system table instead of direct `resourceId` fields.
- **`eventSchedulerAvailability`**: Optional working, blocked, holiday, or break windows for resource-specific availability and outside-availability conflict checks.
- **`eventSchedulerTemplates`**: Optional reusable event templates for quick creation and recurrence helpers.
- **`eventSchedulerCoverageRequirements`**: Optional staffing or capacity requirements used by coverage summaries and gap highlighting.
- **`columns`**: Usually empty for scheduler pages. The scheduler owns visual rows and columns internally while `source` remains the host-owned event input.
If application records use different property names, map the shared view meanings
once in `eventScheduler.fields`. The same mapping vocabulary is intended for
Scheduler, Gantt, and Kanban:
```ts
grid.eventScheduler = {
weekStartDate: '2026-09-14',
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
};
```
Mappings select direct source properties and do not convert values. Scheduler
reads and writes the mapped fields while preserving other application fields.
`progress` is accepted so one mapping object can be shared by views; Scheduler
preserves it but does not render or edit progress.
Scheduler treats source rows as immutable. When it materializes an authored
mutation, unchanged rows keep their original references and only changed or
created rows are allocated. Replace application rows or the source array when
state changes; do not mutate source row objects in place. History uses shallow
array snapshots over those immutable rows so deleted records and custom fields
remain available to undo without cloning the complete schedule.
## Basic setup
```ts
import {
EventSchedulerPlugin,
type EventSchedulerConfig,
type EventSchedulerEventEntity,
type EventSchedulerResourceEntity,
} from '@revolist/scheduler';
const resources: EventSchedulerResourceEntity[] = [
{ id: 'room-a', name: 'Room A', role: 'Room', group: 'Conference' },
{ id: 'room-b', name: 'Room B', role: 'Room', group: 'Conference' },
];
const events: EventSchedulerEventEntity[] = [
{
id: 'booking-1',
startDateTime: '2026-06-08T09:00:00.000Z',
endDateTime: '2026-06-08T12:00:00.000Z',
title: 'Customer workshop',
resourceId: 'room-a',
status: 'confirmed',
type: 'booking',
},
];
const eventScheduler: EventSchedulerConfig = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-08' },
slotMinutes: 60,
timeRange: { start: '08:00', end: '18:00' },
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
conflicts: { enabled: true, policy: 'mark', scope: 'same-resource' },
};
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = eventScheduler;
grid.eventSchedulerResources = resources;
grid.source = events;
grid.columns = [];
```
For framework wrappers, bind `eventScheduler`, `eventSchedulerResources`, and `source` as grid properties.
## Blocking overlap and conflicts
Existing data can still render with conflict markers so users can review and fix it. To block future overlapping create, move, resize, and edit actions for the same resource, keep the scheduler in mark mode and promote only `overlap` to an error:
```ts
grid.eventScheduler = {
...grid.eventScheduler,
conflicts: {
enabled: true,
policy: 'mark',
scope: 'same-resource',
rules: { overlap: 'error' },
},
};
```
Error conflicts render with the built-in red conflict state. The same conflict result is used by the mutation pipeline, so an overlap introduced by create, move, resize, or edit is rejected before local events are committed.
## Where to start
1. Choose the view. Use `week` for a shift-style calendar, or `resourceTimeline` when each resource needs a row.
2. Define the visible range with `weekStartDate`, `dateRange`, `visibleDays`, `slotMinutes`, and `timeRange`.
3. Provide `eventSchedulerResources` when events need to be placed against staff, rooms, equipment, teams, or other capacity.
4. Provide `source` with ISO datetime strings and stable ids.
5. Add `calendars` for recurring working rules and `eventSchedulerAvailability` for dated working windows, breaks, holidays, or blocked time.
6. Enable editing only when the host app handles emitted changes. Start with `editable: false` for read-only schedules, then turn on `allowCreate`, `allowMove`, `allowResize`, and `allowDelete` as your persistence flow is ready.
7. Add conflict rules, templates, remote data, custom labels, or renderers after the basic schedule renders.
## Handling edits
The scheduler emits a cancelable before-change event before it commits local event changes. This follows the RevoGrid event pattern: call `preventDefault()` on the before event to cancel the mutation.
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
if (event.detail.event?.locked) {
event.preventDefault();
}
});
```
`event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire after the mutation succeeds and after `grid.source` has been updated. Use them for persistence and framework state sync, not cancellation:
```ts
const syncEvents = (event: Event) => {
const events = (event as CustomEvent<{ events: readonly EventSchedulerEventEntity[] }>).detail.events;
setApplicationEvents([...events]);
void saveSchedule(events);
};
grid.addEventListener('event-scheduler-event-created', syncEvents);
grid.addEventListener('event-scheduler-event-changed', syncEvents);
grid.addEventListener('event-scheduler-event-deleted', syncEvents);
```
If your toolbar owns the current range or active view, also handle navigation and view requests:
```ts
grid.addEventListener('event-scheduler-navigate-request', (event) => {
const { action } = (event as CustomEvent<{ action: 'previous' | 'next' | 'today' }>).detail;
// Update weekStartDate/dateRange in grid.eventScheduler here.
});
grid.addEventListener('event-scheduler-view-request', (event) => {
const { view } = (event as CustomEvent<{ view: EventSchedulerConfig['view'] }>).detail;
// Update grid.eventScheduler.view here.
});
```
For remote products, use the same ownership model: the scheduler can optimistically emit changes, but your app or `remote` hooks decide when backend mutations are committed, rejected, or rolled back.
## Next guides
- [Vertical vs Horizontal Time](./vertical-vs-horizontal/) helps choose between calendar-style and resource-timeline scheduling.
- [Timeline Views](../timeline-views/) explains day, week, month, and resource timeline configuration.
- [Week View](../week-view/) is a practical recipe for shift-style vertical week calendars.
- [Editing and Interaction](../event-editing/) covers create, move, resize, delete, editors, selection, clipboard, keyboard shortcuts, permissions, and validation.
- [Conflicts](../scheduling-rules/conflicts/) covers overlap, availability, locked-change, duration, capacity, prevention events, and assignment checks.
- [Event Scheduler Calendars](../scheduling-rules/calendars/) covers working days, holidays, and resource-specific operating hours.
- [Scheduling Rules](../scheduling-rules/) explains how calendars, availability, visual non-working time, and conflict policies work together.
- [Remote Data](../remote-data/) covers loading ranges, optimistic mutations, rollback, and load-more flows.
- [Customization](../customization/) groups labels, event rendering, layout/cells, slots/hours, conflicts/states, context menus, styling, interaction, and editor hooks.
- [Examples](../examples/) collects small product-neutral examples for setup, event templates, day headers, slots, current-time markers, context menus, availability, read-only mode, cancelable selection state, and custom themes.
Start with the Event Staff Scheduler demo for the full business story, or the Employee Shift Planner demo for a focused week-view shift workflow.
---
# Vertical vs Horizontal Time
URL: https://pro.rv-grid.com/guides/event-scheduler/introduction/vertical-vs-horizontal/
Source: src/content/docs/guides/event-scheduler/introduction/vertical-vs-horizontal.mdx
Description: Choose between calendar-style vertical scheduling and resource timeline scheduling.
Scheduling views usually answer one of three questions:
- "What happens during this day or week?"
- "What is happening across this month or year?"
- "Which resource is booked across this date range?"
Use a vertical time view for the first question, a calendar view for the second, and a horizontal resource timeline for the third.
## Vertical Time Scheduling
Vertical time scheduling is the familiar calendar layout. Time moves down the page, days are arranged across columns, and an event duration is shown by the event height.
This layout is best when users think in days, hours, and appointments:
| Good fit | Why |
| :-- | :-- |
| Staff shifts | Managers scan a day or week and adjust start/end times. |
| Appointments | Users compare open slots in a familiar calendar shape. |
| Room bookings for one location | The day is more important than a long resource list. |
| Personal or team calendars | The main task is finding a time window. |
Configure it with `day` or `week` views:
```ts
const verticalWeek = {
view: 'week',
weekStartDate: '2026-06-08',
visibleDays: [1, 2, 3, 4, 5],
slotMinutes: 30,
timeRange: { start: '08:00', end: '18:00' },
timeColumnSize: 88,
dayColumnSize: 180,
};
```
Choose vertical time when the primary action is creating, moving, or resizing events inside a day or week.
## Calendar Overview
Use `month` for a conventional seven-column calendar with whole-day creation and day-preserving move/resize interactions. Use `year` for a twelve-mini-month overview that drills down by emitting a controlled month-view request. Calendar rows fill the data viewport, while the shared scheduler model continues to supply recurrence, filtering, availability, conflicts, selection, and event customization.
```ts
const calendarOverview = {
view: 'month', // change to 'year' for the twelve-month overview
weekStartDate: '2026-07-15',
weekStartsOn: 1,
};
```
## Horizontal Resource Timeline
Horizontal scheduling puts resources on rows and time on columns. Time moves left to right, and an event duration is shown by the event width.
This layout is best when users compare many resources over the same window:
| Good fit | Why |
| :-- | :-- |
| Multi-room booking | Rooms stay fixed on the left while the schedule scrolls horizontally. |
| Equipment planning | Users compare utilization across machines, vehicles, or tools. |
| Field crews | Dispatchers see who is available across several days. |
| Open demand planning | Unassigned work can sit beside staffed resources. |
Configure it with `resourceTimeline`:
```ts
const horizontalTimeline = {
view: 'resourceTimeline',
dateRange: { start: '2026-06-08', end: '2026-06-12' },
slotMinutes: 60,
timeRange: { start: '08:00', end: '18:00' },
resourceColumnSize: 240,
timelineColumnSize: 72,
};
```
Choose a horizontal timeline when the primary action is comparing capacity, coverage, and conflicts across resources.
## Same Data, Different Projection
Both orientations use the same scheduler data:
- events define the booked work
- resources define people, rooms, machines, or teams
- assignments connect events to resources when the model needs many-to-many scheduling
- availability, calendars, conflicts, templates, and remote loading work across both layouts
The difference is projection. Vertical views emphasize time within a day or week, calendar views emphasize dates across a month or year, and horizontal views emphasize resources across a date range.
## Choosing a Default
Start with vertical time scheduling when users mostly edit individual events and think in daily calendars.
Start with a horizontal resource timeline when users mostly compare many resources, load large schedules from a server, or need coverage and utilization signals.
Many applications expose both. For example, a shift planner can use a week view for fast editing and a resource timeline for staffing coverage.
---
# Remote Data
URL: https://pro.rv-grid.com/guides/event-scheduler/remote-data/
Source: src/content/docs/guides/event-scheduler/remote-data.mdx
Description: Load scheduler resources and events from a server, refresh visible ranges, and handle optimistic save failures.
Use `remote` when events or resources are too large to ship up front, or when the scheduler must load only the visible date range.
For a comparison of host-owned full saves, host-owned incremental saves, and remote-backed saves, see [Data ownership and save flows](/guides/event-scheduler/event-editing/#data-ownership-and-save-flows).
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
remote: {
enabled: true,
loadEvents: async ({ dateRange, resourceIds }) => {
const params = new URLSearchParams({
start: dateRange.start,
end: dateRange.end,
resources: resourceIds?.join(',') ?? '',
});
const response = await fetch(`/api/scheduler/events?${params}`);
return { events: await response.json() };
},
loadResources: async () => {
const response = await fetch('/api/scheduler/resources');
return { resources: await response.json() };
},
commitMutation: async (request) => {
const response = await fetch('/api/scheduler/mutations', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
if (!response.ok) {
throw new Error('Save failed');
}
return { accepted: true, events: await response.json() };
},
},
};
```
The resource timeline demo includes remote-mode controls for refresh, load-more resources, and simulated save failure.
## Remote Options and State
Remote mode can load events, resources, availability, unassigned demand, and coverage for the visible range. It can also validate and commit accepted mutations.
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-14' },
remote: {
enabled: true,
mode: 'remote',
debounceMs: 150,
overscanDays: 1,
resourcePageSize: 50,
loadEvents: ({ dateRange, filters, signal }) =>
api.loadEvents({ dateRange, filters }, { signal }),
loadResources: ({ cursor, limit, signal }) =>
api.loadResources({ cursor, limit }, { signal }),
loadAvailability: ({ dateRange, signal }) =>
api.loadAvailability({ dateRange }, { signal }),
loadCoverage: ({ dateRange, signal }) =>
api.loadCoverage({ dateRange }, { signal }),
validateMutation: ({ action, event, previousEvent }) =>
api.validateScheduleChange({ action, event, previousEvent }),
commitMutation: ({ action, event, previousEvent, events }) =>
api.saveScheduleChange({ action, event, previousEvent, events }),
optimistic: true,
rollbackOnError: true,
refreshPolicy: 'visible-range',
},
};
```
Remote modes:
| Mode | Behavior |
| :-- | :-- |
| `local` | Remote controller is effectively off unless enabled hooks are used by the host. |
| `remote` | Visible data is loaded through remote hooks. |
| `hybrid` | Host can combine local side-channel data with remote range data. |
Remote state is available from `scheduler.getRemoteState()`. The plugin emits:
| Event | Purpose |
| :-- | :-- |
| `event-scheduler-remote-load-start` | A visible-range load begins. |
| `event-scheduler-remote-load-success` | A load target resolves. |
| `event-scheduler-remote-load-error` | A load target fails. |
| `event-scheduler-remote-mutation-pending` | A remote mutation starts. |
| `event-scheduler-remote-mutation-committed` | The backend accepts a mutation. |
| `event-scheduler-remote-mutation-rejected` | The backend rejects a mutation. |
| `event-scheduler-remote-state-change` | Loading, error, pending, paging, or empty state changes. |
Use `scheduler.refreshVisibleRange()` to reload the current range and `scheduler.loadMoreResources()` when paged resources report `hasMoreResources`.
---
# Scheduling Rules
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/index.mdx
Description: Understand how Event Scheduler combines availability, calendars, and conflict policies into scheduling rules.
Event Scheduler separates the source of a scheduling rule from the way that rule is enforced. Availability and calendars describe when a resource can be used. Conflict settings decide whether a violation is shown as a warning, ignored, confirmed, or rejected.
This matters because closed time is not a single setting. A lunch break, a public holiday, an operating calendar, and a one-off shift extension all participate in the same conflict model, but they do not have the same precedence.
## Start With The Outcome
Most applications choose one of these setups:
| Goal | Configuration |
| :-- | :-- |
| Show conflicts but allow the user to save | `conflicts: { enabled: true, policy: 'mark' }` |
| Block every detected conflict | `conflicts: { enabled: true, policy: 'prevent' }` |
| Block only selected conflict types | `conflicts: { enabled: true, policy: 'mark', rules: { ... } }` |
| Turn conflict detection off | `conflicts: { enabled: false }` |
Use `rules` when different mistakes need different behavior:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
rules: {
overlap: 'error',
'outside-availability': 'error',
'blocked-time': 'error',
},
},
};
```
In this example:
- `overlap: 'error'` rejects an event when it overlaps another event in the same resource row.
- `'outside-availability': 'error'` rejects an event when it is outside the resource's working time.
- `'blocked-time': 'error'` rejects an event when it touches a blocked, holiday, or break window.
`error` means the local scheduler mutation is not committed for the changed event. Create, move, resize, edit, paste, template, recurrence, and bulk changes are checked against the projected next schedule before the event list is updated. Existing unrelated conflicts can remain on screen; they do not block an unrelated edit unless the edited event is part of a blocking conflict.
If you only want to display a conflict, use `warning`. If you want to hide a conflict from scheduler results, use `ignore`. If you want to require a custom confirmation flow, use `confirm`.
## Rule Names
The three most common rule names answer different user questions:
| Rule | User-facing meaning | Triggered by | Typical setting |
| :-- | :-- | :-- | :-- |
| `overlap` | "This booking collides with another booking." | Two visible event segments overlap on the same date. By default this is checked within the same resource. | Use `warning` when overlaps are allowed but should be visible. Use `error` for rooms, equipment, or people that cannot be double-booked. |
| `outside-availability` | "This booking is outside allowed working time." | The event is not fully covered by a dated `kind: 'working'` availability entry, or by the resolved calendar when no dated working availability applies. | Use `error` when users must schedule only inside shifts, operating hours, or resource calendars. |
| `blocked-time` | "This booking touches unavailable time." | The event overlaps a matching `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` availability entry. | Use `error` for PTO, maintenance, lunch breaks, holidays, freezes, and other hard closures. |
The policy value attached to each rule controls enforcement:
| Rule policy | What the user sees | Does it block the local change? |
| :-- | :-- | :-- |
| `warning` | Conflict styling and conflict data with warning severity. | No. |
| `error` | Conflict styling and conflict data with error severity. | Yes, when the changed event is part of that conflict. |
| `confirm` | Conflict data with confirm severity for custom confirmation handling. | No automatic blocking by the conflict engine. Use mutation events or `validateMutation` if your app should stop the change until the user confirms. |
| `allow` | Conflict data stays visible as a warning. | No. |
| `ignore` | Conflict is removed from visible conflict results. | No. |
## Rule Inputs
The scheduler uses these inputs when it decides whether an event is valid for a resource and time range:
| Input | Purpose |
| :-- | :-- |
| `eventSchedulerAvailability` | Dated working, blocked, holiday, or break intervals. Use it for operational data that happens on specific dates. |
| `eventScheduler.calendars` | Recurring working days, holidays, working hours, and per-resource calendar resolution. Use it for the normal schedule baseline. |
| `eventScheduler.conflicts` | Converts detected conflicts into warnings, blocking errors, confirmation states, or ignored results. |
| `eventScheduler.nonWorkingTime` | Visual closed-cell styling for simple schedules. It does not create blocking conflicts by itself. |
Use availability when the answer depends on a date, such as PTO, maintenance, a temporary room closure, or an extra working shift. Use calendars when the answer is recurring, such as weekday business hours or a weekend crew calendar.
## Availability Kinds
`eventSchedulerAvailability` entries use `EventSchedulerAvailabilityKind`:
| Kind | Meaning | Conflict behavior |
| :-- | :-- | :-- |
| `working` | A dated interval where matching resources are available. | If any matching `working` availability exists for the resource/date, the event must be fully covered by matching working intervals or it receives `outside-availability`. |
| `blocked` | A dated unavailable interval, often for maintenance, holds, or PTO. | Events that overlap it receive `blocked-time`. |
| `holiday` | A dated unavailable interval for holiday closure or non-operating time. | Events that overlap it receive `blocked-time`. |
| `break` | A dated unavailable interval inside an otherwise working day. | Events that overlap it receive `blocked-time`. |
Unavailable entries are checked by overlap. If an event touches a matching `blocked`, `holiday`, or `break` interval, the scheduler raises a `blocked-time` conflict for the overlapping part.
Working entries are checked by coverage. If an event runs from `09:00` to `13:00`, the matching `working` availability must cover that whole range. A `09:00` to `12:00` working interval is not enough; the uncovered `12:00` to `13:00` portion creates an `outside-availability` conflict.
## Precedence
For each visible event segment, Event Scheduler applies rules in this order:
1. Check matching unavailable availability entries.
2. Check matching dated `kind: 'working'` availability for that resource/date.
3. If no dated working availability matches, check the resolved calendar.
4. Apply conflict policy and per-type rules to decide whether the detected conflict is shown or blocks the mutation.
Unavailable availability is always considered. Dated working availability is more specific than calendars for outside-availability checks. Calendars are the fallback only when no matching dated `kind: 'working'` availability exists for that resource/date.
This means a dated working entry can narrow or extend the calendar for that date. If a room normally follows a `08:00` to `18:00` calendar but has a dated `working` entry from `10:00` to `14:00`, events for that room/date must be covered by `10:00` to `14:00`. The calendar does not fill the rest of the day for outside-availability checks.
## Resource Matching
Availability can be global or resource-specific:
```ts
grid.eventSchedulerAvailability = [
{
id: 'all-hands-break',
kind: 'break',
title: 'Company break',
startDateTime: '2026-06-08T12:00:00.000Z',
endDateTime: '2026-06-08T13:00:00.000Z',
},
{
id: 'room-a-maintenance',
resourceId: 'room-a',
kind: 'blocked',
title: 'Projector maintenance',
startDateTime: '2026-06-08T15:00:00.000Z',
endDateTime: '2026-06-08T16:00:00.000Z',
},
{
id: 'training-team-shift',
resourceIds: ['trainer-a', 'trainer-b'],
kind: 'working',
title: 'Training shift',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T18:00:00.000Z',
},
];
```
An availability entry without `resourceId` or `resourceIds` applies globally. An entry with `resourceId` applies to that one resource. An entry with `resourceIds` applies to any listed resource.
Calendars resolve per resource in this order:
1. `resource.calendarId`
2. `eventScheduler.calendars.resourceCalendarId(resource)`
3. `eventScheduler.calendars.primaryCalendarId`
If calendar resolution finds no calendar, calendar-based `outside-availability` checks are skipped for that resource/date. Availability rules can still create conflicts.
## Conflict Outcomes
Rule detection and mutation blocking are separate steps.
The scheduler first creates conflict records. Each conflict has a `type`, `policy`, `severity`, `message`, related event ids, related segment ids, and date/time metadata. Then the configured policy decides whether that conflict is just reported or whether it prevents a local mutation.
`blocked-time` is raised when an event overlaps matching `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` availability. The conflict includes the event id, segment id, resource id when available, date, overlapping time range, and the availability id.
`outside-availability` is raised when an event is outside allowed working time. This can come from dated `kind: 'working'` availability or from the resolved calendar fallback.
`overlap` is raised when two events overlap on the same date. With the default `scope: 'same-resource'`, the events must also share the same resource. With `scope: 'global'`, overlaps are detected across resources.
By default, conflict behavior depends on `eventScheduler.conflicts.policy`:
| Policy | Default result |
| :-- | :-- |
| `mark` | Conflicts are warnings. Mutations are allowed unless a rule overrides the type. |
| `prevent` | Conflicts are blocking errors by default. |
| `allow` | Conflicts are ignored unless a rule overrides the type. |
Use `rules` when some conflict types should be stricter than others:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
rules: {
'outside-availability': 'error',
'blocked-time': 'error',
overlap: 'warning',
},
},
};
```
With this configuration, outside availability and blocked time reject create, move, resize, and edit mutations. Overlaps remain visible warnings.
You can customize conflict text with `messages`:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
rules: {
'blocked-time': 'error',
},
messages: {
'blocked-time': 'This slot is closed. Choose another time.',
},
},
};
```
## Custom Event Prevention
Conflict rules are the declarative way to stop known scheduler violations. For product-specific checks that do not fit calendars, availability, or built-in conflict types, use the scheduler mutation lifecycle.
The main event-based prevention point is `event-scheduler-before-event-change`. It fires before the local event array is updated. Call `preventDefault()` to cancel the pending create, move, resize, edit, delete, paste, template, recurrence, or bulk mutation:
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
if (event.detail.action === 'move' && event.detail.event?.status === 'completed') {
event.preventDefault();
}
});
```
Use `validateMutation` when you prefer a config-level hook instead of a DOM event. Return `false` or a string message to reject the same pending mutation:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
validateMutation: ({ changes }) => {
if (changes?.resourceId === 'locked-room') {
return 'That room is locked.';
}
},
};
```
Do not use final events such as `event-scheduler-event-created`, `event-scheduler-event-changed`, or `event-scheduler-event-deleted` for prevention. Those fire after the scheduler has accepted the change and are meant for persistence and state sync.
For the full local mutation lifecycle, cancelable before events, permission hooks, and persistence events, see [Editing and Interaction](../event-editing/).
## Calendar Fallback
Calendars are the normal baseline when no dated working availability exists:
```ts
import type { EventSchedulerConfig } from '@revolist/scheduler';
type CalendarEntity = NonNullable['calendars']>[number];
const weekdayCalendar: CalendarEntity = {
id: 'weekday',
name: 'Weekday office hours',
timeZone: 'UTC',
hoursPerDay: 8,
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
holidays: ['2026-06-19'],
};
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars: [weekdayCalendar],
},
conflicts: {
enabled: true,
rules: { 'outside-availability': 'error' },
},
};
```
With no matching `kind: 'working'` availability for a resource/date, the calendar decides whether the event is inside working time. An event on a holiday, a closed weekday, or outside `workingHours` receives `outside-availability`.
## Dated Working Overrides Calendar Hours
A dated working entry becomes the outside-availability rule for its matching resource/date:
```ts
grid.eventSchedulerAvailability = [
{
id: 'room-a-short-day',
resourceId: 'room-a',
kind: 'working',
title: 'Short operating day',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T14:00:00.000Z',
},
];
```
If `room-a` also has a calendar with `08:00` to `18:00` working hours, the dated working entry wins for June 8, 2026. A `09:00` to `10:00` event and a `14:00` to `15:00` event both receive `outside-availability` because the event is not fully covered by the dated working interval. The calendar is not used to fill those hours.
## Lunch Breaks and Blocked Time
Breaks are unavailable windows, not holes in working coverage:
```ts
grid.eventSchedulerAvailability = [
{
id: 'nurse-a-shift',
resourceId: 'nurse-a',
kind: 'working',
title: 'Day shift',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T16:00:00.000Z',
},
{
id: 'nurse-a-lunch',
resourceId: 'nurse-a',
kind: 'break',
title: 'Lunch',
startDateTime: '2026-06-08T12:00:00.000Z',
endDateTime: '2026-06-08T12:30:00.000Z',
},
];
```
An event from `11:30` to `12:30` is covered by the working shift, but it overlaps the lunch break. The result is `blocked-time`, not `outside-availability`.
## Holidays and Maintenance
Use `holiday` and `blocked` for unavailable windows that should conflict even when the calendar or working availability would otherwise allow the event:
```ts
grid.eventSchedulerAvailability = [
{
id: 'clinic-holiday',
kind: 'holiday',
title: 'Clinic closed',
startDateTime: '2026-06-19T00:00:00.000Z',
endDateTime: '2026-06-20T00:00:00.000Z',
},
{
id: 'room-b-maintenance',
resourceId: 'room-b',
kind: 'blocked',
title: 'Equipment maintenance',
reason: 'Calibration',
startDateTime: '2026-06-08T13:00:00.000Z',
endDateTime: '2026-06-08T15:00:00.000Z',
},
];
```
The holiday applies globally because it has no resource id. The maintenance entry applies only to `room-b`. Events that overlap either entry receive `blocked-time`.
## Split Working Coverage
Multiple working entries can combine to cover one event, but there cannot be a gap:
```ts
grid.eventSchedulerAvailability = [
{
id: 'room-c-morning',
resourceId: 'room-c',
kind: 'working',
title: 'Morning setup',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T12:00:00.000Z',
},
{
id: 'room-c-afternoon',
resourceId: 'room-c',
kind: 'working',
title: 'Afternoon setup',
startDateTime: '2026-06-08T13:00:00.000Z',
endDateTime: '2026-06-08T17:00:00.000Z',
},
];
```
An event from `09:00` to `11:00` is covered. An event from `11:00` to `14:00` is not covered because `12:00` to `13:00` is a gap between working intervals. The scheduler raises `outside-availability` for that event.
If you want the gap to be visibly unavailable and report a more specific conflict, add a `kind: 'break'` entry for the gap. The event will then receive `blocked-time` for the break overlap.
## Visual Non-Working Time
`nonWorkingTime` is useful for simple visual backgrounds:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
nonWorkingTime: {
enabled: true,
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
className: 'closed-cell',
},
};
```
This marks closed cells, but it is not the rule source for blocking conflicts. Use `eventScheduler.calendars` or `eventSchedulerAvailability` when outside-working-time edits must produce `outside-availability`, then configure `eventScheduler.conflicts` to decide whether that conflict blocks.
---
# Availability
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/availability/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/availability.mdx
Description: Show working time, blocked time, breaks, holidays, and resource-specific availability in Event Scheduler.
Availability entries describe time ranges that are working, blocked, break, or holiday periods. They can apply globally or to specific resources.
For the full precedence model between availability, calendars, and conflict policies, see the [Scheduling Rules overview](../).
```ts
grid.eventSchedulerAvailability = [
{
id: 'business-hours',
kind: 'working',
title: 'Business hours',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T18:00:00.000Z',
},
{
id: 'room-a-maintenance',
resourceId: 'room-a',
kind: 'blocked',
title: 'Maintenance',
reason: 'Projector replacement',
startDateTime: '2026-06-08T13:00:00.000Z',
endDateTime: '2026-06-08T15:00:00.000Z',
},
];
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
nonWorkingTime: {
enabled: true,
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '19:00' },
className: 'outside-shift-hours',
},
};
```
Use `nonWorkingTime.workingHours` when the whole scheduler shares one operating window. Slots outside the configured windows are marked as closed/outside working hours and receive the scheduler non-working background. They also expose `data-event-scheduler-non-working="true"` and `data-event-scheduler-availability-reason="Outside working hours"` so apps can inspect or customize the cells without replacing the renderer.
Use named calendars when working hours differ by calendar or resource:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'hospital-weekday',
className: 'outside-shift-hours',
calendars: [{
id: 'hospital-weekday',
name: 'Hospital weekday',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '19:00' },
holidays: [],
hoursPerDay: 8,
}],
},
};
```
## Availability and Calendars
`eventSchedulerAvailability` and `eventScheduler.calendars` can be used together. They are resolved with these rules:
- `kind: 'blocked'`, `kind: 'holiday'`, and `kind: 'break'` entries are unavailable windows. Events that overlap them receive `blocked-time` conflicts.
- `kind: 'working'` entries are dated working windows. If matching working availability exists for a resource on a date, the event must be fully covered by those working intervals or it receives an `outside-availability` conflict.
- Calendars are the fallback for `outside-availability` checks when no matching dated `kind: 'working'` availability exists for that resource/date.
Use calendars for the recurring baseline, such as weekday hours, weekend crews, and resource calendars. Use availability for dated changes, such as extra opening hours, PTO, maintenance, one-off breaks, or temporary closures.
Timeline cells expose availability data attributes so apps can style or inspect availability state without replacing the scheduler renderer.
---
# Calendars
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/calendars/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/calendars.mdx
Description: Define working days, holidays, working hours, and per-resource calendars for Event Scheduler.
Calendars describe when work is normally allowed. Event Scheduler uses them to mark closed cells, validate drag/create targets, skip non-working days during rollover, create outside-availability conflicts, and resolve the calendar that applies to a resource.
Use calendars for repeating business rules such as weekday schedules, weekend crews, holiday closures, and resource-specific operating hours. Use availability entries for dated working windows or exceptions such as one-off maintenance, PTO, blocked rooms, or breaks.
For detailed calendar-vs-availability precedence, including how dated working windows override calendar fallback checks, see the [Scheduling Rules overview](../).
Calendars do not automatically reject every edit outside working hours. They provide the working-hours model and produce `outside-availability` conflicts. To make those conflicts block create, move, resize, and edit mutations, configure the conflict rule as an error:
```ts
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars: [weekdayCalendar],
},
conflicts: {
enabled: true,
rules: { 'outside-availability': 'error' },
},
};
```
## Calendar Shape
Event Scheduler uses the same `CalendarEntity` shape as Gantt, so calendar definitions can be shared between planning views:
```ts
import { EventSchedulerPlugin } from '@revolist/scheduler';
import type { EventSchedulerConfig } from '@revolist/scheduler';
type CalendarEntity = NonNullable['calendars']>[number];
const weekdayCalendar: CalendarEntity = {
id: 'weekday',
name: 'Weekday coverage',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
holidays: ['2026-06-19'],
hoursPerDay: 8,
workingHours: { start: '08:00', end: '18:00' },
};
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars: [weekdayCalendar],
},
};
```
Define calendars explicitly in your application. The shared calendar shape is available through the public `EventSchedulerConfig` type; internal date utilities are workspace-only.
Calendar `workingDays` use ISO weekday numbers: Monday is `1` and Sunday is `7`. The older `nonWorkingTime.workingDays` option uses JavaScript day indexes, where Sunday is `0` and Saturday is `6`.
## Configuring Scheduler Calendars
Add calendars through `eventScheduler.calendars`:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
timeRange: { start: '06:00', end: '22:00' },
calendars: {
enabled: true,
primaryCalendarId: 1001,
className: 'scheduler-closed-cell',
calendars: [
{
id: 1001,
name: 'Hospital weekday calendar',
workingHours: { start: '08:00', end: '18:00' },
},
{
id: 1002,
name: 'Weekend coverage',
workingDays: [5, 6, 7],
workingHours: { start: '10:00', end: '20:00' },
},
],
},
};
```
Calendar ids can be strings or numbers. Internally the scheduler normalizes ids for lookup, so `primaryCalendarId`, resource `calendarId`, and entries in the `calendars` array should refer to the same logical id.
## Per-Resource Calendars
Each resource can point at its own calendar:
```ts
grid.eventSchedulerResources = [
{ id: 'alex', name: 'Alex Kim', role: 'RN', calendarId: 1001 },
{ id: 'weekend-team', name: 'Weekend team', role: 'RN', calendarId: 1002 },
];
```
The resolver order is:
1. `resource.calendarId`
2. `calendars.resourceCalendarId(resource)`
3. `calendars.primaryCalendarId`
Use `resourceCalendarId` when the calendar comes from metadata or an external rules table:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
calendars: {
primaryCalendarId: 'weekday',
calendars,
resourceCalendarId: (resource) =>
resource.metadata?.employmentType === 'weekend' ? 'weekend' : undefined,
},
};
```
## What Calendars Affect
Calendars are applied during projection and interaction:
- Closed days and holidays receive the calendar closed state.
- Slots outside `workingHours` are marked as outside working time.
- Events placed outside the resolved calendar produce an `outside-availability` conflict.
- Drag, resize, and create previews use the configured calendar when finding valid target dates.
- Moving an event past the bottom of a day can roll to the next schedulable day instead of stopping on a closed weekend or holiday.
- Conflict and permission hooks still run after calendar checks; calendars do not bypass app-level validation.
- Blocking is controlled by `eventScheduler.conflicts`, usually with `rules: { 'outside-availability': 'error' }` or `policy: 'prevent'`.
Calendar cells expose the same availability-style state used by the scheduler renderer. Use `className` on the calendars config for product-specific closed-cell styling:
```css
.scheduler-closed-cell {
background: #f4f6f8;
color: #8b929c;
}
```
## Working Hours
`workingHours` can be a single range or multiple ranges:
```ts
const splitShiftCalendar: CalendarEntity = {
id: 'split-shift',
name: 'Split shift',
workingHours: [
{ start: '07:00', end: '11:00' },
{ start: '15:00', end: '21:00' },
],
};
```
The scheduler treats a slot as working when it overlaps at least one configured working-hours range. If `workingHours` is omitted, every visible time slot on a working day is treated as working.
An event is considered outside availability when its visible segment is not covered by a working calendar or by matching `eventSchedulerAvailability` entries. With the default conflict policy, that state is shown as a warning. With `rules: { 'outside-availability': 'error' }`, the scheduler rejects the mutation before it updates `source`.
`nonWorkingTime.workingHours` is different: it is a visual fallback for closed cells. Use it for simple background styling when you do not need calendar/resource rules. Use `calendars` or `eventSchedulerAvailability` when outside-working-hours edits must participate in conflict detection.
## Calendars vs Availability
Calendars are recurring operating rules. Availability is dated operational data. They are not mutually exclusive: the scheduler can use a calendar as the baseline and dated availability as the more specific rule for a particular resource/date.
Use calendars for:
- Standard weekday business hours
- Weekend-only teams
- Region-specific public holidays
- Resource calendars copied from workforce or Gantt planning data
Use availability for:
- Extra dated working windows
- One-time room maintenance
- PTO or sick leave
- Lunch breaks that vary by date
- Temporary blocked equipment
When both are present, availability is evaluated first for visible slot state. Unavailable entries win in this order: `kind: 'holiday'`, then `kind: 'blocked'`, then `kind: 'break'`. If any matching `kind: 'working'` availability exists for the resource/date, the slot is open only when it overlaps one of those dated working intervals; otherwise it is treated as closed for that date. If no matching dated working availability exists, the scheduler falls back to the resolved calendar.
The same precedence applies to conflicts. If dated availability entries define `kind: 'working'` for a resource/date and an event is not fully covered by those working intervals, the scheduler raises `outside-availability` from availability instead of checking the calendar. If no dated working availability applies, the calendar can raise `outside-availability`. Availability entries with `kind: 'blocked'`, `kind: 'holiday'`, or `kind: 'break'` raise `blocked-time` when events overlap them. Both conflict types can be made blocking from the conflicts configuration.
## Demo
The `event-scheduler-shift-week` demo includes a calendar selector with weekday, open, and training calendars. Switch between them to see closed hours, closed days, and holiday behavior update without changing the event source.
---
# Conflicts
URL: https://pro.rv-grid.com/guides/event-scheduler/scheduling-rules/conflicts/
Source: src/content/docs/guides/event-scheduler/scheduling-rules/conflicts.mdx
Description: Configure warning and blocking conflict rules for overlapping scheduler events and invalid mutations.
Conflict detection can mark overlaps, warn users, or block changes through the mutation lifecycle. This is also how you forbid events outside working hours: configure working time with `calendars` or `eventSchedulerAvailability`, then make the resulting `outside-availability` conflict blocking.
For a detailed explanation of how availability and calendar inputs become `outside-availability` and `blocked-time`, see the [Scheduling Rules overview](../).
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
conflicts: {
enabled: true,
policy: 'mark',
scope: 'same-resource',
minDurationMinutes: 30,
rules: {
overlap: 'error',
'outside-availability': 'error',
'blocked-time': 'error',
},
},
validateMutation: (detail) => {
const blocking = detail.conflicts?.some((conflict) => conflict.severity === 'error');
if (blocking) {
return 'This change creates a blocking scheduling conflict.';
}
},
};
```
`policy` sets the default behavior for all conflict types:
| Policy | Behavior |
| :-- | :-- |
| `mark` | Show conflicts as warnings and allow the mutation unless a specific rule is blocking. |
| `prevent` | Treat conflicts as blocking errors by default. |
| `allow` | Ignore conflicts unless a specific rule overrides the type. |
`rules` override the default policy per conflict type. Use `error` to reject the mutation, `warning` to mark it but allow it, `confirm` when your product wants a confirmation flow, and `ignore` to hide the conflict.
Common blocking rules:
```ts
conflicts: {
enabled: true,
policy: 'mark',
rules: {
overlap: 'error',
'outside-availability': 'error',
'blocked-time': 'error',
'invalid-duration': 'error',
},
}
```
`outside-availability` is raised when an event falls outside calendar working days/hours or outside explicit `kind: 'working'` availability intervals. `blocked-time` is raised when an event overlaps explicit blocked, holiday, or break availability. `nonWorkingTime` alone is visual and does not create blocking conflicts.
## Conflict Appearance
Conflict detection and conflict presentation are configured separately. Keep rules under `conflicts`, and choose how detected conflicts look under `customization.conflicts.appearance`:
```ts
grid.eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-08-17',
conflicts: {
enabled: true,
policy: 'mark',
scope: 'same-resource',
},
customization: {
conflicts: {
appearance: 'fill',
},
},
};
```
| Appearance | Behavior |
| :-- | :-- |
| `outline` | Keeps the existing conflict rings around the event. This is the default for backward compatibility. |
| `fill` | Uses the conflict severity background and text colors inside the event bar without the oversized conflict rings. |
Filled conflicts keep the warning or conflict state icon and the normal event shadow. Selection and keyboard focus indicators are unchanged, so `fill` removes only the conflict decoration rings—not accessibility or interaction feedback. The option works across scheduler views; it is especially useful in dense resource timelines where multiple outlines would consume lane space.
You can combine the built-in appearance with `customization.conflicts.className`, `properties`, `indicator`, and `tooltip` when a product needs additional presentation hooks. These hooks do not change whether a conflict blocks a mutation.
## Preventing Mutations
Conflict rules are the declarative way to stop invalid changes. When a conflict resolves to `severity: 'error'`, the scheduler rejects the mutation before it updates `source`.
For business rules that need code, use the same cancelable event pattern as RevoGrid editing. Listen to `event-scheduler-before-event-change` and call `preventDefault()` before the local commit:
```ts
grid.addEventListener('event-scheduler-before-event-change', (event) => {
const hasBlockingConflict = event.detail.conflicts?.some((conflict) =>
conflict.severity === 'error' &&
event.detail.eventId !== null &&
conflict.eventIds.includes(event.detail.eventId)
);
if (hasBlockingConflict || event.detail.event?.locked) {
event.preventDefault();
}
});
```
`validateMutation` is the config-level equivalent for synchronous validation. Return `false` or a string to reject the same pending mutation.
```ts
const eventScheduler = {
validateMutation: (detail) => {
if (detail.changes.resourceId === 'locked-room') {
return 'That room cannot accept bookings.';
}
},
};
```
Do not use `event-scheduler-event-changed` for prevention. `event-scheduler-event-created`, `event-scheduler-event-changed`, and `event-scheduler-event-deleted` fire after the scheduler has accepted the mutation; use those events to persist or sync the emitted `detail.events` array.
Listen to conflict updates for side panels, badges, filters, or exports.
```ts
grid.addEventListener('event-scheduler-conflicts-updated', (event) => {
renderConflictPanel(event.detail.conflicts);
});
```
Event blocks expose conflict classes and data attributes for custom styles.
---
# Templates and Recurrence
URL: https://pro.rv-grid.com/guides/event-scheduler/templates-recurrence/
Source: src/content/docs/guides/event-scheduler/templates-recurrence.mdx
Description: Create scheduler events from reusable templates and generate repeating shifts or bookings.
Templates define reusable event shapes. The plugin exposes helper methods for single events and recurring series.
```ts
grid.eventSchedulerTemplates = [
{
id: 'morning-shift',
title: 'Morning shift',
durationMinutes: 480,
status: 'planned',
type: 'shift',
color: '#2563eb',
},
];
const scheduler = (await grid.getPlugins()).find(
(plugin) => plugin.constructor?.name === 'EventSchedulerPlugin',
);
scheduler.createRecurringEvents({
templateId: 'morning-shift',
resourceId: 'emp-alex',
startDateTime: '2026-06-08T08:00:00.000Z',
recurrence: {
frequency: 'weekly',
weekdays: [1, 3, 5],
count: 6,
},
seriesId: 'alex-morning-series',
});
```
Generated events keep recurrence metadata so applications can present edit-series or delete-series workflows.
## Copy, Paste, Duplicate, and Bulk Helpers
Get the plugin instance to run imperative scheduler helpers:
```ts
const plugins = await grid.getPlugins();
const scheduler = plugins.find(
(plugin) => plugin.constructor?.name === 'EventSchedulerPlugin',
);
```
Create one event from a template:
```ts
scheduler?.createEventFromTemplate({
templateId: 'morning-shift',
startDateTime: '2026-06-08T08:00:00.000Z',
resourceId: 'team-a',
});
```
Copy a day or pattern into another date range:
```ts
scheduler?.duplicateScheduleRange({
sourceStartDateTime: '2026-06-08T00:00:00.000Z',
sourceEndDateTime: '2026-06-09T00:00:00.000Z',
targetStartDateTime: '2026-06-09T00:00:00.000Z',
});
```
Selection helpers support copy, paste, duplicate, bulk delete, and bulk status changes:
```ts
scheduler?.setSelectedEventIds(['shift-a', 'shift-b']);
scheduler?.copySelectedEvents();
scheduler?.pasteCopiedEvents({ startDateTime: '2026-06-10T08:00:00.000Z' });
scheduler?.bulkUpdateSelectedEventStatus('confirmed');
```
All helper actions run through the same permissions, conflict, before-change, local mutation, event emission, and remote mutation pipeline as pointer and editor interactions.
---
# Timeline Views
URL: https://pro.rv-grid.com/guides/event-scheduler/timeline-views/
Source: src/content/docs/guides/event-scheduler/timeline-views.mdx
Description: Configure day/week time grids, month/year calendars, and horizontal resource timelines.
Event Scheduler has three projection families: vertical `day` and `week` time grids, true `month` and `year` calendar layouts, and the horizontal `resourceTimeline`. All of them share the same events, resources, filtering, recurrence, conflicts, permissions, selection, history, remote loading, and mutation APIs. For the full grid bootstrap, start with [Getting Started](../introduction/).
## Framework bindings
React, Vue, and Angular wrappers bind the same grid props. In React, keep the plugin array stable and pass empty host rows and columns:
```tsx
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { EventSchedulerPlugin } from '@revolist/scheduler';
export function ResourceTimeline() {
const plugins = useMemo(() => [EventSchedulerPlugin], []);
return (
);
}
```
This example assumes `eventScheduler`, `events`, and `resources` are the same constants you would pass in Vanilla TS. Vue and Angular use the same prop names through their normal bindings. In Vue, use property bindings for direct grid properties, for example `:event-scheduler.prop="eventScheduler"`, `:source.prop="events"`, and `:event-scheduler-resources.prop="resources"`. In Angular, use `[eventScheduler]="eventScheduler"`, `[source]="events"`, and `[eventSchedulerResources]="resources"`.
## Choose a view
Use `day` and `week` when time should run vertically and days should be generated as columns. These views work well for shift calendars, appointment books, and detailed weekly planning.
Use `month` for a conventional seven-column calendar whose natural four to six week rows fill the available data viewport. Multi-day events are clipped into continuous week bars, adjacent-month dates remain visible, and dense days expose a `+N more` overlay. Use `year` for a responsive overview of twelve mini-months; choosing a month or day requests the detailed month view.
Use `resourceTimeline` when resources should be generated as rows and time slots should run horizontally. This layout is better for room booking, equipment planning, staff coverage, machine schedules, and custom multi-day resource timelines.
The main range controls are:
- `weekStartDate` anchors the active day, week, month, or year.
- `dateRange` sets an explicit visible range for timeline views. It does not reshape month/year calendars.
- `visibleDays` filters generated weekdays in time-grid/timeline views. Calendar month/year layouts keep seven weekday columns.
- `slotMinutes` controls visible slot granularity.
- `snapMinutes` controls drag, resize, paste, create, and keyboard snapping. When omitted, it follows the active `slotMinutes`, including zoom changes. An explicit value stays configured across zoom levels. Pointer move, resize, and create gestures cap their effective snap at the relevant rendered slot duration.
- `timeRange` limits the visible hours within each day.
### Nightly bookings across daylight-saving changes
Set `durationMode: 'calendarDays'` with the property's `timeZone` to preserve the
number of nights when moving or copying a stay. This applies to individual and
bulk moves, drag previews, duplicate, paste and copying a schedule range. Copying
multiple bookings also preserves their civil date/clock spacing.
```ts
const nightlyTimeline = {
view: 'resourceTimeline',
weekStartDate: '2026-03-20',
dateRange: { start: '2026-03-20', end: '2026-04-05' },
timeZone: 'Europe/Lisbon',
durationMode: 'calendarDays',
slotMinutes: 1440,
timeRange: { start: '00:00', end: '24:00' },
visibleDays: [0, 1, 2, 3, 4, 5, 6],
};
```
A three-night stay moved to March 28, 2026 still checks out at local midnight on
March 31, even though that stay spans 71 elapsed hours. Event inputs and outputs
remain instants; explicitly edited or resized boundaries remain authoritative.
The default `durationMode: 'elapsed'` preserves elapsed duration. Template/backlog
`durationMinutes` and duration-limit rules continue to use elapsed minutes.
The main layout controls are:
- `timeColumnSize` controls the pinned time column in `day` and `week` views.
- `dayColumnSize` controls day columns in `day` and `week`; month columns are distributed across the viewport.
- `resourceColumnSize` controls the pinned resource column in `resourceTimeline`.
- `timelineColumnSize` controls each horizontal timeline slot in `resourceTimeline`.
- `columnGrouping` enables or disables grouped scheduler headers.
- `timelineHeaderRows` defines one to three resource-timeline header rows from coarse to fine.
- `eventLayout`, `maxStackedEvents`, and `compactThreshold` control overlapping event presentation.
- `rowSize` sets the baseline scheduler row height.
- `resourceTimelineRowSizing` lets dense resource rows grow with their simultaneous event lanes.
```ts
const resourceDayTimeline = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
dateRange: { start: '2026-06-08', end: '2026-06-08' },
slotMinutes: 60,
timeRange: { start: '6 AM', end: '10 PM' },
resourceColumnSize: 220,
timelineColumnSize: 120,
};
const resourceWeekTimeline = {
...resourceDayTimeline,
dateRange: { start: '2026-06-08', end: '2026-06-14' },
customization: {
columnSizes: {
resourceTimeline: {
resourceColumnSize: 240,
timelineColumnSize: 132,
},
},
headers: {
timelineTemplate: (h, context) =>
h('span', { class: 'scheduler-slot-header' }, context.defaultLabel),
timelineProperties: (context) => ({
'data-scheduler-date': context.date,
}),
},
},
};
const customRange = {
...resourceDayTimeline,
dateRange: { start: '2026-06-08', end: '2026-06-10' },
slotMinutes: 30,
timelineColumnSize: 132,
};
```
For a practical vertical week setup, continue to [Week View](../week-view/). For raw configuration types, see the [Event Scheduler API reference](/api/event-scheduler/).
```ts
const monthCalendar = {
view: 'month',
weekStartDate: '2026-07-15',
weekStartsOn: 1,
};
const yearOverview = {
...monthCalendar,
view: 'year',
};
```
`month` now means this conventional calendar layout. Earlier releases projected it as a 31-column vertical time grid; applications that depended on that legacy geometry should use an explicit timeline range instead. There is no compatibility flag for the former rendering.
## Demo Sources
The examples keep two focused Event Scheduler demos:
| Demo id | Implementation |
| :-- | :-- |
| `event-scheduler-shift-week` | Shift planner with day, week, month, year, and resource views. |
| `equipment-machine-scheduler` | Dedicated seven-day equipment view with continuous multi-day runs and context-menu zoom. |
Useful source references:
- `examples/revogrid-demos/pro-advanced-scheduler/src/data.ts`
- `examples/revogrid-demos/pro-advanced-scheduler/src/scheduler.ts`
- `examples/components/src/components/event-scheduler/equipment-machine/base-config.ts`
- `examples/components/src/components/event-scheduler/equipment-machine/data.ts`
- `examples/components/src/components/event-scheduler/equipment-machine/index.ts`
## Column Widths
Scheduler column widths are controlled by the scheduler config and are applied when the view is projected into RevoGrid columns:
- `timeColumnSize` controls the pinned time column in day and week views.
- `dayColumnSize` controls time-grid day columns and supplies the initial calendar sizing hint.
- `resourceColumnSize` controls the pinned resource column in resource timeline views.
- `timelineColumnSize` controls each horizontal timeline slot in resource timeline day, week, and custom ranges.
Use wider `timelineColumnSize` values for custom ranges with fine-grained slots, such as 15-minute or 30-minute columns. Wider timeline slots improve header and event-label readability and naturally create more horizontal scroll space.
Use `customization.columnSizes` when the same scheduler instance switches modes and each mode needs a different width profile. Values under `customization.columnSizes[view]` take precedence over the legacy top-level width options:
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
columnSizes: {
week: {
timeColumnSize: 96,
dayColumnSize: 220,
},
resourceTimeline: {
resourceColumnSize: 260,
timelineColumnSize: 144,
},
},
},
};
```
## Timescale Zoom
Scheduler header context menus expose Zoom in and Zoom out when another configured level is available. Zoom changes the projected slot size without changing the configured `timeRange` or event dates. When `snapMinutes` is omitted, its effective value follows the active zoom slot, so zooming from 60-minute slots to 30-minute slots also changes interaction snapping from 60 to 30 minutes. An explicitly configured `snapMinutes` remains unchanged in the configuration across zoom levels. Pointer gestures clamp their effective snap to the active slot duration, so a configured 60-minute snap uses 15-minute steps when the active slots are 15 minutes. If a level does not divide the visible time range evenly, Scheduler keeps a shorter final slot so the exact range end remains visible.
Use the built-in levels by omitting `zoom`, provide product-specific levels, or disable zoom:
```ts
const eventScheduler = {
view: 'week',
weekStartDate: '2026-07-27',
slotMinutes: 30,
snapMinutes: 10,
timeRange: { start: '06:00', end: '17:00' },
zoom: {
levels: [15, 30, 45, 90, 120],
},
};
const fixedTimescale = {
...eventScheduler,
zoom: false,
};
```
The active configured `slotMinutes` is always retained as a zoom level. Invalid, duplicate, and out-of-range level values are ignored. Setting `zoom: false` or `zoom: { enabled: false }` removes the built-in zoom actions and makes the plugin's `canZoomIn()`, `canZoomOut()`, `zoomIn()`, and `zoomOut()` methods return `false`.
## Grouped Headers
Scheduler views use grouped headers by default so day, week, month, and resource timeline ranges can show broader date context above the leaf columns. Disable them when the extra header row is not useful for a compact embedded scheduler:
```ts
const eventScheduler = {
view: 'week',
weekStartDate: '2026-06-08',
columnGrouping: false,
};
```
When `columnGrouping` is `false`, the scheduler returns flat RevoGrid columns. Day/week/month views show only day leaf headers, and resource timeline views show only timeline slot leaf headers after the pinned resource column.
## Adaptive Resource Rows
Resource timelines can grow only the rows that need more vertical space. Enable `resourceTimelineRowSizing` when several events can overlap for the same resource and fixed-height lanes make their labels or status icons difficult to read:
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-08-17',
dateRange: { start: '2026-08-17', end: '2026-08-30' },
rowSize: 44,
resourceTimelineRowSizing: {
enabled: true,
minEventHeight: 24,
},
};
```
`rowSize` remains the minimum row height. Scheduler calculates the maximum number of simultaneously overlapping lanes for each visible resource and grows that row enough to keep every bar at least `minEventHeight` pixels high. It does not use the resource's total event count, so sequential events that reuse one lane do not make the row taller.
Adaptive sizing is disabled by default. Use `resourceTimelineRowSizing: true` for the default `24` pixel minimum, or provide an object when the product needs a different minimum. Empty, grouped, sparse, and non-stacked rows keep `rowSize`; dense rows shrink back when filtering, reassignment, deletion, or another committed update reduces their lane count. Day, week, month, and year views continue to use their normal row behavior.
Row sizes come from projected scheduler lanes rather than DOM measurement, so vertical virtualization remains available. During a live drag or resize preview, row heights stay stable to avoid moving the pointer target; Scheduler recalculates them after the change is committed.
### Continuous multi-day bars
In `resourceTimeline`, an event spanning several dates is rendered as one continuous horizontal bar. Its top and bottom borders remain solid, its outer corners remain rounded, and it does not receive the dashed clipped-edge treatment used by vertically clipped day/week events. Continuation metadata is still retained for customization and accessibility.
Compact titles and state icons stay vertically centered when adaptive rows use the minimum event height. If a title is too wide, normal single-line ellipsis behavior still applies.
### Mode-aware resource timeline rows
Resource timelines use compact, scale-aware defaults based on the number of leaf columns per date. When a date spans multiple time columns, Scheduler combines its localized weekday, month, day, and year into one `day / time` hierarchy, such as `Thu, Dec 4, 2025` above hourly labels. When each date owns one column, it keeps separate `month / day / time` tiers so the shorter labels fit. Day-sized slots use `month / day`. A product can still define the hierarchy per mode, similar to a Gantt timescale:
```ts
const overviewMode = {
slotMinutes: 360,
timelineColumnSize: 64,
timelineHeaderRows: [
{ id: 'day', unit: 'day' },
{ id: 'time', unit: 'time' },
],
};
const planningMode = {
slotMinutes: 180,
timelineColumnSize: 76,
timelineHeaderRows: [
{ id: 'month', unit: 'month' },
{
id: 'day',
unit: 'day',
formatter: ({ defaultLabel }) => defaultLabel.toUpperCase(),
},
{ id: 'time', unit: 'time' },
],
};
```
Rows must be ordered from the broadest unit to the leaf unit. Sub-day scales end in `time`; a `1440`-minute scale ends in `day`. Each row accepts its own formatter, while the existing `timelineHeaderFormatter` remains a compatible shortcut for the leaf row.
Header customization hooks map to RevoGrid column templates and properties. Returning `null` or `undefined` from a template keeps the built-in header content:
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-08',
customization: {
headers: {
groupTemplate: (h, context) =>
h('strong', { class: 'scheduler-date-group' }, context.defaultLabel),
timelineProperties: (context) => ({
class: 'scheduler-slot-header',
'data-slot-start': context.startDateTime,
}),
},
},
};
```
## Daily booking summary footer
For nightly property bookings, set `timelineSummary` in a `resourceTimeline` view with `slotMinutes: 1440`. Native pinned-bottom cells share the timeline's scrolling and column widths. Other views and sub-day scales hide the summary.
```ts
const eventScheduler = {
view: 'resourceTimeline',
weekStartDate: '2026-06-01',
slotMinutes: 1440,
timeRange: { start: '00:00', end: '24:00' },
timelineSummary: {
rowSize: 32,
rows: [
{ id: 'properties', label: 'Booked properties', aggregate: 'bookedResources' },
{
id: 'guests',
label: 'Guests',
aggregate: ({ events }) => events.reduce(
(total, event) => total + Number(event.metadata?.guests ?? 0), 0,
),
},
],
},
};
```
Labels are application-owned and can be localized. Custom aggregates receive `date`, `startDateTime`, `endDateTime`, scoped `resources`, overlapping `events`, and distinct `bookedResourceIds`. Each booking appears once even when assigned to several properties. An optional `formatter(value, context)` returns plain text.
Set `timelineSummary.resourceIds` to your selected property IDs to show selected-property totals. Omit it to include all filtered properties; pass `[]` for an empty selection. This scope is independent of selected booking bars. Update the config when the property selection changes.
Totals use loaded, filtered data, including collapsed and offscreen properties. They count distinct booked properties and use half-open day intervals in the configured time zone, so checkout is excluded and daylight-saving changes preserve calendar-day boundaries. For remote data, load the complete booking scope needed for the totals. Guest counts come from your booking metadata through the callback; property capacity is unrelated.
Existing pinned-bottom rows remain above the summary and are restored when `timelineSummary` is `false`, the view changes, or Scheduler is removed.
## Troubleshooting
- No rows or events render: check that `grid.plugins` includes `EventSchedulerPlugin`, `eventScheduler` is set, `source` is populated, and each event's `resourceId` matches an item in `eventSchedulerResources` for resource timelines.
- Events render but cannot be created, moved, or resized: enable `editable` and the needed `allowCreate`, `allowMove`, or `allowResize` flags. Permission callbacks, locked resources, and locked event statuses can still block a specific action.
- Custom `source` or `columns` disappear: this is expected while `eventScheduler` is active. The plugin projects scheduler rows and columns into RevoGrid and restores the host grid state when the scheduler config is removed.
- Range or slot labels look wrong: verify ISO `YYYY-MM-DD` dates, `dateRange`, `timeRange`, `slotMinutes`, and any `timelineHeaderFormatter` or `customization.headers` hooks.
---
# Week View
URL: https://pro.rv-grid.com/guides/event-scheduler/week-view/
Source: src/content/docs/guides/event-scheduler/week-view.mdx
Description: Build a practical calendar-style week scheduler for shifts, appointments, and short-range planning.
Use `view: 'week'` when users think in days and hours: employee shifts, appointment books, clinic rosters, classroom timetables, service windows, and team calendars. Time runs vertically, days are generated as columns, and event duration is shown by event height.
This page focuses on a vertical week scheduler. For the general setup, props, and data model, start with [Getting Started](../introduction/). For choosing between vertical time and horizontal resource rows, see [Vertical vs Horizontal Time](../introduction/vertical-vs-horizontal/). For `day`, `month`, `resourceTimeline`, column widths, and grouped headers, see [Timeline Views](../timeline-views/).
## Build a Week Planner
1. Configure the visible week.
```ts
import {
EventSchedulerPlugin,
type EventSchedulerConfig,
type EventSchedulerEventEntity,
type EventSchedulerResourceEntity,
} from '@revolist/scheduler';
const eventScheduler: EventSchedulerConfig = {
view: 'week',
weekStartDate: '2026-06-08',
weekStartsOn: 1,
locale: 'en-US',
timeZone: 'UTC',
visibleDays: [1, 2, 3, 4, 5],
slotMinutes: 30,
snapMinutes: 15,
timeRange: { start: '06:00', end: '22:00' },
timeColumnSize: 88,
dayColumnSize: 190,
editable: true,
allowCreate: true,
allowMove: true,
allowResize: true,
allowDelete: true,
};
```
`slotMinutes` controls visible row granularity and has a 15-minute minimum. Use values such as `15`, `30`, or `60` minutes depending on the planning detail your product needs. `snapMinutes` controls create, drag, resize, paste, and keyboard snapping. When omitted, it follows the active slot size as the Scheduler zooms. Set it explicitly when editing precision should remain fixed across zoom levels; for example, display 30-minute rows while allowing 5-minute edits.
`timeRange` controls the visible start/end time, so night hours can be hidden instead of rendered. `rowSize` controls density for compact, dense, or comfortable schedules, and `timeLabelFormatter` / `timeLabelTemplate` can replace the default `06:00` style labels.
`locale` and `timeZone` control built-in day and range labels. Use `dayHeaderFormatter`, `timeLabelFormatter`, and `timelineHeaderFormatter` when your product needs a fully custom display format.
2. Provide the events and optional resources.
```ts
const resources: EventSchedulerResourceEntity[] = [
{ id: 'alex', name: 'Alex Kim', role: 'Coordinator', group: 'Front desk' },
{ id: 'maria', name: 'Maria Cruz', role: 'Nurse', group: 'Clinical' },
];
const events: EventSchedulerEventEntity[] = [
{
id: 'shift-alex-mon',
resourceId: 'alex',
title: 'Front desk',
startDateTime: '2026-06-08T08:00:00.000Z',
endDateTime: '2026-06-08T14:00:00.000Z',
status: 'confirmed',
type: 'shift',
},
{
id: 'shift-maria-mon',
resourceId: 'maria',
title: 'Clinical floor',
startDateTime: '2026-06-08T10:00:00.000Z',
endDateTime: '2026-06-08T18:00:00.000Z',
status: 'planned',
type: 'shift',
},
];
```
In week view, resources do not become rows; days and time slots define the grid. Resource ids still matter for ownership, permissions, conflicts, calendars, availability, filters, exports, and later switching the same data to `resourceTimeline`.
3. Attach the scheduler to an empty grid.
```ts
const grid = document.querySelector('revo-grid')!;
grid.plugins = [EventSchedulerPlugin];
grid.eventScheduler = eventScheduler;
grid.eventSchedulerResources = resources;
grid.source = events;
```
## Keep Edits Controlled
The scheduler updates `grid.source` and emits the next event array after accepted create, move, resize, edit, delete, template, paste, recurrence, and bulk actions. Persist that array and use it to update framework-owned state.
```ts
function syncEvents(event: CustomEvent<{ events: readonly EventSchedulerEventEntity[] }>) {
void saveSchedule(event.detail.events);
}
grid.addEventListener('event-scheduler-event-created', syncEvents as EventListener);
grid.addEventListener('event-scheduler-event-changed', syncEvents as EventListener);
grid.addEventListener('event-scheduler-event-deleted', syncEvents as EventListener);
```
Use `event-scheduler-before-event-change`, `validateMutation`, or permission hooks when your product needs to reject a pending change before it is committed. The full lifecycle is covered in [Editing and Interaction](../event-editing/).
## Block Invalid Hours
To make a week planner practical, define working time with calendars or availability and make the resulting conflicts blocking.
```ts
grid.eventScheduler = {
...eventScheduler,
calendars: {
primaryCalendarId: 'weekday',
calendars: [{
id: 'weekday',
name: 'Weekday shifts',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
workingHours: { start: '08:00', end: '18:00' },
holidays: ['2026-06-19'],
hoursPerDay: 8,
}],
},
conflicts: {
enabled: true,
policy: 'mark',
rules: {
'outside-availability': 'error',
'blocked-time': 'error',
overlap: 'warning',
},
},
};
```
Calendars are the recurring baseline. Use `eventSchedulerAvailability` for dated changes such as PTO, lunch breaks, holiday closures, maintenance, or temporary extra coverage. `nonWorkingTime` is useful for visual closed-cell styling, but it does not block edits by itself. The precedence model is covered in [Scheduling Rules](../scheduling-rules/).
## Switch the Visible Week
Keep `weekStartDate`, `visibleDays`, and `timeRange` in application state when the host toolbar owns navigation.
```ts
function setScheduler(next: Partial) {
grid.eventScheduler = {
...grid.eventScheduler,
...next,
};
}
grid.addEventListener('event-scheduler-navigate-request', (event) => {
const { action } = (event as CustomEvent<{ action: 'previous' | 'next' | 'today' }>).detail;
setScheduler({ weekStartDate: resolveNextWeekStart(grid.eventScheduler.weekStartDate, action) });
});
grid.addEventListener('event-scheduler-view-request', (event) => {
const { view } = (event as CustomEvent<{ view: EventSchedulerConfig['view'] }>).detail;
setScheduler({ view });
});
```
Use `day` for a focused daily schedule, `week` for the main shift-planning workflow, and `month` for a higher-level calendar. Use `resourceTimeline` when resources need their own rows and users compare capacity horizontally.
## Production Checklist
- Use ISO datetime strings and stable event ids.
- Decide whether `weekStartsOn` and `visibleDays` follow locale, business rules, or user settings.
- Put canonical events in `source`; keep `columns` empty while the scheduler owns the visual projection.
- Enable only the edit permissions your persistence flow can handle.
- Configure calendars or availability before turning outside-hours conflicts into blocking errors.
- Add resources even in week view when conflicts, permissions, reporting, or future resource timelines need them.
- Use the Employee Shift Planner demo as a focused week-view reference.
## Next Pages
- [Timeline Views](../timeline-views/) covers all scheduler views, date ranges, column widths, framework bindings, grouped headers, and demo source locations.
- [Editing and Interaction](../event-editing/) covers host-owned persistence, mutation events, validation, permissions, selection, clipboard, keyboard shortcuts, and editor replacement.
- [Scheduling Rules](../scheduling-rules/) covers calendar, availability, non-working time, and conflict-rule precedence.
- [Grouping](../grouping/) covers resource timelines, unassigned demand, coverage, utilization, filters, and saved views.
- [Templates and Recurrence](../templates-recurrence/) covers template creation, recurrence, duplicate, paste, selection, and bulk helpers.
- [Remote Data](../remote-data/) covers server-backed loading, optimistic saves, refresh, paging, and remote state events.
- [Customization](../customization/) covers the full visual customization category. Start with [Labels and Formatters](../customization/labels-formatters/), [Event Rendering](../customization/events/), and [Layout, Headers, and Cells](../customization/layout/).
- [Examples](../examples/) has compact weekly scheduler examples for custom event bars, day headers, current-time markers, closed hours, read-only mode, cancelable selection state, and themes.
- [Export](../export/) covers export helpers and production readiness.
- [Event Scheduler API reference](/api/event-scheduler/) lists raw config, hook, and event types.
---
# Excel Export
URL: https://pro.rv-grid.com/guides/excel-export/
Source: src/content/docs/guides/excel-export/index.mdx
Description: Export XLSX files and import CSV data with RevoGrid Pro.
RevoGrid Pro exports grid data to `.xlsx` workbooks through `ExportExcelPlugin`. The same plugin also keeps the historical CSV drag-and-drop import path, so existing integrations can keep using the `ExportExcelPlugin`, `grid.exportExcel`, and `export-excel` event names.
Use this page for the quick setup. Provider adapters, cell formatting, CSV import, and migration details are split into focused pages linked below.
## Quick Setup
Attach `ExportExcelPlugin` with your grid plugins:
```ts
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ExportExcelPlugin];
```
Trigger export through the event API:
```ts
grid.dispatchEvent(new CustomEvent('export-excel', {
detail: {
workbookName: 'orders.xlsx',
sheetName: 'Orders',
},
}));
```
Or call the plugin instance directly:
```ts
const plugins = await grid.getPlugins();
const excel = plugins.find((plugin) => plugin instanceof ExportExcelPlugin);
await excel?.export({
workbookName: 'orders.xlsx',
sheetName: 'Orders',
});
```
## Default Behavior
- The bundled provider is `write-excel-file` and exports `.xlsx` files only.
- Extensionless `workbookName` values receive `.xlsx`.
- `sheetName` is normalized for Excel worksheet rules.
- Export reads grid data stores, not rendered DOM rows, so virtualized offscreen rows are included.
- Body export follows the store's visible row projection, preserving current sort and filter order and the expanded/collapsed grouping view.
- Row order is pinned top rows, body rows, then pinned bottom rows.
- Columns follow `providers.column.getColumns('all')`.
- Grid-derived widths and freeze panes are inferred by default from column sizes, `colPinStart` columns, and pinned-top rows.
Direct export intentionally fails while `InfinityScrollPlugin` owns the row source because the browser holds only a moving window of remote rows. Fetch the complete dataset from your backend and export it from a temporary grid with a local source. See [Exporting to Excel with Infinity Scroll](/guides/infinity-scroll/#exporting-to-excel).
## Formatted And Grouped Exports
When `DataGridFormattingPlugin` and `ExportExcelPlugin` are active together,
Format Cells settings are transferred automatically to the workbook. This is
also the normal setup when `DataGridContextMenuPlugin` supplies Format Cells:
```ts
import {
DataGridContextMenuPlugin,
ExportExcelPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [DataGridContextMenuPlugin, ExportExcelPlugin];
```
The export keeps raw numeric and date values as workbook values and attaches
native Excel number formats for Number, Currency, Accounting, Percentage,
Scientific, Date, Time, Date & time, and Plain text presets. Excel can therefore
continue to calculate, sort, and edit those cells. Font family and size,
emphasis, text and fill colors, alignment, wrapping, and borders are mapped to
workbook cell styles.
Row grouping is exported from the visible provider projection. Group records
become merged, indented group-heading rows, while expanded descendants retain
their displayed order and descendants hidden by a collapsed group are omitted.
`context.exportMetadata.rowGrouping` also exposes each exported group row's
worksheet row, depth, label, and expanded state to custom providers.
Format Cells state is owned by the current `DataGridFormattingPlugin` instance.
Export it before destroying that plugin or the grid. Formatting follows
physical source coordinates, so replacing row objects at the same indexes does
not require identity configuration.
## Focused Guides
- [Export Providers](./providers/) explains provider precedence, default `.xlsx` limits, `providerOptions`, `deriveGridOptions`, optional ExcelJS and SheetJS adapters, workbook-module adapters, and custom providers.
- [Cell Formatting](./cell-formatting/) explains automatic Format Cells transfer, `column.excelExport`, formula/date/numeral/select helpers, grouped rows and headers, cell merge export support, and the provider context matrix.
- [CSV Import](./csv-import/) explains CSV-only drag/drop import, `allowDrag`, `allowedExtensions`, `skipColumnGeneration`, events, and plugin import helpers.
- [Migrations](/guides/migrations/) explains the SheetJS removal, CSV-only default import, app-owned XLSX import, and provider migration paths as a breaking change from `2.0.8`.
## Related Export Guides
- [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/) compares SheetJS, ExcelJS, read-excel-file, and write-excel-file for app-owned workbook workflows.
- [Gantt Grid, Export, Rendering](/guides/gantt/features/grid-export-rendering/) covers Gantt task-row export mapping and reporting recipes.
- [Pivot Export And State](/guides/pivot/concepts/export-state/) covers Pivot CSV, TSV, and state JSON helpers.
- [Export Excel API](/api/export-excel/) is the generated API reference for the complete public type surface.
---
# Cell Formatting
URL: https://pro.rv-grid.com/guides/excel-export/cell-formatting/
Source: src/content/docs/guides/excel-export/cell-formatting.mdx
Description: Format Excel export cells, formulas, column types, grouped headers, and merge spans.
Use `column.excelExport` when workbook output needs values or styles that differ from the rendered grid. Export formatting is data-source based and runs during export only.
When `DataGridFormattingPlugin` is also active, formatting applied through the
Format Cells UI is transferred automatically after column export callbacks.
The callback result remains the base cell configuration; explicitly stored
Format Cells value and appearance fields override only their corresponding
workbook fields. See [Format Cells](/guides/data-grid/format-cells/) for presets,
selection scope, and stable row identity.
## Column Export Callbacks
```ts
import type { ColumnRegular } from '@revolist/revogrid';
const columns: ColumnRegular[] = [
{
prop: 'price',
name: 'Price',
excelExport: {
columnProperties: ({ value }) => ({
value,
fontWeight: 'bold',
backgroundColor: 'E8EEF9',
}),
cellProperties: ({ value, rowType }) => ({
value,
type: Number,
format: '$#,##0.00',
align: 'right',
fontWeight: rowType === 'rowPinEnd' ? 'bold' : undefined,
}),
},
},
];
```
`columnProperties` formats the generated header cell. `cellProperties` formats body, pinned-top, and pinned-bottom cells.
`cellProperties` receives:
| Field | Meaning |
| --- | --- |
| `value` | Prepared export value after formula evaluation |
| `rawValue` | Original source value for the column |
| `model` | Original source row object |
| `rowIndex` | Index inside its source row segment |
| `rowType` | `rowPinStart`, `rgRow`, or `rowPinEnd` |
| `column` | Current column definition |
| `columnIndex` | Export column index |
| `revogrid` | Grid element |
| `providers` | Runtime plugin providers |
Callbacks can return:
- a primitive value to replace the exported value
- a cell object with `value` and style fields
- a style-only object, which keeps the prepared value
- `undefined`, which keeps the default prepared cell
## Style Fields
Common workbook style fields include:
| Field | Example |
| --- | --- |
| `fontFamily`, `fontSize`, `fontWeight`, `fontStyle` | text styling |
| `textColor`, `backgroundColor`, `fillPatternColor` | colors |
| `borderColor`, `leftBorderColor`, `rightBorderColor`, `topBorderColor`, `bottomBorderColor` | borders |
| `align`, `alignVertical`, `wrap`, `textRotation`, `indent` | alignment |
| `format` | number/date format |
| `height`, `columnSpan`, `rowSpan` | layout |
The bundled `write-excel-file` provider accepts `#E8EEF9` and bare RGB hex values such as `E8EEF9` for known color fields. Bare colors are prefixed before the provider calls `write-excel-file`.
Optional providers map this structural shape to their own workbook library. Unsupported fields may be ignored by a custom provider.
## Formula Export
Formula cells are evaluated by default. To preserve workbook formulas, use the final export-context transformer:
```ts
import { createFormulaExcelExportTransformer } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportTransformers: [
createFormulaExcelExportTransformer({
includeEvaluatedResult: true,
}),
],
};
```
Because this transformer runs after the final visible row projection, generated headers, grouping, merges, and Format Cells integration, it translates grid A1 references to their actual worksheet row and column coordinates. If filtering or collapsed groups remove a referenced row or range, the transformer safely keeps the evaluated scalar instead of emitting a formula that points at the wrong record. Formula names are expanded by default; set `expandFormulaNames: false` only when your provider creates matching workbook-defined names.
When `includeEvaluatedResult` is enabled, providers that support cached formula results receive both the translated formula and the prepared result. The lower-level `createFormulaExcelExportCellProperties()` helper preserves a raw formula string but cannot remap references after rows or columns move, so reserve it for layouts where exported coordinates are guaranteed to match the source grid exactly.
## External Column Type Helpers
Date, numeral, and select column packages are optional app dependencies, so Pro exposes explicit Excel export helpers instead of importing those packages automatically:
```ts
import {
createDateExcelExportColumnOptions,
createNumeralExcelExportColumnOptions,
createSelectExcelExportColumnOptions,
} from '@revolist/revogrid-pro';
const columns = [
{
prop: 'createdAt',
name: 'Created',
columnType: 'date',
excelExport: createDateExcelExportColumnOptions({ format: 'yyyy-mm-dd' }),
},
{
prop: 'amount',
name: 'Amount',
columnType: 'currency',
excelExport: createNumeralExcelExportColumnOptions({ format: '$#,##0.00' }),
},
{
prop: 'status',
name: 'Status',
columnType: 'select',
source: [
{ value: 'open', label: 'Open' },
{ value: 'closed', label: 'Closed' },
],
labelKey: 'label',
valueKey: 'value',
excelExport: createSelectExcelExportColumnOptions({
column: {
prop: 'status',
source: [
{ value: 'open', label: 'Open' },
{ value: 'closed', label: 'Closed' },
],
labelKey: 'label',
valueKey: 'value',
},
}),
},
];
```
Date helpers convert valid date-like values to `Date` cells. Numeral helpers convert finite numbers and numeric strings to number cells. Select helpers export the user-facing label for stored values.
## Grouped Rows, Grouped Headers, And Cell Merge
Row grouping follows the body store's visible item projection. Exported group
records become merged, indented heading rows, expanded descendants keep their
visible order, and descendants hidden by a collapsed group are omitted. Custom
providers can inspect `context.exportMetadata.rowGrouping` for exported
worksheet row numbers, group depth, labels, and expanded state.
Grouped column headers are applied automatically when the grid uses a grouped `columns` tree. Group header rows are inserted before the flattened leaf header row.
Cell merge spans are applied automatically when the grid has `cellMerge` data. The export transform maps grid row and column segment coordinates into provider matrix coordinates, accounting for generated header rows.
The built-in `write-excel-file` provider and ExcelJS adapter consume `columnSpan` and `rowSpan` values from `cellRows`. Custom providers can also read merge metadata from `context.exportMetadata.cellMerge`.
## Provider Context
Custom providers and transformers receive a prepared `ExcelExportProviderContext`:
| Field | Purpose |
| --- | --- |
| `cellRows` | Provider-ready matrix with header rows and formatted cells |
| `rows` | Plain row objects keyed by column prop, with formulas evaluated |
| `sourceRows` | Visible original row models with physical source index and row segment metadata |
| `headerRowsCount` | Number of generated header rows before data rows in `cellRows` |
| `gridDerivedOptions` | Width and freeze-pane hints inferred from current grid state |
| `exportMetadata` | Feature metadata such as row grouping, grouped headers, and cell merge ranges |
Use `cellRows` for formatted workbook output. Use `rows` for simple JSON-style providers that do not need formatting.
## Export Transformers
Use `exportTransformers` for final app-owned changes to the provider context:
```ts
grid.exportExcel = {
exportTransformers: [
(context) => ({
exportMetadata: {
generatedBy: 'orders-report',
},
providerOptions: {
hideGridLines: true,
},
}),
],
};
```
Grid-level transformers run before per-export transformers:
```ts
await excel.export({
exportTransformers: [
(context) => {
context.cellRows[0][0] = { value: 'Exported Orders', fontWeight: 'bold' };
},
],
});
```
---
# CSV Import
URL: https://pro.rv-grid.com/guides/excel-export/csv-import/
Source: src/content/docs/guides/excel-export/csv-import.mdx
Description: Import CSV files with ExportExcelPlugin drag/drop, events, and explicit import options.
`ExportExcelPlugin` imports CSV files by default. `.xlsx` and `.xls` parsing is app-owned; use a parser such as SheetJS, ExcelJS, or read-excel-file and assign normalized rows to the grid yourself.
For app-owned XLSX parsing choices, see [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/).
## Drag And Drop
By default, users can drop a `.csv` file onto the grid:
Download sample CSV
Download the sample, then drag it onto the grid to verify the import flow.
Control drag/drop import with `grid.exportExcel`:
```ts
grid.exportExcel = {
allowDrag: true,
allowedExtensions: ['.csv'],
};
```
The same options are still accepted through legacy `additionalData.excel`, but new code should prefer `grid.exportExcel`.
## Import Options
| Option | Default | Description |
| --- | --- | --- |
| `allowDrag` | `true` | Enables CSV drag/drop import unless the grid is readonly |
| `allowedExtensions` | `['.csv']` | Case-insensitive filename suffixes accepted in addition to files whose MIME type is `text/csv` |
| `skipColumnGeneration` | `false` | Keeps current grid columns and maps CSV cells by current column order |
When `skipColumnGeneration` is `false`, the first CSV row becomes generated grid columns:
```csv
Name,Status
Ada,Open
Grace,Closed
```
The imported grid receives columns similar to:
```ts
[
{ prop: 0, name: 'Name' },
{ prop: 1, name: 'Status' },
]
```
When `skipColumnGeneration` is `true`, all CSV rows are imported as data and mapped to the existing export column order:
```ts
grid.columns = [
{ prop: 'name', name: 'Name' },
{ prop: 'status', name: 'Status' },
];
await excel.importFile(file, {
skipColumnGeneration: true,
});
```
## Plugin Methods
Use `importFile()` when import is triggered by an app-owned upload control:
```ts
const plugins = await grid.getPlugins();
const excel = plugins.find((plugin) => plugin instanceof ExportExcelPlugin);
await excel?.importFile(file, {
allowedExtensions: ['.csv', '.txt'],
skipColumnGeneration: true,
});
```
Use `isCsv()` to validate a file before import:
```ts
if (excel?.isCsv(file, ['.csv'])) {
await excel.importFile(file);
}
```
`isExcel()` remains as a deprecated compatibility alias. It is CSV-only and returns `false` for `.xlsx` files unless the caller explicitly allows that extension. It does not parse workbook files.
Acceptance is MIME-or-suffix based: `isCsvFile()` returns `true` when `file.type` is `text/csv` **or** the lower-cased filename ends with one of `allowedExtensions`. The configured suffixes are not a strict allowlist for files already identified with the CSV MIME type. Adding `.txt` or `.xlsx` only permits that filename suffix to enter the CSV text parser; it does not add a new parser.
## Import Events
Before applying imported rows, the plugin dispatches CSV-oriented events:
| Event | Detail |
| --- | --- |
| `excel-before-import` | `file`, `sheetName`, `csv`, and parsed `rows` |
| `excel-before-set` | All import detail plus mapped `jsonData` |
`sheetName` is a compatibility label and contains the filename.
Prevent either event to cancel replacing the grid source:
```ts
grid.addEventListener('excel-before-set', (event) => {
const { jsonData } = event.detail;
if (!jsonData.length) {
event.preventDefault();
}
});
```
## Parser Behavior
The default importer reads files with `FileReader.readAsText` and parses CSV values. It handles BOMs, CRLF/LF line endings, quoted values, escaped quotes, empty cells, and trailing blank lines.
When a file matches neither the CSV MIME type nor an allowed suffix, the plugin logs `Only CSV files are supported.` and returns without changing the grid. File-reading or parsing failures are caught, logged as `Failed to load CSV file:`, and also resolve `importFile()` without throwing. If your UI needs a rejected promise or a user-visible error state, validate and read the file in app code before calling the plugin. If the grid is readonly or `allowDrag` is `false`, drag/drop import does not activate.
For `.xlsx` uploads, parse the workbook outside the plugin and assign normalized data:
```ts
const { columns, rows } = await parseWorkbook(file);
grid.columns = columns;
grid.source = rows;
```
---
# Excel Import and Export Libraries
URL: https://pro.rv-grid.com/guides/excel-export/excel-import-export-libraries/
Source: src/content/docs/guides/excel-export/excel-import-export-libraries.mdx
Description: Compare SheetJS, ExcelJS, read-excel-file, and write-excel-file for XLSX import/export workflows with RevoGrid.
RevoGrid Pro includes an Excel export plugin, but most production apps still need to choose how uploaded workbook files are parsed, validated, normalized, and streamed into the grid.
Use this guide when your workflow is:
1. User uploads an Excel file.
2. Your app parses `.xlsx` data.
3. Your app converts the first sheet, selected sheet, or validated sheet into JSON.
4. RevoGrid receives normalized `columns` and `source` data.
The bundled `ExportExcelPlugin` exports `.xlsx` files through `write-excel-file` and imports CSV files by default. XLSX import is app-owned: install the parser you want, parse the workbook, then assign normalized rows to RevoGrid. For plugin options, see the [Excel export guide](/guides/excel-export/).
## Quick Recommendation
| Goal | Recommended library | Why |
| --- | --- | --- |
| Fast `.xlsx` upload to grid JSON | SheetJS | Broad format support, direct `sheet_to_json()` conversion, strong compatibility with real-world workbooks. |
| Full workbook editing or styled report generation | ExcelJS | Good API for worksheets, rows, formatting, formulas, tables, merged cells, and generated reports. |
| Simple validated upload forms | read-excel-file | Small API surface with schema-based parsing and validation. |
| Default RevoGrid Pro `.xlsx` export | write-excel-file | Lightweight writer used by the bundled export provider. |
| Huge browser imports | SheetJS or read-excel-file in a Web Worker | Keep parse work away from the UI thread and load rows into RevoGrid incrementally. |
| Backend import/export processing | SheetJS or ExcelJS | Pick SheetJS for broad import compatibility; pick ExcelJS when workbook editing and formatting matter more. |
## SheetJS
[SheetJS](https://docs.sheetjs.com/) is the strongest general-purpose choice for Excel upload workflows. It works in the browser and Node.js, reads workbook bytes from an `ArrayBuffer`, supports common spreadsheet formats, and converts worksheets directly into JSON.
Use it when the import goal is "Excel file in, JSON rows out".
```sh
pnpm add xlsx
```
```ts
import * as XLSX from 'xlsx';
import type { ColumnRegular, DataType } from '@revolist/revogrid';
export async function parseSheetJsFile(file: File): Promise<{
columns: ColumnRegular[];
rows: DataType[];
}> {
const data = await file.arrayBuffer();
const workbook = XLSX.read(data, { cellDates: true });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json>(worksheet, {
defval: '',
});
const columns = Object.keys(rows[0] ?? {}).map((key) => ({
prop: key,
name: key,
}));
return { columns, rows };
}
```
```ts
const { columns, rows } = await parseSheetJsFile(file);
grid.columns = columns;
grid.source = rows;
```
SheetJS is usually the best RevoGrid import fit for admin panels, ERP screens, analytics tools, and customer-provided enterprise spreadsheets where file shape is not perfectly controlled.
The tradeoff is that parsing large files on the main browser thread can freeze the UI. For large files, parse in a Web Worker and only send normalized data back to the page.
```ts title="xlsx-import.worker.ts"
import * as XLSX from 'xlsx';
self.onmessage = async (event: MessageEvent) => {
const workbook = XLSX.read(await event.data.arrayBuffer(), { cellDates: true });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = XLSX.utils.sheet_to_json>(worksheet, {
defval: '',
});
const columns = Object.keys(rows[0] ?? {}).map((key) => ({
prop: key,
name: key,
}));
self.postMessage({ columns, rows });
};
```
```ts title="import-ui.ts"
const worker = new Worker(new URL('./xlsx-import.worker.ts', import.meta.url), {
type: 'module',
});
worker.onmessage = ({ data }) => {
grid.columns = data.columns;
grid.source = data.rows;
};
worker.postMessage(file);
```
## ExcelJS
[ExcelJS](https://github.com/exceljs/exceljs) is better when you need to read, edit, preserve, or generate richer workbook structures. It is a good fit for financial reports, styled exports, workbook templates, formulas, tables, and merged-cell workflows.
Use it when the app needs workbook manipulation, not only raw import speed.
```sh
pnpm add exceljs
```
```ts
import ExcelJS from 'exceljs';
import type { ColumnRegular, DataType } from '@revolist/revogrid';
export async function parseExcelJsFile(file: File): Promise<{
columns: ColumnRegular[];
rows: DataType[];
}> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(await file.arrayBuffer());
const worksheet = workbook.worksheets[0];
const headerRow = worksheet.getRow(1);
const headerValues = Array.isArray(headerRow.values)
? headerRow.values.slice(1)
: Object.values(headerRow.values);
const headers = headerValues
.map((value) => String(value ?? '').trim())
.filter(Boolean);
const columns = headers.map((header) => ({
prop: header,
name: header,
}));
const rows: DataType[] = [];
worksheet.eachRow((row, rowNumber) => {
if (rowNumber === 1) {
return;
}
const model: DataType = {};
headers.forEach((header, index) => {
model[header] = row.getCell(index + 1).value ?? '';
});
rows.push(model);
});
return { columns, rows };
}
```
ExcelJS can also be used as an optional RevoGrid export provider when you need workbook features beyond the bundled provider.
```ts
import ExcelJS from 'exceljs';
import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createExcelJsExcelExportProvider(ExcelJS),
};
```
The tradeoff is size and speed. ExcelJS is usually heavier than SheetJS for pure "parse and convert to JSON" imports.
## read-excel-file
[read-excel-file](https://www.npmjs.com/package/read-excel-file) is the best fit when uploads are simple and validation matters more than workbook feature coverage. It reads `.xlsx` files in the browser or Node.js and can map columns into typed JSON with a schema.
```sh
pnpm add read-excel-file
```
```ts
import readXlsxFile from 'read-excel-file';
const schema = {
name: {
column: 'Name',
type: String,
required: true,
},
age: {
column: 'Age',
type: Number,
},
};
const { rows, errors } = await readXlsxFile(file, { schema });
if (errors.length) {
throw new Error(`Invalid spreadsheet: ${errors[0].error}`);
}
grid.columns = [
{ prop: 'name', name: 'Name' },
{ prop: 'age', name: 'Age' },
];
grid.source = rows;
```
This is a strong choice for import forms, onboarding flows, and ETL-style screens where the workbook is really a typed table.
The tradeoff is that it is not intended to be a full workbook manipulation layer. If users upload complex workbooks with formulas, styles, merged headers, comments, or multiple business sheets, use SheetJS or ExcelJS instead.
## write-excel-file
[write-excel-file](https://www.npmjs.com/package/write-excel-file) is a lightweight `.xlsx` writer for browser and Node.js. It is also the default writer behind RevoGrid Pro's bundled Excel export provider.
For most RevoGrid exports, use `ExportExcelPlugin` directly:
```ts
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ExportExcelPlugin];
const plugins = await grid.getPlugins();
await plugins.find((plugin) => plugin instanceof ExportExcelPlugin)?.export({
workbookName: 'orders.xlsx',
sheetName: 'Orders',
});
```
Use `write-excel-file` directly when you are exporting app data that does not need the grid export pipeline.
```sh
pnpm add write-excel-file
```
```ts
import writeXlsxFile from 'write-excel-file';
await writeXlsxFile(
[
[
{ value: 'Name', fontWeight: 'bold' },
{ value: 'Revenue', fontWeight: 'bold' },
],
[
{ value: 'North', type: String },
{ value: 12000, type: Number, format: '$#,##0' },
],
],
{
fileName: 'summary.xlsx',
sheet: 'Summary',
},
);
```
The tradeoff is intentional simplicity. If export needs template workbooks, charts, complex formulas, images, or heavy workbook editing, use ExcelJS or a custom provider.
## RevoGrid Import Pipeline
For RevoGrid imports, keep the parser separate from the grid:
1. Read the uploaded file.
2. Parse workbook data with the selected library.
3. Normalize headers into stable `ColumnRegular` objects.
4. Normalize each row into a plain object keyed by `column.prop`.
5. Validate and coerce values before assigning them to the grid.
6. Assign `grid.columns` before `grid.source`.
```ts
type ImportedTable = {
columns: { prop: string; name: string }[];
rows: Record[];
};
async function importWorkbook(file: File): Promise {
return parseSheetJsFile(file);
}
const imported = await importWorkbook(file);
grid.columns = imported.columns;
grid.source = imported.rows;
```
Avoid putting the raw workbook object into reactive application state. Workbooks can be large and contain circular or library-specific objects. Store the normalized rows, validation errors, and lightweight import metadata instead.
## Performance Notes
Browser-side Excel parsing becomes visible to users once files are large enough. The exact threshold depends on column count, formulas, styles, browser, device, and parser options, but imports around `100k+` rows are where main-thread parsing commonly starts to feel expensive.
For larger files:
- Parse inside a Web Worker.
- Limit the first preview to a small row count.
- Normalize rows in chunks.
- Keep the original workbook out of framework state.
- Show validation errors separately from accepted rows.
- Let RevoGrid render the normalized dataset with virtualization.
For very large or regulated imports, parse on the backend and send RevoGrid paged or streamed JSON instead of asking the browser to own the whole workbook lifecycle.
## Practical Choice
For most RevoGrid apps:
- Use `ExportExcelPlugin` with the bundled `write-excel-file` provider for grid export.
- Use SheetJS for flexible `.xlsx` upload and header inference.
- Use `read-excel-file` for small validated upload forms.
- Use ExcelJS when the workbook itself is the product: formatted reports, templates, formulas, tables, and workbook editing.
That gives the fastest path for common XLSX-to-grid imports while keeping advanced export and workbook-editing cases open through RevoGrid's custom provider contract.
---
# Export Providers
URL: https://pro.rv-grid.com/guides/excel-export/providers/
Source: src/content/docs/guides/excel-export/providers.mdx
Description: Choose the default XLSX provider, optional adapters, or a custom Excel export provider.
`ExportExcelPlugin` prepares one provider context, applies built-in feature transforms, applies custom `exportTransformers`, then passes the context to the selected provider.
Provider resolution is:
1. `plugin.export({ provider })`
2. `grid.exportExcel.exportProvider`
3. bundled `'write-excel-file'`
## Default Provider
The bundled provider is backed by `write-excel-file/browser` and exports `.xlsx` only:
```ts
await excel.export({
workbookName: 'orders',
sheetName: 'Orders',
});
```
Extensionless workbook names receive `.xlsx`, so `orders` becomes `orders.xlsx`.
The default provider rejects incompatible workbook output clearly:
- `writingOptions.bookType` must be `xlsx`.
- `workbookName` must be extensionless or end in `.xlsx`.
- Use an optional adapter or custom provider for formats such as `.xlsb`, `.xls`, `.csv`, or `.ods`.
## Provider Options
`providerOptions` are forwarded to the selected provider. For the bundled provider, they are merged into the options passed to `write-excel-file`.
```ts
await excel.export({
workbookName: 'orders.xlsx',
providerOptions: {
fontFamily: 'Arial',
fontSize: 12,
hideGridLines: true,
zoomScale: 90,
},
});
```
By default, the export pipeline derives provider hints from the live grid:
| Derived option | Source |
| --- | --- |
| `columns` widths | Current grid column sizes |
| `stickyRowsCount` | Generated header row plus pinned-top rows |
| `stickyColumnsCount` | `colPinStart` columns |
Explicit `providerOptions` override derived values. Disable derivation for a single export with `deriveGridOptions: false`:
```ts
await excel.export({
deriveGridOptions: false,
providerOptions: {
stickyRowsCount: 0,
stickyColumnsCount: 2,
},
});
```
## Grid-Level Provider
Use `grid.exportExcel.exportProvider` when one grid should consistently use the same workbook library:
```ts
grid.exportExcel = {
exportProvider: myProvider,
};
```
Override it for one export call:
```ts
await excel.export({
provider: anotherProvider,
workbookName: 'report.xlsx',
});
```
## Optional ExcelJS Provider
ExcelJS is not bundled. Install it in your app and pass the module object to the provider factory:
```sh
pnpm add exceljs
```
```ts
import ExcelJS from 'exceljs';
import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createExcelJsExcelExportProvider(ExcelJS),
};
```
The ExcelJS adapter consumes the formatted `cellRows` matrix, maps common cell formatting to ExcelJS styles, applies grid-derived widths and freeze panes, and writes/downloads the workbook through ExcelJS.
Use ExcelJS when export needs richer workbook styling, merge support, row heights, freeze panes, or library-specific worksheet setup.
## Optional SheetJS Provider
SheetJS is not bundled. Install it in your app and pass the module object to the provider factory:
```sh
pnpm add xlsx
```
```ts
import * as XLSX from 'xlsx';
import { createSheetJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createSheetJsExcelExportProvider(XLSX),
};
```
The SheetJS adapter writes plain row objects from `context.rows`. It does not consume formatted `cellRows`, grouped header rows, or merge spans. Use ExcelJS or a custom provider when workbook formatting must be preserved.
## Workbook Module Adapter
Use `createWorkbookModuleExcelExportProvider()` for simple workbook-like modules that can create a worksheet from plain row objects:
```ts
import { createWorkbookModuleExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createWorkbookModuleExcelExportProvider({
createWorkbook,
createWorksheet,
appendWorksheet,
writeFile,
}),
};
```
Like the SheetJS adapter, the workbook-module adapter uses `context.rows`. Use a full custom provider when the library needs formatted cells, grouped headers, merge metadata, or custom workbook setup.
## Custom Provider
Any object that implements `ExcelExportProvider` can write the workbook:
```ts
import type { ExcelExportProvider } from '@revolist/revogrid-pro';
const myProvider: ExcelExportProvider = {
id: 'app-provider',
async export(context) {
await writeWorkbook({
fileName: context.workbookName,
sheetName: context.sheetName,
cells: context.cellRows,
metadata: context.exportMetadata,
});
},
};
```
Custom providers should use the prepared context instead of reading rendered DOM rows. Virtualized rows are already included in `rows`, `sourceRows`, and `cellRows`.
Ordinary client-side virtualization includes every source row, but `InfinityScrollPlugin` deliberately retains only a moving window. `ExportExcelPlugin` therefore rejects direct export while Infinity Scroll owns the source. Fetch all rows from the backend and export them through a temporary grid with a local source; see [Exporting to Excel with Infinity Scroll](/guides/infinity-scroll/#exporting-to-excel).
## Provider Context Notes
The export context is built from RevoGrid data stores first, with grid element sources as a compatibility fallback. This matters for virtualized grids: providers should not query rendered rows because offscreen records may not be mounted in the DOM.
Pinned row segments are preserved in `sourceRows`:
- `rowPinStart` for pinned top rows
- `rgRow` for body rows
- `rowPinEnd` for pinned bottom rows
The prepared `rows` array keeps the same pinned-top, body, pinned-bottom order after formula evaluation. The formatted `cellRows` matrix adds generated header rows before those data rows.
For workbook providers:
- Use `cellRows` when you need formatted values, column-level cell properties, grouped headers, formulas, or merge spans.
- Use `sourceRows` when you need row-segment semantics, original models, or source indexes.
- Use `rows` only for plain row-object export where formatting and merge metadata do not matter.
If a grid data store is not initialized yet, export falls back to the grid element source for that segment. Missing pinned sources are treated as empty arrays.
For broader library selection guidance, see [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/).
---
# Managing Reference Data
URL: https://pro.rv-grid.com/guides/faq/data-reference/
Source: src/content/docs/guides/faq/data-reference.mdx
Reference data represents static data in a key/value pair format.
This type of data typically remains constant between server requests, making it ideal for scenarios where meaningful labels need to be displayed alongside coded values. For example, a car make might be represented as `'aud': 'Audi'`.
Reference data mapping is a Core RevoGrid pattern. It uses column value handlers such as `cellParser` and does not require Pro plugins.
{/*
### Example Data Structure
The data returned from the server usually consists of codes (keys) that need to be mapped to their corresponding names (values) for display in the grid.
Here’s an example of how this data can be structured:
```javascript
// Data from server
const rowData = [
{ prod: 'toy', price: 28000 },
{ prod: 'bmw', price: 55000 },
// ...
];
// Supporting reference data
const carMappings = {
'toy': 'Toyota',
'bmw': 'BMW',
'aud': 'Audi',
'hnd': 'Honda'
};
``` */}
## Strategies for Managing Reference Data
Value handlers provide a flexible way to map codes to display values. This method involves more coding but allows for greater customization in how data is formatted and saved.
- **Value Formatter**: Converts the key to a user-friendly value for display in the grid.
- **Value Parser**: Converts the user-selected value back into a key for saving in the underlying data.
{/* #### Example Configuration
Here’s how you can configure the grid to use value handlers:
```javascript
const gridOptions = {
columns: [
{
prop: 'prod',
cellTemplate: (h, params) => carMappings[params.value], // Convert code to name
},
cellCompare: (a, b) => { // Map data for sorting
return carMappings[a] - carMappings[b];
},
cellParser: (model, column) => { // Map data for filtering, editing and saving
return carMappings[model[column.prop]];
}
],
// Other grid options...
};
``` */}
## Conclusion
Effectively managing reference data is essential for ensuring that users see meaningful labels alongside coded values.
```javascript
// fixing render for multiframework
```
---
# Data Synchronization
URL: https://pro.rv-grid.com/guides/faq/data-sync/
Source: src/content/docs/guides/faq/data-sync.mdx
Description: A guide covers core concepts and advanced tips for handling data.
We assume that you have already set up the basic version of RevoGrid and are familiar with configuring the `source` and `columns`. It is crucial to always mutate the data you pass to the grid, as this ensures optimal performance.
RevoGrid offers several popular approaches for working with data:
1. ### Grid-Centric Data Management:
- You can work with the grid and, in the end, request the data directly from it.
This approach allows you to subscribe to the `afteredit` event, which is triggered after a cell edit or range edit occurs.
You can retrieve the current data from the `source` whenever you need it.
Try to modify the data in the grid and check how it updates in real time.
Check `Source code` for more details.
---
---
2. ### Source-Centric Data Management:
- This method involves managing your data independently and handling events before cell editing.
In this case, you need to pass your source array to the grid each time an edit occurs, which will trigger a grid re-render to display the updated data.
As part of the Pro version, we provide the [**EventManagerPlugin**](../data-manage/event-manager-explanation), which consolidates range, clipboard, and cell edit events into a single common event called `gridedit`.
You can utilize this plugin to catch the event, modify it, or even create your own custom event manager.
Try to modify the data in the grid and check how it updates in real time.
---
---
3. ### Pagination and Dynamic Data Loading:
- These two features are two sides of the same coin:
- Pagination allows you to break your data source into manageable parts and load data from a server or cache for each page. For more information, check out the [**PaginationPlugin** and its documentation](../grid-utilities/pagination).
- Dynamic data loading is an advanced version of pagination that loads data in chunks based on user interactions, such as scrolling, without displaying a page panel.
With this approach, you can combine the two methods mentioned above to work with a complete or partial grid dataset, updating the data and passing it to the grid as needed.
## Best Practices
For best practices on data management and manipulation, please refer to our [Best Practices Guide](../faq/patterns). This section will help you optimize your grid's performance and usability.
---
# Best Practices
URL: https://pro.rv-grid.com/guides/faq/patterns/
Source: src/content/docs/guides/faq/patterns.mdx
When working with advanced data grids designed for large datasets, several key UI patterns and practices should be followed to ensure a seamless user experience and maintain high performance.
These patterns help create an interface that is intuitive, responsive, and efficient, even with significant data volumes. Here are some of the most important patterns to follow:
1. **Virtual Scrolling**
- **Pattern**: Efficient handling of large datasets by rendering only visible rows and columns, reducing browser load.
- **Best Practice**: Ensure custom rendering or plugins are compatible with virtual scrolling. Avoid heavy DOM manipulations outside the visible viewport to optimize performance.
2. **Lazy Loading**
- **Pattern**: Load data as needed rather than all at once to improve performance.
- **Best Practice**: Implement lazy loading, particularly with external data sources or APIs, ensuring data is fetched and rendered efficiently in chunks.
3. **Data Virtualization**
- **Pattern**: Keep only a subset of data in memory, fetching and rendering only the data that is visible or near visible.
- **Best Practice**: Ensure features like sorting, filtering, and grouping are optimized for large datasets and compatible with data virtualization.
4. **Custom Cell Renderers**
- **Pattern**: Create custom renderers for complex cell content, such as images, buttons, or interactive elements.
- **Best Practice**: Keep custom cell renderers lightweight to avoid performance bottlenecks. Optimize expensive operations that are repeated across many cells. Use cache mechanisms to improve performance.
5. **Responsive Design**
- **Pattern**: Adapt grid layouts to varying screen sizes and resolutions.
- **Best Practice**: Ensure the grid adjusts to different screen sizes with resizable columns and scrollable content. Use responsive features to manage column visibility or prioritize important data on smaller screens.
6. **Keyboard Navigation**
- **Pattern**: Support keyboard navigation for seamless user interaction, especially for power users.
- **Best Practice**: Ensure custom features integrate well with keyboard navigation, maintaining or enhancing keyboard accessibility. Consider custom keyboard shortcuts where applicable.
7. **Infinite Scrolling**
- **Pattern**: Load additional data as the user scrolls down or across the grid.
- **Best Practice**: Implement or support infinite scrolling, especially for large datasets, providing a smoother user experience compared to traditional pagination.
8. **Column and Row Pinning**
- **Pattern**: Keep specific rows or columns visible while scrolling through the data.
- **Best Practice**: Support pinning in customizations, especially for grids where headers or key identifiers need to remain visible.
9. **Custom Sorting and Filtering**
- **Pattern**: Implement custom logic for sorting and filtering to provide more complex or domain-specific data interactions.
- **Best Practice**: Ensure custom sorting and filtering are optimized for performance, potentially applying logic on the server-side for large datasets.
10. **Editable Cells and Inline Editing**
- **Pattern**: Allow users to edit data directly within the grid for more intuitive data manipulation.
- **Best Practice**: Ensure inline editing is user-friendly, supports keyboard shortcuts, and includes robust data validation and error handling.
11. **Event Handling and Customization**
- **Pattern**: Hook into various events (e.g., cell click, row hover) for custom behaviors.
- **Best Practice**: Optimize event listeners and clean them up when no longer needed to avoid performance issues and memory leaks.
12. **Context Menus and Tooltips**
- **Pattern**: Enhance user interaction by providing additional options or information through context menus and tooltips.
- **Best Practice**: Implement contextually aware and lightweight context menus and tooltips to ensure they don't interfere with performance.
13. **Theming and Custom Styling**
- **Pattern**: Customize themes and styling to align the grid’s appearance with the overall application design.
- **Best Practice**: Use CSS variables and custom properties for consistent and easy-to-manage styles, ensuring custom styles do not conflict with core styles or degrade performance.
14. **State Management**
- **Pattern**: Manage the grid’s state (e.g., selected rows, applied filters) for consistency, especially in complex applications.
- **Best Practice**: Integrate with a state management solution if necessary, ensuring the grid’s state is preserved across sessions or navigation.
15. **Big O Notation Considerations**
- **Pattern**: Understand the computational complexity of operations to ensure performance remains optimal as data size increases.
- **Best Practice**: Evaluate and optimize algorithms, especially for sorting, filtering, and rendering, to ensure they are efficient and scale well with large datasets.
16. **Accessibility (a11y)**
- **Pattern**: Ensure the grid is accessible to all users, including those with disabilities.
- **Best Practice**: Follow accessibility best practices, such as providing proper ARIA labels, ensuring keyboard accessibility, and supporting screen readers. Test with accessibility tools to identify and fix issues.
By adhering to these patterns, you can create an implementation that is not only performant and scalable but also user-friendly and accessible. These practices will help you build grids that efficiently handle large datasets while providing a seamless and intuitive user experience.
## Pivot calculation questions
Use the [Pivot calculation FAQ](/guides/pivot/concepts/aggregations/#calculation-faq) for numeric/scalar input policies, row versus value counts, saved-report engine pins and nonadditive totals. The recommended local configuration uses V2 numeric/scalar operations with explicit invalid errors; an execution pin prevents accidental switching and does not guarantee equivalent local/server results.
---
# Form Edit Example
URL: https://pro.rv-grid.com/guides/form-edit/
Source: src/content/docs/guides/form-edit.mdx
Description: Learn how to implement form-based row editing in RevoGrid
This example demonstrates how to implement form-based row editing using RevoGrid's row expand functionality. Instead of editing cells directly in the grid, users can click on a row to expand it and edit the data in a form format.
## Implementation Details
The form edit functionality is implemented using the `RowExpandPlugin`. When a row is expanded, a custom template renders a form with the row's data. The form is split into two columns for better organization.
Key implementation points:
1. **Row Expansion**: Uses RevoGrid's `RowExpandPlugin` to show/hide the form
2. **Form Layout**: Implements a two-column grid layout using Tailwind/UnoCSS
3. **Data Handling**: Updates the grid's source data when the form is saved
4. **Validation**: Can be extended to add form validation (not implemented in this basic example)
## Features
- Form layout
- Save and Cancel buttons
- Responsive form design
- Custom form fields for different data types
- Automatic grid update on save
## Usage
To implement this in your own project:
1. Import required plugins
2. Configure the grid columns
3. Set up the row expand template with your form fields
4. Handle form submission and data updates
```typescript
grid.additionalData = {
rowExpand: {
expandedRowHeight: 300,
template(h, props) {
// Form template implementation
}
}
};
```
## Customization
You can customize the form by:
- Adding more form fields
- Implementing validation
- Changing the layout
- Adding custom styling
- Implementing different field types
---
# Formula
URL: https://pro.rv-grid.com/guides/formula/
Source: src/content/docs/guides/formula/index.mdx
Description: Build Excel-style calculations in RevoGrid with formulas, A1 references, named ranges, and workbook data.
---
# A1 References and Autofill
URL: https://pro.rv-grid.com/guides/formula/a1-references-autofill/
Source: src/content/docs/guides/formula/a1-references-autofill.mdx
Description: Understand Formula coordinates and translate formulas safely during fill, pinning, sorting, insertion, and deletion.
Formula references use authored row and column coordinates. Start with this compact fixture:
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
grid.range = true;
grid.plugins = [FormulaPlugin];
grid.columns = [
{ prop: 'qty', name: 'Quantity' }, // A
{ prop: 'rate', name: 'Rate' }, // B
{ prop: 'charge', name: 'Charge' }, // C
];
grid.source = [
{ qty: 2, rate: 5, charge: '=A1*$B$1' },
{ qty: 4, rate: 8, charge: null },
];
```
**Expected result:** C1 displays `10`. Filling C1 down writes `=A2*$B$1` to C2 and displays `20`: A's row is relative, while `$B$1` remains fixed.
## Autofill formulas like Excel
Set `grid.range = true` to expose the fill handle. `FormulaPlugin` automatically
installs or reuses `AutoFillPlugin`; applications should not add another formula
fill strategy or duplicate the fill pipeline. AutoFill continues to own the drag,
base sequence, preview, and commit. Formula only translates A1 references in seed
cells whose raw source value starts with `=`.
```ts
import {
AutoFillPreviewPlugin,
FormulaPlugin,
} from '@revolist/revogrid-pro';
grid.range = true;
grid.plugins = [FormulaPlugin, AutoFillPreviewPlugin]; // Preview is optional.
```
Vertical copy by one row:
| Reference | Seed | Result |
| --- | --- | --- |
| Relative | `=A1` | `=A2` |
| Fixed column and row | `=$A$1` | `=$A$1` |
| Relative column, fixed row | `=A$1` | `=A$1` |
| Fixed column, relative row | `=$A1` | `=$A2` |
| Mixed range | `=SUM(A1:B2,$C1:D$4)` | `=SUM(A2:B3,$C2:D$4)` |
Horizontal copy by one column:
| Reference | Seed | Result |
| --- | --- | --- |
| Relative | `=A1` | `=B1` |
| Fixed column and row | `=$A$1` | `=$A$1` |
| Relative column, fixed row | `=A$1` | `=B$1` |
| Fixed column, relative row | `=$A1` | `=$A1` |
| Mixed range | `=SUM(A1:B2,$C1:D$4)` | `=SUM(B1:C2,$C1:E$4)` |
References translate in vertical, horizontal, upward, leftward, and
two-dimensional fills. Multi-cell source ranges tile from their corresponding
seed cells. Sheet qualifiers and anchor markers are preserved, including
`Products!A1` and `'Product List'!$A1:B$2`. Named ranges, text literals, and
existing `#REF!` values are not rewritten. If a relative reference moves before
A1, that endpoint becomes `#REF!`.
Pinning, sorting, and filtering do not change these offsets: Formula resolves
the source and destination in the authored A1 coordinate space described below.
For non-contiguous projected selections, seed tiling follows the visible source
order. Preview and commit use the identical translated formula matrix.
For a two-dimensional fill from `=A1*$B$1`, the exact destinations are:
| Destination | Raw formula |
| --- | --- |
| One row down | `=A2*$B$1` |
| One column right | `=B1*$B$1` |
| One row down and one column right | `=B2*$B$1` |
With a multi-cell seed, Formula tiles each destination from the corresponding seed rather than treating the whole selection as one formula.
## How A1 references behave when the grid changes
RevoGrid separates a cell's **formula identity** from its **rendered position**.
Formula letters and row numbers describe the authored data coordinate. Pinning,
hiding, sorting, filtering, grouping, sticky rows, and row drag-order can render
that cell somewhere else without changing what its A1 address means.
This is the same principle as Excel Freeze Panes: freezing a column changes what
stays visible while scrolling, but it does not rename the column or rewrite its
formulas.
```text
Authored formula coordinates
A B C D
Product Region Price Total
=C1*124
After pinning Price left (rendered order only)
C A B D
Price Product Region Total
=C1*124
C1 still means the authored Price column. The raw formula and result are unchanged.
```
### Authored order versus rendered order
Columns use the flattened leaf order supplied through `grid.columns`. In this
example the authored order is `[product, region, price, total]`, even while the
Price column is rendered in the left-pinned viewport.
| A1 letter | Authored property | Position after pinning Price left |
| --- | --- | --- |
| `A` | `product` | Second |
| `B` | `region` | Third |
| `C` | `price` | First, pinned left |
| `D` | `total` | Fourth |
Pinning or unpinning therefore never rewrites formula strings or named-range
definitions. A formula such as `=C1*124` remains bound to `price`, including
when the formula column itself is pinned separately from its precedents.
Column occurrences are matched by identity before RevoGrid considers their
`prop`. This matters when two authored columns intentionally use the same
property. If an application replaces every column object and those replacement
columns still have duplicate properties, there is no reliable way to tell the
occurrences apart after pinning. RevoGrid therefore leaves that mapping
unresolved instead of guessing from the rendered pin order. Preserve the column
objects across presentation changes, or use unique properties when columns need
independent A1 identities.
Rows use one initial authored sequence. Each source can contribute zero, one, or
many rows:
```text
pinnedTopSource → source → pinnedBottomSource
first rows next rows final rows
```
After initialization, moving an existing row between those partitions keeps
its A1 identity. The same applies to sorting, filtering, grouping, sticky-row
duplicates, and row drag-order projections. Tracking is automatic and does not
require an id property in application rows. Applications that replace rows with
new immutable objects can optionally provide a stable row key; see
[Immutable row replacement](#immutable-row-replacement-advanced).
```text
Authored identity After pinning Noah to the top
Row 1 Maya C1 = 10 Noah still C2 = 20
Row 2 Noah C2 = 20 Maya still C1 = 10
Row 3 Total =SUM(C1:C2) Total still =SUM(C1:C2) = 30
Rendered position changed; the underlying row addresses did not.
```
### Presentation changes and structural changes
| Operation | What happens to A1 identity? | Are raw formulas rewritten? |
| --- | --- | --- |
| Pin or unpin a row/column | Preserved | No |
| Hide a column | Preserved; the hidden column still calculates | No |
| Sort, filter, group, trim, or restore rows | Preserved | No |
| Render a sticky-row duplicate | Resolves to its underlying authored row | No |
| Drag-order existing rows | Preserved as a runtime projection | No |
| Insert or delete rows/columns | Following coordinates shift | Yes, unless `autoUpdateRefs` is `false` |
| Explicitly reorder the authored column schema | Letters follow the new authored order | Existing formulas are not currently rewritten for the move |
| Replace all rows with new immutable clones, without `rowIdProp` | Treated as a newly authored dataset | No speculative rewrite |
| Replace rows with immutable clones, with `rowIdProp` | Existing IDs retain A1 identity; genuine inserts/deletes remain structural | Only for genuine inserts/deletes |
| Call `evaluateRawValuesFormula` directly | Uses the row and column arrays supplied by the caller | Not applicable; the helper only evaluates |
### Immutable row replacement (advanced)
Most applications do not need row identity configuration. Built-in pinning,
sorting, filtering, grouping, sticky rows, and drag ordering reuse the existing
row models and are tracked automatically.
If an application replaces source rows with fresh immutable clones, object
identity is no longer available. Set `rowIdProp` only when the configured field
is present, stable, and unique for every row:
```ts
grid.formulaNames = {
rowIdProp: 'id',
names: [{ name: 'Revenue', kind: 'range', ref: 'C2:C20' }],
};
grid.source = previousRows.map(row => ({ ...row }));
```
With that key, a cloned row keeps its authored A1 identity even if the new source
order changes or the row moves between pinned and body sources. If the same
immutable update genuinely inserts or deletes IDs, references shift using the
normal structural-edit rules. Rows with a missing key are not assumed to match,
and duplicate keys are ambiguous; use a real record key rather than a row index.
### Explicit schema reorder and standalone evaluation
The explicit column-reorder case is intentionally separate from pinning. If the
authored schema changes from `[A, B, C]` to `[B, A, C]`, `A1` now means the first
column in that new schema. Existing formula text is currently retained; Excel-style
move rewriting is not part of the pinning contract.
The standalone `evaluateRawValuesFormula` helper has no grid viewport or authored
schema to consult. Its A1 coordinates always follow the row and column arrays
passed to that call.
### What deletion does to formulas and ranges
Deletion is structural, so surviving references are normalized to the new row
numbers. For example, begin with:
```text
Row 1 A1 = 10
Row 2 A2 = 20
Row 3 A3 = 30
Direct reference: =A3
Range formula: =SUM(A1:A3)
```
After deleting row 2, the old row 3 becomes row 2:
```text
Row 1 A1 = 10
Row 2 A2 = 30
=A3 → =A2
=SUM(A1:A3) → =SUM(A1:A2)
```
If the deleted row was itself the target of a direct reference, that reference
becomes `#REF!`. A range that only partially overlaps the deleted block shrinks
to its surviving cells. Named ranges and named formulas follow the same rules.
### Disable structural rewriting
Set `autoUpdateRefs: false` when the application owns structural ref updates:
```ts
grid.formulaNames = {
autoUpdateRefs: false,
names: [],
};
```
**Expected result:** inserting or deleting rows/columns does not rewrite existing raw formula strings or named refs. Presentation operations still behave as described above.
:::tip[Recommended mental model]
Treat pinning, hiding, and runtime projections as viewport mapping. Treat genuine
insertion and deletion as spreadsheet structure changes. This keeps formulas
stable during ordinary grid interaction while keeping their visible A1 text
correct after the dataset structure changes.
:::
## Next steps
**Previous:** [Quick Start](/guides/formula/quick-start/) · **Next:** [Named Ranges](/guides/formula/named-ranges/) · **Deeper reference:** [Autofill guide](/guides/data-manage/autofill/)
---
# Async Formula Functions
URL: https://pro.rv-grid.com/guides/formula/async-functions/
Source: src/content/docs/guides/formula/async-functions.mdx
Description: Register grid-local service functions with loading, errors, events, caching, invalidation, and cancellation.
Use an async formula function when a mounted cell needs data from an application service. Calls are grid-local, case-insensitive, and must be the top-level formula call.
## A complete service-backed function
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
grid.plugins = [FormulaPlugin];
grid.columns = [
{ prop: 'sku', name: 'SKU' }, // A
{ prop: 'service', name: 'Service' }, // B
{ prop: 'price', name: 'Service price' }, // C
];
grid.formulaWorkbook = { sheetId: 'Orders', rowIdProp: 'id' };
const plugins = await grid.getPlugins();
const formulas = plugins.find(plugin => plugin instanceof FormulaPlugin);
if (!formulas) throw new Error('FormulaPlugin is not mounted');
const disposeServicePrice = formulas.registerAsyncFormulaFunction({
name: 'SERVICEPRICE',
async handler([sku, service], { signal, rawArgs }) {
const response = await fetch(
`/api/prices/${encodeURIComponent(String(sku))}?service=${encodeURIComponent(String(service))}`,
{ signal },
);
if (!response.ok) throw new Error(`Price request failed: ${response.status}`);
console.debug('Authored arguments:', rawArgs);
return (await response.json()).price;
},
});
grid.source = [{
id: 'order-1',
sku: 'B-200',
service: 'priority',
price: '=SERVICEPRICE(A1,B1)',
}];
// For example, dispose when this page is torn down.
window.addEventListener('pagehide', disposeServicePrice, { once: true });
```
**Expected result:** C1 first renders the default `#LOADING` state, then the returned price. The handler receives evaluated values (`B-200`, `priority`); `rawArgs` retains `A1` and `B1`.
## Map service errors
The default rejected value is `#ERROR`. Use `mapError` when users need a domain-specific state:
```ts
const dispose = formulas.registerAsyncFormulaFunction({
name: 'SERVICEPRICE',
handler: async ([sku], { signal }) => loadPrice(String(sku), signal),
mapError: error =>
error instanceof Error && error.message.includes('404') ? '#NOT_FOUND!' : '#SERVICE!',
});
```
**Expected result:** a missing SKU displays `#NOT_FOUND!`; other rejections display `#SERVICE!`. `mapError` takes precedence over `errorValue`.
## Observe and refresh states
```ts
const onStateChange = (event: CustomEvent) => {
const { name, rowId, status, value, error } = event.detail;
console.log({ name, rowId, status, value, error });
};
grid.addEventListener('asyncformulastatechange', onStateChange);
// Abort and clear cached SERVICEPRICE cells, then recalculate them on render.
formulas.invalidateAsyncFormulaFunction('SERVICEPRICE');
// Teardown
grid.removeEventListener('asyncformulastatechange', onStateChange);
dispose();
```
**Expected result:** a call emits `pending`, then `resolved` or `rejected`. Repeated renders of the same stable cell/formula/arguments share one request and cached result; invalidation causes a new request.
## Cancellation behavior
The handler's `signal` is aborted when its cell arguments or formula change, the function is invalidated/unregistered, or the plugin is destroyed. Pass that signal to `fetch` or your service client. A stale request's later resolution is ignored.
```ts
async handler([sku], { signal }) {
const response = await fetch(`/api/prices/${sku}`, { signal });
return (await response.json()).price;
}
```
**Expected result:** editing A1 from `B-200` to `A-100` aborts the B-200 request and starts one for A-100; only the current request can update the cell.
## Advanced: override a built-in name
Registering `VLOOKUP` intentionally shadows the synchronous built-in only for this mounted grid and only until its disposer runs. Other grids and `calculateFormulaWorkbook` retain the built-in implementation.
Async functions receive evaluated scalar arguments plus `rawArgs`. They cannot be nested inside another expression, and headless `calculateFormulaWorkbook` does not execute them. Load external service data before a backend calculation.
## Next steps
**Previous:** [Cross-sheet Formulas](/guides/formula/cross-sheet/) · **Next:** [Backend Calculation and Persistence](/guides/formula/backend-calculation/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Backend Calculation and Persistence
URL: https://pro.rv-grid.com/guides/formula/backend-calculation/
Source: src/content/docs/guides/formula/backend-calculation.mdx
Description: Recalculate workbooks without a grid and choose whether to persist formulas, caches, or resolved snapshots.
Formula-derived values are not written back into source rows. Formula-aware mounted rendering, sorting, and filtering can consume derived values, while `grid.source` remains the authoritative raw data.
## Choose what to persist
| Strategy | Persist | Use when |
| --- | --- | --- |
| Formulas only | Raw rows containing `=...` | Every consumer can recalculate and no queryable cache is needed. |
| Formulas + calculated cache | Raw formulas plus `values`/patches | APIs, reports, or searches need current values without losing expressions. |
| Resolved snapshot | A separate copy with formulas replaced by values | Exporting or handing data to a system that cannot evaluate formulas. |
## Recommended: calculate a workbook
`calculateFormulaWorkbook` is synchronous, does not mount a grid, and does not mutate supplied rows. Each sheet needs a case-insensitively unique id, unique column properties, a stable `rowIdProp`, and a valid id on every row.
```ts
import { calculateFormulaWorkbook } from '@revolist/revogrid-pro';
const orders = {
id: 'Orders',
rowIdProp: 'id',
columns: [
{ prop: 'sku', name: 'SKU' },
{ prop: 'qty', name: 'Quantity' },
{ prop: 'price', name: 'Price' },
{ prop: 'total', name: 'Total' },
],
rows: [{
id: 'order-1',
sku: 'B-200',
qty: 2,
price: '=VLOOKUP(A1,\'Product List\'!A1:B2,2,FALSE)',
total: '=B1*C1',
}],
};
const products = {
id: 'Product List',
rowIdProp: 'id',
columns: [
{ prop: 'sku', name: 'SKU' },
{ prop: 'price', name: 'Price' },
],
rows: [
{ id: 'product-1', sku: 'A-100', price: 10 },
{ id: 'product-2', sku: 'B-200', price: 25 },
],
};
const result = calculateFormulaWorkbook({
sheets: [orders, products],
previousValues,
});
if (result.errors.length) {
reportFormulaErrors(result.errors);
}
await database.transaction(async transaction => {
for (const patch of result.patches) {
await persistCalculatedCell(transaction, patch);
}
});
previousValues = result.values;
```
**Expected result:** `values.Orders['order-1']` is `{ price: 25, total: 50 }`. On the first run, patches identify `Orders!C1` and `Orders!D1`, including sheet id, row id, property, formula, and calculated value. Passing the returned `values` unchanged as the next `previousValues` baseline produces no patches.
## Isolate formula errors
Errors are cell-level. An unknown function or circular dependency yields an entry in `errors` and an error-bearing patch, while independent cells still calculate.
```ts
const result = calculateFormulaWorkbook({
sheets: [{
id: 'Orders',
rowIdProp: 'id',
columns: [
{ prop: 'broken', name: 'Broken' }, // A
{ prop: 'qty', name: 'Quantity' }, // B
{ prop: 'price', name: 'Price' }, // C
{ prop: 'total', name: 'Total' }, // D
],
rows: [{
id: 'order-1',
broken: '=UNKNOWN(A1)',
qty: 2,
price: 25,
total: '=B1*C1',
}],
}],
});
```
**Expected result:** `errors` contains A1 with `#ERROR`, while D1 still calculates to `50`. Decide explicitly whether your transaction persists successful patches, rejects the whole workbook, or queues a correction.
## Scoped browser snapshot
For a simple one-sheet browser snapshot, evaluate only formula cells against the exact flat row and column arrays you supply:
```ts
import {
evaluateRawValuesFormula,
isFormula,
} from '@revolist/revogrid-pro';
const source = await grid.getSource();
const columns = await grid.getColumns();
const snapshot = source.map(row => {
const resolved = { ...row };
for (const column of columns) {
const raw = row[column.prop];
if (isFormula(raw)) {
resolved[column.prop] = evaluateRawValuesFormula(raw, source, columns);
}
}
return resolved;
});
```
**Expected result:** `snapshot` contains calculated values and `source` remains unchanged. This utility does not automatically reproduce mounted pin partitions, external sheets, async functions, or the grid's authored-identity runtime; use the workbook API for production multi-sheet recalculation.
The application owns scheduling, transactions, authorization, retries, conflict handling, and persistence. RevoGrid calculates the supplied snapshot but does not watch or write your database.
## Next steps
**Previous:** [Async Formula Functions](/guides/formula/async-functions/) · **Next:** [Supported Functions](/guides/formula/supported-functions/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Cross-sheet Formulas
URL: https://pro.rv-grid.com/guides/formula/cross-sheet/
Source: src/content/docs/guides/formula/cross-sheet.mdx
Description: Look up values in external sheet datasets at mount time, after loading, and after updates.
External sheets are datasets available to the Formula engine; they are not additional mounted grids. Sheet ids are case-insensitively unique. Quote an id containing spaces, as in `'Product List'!A1:B2`, and give every sheet stable, unique row ids.
## Complete Orders and Product List fixture
Orders maps A=`sku`, B=`qty`, C=`catalogPrice`, D=`total`. Product List maps A=`sku`, B=`price`.
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
const orderColumns = [
{ prop: 'sku', name: 'SKU' },
{ prop: 'qty', name: 'Quantity' },
{ prop: 'catalogPrice', name: 'Catalog price' },
{ prop: 'total', name: 'Total' },
];
const productColumns = [
{ prop: 'sku', name: 'SKU' },
{ prop: 'price', name: 'Price' },
];
const productRows = [
{ id: 'product-1', sku: 'A-100', price: 10 },
{ id: 'product-2', sku: 'B-200', price: 25 },
];
const orderRows = [{
id: 'order-1',
sku: 'B-200',
qty: 2,
catalogPrice: '=VLOOKUP(A1,\'Product List\'!A1:B2,2,FALSE)',
total: '=B1*C1',
}];
grid.plugins = [FormulaPlugin];
grid.columns = orderColumns;
grid.formulaWorkbook = {
sheetId: 'Orders',
rowIdProp: 'id',
externalSheets: [{
id: 'Product List',
rowIdProp: 'id',
columns: productColumns,
rows: productRows,
}],
};
grid.source = orderRows;
```
**Expected result:** C1 displays `25` and D1 displays `50`. The raw formulas remain in `orderRows`.
## INDEX and MATCH
Use INDEX/MATCH when the lookup and return ranges should be explicit:
```ts
orderRows[0].catalogPrice =
'=INDEX(\'Product List\'!B1:B2,MATCH(A1,\'Product List\'!A1:A2,0))';
grid.source = [...orderRows];
```
**Expected result:** C1 still displays `25`, then D1 displays `50`.
The tested lookup subset is `LOOKUP`, `VLOOKUP`, `HLOOKUP`, and `INDEX`/`MATCH`. `XLOOKUP` and arbitrary Excel workbook features are not supported.
## Load an external sheet later
Mount Orders with no Product List, then register it when the application finishes loading:
```ts
grid.formulaWorkbook = {
sheetId: 'Orders',
rowIdProp: 'id',
externalSheets: [],
};
grid.source = orderRows;
const plugins = await grid.getPlugins();
const formulas = plugins.find(plugin => plugin instanceof FormulaPlugin);
if (!formulas) throw new Error('FormulaPlugin is not mounted');
formulas.upsertFormulaSheet({
id: 'Product List',
rowIdProp: 'id',
columns: productColumns,
rows: productRows,
});
```
**Expected result:** C1 first renders the mounted missing-sheet loading state, then changes to `25`; D1 changes to `50`. The synchronous backend calculator reports a missing sheet as an error instead of waiting.
## Update an external cell
```ts
formulas.updateFormulaSheetCell('Product List', 'product-2', 'price', 30);
```
**Expected result:** C1 changes from `25` to `30`, and D1 changes from `50` to `60`. The row id selects `product-2`; the final argument uses the column property `price`, not the A1 letter.
## Workbook lifecycle API
| Method | Use it when |
| --- | --- |
| `setFormulaWorkbook(config)` | Replacing the mounted sheet identity or the complete set of external sheets. |
| `upsertFormulaSheet(sheet)` | Adding a late-loaded sheet or replacing one dataset. |
| `updateFormulaSheetCell(sheetId, rowId, prop, value)` | Applying one known external cell update. |
| `removeFormulaSheet(sheetId)` | Making one external sheet unavailable. Dependent mounted formulas return to the missing-sheet loading state. |
| `setFormulaWorkbook(null)` | Detaching the mounted grid from workbook context. |
## Next steps
**Previous:** [Formula UI and Helpers](/guides/formula/formula-ui/) · **Next:** [Async Formula Functions](/guides/formula/async-functions/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Formula UI and Helpers
URL: https://pro.rv-grid.com/guides/formula/formula-ui/
Source: src/content/docs/guides/formula/formula-ui.mdx
Description: Add a Formula Bar, dependency highlights, named dropdowns, and formula-driven styling independently.
`FormulaPlugin` owns calculation. The integrations below are optional and independently configurable.
## 1. Formula Bar
```html
```
```ts
import {
FormulaBarPlugin,
FormulaPlugin,
} from '@revolist/revogrid-pro';
const grid = document.querySelector('#orders');
const host = document.querySelector('#formula-bar');
if (!grid || !host) throw new Error('Formula UI hosts are missing');
grid.plugins = [FormulaBarPlugin, FormulaPlugin];
grid.columns = [
{ prop: 'qty', name: 'Quantity' },
{ prop: 'price', name: 'Price' },
{ prop: 'total', name: 'Total' },
];
grid.formulaBar = { host };
grid.source = [{ qty: 3, price: 10, total: '=A1*B1' }];
// Remove the external editor without removing Formula.
grid.formulaBar = null;
```
**Expected result:** focusing C1 shows raw `=A1*B1` in the Formula Bar while C1 renders `30`. Text and number drafts commit on Enter; dropdown, boolean, and date/time choices commit when selected.
The adaptive host selects a formula/text, number, percent, dropdown, boolean, date/time, or fallback cell editor. Existing text-only integrations may pass `{ el: input }` instead. See the [Formula API](/api/formula/) for custom adapters and the typed `getFormulaBarState()` / `setFormulaBarValue()` runtime.
## 2. Dependency highlighting
Formula installs or reuses `FormulaDependencyHighlightPlugin`. By default it highlights references only while a formula is being edited. Set `highlightOnFocus: true` to also highlight when a formula cell is merely focused.
```ts
grid.plugins = [FormulaPlugin];
grid.formulaDependencyHighlight = {
highlightOnFocus: true,
includeNamedRanges: true,
dependencyClass: 'formula-source-cell',
formulaCellClass: 'formula-active-cell',
dependencyColors: ['#2563eb', '#dc2626', '#16a34a'],
};
```
**Expected result:** focusing C1 with raw `=A1*B1` marks A1 and B1 as dependencies and C1 as the active formula cell. Set `grid.formulaDependencyHighlight = false` to disable the behavior.
Highlights cover direct A1 cells/ranges and named ranges. They do not expand named formulas or a complete transitive dependency graph.
## 3. Named dropdown
`createNamedRangeDropdown` deduplicates a range into dropdown options. Register `ColumnDropdown` as the column type.
```ts
import {
ColumnDropdown,
FormulaPlugin,
createNamedRangeDropdown,
} from '@revolist/revogrid-pro';
const source = [
{ allowed: 'Open', selected: 'Open' },
{ allowed: 'Open', selected: 'Closed' },
{ allowed: 'Closed', selected: null },
];
const names = [{ name: 'AllowedStatuses', kind: 'range' as const, ref: 'A1:A3' }];
const columns = [
{ prop: 'allowed', name: 'Allowed status' }, // A
{
prop: 'selected',
name: 'Selected status',
columnType: 'statusDropdown',
dropdown: createNamedRangeDropdown('AllowedStatuses', {
allSources: source,
columns: [
{ prop: 'allowed', name: 'Allowed status' },
{ prop: 'selected', name: 'Selected status' },
],
names,
}),
},
];
grid.plugins = [FormulaPlugin];
grid.columnTypes = { statusDropdown: ColumnDropdown };
grid.formulaNames = { names };
grid.columns = columns;
grid.source = source;
```
**Expected result:** the Selected status editor offers `Open` and `Closed` once each, even though `Open` appears twice in the named range.
## 4. Conditional formatting
The helper receives the current cell through `cellvalue` and 1-based `row` / `column` tokens.
```ts
import { createFormulaConditionalCellProperties } from '@revolist/revogrid-pro';
const source = [
{ status: 'Open' },
{ status: 'Closed' },
];
const columns = [{ prop: 'status', name: 'Status' }];
columns[0].cellProperties = createFormulaConditionalCellProperties(
'=cellvalue="Closed"',
{ class: 'is-closed' },
{ allSources: source, columns },
);
grid.columns = columns;
grid.source = source;
```
```css
revo-grid .is-closed {
color: #166534;
background: #dcfce7;
font-weight: 600;
}
```
**Expected result:** only row 2 receives `is-closed`. `=row=2` matches the same row; `=column=1` matches cells in the first authored column. Comparisons support `>`, `>=`, `<`, `<=`, `=`, and `<>` for numeric and string values.
## Composition checklist
- Configure `FormulaPlugin` once.
- Add a Formula Bar only when users need an editor outside the cell.
- Use `highlightOnFocus: true` only when passive focus should reveal dependencies.
- Register `ColumnDropdown` before assigning a named-range dropdown column.
- Supply the same source, columns, and names to helper factories that the grid uses.
- Tear down external listeners and set `grid.formulaBar = null` when removing their hosts.
## Next steps
**Previous:** [Named Ranges](/guides/formula/named-ranges/) · **Next:** [Cross-sheet Formulas](/guides/formula/cross-sheet/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Named Ranges
URL: https://pro.rv-grid.com/guides/formula/named-ranges/
Source: src/content/docs/guides/formula/named-ranges.mdx
Description: Use workbook and sheet names for constants, ranges, reusable formulas, and runtime navigation.
Names replace repeated coordinates and values with business terms. `FormulaPlugin` installs or reuses the named-range runtime automatically. Register `NamedRangesPlugin` explicitly when your application needs its runtime methods, events, or Name Manager.
Names are case-insensitive. A name must start with a letter, underscore, or backslash; it cannot contain spaces, resemble an A1 cell such as `B12`, or match a FormulaJS function. Formula names must store a value beginning with `=`.
## 1. Named constant
```ts
import { FormulaPlugin } from '@revolist/revogrid-pro';
grid.plugins = [FormulaPlugin];
grid.columns = [
{ prop: 'subtotal', name: 'Subtotal' }, // A
{ prop: 'total', name: 'With tax' }, // B
];
grid.formulaNames = {
names: [{ name: 'TaxRate', kind: 'constant', value: 0.2 }],
};
grid.source = [{ subtotal: 100, total: '=A1*(1+TaxRate)' }];
```
**Expected result:** B1 displays `120`. Changing `TaxRate` to `0.25` and reassigning `grid.formulaNames` recalculates B1 to `125`.
## 2. Named range
```ts
grid.columns = [
{ prop: 'label', name: 'Period' }, // A
{ prop: 'amount', name: 'Amount' }, // B
];
grid.formulaNames = {
names: [{ name: 'SalesRevenue', kind: 'range', ref: 'B1:B3' }],
};
grid.source = [
{ label: 'Jan', amount: 100 },
{ label: 'Feb', amount: 200 },
{ label: 'Mar', amount: 300 },
{ label: 'Total', amount: '=SUM(SalesRevenue)' },
];
```
**Expected result:** B4 displays `600`.
:::note
`Revenue` is a FormulaJS function name, so this guide uses `SalesRevenue`. Name validation rejects function-name collisions instead of creating an ambiguous expression.
:::
## 3. Named formula
Build on the preceding range and add a reusable formula:
```ts
grid.formulaNames = {
names: [
{ name: 'TaxRate', kind: 'constant', value: 0.2 },
{ name: 'SalesRevenue', kind: 'range', ref: 'B1:B3' },
{
name: 'AfterTaxSales',
kind: 'formula',
value: '=SUM(SalesRevenue)-SUM(SalesRevenue)*TaxRate',
},
],
};
grid.source = [
{ label: 'Jan', amount: 100 },
{ label: 'Feb', amount: 200 },
{ label: 'Mar', amount: 300 },
{ label: 'Net', amount: '=AfterTaxSales' },
];
```
**Expected result:** B4 displays `480`. Circular names, including two formula names that refer to each other, resolve to `#ERROR`.
## 4. Workbook and sheet scope
An unqualified name first uses the active sheet's local definition, then the workbook definition. Qualify a name to bypass that precedence.
```ts
grid.formulaNames = {
activeSheetId: 'Q4',
names: [
{ name: 'TaxRate', scope: 'workbook', kind: 'constant', value: 0.2 },
{ name: 'TaxRate', scope: 'sheet', sheetId: 'Q4', kind: 'constant', value: 0.25 },
{ name: 'TaxRate', scope: 'sheet', sheetId: 'Q1', kind: 'constant', value: 0.1 },
],
};
grid.columns = [
{ prop: 'local', name: 'Local' },
{ prop: 'global', name: 'Workbook' },
{ prop: 'q1', name: 'Q1' },
];
grid.source = [{
local: '=TaxRate',
global: '=workbook!TaxRate',
q1: '=Q1!TaxRate',
}];
```
**Expected result:** A1 displays `0.25`, B1 displays `0.2`, and C1 displays `0.1`. Duplicate display names are allowed across distinct scopes, but not twice within the same scope.
## Runtime management
Register the runtime explicitly, then retrieve it with a typed guard:
```ts
import {
FormulaPlugin,
NamedRangesPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [NamedRangesPlugin, FormulaPlugin];
const plugins = await grid.getPlugins();
const namedRanges = plugins.find(plugin => plugin instanceof NamedRangesPlugin);
if (!namedRanges) throw new Error('NamedRangesPlugin is not mounted');
const validation = namedRanges.validateFormulaNameRef({
name: 'ForecastRange',
kind: 'range',
ref: 'D2:D20',
});
if (!validation.valid) throw new Error(validation.errors.join('; '));
namedRanges.upsertFormulaName({
name: 'ForecastRange',
kind: 'range',
ref: 'D2:D20',
});
await namedRanges.jumpToFormulaName('ForecastRange');
namedRanges.deleteFormulaName('ForecastRange', 'workbook');
```
Listen on the grid for `formulanameschange`, `formulanamevalidationerror`, and `formulanamejump` when application UI needs to react to those operations.
```ts
const onNamesChange = (event: CustomEvent) => console.log(event.detail);
grid.addEventListener('formulanameschange', onNamesChange);
// Teardown
grid.removeEventListener('formulanameschange', onNamesChange);
```
The bundled manager provides create, edit, delete, validation, and range navigation:
```ts
import { defineFormulaNameManager } from '@revolist/revogrid-pro';
const panel = document.querySelector('#name-manager');
if (panel) defineFormulaNameManager(panel, grid, { pageSize: 50 });
```
## Reference updates and immutable data
With `autoUpdateRefs` enabled (the default), deterministic row/column insertions and deletions update range refs, named formulas, and raw source formulas. If row 1 is inserted before `B1:B3`, the name becomes `B2:B4`; deleting a referenced row shrinks the range. Set `autoUpdateRefs: false` to preserve authored strings unchanged.
Pinning, sorting, filtering, hiding, and other presentation changes never rewrite refs. Applications that replace every row with immutable clones may set `rowIdProp` to a stable, present, unique record key so surviving rows retain their authored identity. See [A1 References and Autofill](/guides/formula/a1-references-autofill/) for the full structural model.
## Putting names into an application
Add only the surface the user needs: Formula Bar for direct editing, Name Manager for registry maintenance, dependency highlighting for inspection, named dropdowns for list choices, and conditional formulas for styling. Each integration has an independent copyable example in [Formula UI and Helpers](/guides/formula/formula-ui/).
## Next steps
**Previous:** [A1 References and Autofill](/guides/formula/a1-references-autofill/) · **Next:** [Formula UI and Helpers](/guides/formula/formula-ui/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Overview
URL: https://pro.rv-grid.com/guides/formula/overview/
Source: src/content/docs/guides/formula/overview.mdx
Description: Understand what Formula calculates, what remains in source data, and which guide to use next.
Formula adds spreadsheet-style calculations to RevoGrid. A source value beginning with `=` is stored as an expression, while the mounted grid renders its derived result. For example, source data can retain `=A1*B1` while the cell displays `30`.
The contract is deliberately narrower than Excel:
- A1 coordinates follow the authored row and column order, not the current sorted, filtered, pinned, or hidden presentation.
- Formula recalculates mounted cells when configured source or workbook data changes. It does not replace the raw formula in `grid.source` with the result.
- Invalid syntax, unknown functions, and circular references resolve to `#ERROR`. Autofill that moves a relative reference before A1 produces `#REF!`.
- Functions come from FormulaJS, but RevoGrid owns the expression parser. Use the [documented FormulaJS-backed subset](/guides/formula/supported-functions/), not assumed full Excel compatibility.
## Try the demo
The demo is an Orders grid connected to a Product List dataset. In the workbook legend, **Orders** is the mounted RevoGrid. **Product List** is external sheet data available to the formula engine; it is not a second mounted grid. On narrow screens the legend stacks these parts without changing that relationship.
| Control | What to observe |
| --- | --- |
| Select a formula cell | The Formula Bar shows the raw `=...` expression while the grid keeps showing the calculated value. |
| **Names on/off** | Formulas switch between readable names and direct A1 references without changing their results. |
| **Insert row** | References below the inserted row update to keep pointing at the same logical data. |
| **Name manager** | View and edit range, constant, and formula names used by the workbook. |
| **Raise catalog price** | Product List changes through the mounted Formula API and dependent Orders cells recalculate. |
| **Reload service lookup** | The async formula is invalidated, briefly shows its loading state, then renders the refreshed service value. |
## Choose your path
| Goal | Start here | Helpful first |
| --- | --- | --- |
| Add your first calculated column | [Quick Start](/guides/formula/quick-start/) | RevoGrid columns and source data |
| Copy formulas or understand A1 identity | [A1 References and Autofill](/guides/formula/a1-references-autofill/) | Quick Start |
| Replace coordinates with readable names | [Named Ranges](/guides/formula/named-ranges/) | A1 references |
| Add a Formula Bar, highlights, lists, or styling | [Formula UI and Helpers](/guides/formula/formula-ui/) | Quick Start |
| Look up values in another dataset | [Cross-sheet Formulas](/guides/formula/cross-sheet/) | A1 references |
| Call an application service from a formula | [Async Formula Functions](/guides/formula/async-functions/) | Quick Start |
| Recalculate and persist without a grid | [Backend Calculation and Persistence](/guides/formula/backend-calculation/) | Cross-sheet formulas |
| Check syntax and available functions | [Supported Functions](/guides/formula/supported-functions/) | Quick Start |
## Next steps
**Next:** [Quick Start](/guides/formula/quick-start/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Quick Start
URL: https://pro.rv-grid.com/guides/formula/quick-start/
Source: src/content/docs/guides/formula/quick-start.mdx
Description: Install Formula, calculate line totals, aggregate results, and fill formulas into new rows.
## Install
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro
```
## 1. Calculate line totals
This complete Vanilla TypeScript example maps A to `qty`, B to `unitPrice`, and C to `total`. Register the custom element, attach the grid, configure Formula, and assign `source` last.
```html
```
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { FormulaPlugin } from '@revolist/revogrid-pro';
import '@revolist/revogrid/dist/revo-grid/revo-grid.css';
import '@revolist/revogrid-pro/styles.css';
defineCustomElements();
const grid = document.createElement('revo-grid');
grid.columns = [
{ prop: 'qty', name: 'Quantity' }, // A
{ prop: 'unitPrice', name: 'Unit price' }, // B
{ prop: 'total', name: 'Total' }, // C
];
grid.plugins = [FormulaPlugin];
document.querySelector('#grid')?.append(grid);
grid.source = [
{ qty: 3, unitPrice: 10, total: '=A1*B1' },
{ qty: 2, unitPrice: 12, total: '=A2*B2' },
];
```
**Expected result:** C1 displays `30` and C2 displays `24`. Reading `grid.source` still returns the raw strings `=A1*B1` and `=A2*B2`.
## 2. Aggregate calculated cells
Add a third ordinary row whose C cell sums the first two calculated totals:
```ts
grid.source = [
{ qty: 3, unitPrice: 10, total: '=A1*B1' },
{ qty: 2, unitPrice: 12, total: '=A2*B2' },
{ qty: '', unitPrice: 'Grand total', total: '=SUM(C1:C2)' },
];
```
**Expected result:** C3 displays `54`. Formula resolves C1 and C2 before calculating their range total.
:::note
This example uses an ordinary source row so its A1 address is unambiguous. Pinned-top, main, and pinned-bottom rows share one authored coordinate space; learn that ordering in [A1 References and Autofill](/guides/formula/a1-references-autofill/).
:::
## 3. Fill a formula
Enable range selection to show the fill handle. Seed C1, then drag its handle down to C2.
```ts
grid.range = true;
grid.source = [
{ qty: 3, unitPrice: 10, total: '=A1*B1' },
{ qty: 2, unitPrice: 12, total: null },
];
```
**Expected result:** the fill writes raw `=A2*B2` into C2, which displays `24`. Relative references move with the destination; `$` anchors remain fixed.
`FormulaPlugin` installs or reuses its required Autofill, dependency-highlight, and named-range runtimes. You may register those plugins explicitly when you need their public APIs or custom configuration, but basic formulas do not require duplicate registrations.
## What happens when data changes?
Editing a referenced mounted cell causes dependent formula cells to recalculate. The source continues to own the expression, so persistence code sees `=A1*B1`, not `30`. Use [Backend Calculation and Persistence](/guides/formula/backend-calculation/) when you also need stored calculated values.
| Symptom | Cause |
| --- | --- |
| The cell displays text such as `A1*B1` | The source string is missing its leading `=`. |
| `#ERROR` | Syntax is invalid, a function/name is unknown, or the dependency is circular. |
| `#REF!` after fill | A translated relative reference moved before column A or row 1. |
## Framework examples
The complete demo has equivalent [Vanilla TypeScript](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/Formula.ts), [React](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/Formula.tsx), [Vue 3](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/Formula.vue), and [Angular](https://github.com/revolist/revogrid-pro/blob/master/examples/components/src/components/formula/FormulaAngular.ts) sources.
## Next steps
**Previous:** [Overview](/guides/formula/overview/) · **Next:** [A1 References and Autofill](/guides/formula/a1-references-autofill/) · **Deeper reference:** [Formula API](/api/formula/)
---
# Supported Functions
URL: https://pro.rv-grid.com/guides/formula/supported-functions/
Source: src/content/docs/guides/formula/supported-functions.mdx
Description: Learn Formula expression syntax and browse verified FormulaJS-backed grid patterns.
Formula uses functions from FormulaJS through RevoGrid's own expression parser. This is a documented FormulaJS-backed subset, not full Excel compatibility and not every call accepted by the standalone FormulaJS JavaScript API.
## Grid formula syntax
- Stored grid formulas begin with `=`.
- Function arguments use commas.
- Values can come from A1 cells, rectangular A1 ranges, names, numbers, quoted strings, and `TRUE` / `FALSE`.
- Sheet ids containing spaces use quotes: `'Product List'!A1:B3`.
- Arithmetic supports `+`, `-`, `*`, `/`, parentheses, and unary signs.
- Comparisons support `>`, `>=`, `<`, `<=`, `=`, and `<>`.
- Nested supported function calls are evaluated from the inside out.
JavaScript array literals such as `[1,2,3]` are not grid formula syntax. Put values in cells and pass an A1 range such as `A1:A3`, or use a named range. Low-level `evaluate()` examples in the FormulaJS project are JavaScript API calls and may not be copyable grid formulas.
## Verified patterns
The following catalog uses one deterministic fixture and is exercised by the Formula documentation test.
## Lookup support
The tested lookup set is `LOOKUP`, `VLOOKUP`, `HLOOKUP`, and `INDEX` with `MATCH`. `XLOOKUP` is not currently supported. For a complete external-sheet fixture, see [Cross-sheet Formulas](/guides/formula/cross-sheet/).
## Error values
| Situation | Result |
| --- | --- |
| Unknown syntax/function or circular formula/name | `#ERROR` |
| Autofill translates a relative reference before A1 | `#REF!` |
| A mounted formula explicitly references an external sheet that is not registered | `#LOADING` |
| An async formula rejects without a custom `errorValue` or `mapError` | `#ERROR` |
FormulaJS exposes more functions than are shown here, but parser compatibility and argument shapes still matter. Verify an unlisted function with your actual data before treating it as part of an application contract.
## Next steps
**Previous:** [Backend Calculation and Persistence](/guides/formula/backend-calculation/) · **Next:** [Formula API](/api/formula/) · **Deeper reference:** [Quick Start](/guides/formula/quick-start/)
---
# Gantt Overview
URL: https://pro.rv-grid.com/guides/gantt/
Source: src/content/docs/guides/gantt/index.mdx
Description: RevoGrid Gantt with deterministic scheduling, timeline editing, dependencies, resources, baselines, and diagnostics.
Surface: gantt
RevoGrid Gantt is a standalone commercial project-planning package built on RevoGrid. It combines a virtualized task table with a scheduling engine and interactive timeline for dependencies, resources, progress, critical path, baselines, and project analysis.
Create a `revo-grid` element and configure Gantt through public element properties.
[Start with TypeScript](/guides/gantt/introduction/vanilla-typescript/)
Bind Gantt properties through `@revolist/react-datagrid`.
[Start with React](/guides/gantt/introduction/react/)
Use `@revolist/vue3-datagrid` with reactive project collections.
[Start with Vue](/guides/gantt/introduction/vue/)
Use the standalone `RevoGrid` wrapper and property bindings.
[Start with Angular](/guides/gantt/introduction/angular/)
## How Gantt Fits Together
```txt
tasks + calendars + dependencies + resources + assignments
│
▼
deterministic scheduler
│
▼
projected task rows + timeline geometry
│
▼
RevoGrid rendering and interaction surface
```
Your application owns project data and persistence. `GanttPlugin` validates and schedules that data, projects it into task-table rows and timeline geometry, and routes supported edits back through Gantt mutation services. RevoGrid owns virtualization, columns, selection, keyboard navigation, and the browser rendering lifecycle.
## What You Can Build
- Work breakdown structures with tasks, summaries, and milestones.
- Automatic or manual schedules using calendars, dependencies, constraints, and deadlines.
- Resource assignments, capacity analysis, effort modes, and leveling.
- Baseline comparison, progress tracking, slack, and critical-path analysis.
- Editable timelines with drag, resize, progress, dependency, task-editor, and context-menu workflows.
- JSON and Microsoft Project interchange plus grid, Excel, and print-oriented export workflows.
## Recommended Path
1. Complete [Installation](/guides/gantt/introduction/installation/) and one framework quick start.
2. Follow the [project tutorial](/guides/gantt/tutorial/) to add dependencies, resources, editing, baselines, and export.
3. Learn the [data model](/guides/gantt/concepts/data-model/) and [scheduling model](/guides/gantt/scheduling/overview/).
4. Explore the searchable [Gantt examples](/guides/gantt/examples/).
5. Use the [Gantt API reference](/api/gantt/) for exact signatures.
These guides document capabilities present in this repository. See Limitations for intentional boundaries and Microsoft Project differences.
---
# Gantt Baselines
URL: https://pro.rv-grid.com/guides/gantt/concepts/baselines/
Source: src/content/docs/guides/gantt/concepts/baselines.mdx
Description: Capture a baseline snapshot of your schedule and overlay it beneath current task bars to visualise drift.
Surface: gantt
A **baseline** is a saved snapshot of task dates and progress at a specific point in time (e.g. when the project was approved).
The Gantt chart can overlay these baseline bars beneath the live bars, making schedule drift immediately visible.
---
## Adding a Baseline
Pass one or more `BaselineSnapshot` objects via `grid.ganttBaselines`:
```typescript
grid.ganttBaselines = [
{
id: 'baseline-approved',
name: 'Approved Plan',
capturedAt: '2026-04-01T08:00:00Z',
tasks: [
{ taskId: 't1', startDate: '2026-04-06', endDate: '2026-04-22', duration: 13, progressPercent: 0 },
{ taskId: 't2', startDate: '2026-04-06', endDate: '2026-04-10', duration: 5, progressPercent: 0 },
{ taskId: 't3', startDate: '2026-04-10', endDate: '2026-04-10', duration: 0, progressPercent: 0 },
{ taskId: 't4', startDate: '2026-04-13', endDate: '2026-04-22', duration: 8, progressPercent: 0 },
{ taskId: 't5', startDate: '2026-04-23', endDate: '2026-05-16', duration: 18, progressPercent: 0 },
{ taskId: 't6', startDate: '2026-04-23', endDate: '2026-05-09', duration: 13, progressPercent: 0 },
{ taskId: 't7', startDate: '2026-04-23', endDate: '2026-05-16', duration: 18, progressPercent: 0 },
{ taskId: 't8', startDate: '2026-05-16', endDate: '2026-05-16', duration: 0, progressPercent: 0 },
],
},
];
```
You can also capture a snapshot from the current scheduled state:
```typescript
const plugins = await grid.getPlugins();
const gantt = plugins.find((plugin) => plugin.constructor?.name === 'GanttPlugin');
const baseline = gantt?.captureBaseline?.({
id: 'baseline-current',
name: 'Current Approved Plan',
capturedAt: '2026-04-15T08:00:00Z',
});
if (baseline) {
grid.ganttBaselines = [
...(grid.ganttBaselines ?? []),
baseline,
];
}
```
## Enabling the Overlay
Turn on baseline rendering through the `visuals` key in your project config:
```typescript
grid.gantt = {
// …required fields
visuals: {
showBaseline: true,
baselineId: 'baseline-approved', // optional — defaults to the latest snapshot
},
};
```
When `showBaseline` is `true`, a translucent bar is drawn behind each live task bar showing its baseline position.
Only tasks listed in `BaselineSnapshot.tasks` show a baseline bar. Tasks added after the snapshot was captured render without one.
## Multiple Baselines
You can store several snapshots and switch between them at runtime:
```typescript
// Initially show the approved plan
grid.gantt = { ...config, visuals: { showBaseline: true, baselineId: 'baseline-approved' } };
// Switch to a re-baseline after scope change
grid.gantt = { ...config, visuals: { showBaseline: true, baselineId: 'baseline-replan' } };
```
## Variance Columns
When a baseline is visible, each projected task row also exposes optional variance fields:
| Field | Description |
|---|---|
| `startVarianceDays` | Current scheduled start minus baseline start, in calendar days |
| `finishVarianceDays` | Current scheduled finish minus baseline finish, in calendar days |
| `durationVariance` | Current scheduled duration minus baseline duration |
| `progressVariancePercent` | Current progress minus baseline progress, in percentage points |
Add the default columns when users need to inspect drift numerically:
```typescript
grid.columns = [
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startVarianceDays'),
createDefaultTaskTableColumn('finishVarianceDays'),
createDefaultTaskTableColumn('durationVariance'),
createDefaultTaskTableColumn('progressVariancePercent'),
];
```
## BaselineSnapshot Fields
| Field | Type | Description |
|---|---|---|
| `id` | `BaselineId` | Unique identifier for this snapshot |
| `name` | `string` | Human-readable label (e.g. `"Q1 Approved Plan"`) |
| `capturedAt` | `ISODateTimeString` | When the snapshot was taken |
| `tasks` | `BaselineTaskSnapshot[]` | Per-task date and progress records |
### BaselineTaskSnapshot Fields
| Field | Type | Description |
|---|---|---|
| `taskId` | `TaskId` | References the live task |
| `startDate` | `ISODateString` | Baseline start date |
| `endDate` | `ISODateString` | Baseline end date |
| `duration` | `number` | Baseline task duration |
| `progressPercent` | `number` | Baseline progress (0–100) |
---
# Data Model
URL: https://pro.rv-grid.com/guides/gantt/concepts/data-model/
Source: src/content/docs/guides/gantt/concepts/data-model.mdx
Description: Required grid properties and Gantt entity collections.
Surface: gantt
This page is for wiring the minimum data surface.
Required entry points on `HTMLRevoGridElement`:
- `gantt`: project config (`GanttPluginConfig`)
- `source`: flat task rows
- `ganttCalendars`: `CalendarEntity[]`
Optional linked collections:
- `ganttDependencies`: `DependencyEntity[]`
- `ganttResources`: `ResourceEntity[]`
- `ganttAssignments`: `AssignmentEntity[]`
- `ganttBaselines`: `BaselineSnapshot[]`
Public shape is declared in:
- `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`
- `packages/gantt/src/gantt/core/domain.ts`
## Flat Task Source Rows
Gantt reads task data from flat grid rows. Required fields are `id`, `name`, `startDate`, and either `duration` or `endDate`.
The public source row names match task-table editing conventions:
- `duration` maps to internal `duration`.
- `percentDone` maps to internal `progressPercent`.
- `taskMode` maps to internal `manuallyScheduled` (`manual` or `auto`).
- `workflowStatus` stores the optional authored workflow key: `not-started`, `in-progress`, `done`, or `blocked`.
- `status` is calculated after scheduling from `% Done`, actual finish, scheduled dates, calendars, splits, and the project `statusDate`; it is not authored or editable.
When both `duration` and `endDate` are present, `duration` is the scheduling input and Gantt resolves `endDate` from `startDate + duration`. This matches task-table editing, where duration edits drive the finish date. Use `endDate` without `duration` when the finish date should be the authored input.
Projected grid rows remain display-ready. `GanttGridRow.status` is the calculated human-readable label, while `GanttGridRow.statusKey` and `statusIndicator` contain `complete`, `on-schedule`, `late`, or `future-task`. The authored workflow value is projected separately as `workflowStatus` and `workflowStatusKey`. Custom source fields are preserved by task id across recalculation and structural task changes unless the row is replaced without those fields.
Legacy source or JSON tasks that contain one of the old workflow values in `status` are migrated to `workflowStatus` when the new field is absent. New serialization emits only `workflowStatus`.
## Temporal Field Precision
Task temporal fields (`startDate`, `endDate`, `actualStartDate`, `actualFinishDate`) accept:
- ISO date: `YYYY-MM-DD`
- UTC ISO datetime: `YYYY-MM-DDTHH:mm:ss.sssZ`
Runtime normalization rules:
- Datetime input is preserved and used for intraday scheduling precision.
- Date-only input is normalized to `T00:00:00Z`.
For new integrations, prefer explicit UTC datetime values when you need hour/minute planning behavior.
---
# Dependencies
URL: https://pro.rv-grid.com/guides/gantt/concepts/dependencies/
Source: src/content/docs/guides/gantt/concepts/dependencies.mdx
Description: Dependency types, lag/lead, and validation behavior.
Surface: gantt
Dependencies constrain automatic successors using the relationship type and optional lag:
| Type | MSP token | Constraint |
|---|---|---|
| `finish-to-start` | `FS` | Successor starts after predecessor finishes. |
| `start-to-start` | `SS` | Successor starts no earlier than predecessor starts. |
| `finish-to-finish` | `FF` | Successor finishes no earlier than predecessor finishes. |
| `start-to-finish` | `SF` | Successor finishes no earlier than predecessor starts. |
## Using Lag and Lead
Use `lagDays` for lag or lead:
```typescript
grid.ganttDependencies = [
{
id: 'build-test',
predecessorTaskId: 'build',
successorTaskId: 'test',
type: 'finish-to-start',
lagDays: -2,
},
];
```
Positive `lagDays` delays the successor. Negative `lagDays` is lead time and allows overlap, matching Microsoft Project's negative lead syntax.
`lagCalendar` in scheduling config controls whether lag is interpreted as calendar days or working days.
## Inline Cell Syntax
Inline predecessor/successor cells accept MSP-style tokens:
```text
1FS
2SS+3
3FF-1d
task-4SF-2
```
Blank or invalid lag normalizes to `0`. The optional `d` suffix is accepted. Whitespace around tokens is ignored.
## Validation Behavior
Important dependency behavior:
- Circular dependency chains are invalid schedule data. RevoGrid reports them as `dependency-cycle` issues with the involved `taskIds` and `dependencyIds`.
- New dependency edits that would create a cycle are rejected before they change the Gantt store.
- Task row drops that would make dependency partners parent/child through the outline are rejected before the drop is committed.
- Imported projects that already contain a cycle still render, but automatic dependency propagation and drag/resize dependency clamping are not applied while the active dependency graph is cyclic.
- Missing predecessor/successor references produce issues.
- Dependency links are rendered in the overlay layer and can be created/edited interactively.
This matches the Microsoft Project scheduling model: a loop such as `Setup load balancer -> Configure firewall -> Setup load balancer` cannot produce a valid schedule. Remove or change one of the invalid links, then rerun scheduling.
When building diagnostics UI, inspect the scheduling result issues:
```typescript
const cycle = schedulerResult.issues.find((issue) => issue.code === 'dependency-cycle');
if (cycle) {
console.warn('Dependency cycle', {
taskIds: cycle.taskIds,
dependencyIds: cycle.dependencyIds,
});
}
```
## Examples
### Auto Successor Clamps to FS Dependency
An automatic successor cannot silently move before its predecessor allows:
```typescript
grid.source = [
{
id: 'design',
name: 'Design',
startDate: '2026-04-01',
duration: 3,
taskMode: 'auto',
},
{
id: 'build',
name: 'Build',
startDate: '2026-04-01',
duration: 2,
taskMode: 'auto',
},
];
grid.ganttDependencies = [{
id: 'design-build',
predecessorTaskId: 'design',
successorTaskId: 'build',
type: 'finish-to-start',
lagDays: 0,
}];
```
Expected result: `design` ends on `2026-04-03`, so `build` is scheduled no earlier than `2026-04-04`. If the user drags `build` left, the effective bar snaps back to the dependency-valid date.
### Manual Successor Keeps Date and Warns
Manual tasks preserve authored dates, but dependency violations remain visible:
```typescript
grid.source = [
{ id: 'design', startDate: '2026-04-01', duration: 3 },
{
id: 'review',
name: 'Manual Review',
startDate: '2026-04-02',
duration: 1,
taskMode: 'manual',
},
];
grid.ganttDependencies = [{
id: 'design-review',
predecessorTaskId: 'design',
successorTaskId: 'review',
type: 'finish-to-start',
lagDays: 0,
}];
```
Expected result: `review` stays on `2026-04-02` and receives a `manual-dependency-violation` warning because the dependency requires `2026-04-04`.
See the [Advanced dependencies demo](https://demo.rv-grid.com/gantt-dependencies-advanced/ts) for a runnable project containing FS, SS, FF, and SF links with lag and lead.
Implementation sources:
- `packages/gantt/src/gantt/engine/dependency-graph.ts`
- `packages/gantt/src/gantt/engine/scheduler-engine/validation.ts`
- `packages/gantt/src/gantt/features/dependencies/`
---
# Tasks, Summaries, And Milestones
URL: https://pro.rv-grid.com/guides/gantt/concepts/tasks/
Source: src/content/docs/guides/gantt/concepts/tasks.mdx
Description: Task types and hierarchy behavior.
Surface: gantt
Task hierarchy is represented by `parentId`.
- `type: 'task'`: normal scheduled item.
- `type: 'summary'`: rollup row; dates and progress come from children.
- `type: 'milestone'`: zero duration (`startDate === endDate`, `duration: 0`).
Scheduling-relevant source fields include `duration`, `durationUnit`, `durationIsElapsed`, `taskMode`, `constraintType`, `constraintDate`, `deadlineDate`, `effortMode`, `workHours`, `workUnit`, `percentDone`, `actualStartDate`, `actualFinishDate`, `remainingDuration`, `splitRanges`, `priority`, and `canLevel`. See [Duration Units and Conversion](/guides/gantt/scheduling/durations/) for mixed units and text input.
`duration`, `remainingDuration`, and `workHours` are stored in canonical hours. `durationUnit` is retained display/edit metadata for duration spans, while `workUnit` independently controls Work. Text quantities are converted to hours on input and converted from hours only when rendered.
Source row contract: `GanttTaskSourceRow` in `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`. Internal task entities use `manuallyScheduled`, `duration`, and `progressPercent` in `packages/gantt/src/gantt/core/domain.ts`.
---
# How Gantt Builds On RevoGrid
URL: https://pro.rv-grid.com/guides/gantt/customization/how-gantt-builds-on-revogrid/
Source: src/content/docs/guides/gantt/customization/how-gantt-builds-on-revogrid.mdx
Description: Division of responsibilities between RevoGrid and Gantt plugin layers.
Surface: gantt
RevoGrid provides row/column virtualization, editing, selection, scrolling, and base rendering. Gantt extends that with timeline projection, dependency overlays, scheduling, and resource planning.
Implementation layers:
- Grid/plugin orchestration: `packages/gantt/src/gantt/grid/gantt-plugin.ts`
- Scheduling engine: `packages/gantt/src/gantt/engine/`
- Projection and timeline model: `packages/gantt/src/gantt/projection/`
- Interaction features: `packages/gantt/src/gantt/features/`
Use this split when debugging behavior: if data rules changed, inspect `engine`; if visual output changed, inspect `projection/features`; if event routing changed, inspect `grid`.
---
# Gantt Timeline and Zoom
URL: https://pro.rv-grid.com/guides/gantt/customization/timeline-zoom/
Source: src/content/docs/guides/gantt/customization/timeline-zoom.mdx
Description: Configure timeline scale, zoom behavior, wheel interaction, and visual timeline overlays.
Surface: gantt
The Gantt timeline can be configured at two levels:
1. `zoomPreset` for fast setup with built-in levels.
2. `zoom` for full control over levels, wheel behavior, and anchor strategy.
Use `weekStartsOn` when week headers or week ticks should align to a specific
weekday. Gantt defaults to Sunday-start weeks.
---
## Quick Start with `zoomPreset`
Use a built-in preset when you only need a standard timeline scale:
```typescript
grid.gantt = {
// ...required project fields
zoomPreset: 'week-month',
};
```
Supported presets:
- `'minute-hour'` (15-minute ticks grouped by hour)
- `'hour-day'` (hour ticks grouped by day)
- `'day-week'`
- `'week-month'`
- `'month-quarter'`
- `'quarter-year'`
- `'year-quarter'`
- `'multi-year-quarter'`
By default, the interactive zoom ladder starts at `day-week` and excludes the intraday presets for performance. Use `zoomPreset: 'hour-day'`, `zoomPreset: 'minute-hour'`, or explicit `zoom.levels` when an intraday timeline is required.
## Week Start
Week-based timeline headers, week ticks, and padded week ranges use Sunday by default:
```typescript
grid.gantt = {
// ...required project fields
weekStartsOn: 0, // Sunday, default
};
```
Set `weekStartsOn: 1` when the Gantt should use Monday-start weeks:
```typescript
grid.gantt = {
// ...required project fields
weekStartsOn: 1, // Monday
zoomPreset: 'week-month',
};
```
`weekStartsOn` accepts `0` for Sunday or `1` for Monday. It affects timeline week boundaries only; task calendars still use their own `workingDays` configuration.
## Time Modes
Gantt timeline modes are defined by `tickUnit` + `headerRows` in each zoom level.
- `minute-hour`: 15-minute timeline resolution for intraday planning windows.
- `hour-day`: hourly timeline resolution grouped under day headers.
- `day-week`: daily planning view.
- `week-month`: weekly planning view.
- `month-quarter`: monthly planning view.
- `quarter-year`, `year-quarter`, `multi-year-quarter`: portfolio and long-range views.
Recommended usage pattern:
- Opt into `minute-hour` for short execution windows and handoffs.
- Opt into `hour-day` for same-day and next-day coordination.
- Use `day-week` or coarser for baseline and milestone planning.
Intraday modes (`minute-hour`, `hour-day`) control timeline granularity and interaction scale. Temporal fields still use the same names, but when you need intraday precision you should pass UTC datetime values (`...Z`) instead of date-only strings.
## Hour Mode
Use hour mode when task bars should move, resize, and create on hour boundaries
instead of whole-day boundaries. The important settings are:
- `timelinePrecision: 'hour'` makes timeline interactions use hour precision by default.
- `zoomPreset: 'hour-day'` starts the visible timeline with one column per hour.
- `snap.unit: 'hour'` forces task create, move, and resize snapping to hours even if the active zoom level changes later.
- `snap.workingTime` controls whether snapping can use working-time rules. Closed weekdays, holidays, and closed hours are skipped only when `scheduling.excludeHolidaysFromDuration` is also `true`.
```typescript
grid.gantt = {
// ...required project fields
timelinePrecision: 'hour',
zoomPreset: 'hour-day',
snap: {
unit: 'hour',
workingTime: true,
},
scheduling: {
excludeHolidaysFromDuration: true,
},
calendars: [
{
id: 'standard',
name: 'Standard',
workingDays: [1, 2, 3, 4, 5],
workingHours: [{ start: '09:00', end: '17:00' }],
hoursPerDay: 8,
},
],
tasks: [
{
id: 'task-1',
name: 'Same-day implementation',
startDate: '2026-04-06T09:00:00.000Z',
endDate: '2026-04-06T13:00:00.000Z',
},
],
};
```
`timelinePrecision` controls interaction precision. `zoomPreset` and `zoom.defaultLevelId`
control the initial visible scale. Keep them aligned for an hour-first experience,
or set `zoom.defaultLevelId: 'day-week'` if you want hour snapping while opening
on a coarser planning view.
When `snap.workingTime` is `true` or omitted and
`scheduling.excludeHolidaysFromDuration` is `true`, hour snapping uses the
primary project calendar:
- A drag before `09:00` snaps forward to `09:00`.
- A drag after `17:00` snaps forward to the next working opening.
- Weekend or holiday drags snap to the next or previous working opening depending on the interaction direction.
Set `snap.workingTime: false` when users should be able to place tasks in
non-working hours while still snapping to the start of each hour.
If `scheduling.excludeHolidaysFromDuration` is `false` or omitted, hour
snapping rounds to the start of the hour and does not move drags out of
non-working time.
## Full Zoom Configuration
Use `zoom` when you need custom levels, min/max bounds, locale, or wheel interaction rules.
```typescript
grid.gantt = {
// ...required project fields
zoomPreset: 'week-month',
zoom: {
enabled: true,
defaultLevelId: 'hour-day',
minLevelId: 'minute-hour',
maxLevelId: 'quarter-year',
locale: 'en-US',
zoomAnchorMode: 'pointer',
wheelZoomEnabled: true,
wheelZoomTrigger: 'ctrlKey',
wheelZoomMode: 'discrete',
invertWheelDirection: false,
},
};
```
If both `zoomPreset` and `zoom.defaultLevelId` are provided, the resolved zoom config uses the `zoom` values where specified.
## Custom Zoom Levels
You can define your own `zoom.levels` list from finest to coarsest.
```typescript
grid.gantt = {
// ...required project fields
zoom: {
levels: [
{
id: 'minute-hour',
label: '15 Min / Hour',
tickUnit: 'minute',
tickCount: 15,
tickWidth: 44,
headerRows: [
{ id: 'day', unit: 'day' },
{ id: 'hour', unit: 'hour' },
{ id: 'minute', unit: 'minute', count: 15 },
],
},
{
id: 'hour-day',
label: 'Hour / Day',
tickUnit: 'hour',
tickWidth: 52,
headerRows: [
{ id: 'day', unit: 'day' },
{ id: 'hour', unit: 'hour' },
],
},
{
id: 'day-week',
label: 'Day / Week',
tickUnit: 'day',
tickWidth: 56,
headerRows: [
{ id: 'week', unit: 'week' },
{ id: 'day', unit: 'day' },
],
},
{
id: 'week-month',
label: 'Week / Month',
tickUnit: 'week',
tickWidth: 84,
headerRows: [
{ id: 'month', unit: 'month' },
{ id: 'week', unit: 'week' },
],
},
{
id: 'month-quarter',
label: 'Month / Quarter',
tickUnit: 'month',
tickWidth: 120,
headerRows: [
{ id: 'year', unit: 'year' },
{ id: 'month', unit: 'month' },
],
},
],
defaultLevelId: 'hour-day',
},
};
```
## Wheel Zoom Options
Control whether users can zoom with the mouse wheel and which modifier key is required.
```typescript
grid.gantt = {
// ...required project fields
zoom: {
wheelZoomEnabled: true,
wheelZoomTrigger: 'metaKey', // macOS Cmd key
wheelZoomMode: 'smooth-discrete',
invertWheelDirection: false,
},
};
```
Available `wheelZoomTrigger` values:
- `'ctrlKey'`
- `'metaKey'`
- `'altKey'`
- `'shiftKey'`
- `'none'`
## Anchor Behavior During Zoom
`zoomAnchorMode` controls which point in the viewport stays stable when switching levels:
- `'pointer'`: keeps the date under the mouse pointer fixed.
- `'center'`: keeps the center date fixed.
- `'start'`: keeps the left edge date fixed.
```typescript
grid.gantt = {
// ...required project fields
zoom: {
zoomAnchorMode: 'center',
},
};
```
## Timeline Overlays
Timeline visuals are configured in `visuals`.
```typescript
grid.gantt = {
// ...required project fields
visuals: {
projectLineDate: '2026-04-06',
timeRanges: [
{
id: 'sprint-1',
startDate: '2026-04-06',
endDate: '2026-04-17',
label: 'Sprint 1',
color: '#5B8DEF',
},
{
id: 'freeze',
startDate: '2026-05-18',
endDate: '2026-05-22',
label: 'Code Freeze',
color: '#F59E0B',
},
],
shadeNonWorkingTime: true,
},
};
```
`shadeNonWorkingTime` is accepted in both `zoom` and `visuals`. If both are set, zoom-level setting takes priority.
## Plugin Header Contributions
`GanttTimelineHeaderPlugin` owns the custom RevoGrid header template used by
the Gantt column. Other runtime plugins can discover it and append elements to
the resolved timeline header without creating another render root:
```typescript
import { GanttTimelineHeaderPlugin } from '@revolist/gantt';
// Inside another grid plugin, use its PluginProviders instance.
const timelineHeader = providers.plugins.getByClass(GanttTimelineHeaderPlugin);
const unregisterReleaseMarker = timelineHeader?.registerElement(
'release-marker',
(h) => h('div', {
class: { 'my-release-marker': true },
style: { left: '420px' },
}, 'Release'),
);
// Call after closure-backed contribution state changes.
timelineHeader?.refresh();
// Call from the contributing plugin's teardown.
unregisterReleaseMarker?.();
```
Contribution renderers receive RevoGrid's native `HyperFunc` and the
immutable, resolved header geometry. They render after the built-in header
cells, non-working ranges, interaction range, and flag caps, in registration
order. The contributing plugin owns its data and styling; timeline scale,
viewport slicing, and scroll state remain owned by Gantt.
Use `renderGanttTimelineHeader` directly only when building a custom RevoGrid
column template. Normal Gantt integrations should configure `zoom` and use the
discoverable header plugin only for cross-plugin UI contributions.
---
# Visual Configuration
URL: https://pro.rv-grid.com/guides/gantt/customization/visuals/
Source: src/content/docs/guides/gantt/customization/visuals.mdx
Description: Timeline overlays, labels, baseline, critical path, and marker hooks.
Surface: gantt
`gantt.visuals` controls overlays and rendering hooks:
- `percentDoneMode` selects the project-wide `number`, `bar` (default), or `circle` rendering for the managed `% Done` task-table column using the shared Pro progress renderers. An authored `{ prop: 'percentDone', mode: '...' }` column overrides it.
- `showBaseline`, `baselineId`
- `showCriticalPath`
- `showTaskLabels`
- `projectLineDate`, `showTodayLine`, `milestoneLines`
- `timeRanges`, `shadeNonWorkingTime`
- `taskMarkerHook`, `taskBarColorHook`, `taskBarContentHook`
- `taskTooltipFields`, `taskTooltipHook`
Use `taskTooltipFields` when you only need to choose which built-in fields appear in task-bar tooltips:
```ts
grid.gantt = {
...project,
visuals: {
taskTooltipFields: [
'name',
'startDate',
'endDate',
{ field: 'percentDone', label: 'Complete' },
'assignees',
],
},
};
```
## Task tooltip API
`taskTooltipFields` is an ordered array. Each entry can be a field id string or an object with a `field` and optional `label`:
```ts
type GanttTaskTooltipField =
| GanttTaskTooltipFieldId
| { field: GanttTaskTooltipFieldId; label?: string };
```
Object entries let you rename a built-in line. Set `label: ''` to render only the value without a `Label: ` prefix.
```ts
visuals: {
taskTooltipFields: [
{ field: 'name', label: '' },
{ field: 'percentDone', label: 'Done' },
],
}
```
Supported built-in fields:
| Field | Tooltip output |
|---|---|
| `summary` | Task name and completion percent, for example `Design - 50%`. |
| `name` | Task name. |
| `percentDone` | Completion percent. |
| `taskType` | Human-readable task type. |
| `status` | Human-readable task status. |
| `startDate` | Scheduled start date, formatted with `gantt.dateFormats.tooltip`. |
| `endDate` | Scheduled end date, formatted with `gantt.dateFormats.tooltip`. |
| `duration` | Formatted task duration in its retained or project-selected unit. |
| `taskMode` | `Auto Scheduled` or `Manually Scheduled`. |
| `schedulingWarnings` | Scheduling warnings joined into one line; skipped when empty. |
| `cost` | Formatted task cost; skipped when empty. |
| `assignees` | Comma-separated assignee names; skipped when empty. |
When `taskTooltipFields` is omitted, Gantt keeps the default tooltip: `summary`, `taskType`, `status`, `startDate`, `endDate`, `duration`, `taskMode`, `schedulingWarnings`, `cost`, and `assignees`. An empty array intentionally renders an empty tooltip.
`taskTooltipHook` has priority over `taskTooltipFields` and can replace the whole tooltip string:
```ts
visuals: {
taskTooltipFields: ['name'],
taskTooltipHook: ({ row }) => `${row.name}\n${row.status}`,
}
```
Return `null` or `undefined` from `taskTooltipHook` to fall back to `taskTooltipFields` or the default tooltip. Use the hook for custom row metadata that is not part of the built-in field list.
Type source: `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`.
Rendering sources: `features/timeline-background/*`, `grid/gantt-column-type.ts`.
---
# Gantt Examples
URL: https://pro.rv-grid.com/guides/gantt/examples/
Source: src/content/docs/guides/gantt/examples/index.mdx
Description: Search every runnable RevoGrid Gantt demo by framework and planning topic, then open its preview, source, guide, and API reference.
Surface: gantt
The example browser reads the shared demo catalog directly. Every registered Gantt demo and framework variant appears here automatically; there is no separate manual inventory to maintain.
## Copyable application recipes
These helpers live in the examples workspace. Copy the relevant `recipes` files beside your application example and use relative imports; they are not exports of `@revolist/gantt`.
---
# Application State And Persistence
URL: https://pro.rv-grid.com/guides/gantt/features/application-state/
Source: src/content/docs/guides/gantt/features/application-state.mdx
Description: Keep Gantt project collections synchronized with framework state, APIs, databases, and real-time application updates.
Surface: gantt
## Live example
Edit a task, save the project to browser memory, change it again, then load the saved snapshot. The transport is an in-memory example; wire your own backend for durable storage.
Your application owns persisted project data. Gantt owns scheduling, validation, projection, and supported local mutations while the grid is mounted.
## State Boundary
Keep these collections together as one logical project snapshot:
- `source`: authored task rows.
- `ganttDependencies`.
- `ganttCalendars`.
- `ganttResources`.
- `ganttAssignments`.
- `ganttBaselines`.
- `gantt`: project metadata and behavior configuration.
When a server response or collaborative update arrives, replace affected arrays and assign a new `gantt` object when configuration changed. Do not mutate an existing array in place and expect framework or custom-element change detection to observe it.
```ts
function applyProject(snapshot: ProjectSnapshot) {
grid.gantt = { ...snapshot };
grid.ganttCalendars = [...snapshot.calendars];
grid.ganttDependencies = [...snapshot.dependencies];
grid.ganttResources = [...snapshot.resources];
grid.ganttAssignments = [...snapshot.assignments];
grid.ganttBaselines = [...snapshot.baselines];
grid.source = snapshot.tasks.map(toSourceRow);
}
```
## Save Flow
1. Listen for the Gantt before-change events when the application needs authorization or validation.
2. Allow Gantt to commit supported local mutations.
3. Read the current project through the `GanttPlugin` instance or the application state updated by your integration.
4. Persist a complete snapshot or an application-defined patch.
5. Replace the public collections with the canonical server result when the server normalizes data.
Use [JSON project helpers](/guides/gantt/features/json-project-import-export/) for snapshots and [integration recipes](/guides/gantt/features/integrations-and-frameworks/) for REST, GraphQL, PostgreSQL, and Supabase-style persistence.
## Concurrent Updates
- Use stable entity IDs across every collection.
- Version project writes in your backend and reject stale updates.
- Re-run scheduling after dependency, calendar, task, resource, or assignment updates.
- Keep optimistic rollback data outside the rendered DOM.
- Use `gantt.readOnly` while a project is locked or awaiting approval.
See [Events And Methods](/guides/gantt/reference/events-methods/) for cancelable events and plugin access.
---
# Gantt Context Menu
URL: https://pro.rv-grid.com/guides/gantt/features/context-menu/
Source: src/content/docs/guides/gantt/features/context-menu.mdx
Description: Use the built-in Gantt row, column, and timeline context menus and extend them with RevoGrid Pro menu items.
Surface: gantt
## Live example
Right-click a task to inspect the built-in actions and the custom duplicate, assignment-panel, and workbook actions. The assignment action emits an application event for a host-owned panel.
Gantt contributes task and timeline actions through the RevoGrid Pro `DataGridContextMenuPlugin`; it does not replace your `rowContextMenu`, `columnContextMenu`, or `dataGridContextMenu` configuration. Gantt-compatible shared grid commands are composed first, then `dataGridContextMenu.getItems` and application items can extend the result.
The integration is enabled by default for every `GanttPlugin` instance. Set `gantt.contextMenu` to `false` to opt out of generated Gantt row, column, and timeline menu items while leaving application-provided RevoGrid context menu configuration untouched:
```ts
grid.gantt = {
...project,
contextMenu: false,
};
```
You can also disable one generated surface at a time:
```ts
grid.gantt = {
...project,
contextMenu: {
row: false,
column: true,
},
};
```
## Row Menu
Right-click a task-table row or task bar to open task actions:
- `Add`
- `Convert to milestone`
- `Indent`
- `Outdent`
- `Delete`
The default actions dispatch Gantt events:
```ts
grid.addEventListener('gantt-task-create', (event) => {
const { parentId, insertAfterTaskId } = event.detail ?? {};
});
grid.addEventListener('gantt-task-indent', (event) => {
indentTask(event.detail?.taskId);
});
grid.addEventListener('gantt-task-outdent', (event) => {
outdentTask(event.detail?.taskId);
});
grid.addEventListener('gantt-task-delete', (event) => {
deleteTask(event.detail?.taskId);
});
```
`Add` uses the focused row or clicked task bar as the insertion anchor. When the anchor has descendants, Gantt inserts the new sibling after the full descendant block so the task tree stays valid. Context-menu creation names the row `New task 1`, `New task 2`, and so on, and does not open the task editor.
The generated row menu is suppressed on the pinned add-task draft row because that row is owned by the internal `GanttAddTaskRowPlugin` and is not a task entity. If you disable Gantt context-menu integration and provide your own `rowContextMenu`, use `isGanttAddTaskRow()` or the draft cell marker to skip that row. See [Add Task Row](/guides/gantt/interaction/add-task-row/).
### Confirming Delete
The built-in **Delete** item dispatches the normal Gantt delete command, then Gantt emits the cancelable `gantt-before-task-change` event with `detail.action === 'delete'` immediately before it mutates any task. Call `preventDefault()` to keep the task (and its descendants) unchanged.
```ts
grid.addEventListener('gantt-before-task-change', (event) => {
if (event.detail.action !== 'delete') return;
const name = event.detail.previousValues.name ?? 'this task';
if (!window.confirm(`Delete ${name}?`)) {
event.preventDefault();
}
});
```
This protects deletion initiated from the default context-menu item and direct Gantt delete methods, so it is the preferred place for permission checks and a synchronous browser confirmation. It is called once per affected task; applications that allow bulk deletion should make their confirmation copy clear that descendants may also be removed.
`gantt-before-task-change` is synchronous. For an asynchronous application modal, prevent the original deletion, keep the selected task ids in application state, and call `gantt.deleteTasks(taskIds)` only after the user confirms. Guard the follow-up call so the same listener does not reopen the dialog, and recheck permissions before performing it.
## Column Menu
Right-click main grid headers to open column actions:
- `Sort ascending`
- `Sort descending`
- `Filter`
- `Hide column`
- `Columns`
Visibility follows the active column:
- Sort actions appear only when the target column has `sortable: true`.
- Filter appears only when the target column has `filter` configured.
- Hide column is available for a leaf task-table column; Columns opens the standard column-management dialog.
- If contextual filtering leaves no visible items, the menu is not opened.
Sort, filtering, hiding, and column management reuse the shared RevoGrid data-grid context-menu commands. Gantt removes incompatible generic commands such as row mutation, formatting, pinning, and schema operations.
## Timeline Menu
Right-click the pinned Gantt timeline/canvas header to open timeline actions:
- `Zoom`
- `Date range`
- `Show today`
- `Show flags` when milestone lines are configured
Zoom controls are hidden when the current zoom level cannot move further in either direction. Date range changes the visible timeline range; the visual toggles remain open after changing their checked state.
## Read-Only And Locked State
The row menu respects packaged Gantt protection state:
- `gantt.readOnly: true` hides mutating row actions.
- Selected locked tasks hide hierarchy actions.
- Mutation services still guard direct calls, so blocked task operations return `readonly-task`.
## Extending The Menu
Use the normal RevoGrid Pro menu config surfaces. Gantt keeps your custom items and contributes its generated items without mutating those configurations.
```ts
grid.rowContextMenu = {
items: [
{
name: 'Open task details',
action: (_event, focused, _range, _close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
openTaskDetails(task?.id);
},
},
],
};
```
## Customization
Gantt context menu customization has two layers:
- `gantt.contextMenu` controls the generated Gantt row, column, and timeline actions.
- `rowContextMenu` and `columnContextMenu` hold application menu config that should remain in the grid even when generated Gantt actions are disabled.
Use `gantt.contextMenu = false` when the application should fully own the menu:
```ts
grid.rowContextMenu = {
items: [
{
name: 'Open planning panel',
action: (_event, focused, _range, close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
openPlanningPanel(task?.id);
close();
},
},
],
};
grid.gantt = {
...project,
contextMenu: false,
};
```
Use partial opt-out when you want custom row actions but still want generated column and timeline actions:
```ts
grid.rowContextMenu = {
items: [
{
name: 'Assign owner',
action: (_event, focused, _range, close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
openOwnerPicker(task?.id);
close();
},
},
],
};
grid.gantt = {
...project,
contextMenu: {
row: false,
column: true,
},
};
```
Keep `gantt.contextMenu` enabled when you want to append app actions after the default Gantt actions:
```ts
grid.rowContextMenu = {
items: [
{
name: 'Show audit log',
action: (_event, focused, _range, close, context) => {
const task = context?.providers.data.getModel?.(focused?.y ?? 0, 'rgRow');
showTaskAuditLog(task?.id);
close();
},
},
],
};
grid.gantt = project;
```
You can also use a `resolve` function on the base RevoGrid menu config. Gantt preserves the resolver and the context menu plugin applies it to the composed menu:
```ts
grid.rowContextMenu = {
items: [
{ name: 'Open task details', action: openTaskDetailsFromMenu },
],
resolve(context) {
return context.target === 'row' ? undefined : null;
},
};
```
The packaged task editor item is controlled separately:
```ts
grid.ganttTaskEditorDialog = {
contextMenu: false,
};
```
Set `ganttTaskEditorDialog.contextMenu` to `true` when `gantt.contextMenu` is disabled but the application still wants the default **Edit...** item. Leave it unset to follow `gantt.contextMenu`.
For packaged extension items, use the built-in Gantt context-menu helpers:
```ts
import { createGanttContextMenuExtensionItems } from '@revolist/gantt';
const customItems = createGanttContextMenuExtensionItems({
duplicate: {
copyName: (task) => `${task.name ?? 'Task'} copy`,
},
assignResource: {
onAssignResource: (context) => {
if (context.taskId) {
openResourcePicker(context.taskId);
}
},
},
delete: false,
export: { name: 'Export task rows' },
});
grid.rowContextMenu = {
...grid.rowContextMenu,
items: [...(grid.rowContextMenu?.items ?? []), ...customItems],
};
```
## Legacy Context Menu
`rowContextMenu` and `columnContextMenu` remain application-owned low-level menu configuration. Use `dataGridContextMenu.getItems` when you need to transform the Gantt-compatible shared grid commands: registered Gantt contributions run first, then this application resolver receives the filtered items.
Gantt registers and disposes its own contribution with the plugin lifecycle. It does not rewrite host menu properties, so application-provided items are not duplicated during Gantt configuration updates.
The packaged task editor follows `gantt.contextMenu` by default for its **Edit...** row-menu item. Set `ganttTaskEditorDialog.contextMenu` to `false` to disable only the editor item, or `true` to keep the editor item when generated Gantt menus are disabled.
---
# Editing, Validation, Diagnostics
URL: https://pro.rv-grid.com/guides/gantt/features/editing-validation-diagnostics/
Source: src/content/docs/guides/gantt/features/editing-validation-diagnostics.mdx
Description: Wire Gantt permission checks, editor forms, popovers, validation, and diagnostics panels.
Surface: gantt
## Live example
Try moving Visual Design into the April 14–18 freeze window: the before-change handler rejects the move. Inspect the validation result and protected-row indicators, then open the Task Editing guide for the custom editor.
These APIs are presentation-free. They make decisions and shape data, while your application owns forms, popovers, panels, toasts, and persistence.
The application recipes below live in the [examples source browser](/guides/gantt/examples/#copyable-application-recipes). Copy their `recipes/` files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.
```ts
import {
GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT,
GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT,
GANTT_BEFORE_TASK_CHANGE_EVENT,
TASK_EDITOR_FIELD_SCHEMA,
buildDependencyValidationSummary,
buildResourceOverallocationSummary,
createGanttRoleEditabilityHelpers,
createTaskDetailPopoverModel,
createTaskEditorFormValues,
normalizeTaskEditorSubmit,
} from '@revolist/gantt';
import {
createApprovalGateValidator,
createForbiddenDateRangeValidator,
createGanttBeforeTaskChangeValidationHandler,
createLockedPhaseValidator,
createRoleGuardValidator,
} from './recipes/validation-recipes';
```
## Role-Based Editability
Before-change events are cancelable. Use role helpers to decide whether a task, dependency, or assignment mutation should proceed. Call `event.preventDefault()` to stop the packaged mutation before the Gantt store changes; timeline previews are cleared and the next render keeps the previous task, dependency, or assignment data.
```ts
const policy = {
roles: {
viewer: [],
planner: ['task:*', 'dependency:create', 'dependency:delete'],
staffing: ['assignment:edit'],
admin: ['*'],
},
fallback: 'deny',
} as const;
const editability = createGanttRoleEditabilityHelpers(policy);
const getRoleContext = () => ({ roles: currentUser.roles });
grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => {
if (!editability.canEditTask(event.detail, getRoleContext())) {
event.preventDefault();
}
});
grid.addEventListener(GANTT_BEFORE_DEPENDENCY_CHANGE_EVENT, (event) => {
if (!editability.canEditDependency(event.detail, getRoleContext())) {
event.preventDefault();
}
});
grid.addEventListener(GANTT_BEFORE_ASSIGNMENT_CHANGE_EVENT, (event) => {
if (!editability.canEditAssignment(event.detail, getRoleContext())) {
event.preventDefault();
}
});
```
Task before-change actions cover `move`, `resize`, `create`, `edit`, `progress`, `split`, `indent`, `outdent`, and `delete`. Dependency before-change actions cover `create`, `delete`, `edit`, and `replace`; `replace` is emitted by predecessor/successor inline field edits and includes `previousDependencies` plus proposed `dependencies` because one cell edit can add, update, and remove multiple links. Assignment edits emit proposed `assignments` and `previousAssignments`.
For task edits, `event.detail.sourcePatch` contains the flat source-row fields that the packaged Gantt store will write. Use it when persisting custom row fields or when your backend uses public source names such as `duration`, `percentDone`, and `taskMode`.
```ts
grid.addEventListener(GANTT_BEFORE_TASK_CHANGE_EVENT, (event) => {
const { sourceIndex, sourcePatch, previousSourceValues } = event.detail;
if (sourceIndex === undefined || !sourcePatch) {
return;
}
queueTaskRowPersistence({
rowIndex: sourceIndex,
patch: sourcePatch,
previousValues: previousSourceValues ?? {},
});
});
```
## Custom Task Editor Forms
Use the field schema and initial value builder to render a framework form, then normalize submitted values into a `TaskUpdate` patch.
For a complete standalone UI example, see [Task Editing](../../interaction/task-editing/).
```ts
const fields = TASK_EDITOR_FIELD_SCHEMA.filter((field) =>
['name', 'startDate', 'endDate', 'progressPercent', 'resourceLabels'].includes(field.id),
);
const values = createTaskEditorFormValues(task, resources, assignments);
renderTaskEditor({
fields,
values,
onSubmit(nextValues) {
const result = normalizeTaskEditorSubmit(task, nextValues);
if (!result.ok) {
renderErrors(result.errors);
return;
}
updateTask(task.id, result.patch);
},
});
```
## Task Detail Popovers
`createTaskDetailPopoverModel()` returns sections, fields, warnings, and resource labels. Your app handles hover/focus, positioning, and rendering.
```ts
const model = createTaskDetailPopoverModel({
row,
task,
resources,
assignments,
});
renderPopover({
title: model.title,
subtitle: model.subtitle,
sections: model.sections,
resources: model.resourceAssignments.map((assignment) => assignment.displayValue),
});
```
## Dependency Diagnostics
Use dependency summaries to power badges, side panels, or task focus commands.
```ts
const summary = buildDependencyValidationSummary({
conflicts: schedulerResult.conflicts,
issues: schedulerResult.issues,
rows: projectedRows,
});
renderDiagnosticsPanel({
count: summary.totalCount,
groups: summary.groups,
});
```
## Resource Over-Allocation Diagnostics
Use resource summaries when staffing dashboards need to show who is overloaded and when.
```ts
const overloads = buildResourceOverallocationSummary({
rows: resourcePlanningRows,
loadSummaries: resourceLoadSummaries,
conflicts: schedulerResult.conflicts,
});
renderResourceHealth({
count: overloads.totalCount,
peakOverloadUnits: overloads.peakOverloadUnits,
groups: overloads.groups,
});
```
## Custom Validation Recipes
Compose validators and attach the generated handler to `gantt-before-task-change`.
```ts
const validators = [
createRoleGuardValidator({ policy }),
createForbiddenDateRangeValidator({
ranges: [{ startDate: '2026-05-20', endDate: '2026-05-27', label: 'release freeze' }],
}),
createLockedPhaseValidator({
lockedPhaseIds: ['closed'],
getPhaseId: (detail) => detail.taskId ? phaseByTaskId[detail.taskId] : null,
}),
createApprovalGateValidator({
actions: ['move', 'resize', 'progress'],
requiresApproval: (detail) => detail.changes.progressPercent === 100,
}),
];
grid.addEventListener(
GANTT_BEFORE_TASK_CHANGE_EVENT,
createGanttBeforeTaskChangeValidationHandler({
context: () => validationContext,
validators,
onReject: ({ decision }) => showValidationToast(decision.message ?? 'Change rejected'),
}),
);
```
---
# Grid, Export, Rendering
URL: https://pro.rv-grid.com/guides/gantt/features/grid-export-rendering/
Source: src/content/docs/guides/gantt/features/grid-export-rendering.mdx
Description: Use Gantt row export mapping, column presets, status indicators, and context-menu extension recipes.
Surface: gantt
## Live example
Use the toolbar workbook action to export the task table, inspect the locked task styling, toggle baseline and critical-path visuals, or use Print / PDF for a browser report of mounted viewport content.
These helpers package common grid-side Gantt work without coupling your application to internal timeline layout data.
The application recipes below live in the [examples source browser](/guides/gantt/examples/#copyable-application-recipes). Copy their `recipes/` files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.
```ts
import {
createGanttColumnPreset,
createGanttContextMenuExtensionItems,
createTaskStatusIndicators,
mapGanttRowsToExcelExport,
} from '@revolist/gantt';
import {
createGanttExcelExportRecipe,
createGanttPdfExcelReportingRecipe,
createGanttTaskStatusIndicatorRecipe,
} from './recipes/grid-projection-examples';
```
## Gantt Excel Export Mapping
Use `mapGanttRowsToExcelExport()` when exporting projected task rows. The mapper uses an allowlist of user-facing fields and skips renderer-only timeline metadata.
```ts
const rows = mapGanttRowsToExcelExport(taskRows, project, {
includeResourceRows: false,
});
downloadWorkbook({
sheetName: project.name,
rows,
});
```
For shells that prefer a recipe object:
```ts
const recipe = createGanttExcelExportRecipe({
rows: taskRows,
project,
});
downloadWorkbook({
fields: recipe.fieldNames,
rows: recipe.rows,
});
```
## PDF And Excel Reporting
Use the combined reporting recipe when your app has separate toolbar buttons for stakeholder PDF reports and spreadsheet handoff. PDF uses the browser print dialog, so users can choose **Save as PDF**. Excel dispatches the Pro `export-excel` event, so include `ExportExcelPlugin` with the grid plugins.
```ts
import {
GanttPlugin,
} from '@revolist/gantt';
import {
createGanttPdfExcelReportingRecipe,
} from './recipes/grid-projection-examples';
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
const host = document.querySelector('#gantt-report');
if (!grid || !host) {
throw new Error('Gantt report target was not found');
}
grid.plugins = [GanttPlugin, ExportExcelPlugin];
grid.gantt = project;
const reportRange = {
startDate: '2026-05-01',
endDate: '2026-05-31',
};
const reporting = createGanttPdfExcelReportingRecipe({
grid,
target: host,
document,
window,
excel: {
workbookName: `${project.name}.xlsx`,
},
});
pdfButton.addEventListener('click', () => {
const result = reporting.exportPdf(project, reportRange);
if (!result.ok) {
showExportError(result.reason);
}
});
excelButton.addEventListener('click', () => {
reporting.exportExcel({
sheetName: 'Tasks',
});
});
```
For a single "export report" action, call both commands together:
```ts
const result = reporting.exportReport(project, {
range: { label: 'Executive status report' },
excel: { sheetName: 'Task register' },
});
if (!result.pdf.ok) {
showExportError(result.pdf.reason);
}
```
## Column Presets
Column presets group the default Gantt task-table columns into common planning views.
```ts
grid.columns = createGanttColumnPreset('schedule');
grid.source = [...taskRows];
```
Available presets include `core`, `schedule`, `dependencies`, `resources`, `baseline`, `cost`, `progress`, and `all`.
## Read-Only And Locked Indicators
Use indicator helpers to produce labels and class names for custom cell renderers.
```ts
const status = createTaskStatusIndicators({
taskId: row.id,
projectReadOnly,
taskLocked: lockedTaskIds.has(row.id),
});
renderIndicatorCell({
className: status.classNames.join(' '),
title: status.label,
indicators: status.indicators,
});
```
For multiple rows:
```ts
const indicatorRows = createGanttTaskStatusIndicatorRecipe({
rows: taskRows,
projectReadOnly,
lockedTaskIds,
});
```
## Context Menu Extensions
Gantt installs separate RevoGrid Pro row and column context menus. Row menus are task-oriented (`Add`, `Convert to milestone`, `Indent`, `Outdent`, `Delete`). Main grid column menus expose search, sort, and filter actions. The pinned Gantt canvas column menu exposes timeline zoom controls.
Use the built-in context-menu helpers with `rowContextMenu` to add duplicate, assign-resource, export, or custom task actions.
```ts
const contextMenuItems = createGanttContextMenuExtensionItems({
duplicate: {
copyName: (task) => `${task.name ?? 'Task'} copy`,
},
delete: {
confirm: (context) => window.confirm(`Delete ${context.task?.name ?? 'task'}?`),
onDeleteTask: (context) => {
if (context.taskId) {
deleteTask(context.taskId);
}
},
},
assignResource: {
onAssignResource: (context) => {
if (context.taskId) {
openResourcePicker(context.taskId);
}
},
},
export: {
exportOptions: (context) => ({
workbookName: 'gantt.xlsx',
sheetName: context.task?.name ?? 'Gantt',
}),
},
});
grid.rowContextMenu = {
...grid.rowContextMenu,
items: [...(grid.rowContextMenu?.items ?? []), ...contextMenuItems],
};
```
Disable individual items with `false`.
```ts
const exportOnly = createGanttContextMenuExtensionItems({
duplicate: false,
delete: false,
assignResource: false,
export: { name: 'Export visible Gantt tasks' },
});
```
When printing a nested container, the recipe keeps its ancestor path visible and hides sibling application content until cleanup. Printing captures the mounted view; virtualized offscreen rows are not expanded automatically.
---
# Integrations And Frameworks
URL: https://pro.rv-grid.com/guides/gantt/features/integrations-and-frameworks/
Source: src/content/docs/guides/gantt/features/integrations-and-frameworks.mdx
Description: Persist, import, export, print, and embed Gantt projects with framework-neutral recipes.
Surface: gantt
## Live example
Save and load a project through the labeled in-memory transport, or export JSON and import the file again. This demonstrates the snapshot boundary; REST, GraphQL, SQL, and Supabase source recipes below require your own service/client.
These recipes keep transport and framework ownership in your application. Inject `fetch`, state, database clients, DOM targets, and file readers/writers from your host app.
The application recipes below live in the [examples source browser](/guides/gantt/examples/#copyable-application-recipes). Copy their `recipes/` files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.
```ts
import {
cloneProjectSnapshot,
exportProjectSnapshot,
parseProjectSnapshotJson,
} from '@revolist/gantt';
import {
ProjectSnapshotGraphqlAdapter,
} from './recipes/graphql-adapter';
import {
ProjectSnapshotRestAdapter,
} from './recipes/rest-adapter';
import {
ProjectSnapshotSupabaseAdapter,
buildCreateProjectSnapshotTableSql,
buildLoadProjectSnapshotSql,
buildSaveProjectSnapshotSql,
} from './recipes/postgres-persistence';
import {
createJsonSnapshotRecipe,
createPrintPdfExportRecipe,
} from './recipes/persistence-examples';
import {
printGanttContainer,
} from './recipes/print-pdf-recipe';
```
## JSON Snapshot Helpers
Use JSON helpers for local import/export, file handoff, tests, undo buffers, or backend payloads.
```ts
const draft = cloneProjectSnapshot(project);
const json = exportProjectSnapshot(draft, { space: 2 });
const parsed = parseProjectSnapshotJson(json);
if (parsed.ok) {
applyProject(parsed.project);
}
```
For file picker flows:
```ts
const recipe = createJsonSnapshotRecipe({
readText: () => fileInputText,
writeText: (_fileName, contents) => saveTextFile(contents),
});
await recipe.save(project, 2);
const imported = await recipe.load();
```
## REST Persistence
`ProjectSnapshotRestAdapter` loads and saves serialized project snapshots through an injected `fetchImpl`.
```ts
const adapter = new ProjectSnapshotRestAdapter({
url: '/api/gantt/project/demo',
headers: { authorization: 'Bearer token' },
fetchImpl,
space: 2,
});
const project = await adapter.load();
await adapter.save(project);
```
## GraphQL Persistence
Use the GraphQL adapter when the backend exposes project snapshots through operations.
```ts
const adapter = new ProjectSnapshotGraphqlAdapter({
url: '/graphql',
fetchImpl,
loadQuery: 'query LoadProject($id: ID!) { projectSnapshot(id: $id) }',
saveMutation: 'mutation SaveProject($id: ID!, $snapshot: String!) { saveProjectSnapshot(id: $id, snapshot: $snapshot) }',
});
const project = await adapter.load({ variables: { id: 'demo' } });
await adapter.save(project, { variables: { id: 'demo' } });
```
## Supabase And PostgreSQL
Use SQL builders with any PostgreSQL executor, or the Supabase-like adapter with a compatible client shape.
```ts
await executor.query(buildCreateProjectSnapshotTableSql());
await executor.query(buildSaveProjectSnapshotSql(project));
const result = await executor.query(
buildLoadProjectSnapshotSql(project.id),
);
```
```ts
const adapter = new ProjectSnapshotSupabaseAdapter(supabaseLikeClient);
await adapter.save(project);
const restored = await adapter.load(project.id);
```
## Print And Save As PDF
The print recipe prepares a Gantt container for browser printing. Users can choose Print or Save as PDF in the native dialog.
```ts
const result = printGanttContainer(host, {
document,
window,
metadata: {
title: project.name,
subtitle: `${project.tasks.length} tasks`,
range: { startDate: '2026-05-01', endDate: '2026-05-31' },
},
});
```
For a reusable command:
```ts
const printRecipe = createPrintPdfExportRecipe({
target: host,
document,
window,
});
printRecipe.exportProject(project, { label: 'May reporting window' });
```
## Framework Examples
Use the complete public-package workflow for your primary wrapper:
- [Vanilla TypeScript quick start](/guides/gantt/introduction/vanilla-typescript/)
- [React quick start](/guides/gantt/introduction/react/)
- [Vue 3 quick start](/guides/gantt/introduction/vue/)
- [Angular quick start](/guides/gantt/introduction/angular/)
Svelte remains a secondary integration recipe. Use the custom-element approach from the Vanilla guide until its packaged wrapper and support contract match the four primary framework paths.
The [Resource Planning demo](https://demo.rv-grid.com/gantt-resource-planning/ts) shows a host-owned resource filter UI. Filtering updates `grid.gantt.resourceFilterIds` without reading rendered cells.
Resource filtering updates `grid.gantt.resourceFilterIds`:
```ts
grid.gantt = {
...project,
resourceFilterIds: selectedResourceIds.length === allResourceIds.length
? undefined
: selectedResourceIds,
};
```
---
# JSON Project Import And Export
URL: https://pro.rv-grid.com/guides/gantt/features/json-project-import-export/
Source: src/content/docs/guides/gantt/features/json-project-import-export.mdx
Description: Serialize, clone, parse, and lightly validate complete Gantt project snapshots.
Surface: gantt
## Live example
Export JSON, edit a task, then import the exported file to restore the snapshot. Invalid JSON reports an error without replacing the project.
JSON project import/export provides a framework-neutral way to move complete Gantt project snapshots between files, APIs, tests, undo buffers, and server-side workflows. The helpers operate on `ProjectSnapshot` data only. They do not depend on the grid, DOM, or framework wrappers.
Import snapshot data helpers from `@revolist/gantt` and the file-picker recipe from your copied examples:
The application recipes below live in the [examples source browser](/guides/gantt/examples/#copyable-application-recipes). Copy their `recipes/` files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.
```ts
import {
cloneProjectSnapshot,
exportProjectSnapshot,
parseProjectSnapshotJson,
} from '@revolist/gantt';
import {
createJsonSnapshotRecipe,
} from './recipes/persistence-examples';
```
## Export A Snapshot
Use `exportProjectSnapshot()` to serialize the current project snapshot.
```ts
const json = exportProjectSnapshot(project, { space: 2 });
```
The optional `space` value is passed to `JSON.stringify`, so you can use it for readable file exports.
## Import A Snapshot
Use `parseProjectSnapshotJson()` to parse JSON into a typed result instead of throwing for common invalid payloads.
```ts
const parsed = parseProjectSnapshotJson(json);
if (!parsed.ok) {
showImportError(parsed.error);
return;
}
applyProject(parsed.project);
```
The parser checks the top-level project shape:
- Required string fields: `id`, `name`, `version`, `currency`, `timeZone`, `primaryCalendarId`, and `updatedAt`.
- Required array fields: `tasks`, `dependencies`, `resources`, `assignments`, `calendars`, and `baselines`.
It intentionally performs lightweight validation. Run scheduler validation or your application-specific validation after import when you need deeper checks for dates, task references, dependency cycles, resource assignments, or business rules.
## Clone Before Editing
Use `cloneProjectSnapshot()` when you need a JSON-safe deep copy before staging edits.
```ts
const draft = cloneProjectSnapshot(project);
draft.name = 'Scenario copy';
```
The clone is detached from nested task, dependency, resource, assignment, calendar, and baseline arrays.
## File Picker Recipe
For host applications that own file pickers and save dialogs, use `createJsonSnapshotRecipe()` with injected text readers and writers.
```ts
const recipe = createJsonSnapshotRecipe({
readText: () => selectedFile.text(),
writeText: (fileName, contents) => downloadTextFile(fileName, contents),
fileName: (project) => `${project.id}-snapshot.json`,
});
const imported = await recipe.load();
await recipe.save(project, 2);
```
The recipe throws an `Error` when load/save hooks are missing or the imported JSON fails parsing. Use the lower-level parser directly when you prefer result objects over exceptions.
## What The JSON Contains
The exported JSON is the project snapshot, not rendered grid state. It includes project metadata and entity arrays:
- Tasks
- Dependencies
- Resources
- Assignments
- Calendars
- Baselines
It does not include projected Gantt row fields, rendered timeline geometry, current scroll position, expanded row state, active selection, filters, or toolbar state.
## Persistence Adapters
The REST, GraphQL, Supabase, and PostgreSQL examples build on the same JSON snapshot helpers. Use those adapters when the snapshot should be loaded from or saved to an application backend.
See [Integrations And Frameworks](../integrations-and-frameworks/) for adapter examples.
---
# Locked Tasks
URL: https://pro.rv-grid.com/guides/gantt/features/locked-tasks/
Source: src/content/docs/guides/gantt/features/locked-tasks.mdx
Description: Use task-level locking to block packaged Gantt task mutations for protected tasks.
Surface: gantt
## Live example
Compare the gray locked task with its editable neighbors. Try editing or dragging the locked task, then enable Read only to protect the whole project.
Locked tasks let an application protect individual tasks after approval, baseline capture, phase closure, or any other workflow decision. A locked task remains visible in the task table and timeline, but packaged Gantt task mutations are rejected for that task.
Set `locked: true` on the flat task source row:
```ts
const tasks = [
{
id: 'design-signoff',
parentId: null,
name: 'Design sign-off',
type: 'milestone',
workflowStatus: 'done',
startDate: '2026-04-15',
endDate: '2026-04-15',
duration: 1,
percentDone: 100,
calendarId: 'standard',
tags: ['approved'],
locked: true,
},
];
```
## What It Blocks
`locked: true` blocks packaged task mutations for the locked task:
- Timeline task movement and resizing.
- Split creation, movement, and removal.
- Progress updates.
- Inline task-field edits routed through the Gantt task mutation service.
- Indent and outdent actions.
- Child task creation under a locked parent.
- Delete when the selected task subtree contains a locked task.
Blocked task mutations return `readonly-task` and leave task data unchanged.
## What Still Works
Locking a task does not remove it from scheduling, rendering, or inspection flows:
- The task still renders in the table and timeline.
- Scheduler recalculation can still read the task and include it in dependencies, critical path, baselines, and diagnostics.
- Search, filter, sort, expand/collapse, scroll, and zoom remain available.
- Non-locked tasks continue to edit normally.
## Boundaries
The source-row `locked` field is task-level protection, not a complete permission or checkout system.
Dependency changes are not globally disabled by locking a task. Assignment/resource edits are also handled by their own mutation service. Use `gantt.readOnly` when the whole project should reject packaged task, dependency, and assignment mutations.
For application-specific policy, pair locked tasks with cancelable Gantt events or validation recipes.
```ts
grid.addEventListener('gantt-before-task-change', (event) => {
if (lockedTaskIds.has(event.detail.taskId)) {
event.preventDefault();
}
});
```
## Visual Indicators
Use the status indicator helpers when you want to show a lock label, class name, icon, or tooltip in a custom renderer.
```ts
const status = createTaskStatusIndicators({
taskId: row.id,
taskLocked: row.locked === true,
});
renderIndicatorCell({
className: status.classNames.join(' '),
title: status.label,
indicators: status.indicators,
});
```
The helper returns metadata only, so the host application can choose the exact icon and placement.
## Context Menu
The built-in Gantt row context menu hides hierarchy actions when the selected task is locked. Mutating row actions are also guarded by the task mutation service, so direct calls still return `readonly-task`.
---
# Microsoft Project Import And Export
URL: https://pro.rv-grid.com/guides/gantt/features/microsoft-project/
Source: src/content/docs/guides/gantt/features/microsoft-project.mdx
Description: Import Microsoft Project XML or native MPP14 schedules, and export Microsoft Project XML with the optional Gantt converter.
Surface: gantt
## Live example
Import the sample Microsoft Project XML into the grid, export it again, and inspect conversion diagnostics. File conversion runs locally in your browser.
Microsoft Project conversion is shipped as an optional package so applications that do not exchange Project files do not include the XML or native MPP parser.
```sh
pnpm add @revolist/revogrid-gantt-msp
```
## Install the plugin
Register `GanttMspImportPlugin` beside `GanttPlugin` from `@revolist/gantt`:
```ts
import { GanttPlugin } from '@revolist/gantt';
import { GanttMspImportPlugin } from '@revolist/revogrid-gantt-msp';
grid.plugins = [GanttPlugin, GanttMspImportPlugin];
```
Get the installed converter and import a file selected by the user:
```ts
const plugins = await grid.getPlugins();
const converter = plugins.find(
(plugin): plugin is GanttMspImportPlugin =>
plugin instanceof GanttMspImportPlugin,
);
const result = await converter?.importFile(file, {
projectId: 'customer-rollout',
currency: 'EUR',
timeZone: 'Europe/Lisbon',
});
console.table(result?.warnings);
```
The plugin applies imported tasks and project collections through the same reactive properties used by `GanttPlugin`. It returns the complete `ProjectSnapshot` as well, allowing the application to persist or inspect the imported data.
## Convert without a grid
The framework-neutral conversion functions can be used in upload handlers, persistence services, tests, and export actions:
```ts
import {
exportMicrosoftProjectXml,
importMicrosoftProjectMpp,
importMicrosoftProjectXml,
} from '@revolist/revogrid-gantt-msp';
const { project, warnings } = importMicrosoftProjectXml(xml, {
projectId: 'customer-rollout',
});
const exported = exportMicrosoftProjectXml(project);
downloadXml(exported.xml);
const native = await importMicrosoftProjectMpp(await mppFile.arrayBuffer(), {
projectId: 'native-customer-rollout',
});
```
## File support
| Format | Import | Export | Notes |
| --- | --- | --- | --- |
| Microsoft Project XML (`.xml`) | Yes | Yes | Full initial XML mapping. |
| MPP14 (`.mpp`) | Yes | No | Native format produced by Project 2010 and later. |
| MPP8, MPP9, or MPP12 | No | No | Returns `unsupported-mpp-version`. |
| Encrypted MPP | No | No | Returns `encrypted-mpp-not-supported`. |
`importFile()` detects the OLE compound-document signature rather than trusting the extension. Browser `File` objects already provide the required `arrayBuffer()` method. A custom `.mpp` file-like object without binary access returns `binary-reader-unavailable`, and invalid native content returns `invalid-mpp`. Custom XML-only file-like objects can continue implementing only `name` and `text()`.
## Supported XML data
The adapter maps:
- task hierarchy, names, dates, durations, and notes;
- tasks, summary tasks, and milestones;
- completion progress;
- finish-to-start, start-to-start, finish-to-finish, and start-to-finish dependencies;
- the first task baseline exposed by the MSP XML converter.
The conversion result includes warnings when a safe default is introduced or a field cannot be represented. The initial adapter creates a standard Monday-Friday calendar because the underlying converter does not expose Microsoft Project calendar definitions.
## Native MPP14 data
The native importer maps task names, hierarchy, dates, durations, summary tasks, milestones, finish-to-start, start-to-start, finish-to-finish, and start-to-finish dependencies, plus first-baseline dates. It generates WBS codes and a standard Monday-Friday calendar.
The first native release intentionally defaults progress and dependency lag to zero, and omits notes, resources, assignments, Microsoft Project calendars, and additional baselines. The returned `unsupported-fields` warning makes this loss explicit. Native MPP export is not supported; export XML when the resulting schedule must be opened in Microsoft Project.
The converter package ships a genuine legacy `sample.mpp` under `examples/third-party/mpxj/` for signature and version testing. The fixture comes from MPXJ's LGPL-2.1 test corpus and includes its upstream license and exact provenance. Because it is MPP9, importing it returns `unsupported-mpp-version`.
---
# Read-Only Mode
URL: https://pro.rv-grid.com/guides/gantt/features/read-only-mode/
Source: src/content/docs/guides/gantt/features/read-only-mode.mdx
Description: Use Gantt read-only mode to block packaged task, dependency, and assignment mutations while keeping the schedule inspectable.
Surface: gantt
## Live example
Enable Read only, then try changing task dates or dragging a bar. The project remains inspectable: scroll, zoom, expand rows, and inspect dependencies. Disable Read only to edit again.
Read-only mode turns the packaged Gantt editing surface into an inspection surface. Users can still view the task table and timeline, navigate, search, filter, sort, zoom, and use non-mutating UI, but packaged project mutations are rejected.
Enable it on the Gantt config:
```ts
grid.gantt = {
id: 'project-1',
name: 'Project Roadmap',
version: '1',
currency: 'USD',
timeZone: 'UTC',
primaryCalendarId: 'standard',
updatedAt: new Date().toISOString(),
readOnly: true,
};
```
## What It Blocks
`readOnly: true` blocks mutations routed through the packaged Gantt services:
- Task changes: move, resize, split, progress edits, inline task-field edits, task creation, indent, outdent, and delete.
- Dependency changes: create, update, delete, and predecessor/successor field replacement.
- Assignment changes: assignee/resource field updates and assignment replacement.
Blocked mutations return a failure result instead of updating the stores. Task changes return `readonly-task`; dependency and assignment changes return `readonly-project`.
## What Still Works
Read-only mode does not turn the grid into a static screenshot. Non-mutating workflows remain available:
- Timeline rendering, task bars, dependencies, baselines, critical-path visuals, and diagnostics.
- Tree expand/collapse, search, filtering, sorting, scrolling, and zoom.
- External application-controlled state replacement, such as loading a different project snapshot or changing the Gantt config.
The built-in Gantt context menu also hides mutating row actions when the project is read-only.
## Difference From Grid Readonly
`gantt.readOnly` is a Gantt project mutation guard. It protects the packaged task, dependency, and assignment mutation paths.
The RevoGrid `readonly` property is a grid-level editing setting. Use it when you also want to prevent generic cell editing behavior outside Gantt's mutation services.
```ts
grid.readonly = true;
grid.gantt = {
...projectConfig,
readOnly: true,
};
```
---
# Toolbar And Navigation
URL: https://pro.rv-grid.com/guides/gantt/features/toolbar-and-navigation/
Source: src/content/docs/guides/gantt/features/toolbar-and-navigation.mdx
Description: Build custom Gantt toolbar buttons for export, baselines, critical path, and timeline navigation.
Surface: gantt
## Live example
Use the toolbar to search, undo and redo edits, zoom, choose columns, toggle dependencies and critical path, capture a baseline, and export a workbook.
These helpers let your application render its own toolbar while reusing Gantt command behavior.
The built-in `defineGanttToolbar()` command bar includes an Excel export icon button by default. The button dispatches the same Pro `export-excel` event as `exportGanttExcel()`, so add `ExportExcelPlugin` to the grid when you want the packaged plugin to create the workbook. Hide it with `controls: { exportExcel: false }` or hide the export group with `controls: { export: false }`.
## Built-In Toolbar
Use `defineGanttToolbar()` when you want the packaged Gantt command bar instead of rendering your own buttons. Mount it after assigning `grid.gantt` so it can read the initial visual state.
```ts
import { defineGanttToolbar } from '@revolist/gantt';
const toolbar = document.querySelector('#gantt-toolbar');
const grid = document.querySelector('revo-grid');
if (!toolbar || !grid) {
throw new Error('Gantt toolbar host or grid was not found');
}
grid.gantt = projectConfig;
defineGanttToolbar(toolbar, {
grid,
columns: [
{ prop: 'wbs', label: 'WBS' },
{ prop: 'name', label: 'Name' },
{ prop: 'startDate', label: 'Start Date' },
{ prop: 'duration', label: 'Duration' },
],
visuals: {
showDependencies: true,
showBaseline: false,
showCriticalPath: false,
},
});
```
### Adaptive overflow
The toolbar measures its available width and its rendered control groups, including custom slots. When the controls no longer fit on one row, lower-priority groups move into **More actions** (`…`). They return when space becomes available. Each command and custom slot keeps a single live instance, so entered values and custom renderer state survive resizing.
Add task and search remain inline longer. View stays inline because it already owns a popup. Width changes, custom content changes, and font loading trigger measurement; overflow decisions use the measured content rather than screen-width breakpoints.
```ts
defineGanttToolbar(toolbar, {
grid,
overflow: { priorities: { tree: 90, zoom: 85, search: 40 } },
localeText: { more: 'More actions', moreOptions: 'More toolbar actions' },
});
```
Higher priority values keep a whole logical group inline longer. Set `overflow: false` or `controls.overflow: false` to retain wrapping. Hiding `overflowTrigger` or `overflowContent` also disables automatic overflow so commands remain reachable. Use `overflowTrigger`, `overflowContent`, `overflowStart`, and `overflowEnd` for the same before/replace/after DOM slots as other toolbar parts.
### Control Visibility
Every built-in toolbar option can be hidden with `controls`. Omitted control flags are visible by default, except `baseline` and `calendars`, which are opt-in. Working calendars requires `GanttCalendarEditorPlugin`.
```ts
defineGanttToolbar(toolbar, {
grid,
controls: {
search: false,
history: false,
tree: false,
zoom: true,
zoomOut: false,
tasks: true,
indent: false,
outdent: false,
export: false,
columns: false,
dependencies: true,
baseline: false,
criticalPath: true,
},
});
```
Parent group flags hide their child buttons:
| Parent flag | Child buttons |
| --- | --- |
| `history` | `undo`, `redo` |
| `tree` | `expandAll`, `collapseAll` |
| `zoom` | `zoomIn`, `zoomOut`, `zoomSlider` |
| `tasks` | `addTask`, `structure` (`indent`, `outdent`) |
| `export` | `exportExcel` |
| `view` | `viewTrigger`, `viewContent`, `viewTable`, `viewTimeline`, `calendars` |
| `viewTable` | `columns` |
| `viewTimeline` | `dependencies`, `baseline`, `criticalPath` |
| `toolbar` | Entire toolbar and all slots |
Use `spacer: false` to remove the flexible gap, and `historyCounts: false` to hide the visual count badges. History accessible labels still include counts. Hide `zoomIn` and `zoomOut` independently to keep just the slider, or hide `zoomSlider` to keep just the buttons.
Hidden visual controls do not write their local defaults back into `grid.gantt.visuals`. You can still set hidden visual behavior directly in `grid.gantt.visuals`.
The dependency toggle is display-only. Turning it off updates `grid.gantt.visuals.showDependencies = false`, which hides dependency arrows without clearing `grid.ganttDependencies` or changing scheduler dependency inputs.
```ts
import {
captureGanttBaseline,
createGanttToolbarQuickWinActions,
exportGanttCsv,
exportGanttExcel,
fitGanttToProject,
listGanttToolbarQuickWinActions,
scrollGanttToToday,
toggleGanttCriticalPath,
} from '@revolist/gantt';
```
Indent and outdent start disabled and become available only when the current command selection can perform that action. Availability uses the same task resolution and validation as the commands, including checkbox/range selection, hierarchy boundaries, locked tasks, permissions, and project read-only mode. Custom toolbars can query `GanttPlugin.canIndentTasks(taskIds?)` and `canOutdentTasks(taskIds?)`; omitting ids uses the current command selection. Slot context also exposes `canIndent` and `canOutdent`.
## Translations and Direction
`localeText` accepts partial overrides for all toolbar-owned visible text, accessible names, placeholders, and tooltips. Unspecified entries use `GANTT_TOOLBAR_LOCALE`, the exported English defaults. `undo` and `redo` accept a `{count}` placeholder. `locale` formats history counts; it does not automatically translate text. Column labels and custom zoom-level names come from your configuration. Translate slider values by level id with `zoomLevels`.
```ts
defineGanttToolbar(toolbar, {
grid,
locale: 'de-DE',
dir: 'ltr', // use rtl for right-to-left interfaces, including the View popup
localeText: {
toolbar: 'Projektwerkzeuge',
addTask: 'Aufgabe hinzufügen',
search: 'Aufgaben suchen',
searchPlaceholder: 'Suchen…',
undo: 'Rückgängig ({count})',
view: 'Ansicht',
viewSettings: 'Ansichtsoptionen',
columns: 'Spalten',
selectAll: 'Alle auswählen',
zoomSlider: 'Zeitskala',
zoomLevels: { day: 'Tage', week: 'Wochen' }, // use your configured level ids
},
});
```
Translation scope is the toolbar. Task editors, calendar editors, and timeline headers retain their own localization options.
## Custom Content Slots
Use `slots` to insert or replace content without duplicating built-in controls. Every part supports `.before`, `.replace`, and `.after`. Hidden parts and their hidden parents suppress slots. Passing `null` to a `.replace` slot removes that part. `GANTT_TOOLBAR_PART_IDS` exports the complete list:
| Area | Slot part ids |
| --- | --- |
| Entire bar | `toolbar`, `start`, `end` |
| Task creation | `addTask` |
| History | `history`, `undo`, `redo` |
| Hierarchy | `tree`, `expandAll`, `collapseAll` |
| Task structure | `structure`, `indent`, `outdent` |
| Search and spacing | `search`, `spacer` |
| Timeline scale | `zoom`, `zoomOut`, `zoomSlider`, `zoomIn` |
| View popup | `view`, `viewTrigger`, `viewContent`, `viewStart`, `viewEnd`, `viewTable`, `columns`, `viewTimeline` |
| View actions | `dependencies`, `baseline`, `criticalPath`, `calendars` |
| Export | `exportExcel` |
`start` and `end` insert content inside the toolbar; `viewStart` and `viewEnd` insert content inside the View dialog. `toolbar.before` and `toolbar.after` surround the entire toolbar.
A renderer uses `mount(container, context)`, optionally returning `update(context)` and `destroy()`. Context includes `grid`, theme, resolved translations, direction, visibility configuration, query, hidden columns, visual toggle state, history counts, and the current zoom-level id. Treat these state snapshots as read-only. Use the grid's public methods and events for custom commands. Renderers own their custom markup, accessible names, and event cleanup.
```ts
import { defineGanttToolbar, destroyGanttToolbar } from '@revolist/gantt';
defineGanttToolbar(toolbar, {
grid,
slots: {
'addTask.replace': {
mount(container, context) {
// Wrap the original control, preserving its shared button, tooltip and action.
const wrapper = container.ownerDocument.createElement('div');
container.append(wrapper);
context.renderDefault(wrapper);
},
},
'search.after': {
mount(container) {
const status = container.ownerDocument.createElement('output');
status.setAttribute('aria-live', 'polite');
container.append(status);
return {
update(context) {
status.textContent = context.searchQuery;
},
destroy() {
// Dispose framework roots or subscriptions here when needed.
},
};
},
},
},
});
// When your application removes its toolbar host:
destroyGanttToolbar(toolbar);
```
The same DOM lifecycle can mount React, Vue, or Angular content. `renderDefault()` is available for replacement slots; delegated defaults update with toolbar state and are disposed automatically. Menu slots mount when View opens and are destroyed when it closes. Recalling `defineGanttToolbar()` replaces the previous mount and releases its custom renderers, popup and tooltip registrations; it resets local toolbar state. Use `destroyGanttToolbar()` before removing the host. Visibility flags customize presentation; use runtime permissions for editing restrictions.
## Direct Commands
Use direct helpers when you already know which button was clicked.
```ts
const grid = document.querySelector('revo-grid');
if (!grid) {
throw new Error('Gantt grid was not found');
}
await exportGanttCsv(grid, { filename: 'launch-plan' });
exportGanttExcel(grid, { workbookName: 'launch-plan.xlsx' });
await captureGanttBaseline(grid, { name: 'Sprint baseline' });
const criticalPathVisible = toggleGanttCriticalPath(grid);
await scrollGanttToToday(grid, { align: 0.5 });
await fitGanttToProject(grid);
```
## Command Descriptors
Use `createGanttToolbarQuickWinActions()` when a framework toolbar should render generic command objects. Creating descriptors does not export, scroll, or mutate the grid. Work happens only when `run()` is called.
```ts
const actions = createGanttToolbarQuickWinActions(grid, {
csv: { filename: 'launch-plan' },
excel: { workbookName: 'launch-plan.xlsx' },
baseline: { name: 'Sprint baseline' },
today: { align: 0.5 },
fitToProject: {},
});
for (const action of listGanttToolbarQuickWinActions(actions)) {
renderButton({
id: action.id,
label: action.label,
title: action.title,
onClick: () => void action.run(),
});
}
declare function renderButton(command: {
id: string;
label: string;
title: string;
onClick: () => void;
}): void;
```
Disable individual commands by passing `false`.
```ts
const readonlyActions = createGanttToolbarQuickWinActions(grid, {
csv: false,
baseline: false,
});
```
## Behavior Notes
- CSV export uses the RevoGrid export plugin when a plugin with `exportFile()` is installed.
- Excel export dispatches the Pro Excel export event on the grid.
- Baseline capture looks for the Gantt runtime and appends the captured snapshot to `grid.ganttBaselines`.
- Critical path toggling updates `grid.gantt.visuals.showCriticalPath`.
- Today and fit-to-project navigation use the visible timeline pane width and scroll the Gantt pane through `GanttPlugin.scrollToTimelineCoordinate({ x })`. Generic runtimes retain the `grid.scrollToCoordinate()` fallback. An explicit `viewportWidth` overrides automatic pane sizing for fit-to-project.
---
# Add Task Row
URL: https://pro.rv-grid.com/guides/gantt/interaction/add-task-row/
Source: src/content/docs/guides/gantt/interaction/add-task-row.mdx
Description: Use the pinned Gantt add-task row and identify it with isGanttAddTaskRow.
Surface: gantt
## Live example
Type a task name into the pinned bottom add-task row and commit the edit. The draft resets after creation; other draft cells and its selection checkbox remain unavailable.
`GanttPlugin` can show a pinned bottom row for quickly creating tasks by typing a name into the task table.
Enable it with `taskCreateRow: true` and `allowTaskCreate: true`:
```ts
import { GanttPlugin } from '@revolist/gantt';
grid.plugins = [GanttPlugin];
grid.gantt = {
...project,
allowTaskCreate: true,
taskCreateRow: true,
};
```
The row is hidden when Gantt is read-only or when resource planning mode is active.
## How It Works
`GanttPlugin` installs `GanttAddTaskRowPlugin` internally. Do not add `GanttAddTaskRowPlugin` to `grid.plugins` yourself.
The internal plugin owns a draft row in the `rowPinEnd` source. That draft row is not a task entity and is not written into `grid.source`. When the user edits the `name` cell with a non-empty value, Gantt creates a new root task at the end of the project and resets the draft row.
Only the `name` cell is editable. Other cells are rendered as inert cells, row drag is disabled for the draft row, and the generated Gantt context menu is suppressed on it.
## Identifying The Draft Row
Use `isGanttAddTaskRow()` when custom columns or actions need to skip the draft row:
```ts
import {
GanttPlugin,
isGanttAddTaskRow,
} from '@revolist/gantt';
import { RowSelectPlugin } from '@revolist/revogrid-pro';
grid.plugins = [GanttPlugin, RowSelectPlugin];
grid.columns = [
{
prop: 'selected',
name: '',
rowSelect: ({ model }) => !isGanttAddTaskRow(model),
size: 56,
readonly: true,
filter: false,
},
// Task table columns...
];
```
This is useful for checkbox selection, custom cell renderers, and application actions that should apply only to real task rows.
## Custom Context Menus
When Gantt context-menu integration is enabled, the generated row menu does not open on the add-task draft row. If your application fully disables Gantt context-menu integration and owns `rowContextMenu` itself, keep the same rule in your resolver:
```ts
grid.rowContextMenu = {
items: [
{ name: 'Open task details', action: openTaskDetailsFromMenu },
],
resolve(context) {
if (context.triggerElement.closest('[data-gantt-add-task-row-cell="true"]')) {
return null;
}
return undefined;
},
};
grid.gantt = {
...project,
contextMenu: false,
};
```
For model-based checks inside item actions, read the focused row model from the action context and pass it to `isGanttAddTaskRow()`.
---
# Dependency Editing
URL: https://pro.rv-grid.com/guides/gantt/interaction/dependency-editing/
Source: src/content/docs/guides/gantt/interaction/dependency-editing.mdx
Description: Create, select, and delete dependency links in timeline.
Surface: gantt
Dependency editing supports:
- Drag-to-create links from dependency handles.
- Allowed-type filtering via `gantt.allowedDependencyTypes`.
- Hover/select styling and inline delete UI.
- Keyboard delete on selected dependency.
## Mobile And Touch Devices
On touch devices, dependency handles follow the same select-then-edit model as task-bar editing:
- Tap a task bar to select it.
- Gantt reveals larger dependency handles on the selected bar.
- Drag from a visible start or end dependency handle to another task bar.
- If the pointer lands on a target handle, Gantt uses that explicit endpoint.
- If the pointer lands on the target bar but not a handle, Gantt infers start or end from the pointer position relative to the bar midpoint.
Hidden dependency handles are not hit-testable on coarse pointers, so a swipe near the edge of an unselected task does not start dependency creation. This keeps timeline scrolling usable on phones and tablets while preserving precise dependency editing after task selection.
Implementation:
- `features/dependencies/drag-controller.ts`
- `features/dependencies/dependency-tool.ts`
- `features/dependencies/dependency-layer.ts`
---
# Drag, Resize, And Progress Editing
URL: https://pro.rv-grid.com/guides/gantt/interaction/drag-resize-progress/
Source: src/content/docs/guides/gantt/interaction/drag-resize-progress.mdx
Description: Timeline gesture interactions for task bars.
Surface: gantt
Implemented interactions:
- Drag task bars to move.
- Drag start/end handles to resize.
- Drag progress handle to update completion.
Each interaction has preview state and commit state and can trigger warning/diagnostic updates.
## Mobile And Touch Devices
Gantt supports phones, tablets, and hybrid devices through adaptive pointer handling. Desktop users can drag task bars and handles directly. Touch users get a safer select-then-edit flow so scrolling a dense timeline does not accidentally change the schedule.
On coarse-pointer devices:
- Tap a task bar to select it.
- Selection reveals larger resize, dependency, progress, and split handles.
- Drag the selected task bar to move the task.
- Drag a visible start/end handle to resize.
- Drag the visible progress handle to update completion.
- Start swiping from an unselected task bar when you want to pan the task table or timeline; hidden handles are not hit-testable until selection.
The mobile path keeps native panning available for unselected task bars and disables browser touch panning only on selected/active bars or visible edit handles. This preserves scroll-first behavior while still allowing precise edits after the user has selected the task.
The packaged task editor dialog also adapts to phone-width screens with a full-viewport, single-column layout and larger touch targets.
Implementation:
- `features/tasks/controllers/task-drag-controller.ts`
- `features/tasks/controllers/task-resize-controller.ts`
- `features/tasks/controllers/task-progress-controller.ts`
- `features/tasks/controllers/pointer-gesture.ts`
- `features/tasks/timeline-preview-layer.ts`
---
# Row Selection
URL: https://pro.rv-grid.com/guides/gantt/interaction/row-selection/
Source: src/content/docs/guides/gantt/interaction/row-selection.mdx
Description: Use RowSelectPlugin with Gantt task operations and row reordering.
Surface: gantt
## Live example
Select task rows with the left checkboxes, then compare single-task and multi-task hierarchy actions. The pinned add-task draft has no task-selection checkbox.
`GanttPlugin` can work with the RevoGrid Pro `RowSelectPlugin` when you want checkbox-based task selection in the task table.
Add a row-select column, register `RowSelectPlugin`, and enable the row-order bridge when selected rows should move together during Gantt row reordering.
```ts
import { GanttPlugin, isGanttAddTaskRow } from '@revolist/gantt';
import { RowSelectPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [GanttPlugin, RowSelectPlugin];
grid.rowSelect = {
rowOrder: true,
};
grid.columns = [
{
prop: 'selected',
name: '',
rowSelect: ({ model }) => !isGanttAddTaskRow(model),
size: 56,
readonly: true,
filter: false,
},
// Task table columns...
];
```
The checkbox column uses physical task rows from `grid.source`. Gantt ignores resource rows and non-task rows when resolving checkbox selection for task operations.
Use `isGanttAddTaskRow()` when `taskCreateRow` is enabled so the pinned “Add new task” row does not render a checkbox. See [Add Task Row](/guides/gantt/interaction/add-task-row/) for the draft-row contract.
## Bulk Gantt Operations
When no explicit task ids are passed, Gantt resolves task ids in this order:
1. Checkbox-selected rows from `RowSelectPlugin`.
2. Gantt task range selection.
3. The currently focused or selected Gantt task.
This means toolbar buttons, context-menu actions, keyboard handlers, or application code can call the existing Gantt methods without manually collecting checkbox state:
```ts
const plugins = await grid.getPlugins();
const gantt = plugins.find((plugin) => plugin instanceof GanttPlugin);
gantt?.deleteTasks();
gantt?.indentTasks();
gantt?.outdentTasks();
gantt?.convertTasksToMilestones();
```
After a successful delete, Gantt clears the row-select checkbox state for task rows so removed tasks do not remain selected in `RowSelectPlugin`.
## Row Reordering
Gantt installs `RowOrderPlugin` internally. `RowSelectPlugin` does not automatically move all checked rows unless the row-select row-order integration is enabled:
```ts
grid.rowSelect = {
rowOrder: true,
};
```
With `rowOrder: true`, dragging a checked task row moves the visible checked task rows as one compact block. Dragging an unchecked row keeps the normal Gantt row-order behavior.
## Framework Bindings
React:
```tsx
!isGanttAddTaskRow(model),
size: 56,
readonly: true,
filter: false,
},
...taskColumns,
]}
/>
```
Vue:
```vue
```
Angular:
```html
```
For Vue Gantt examples, use `.prop` for `row-select` because it is a direct grid plugin property.
---
# Task Editing
URL: https://pro.rv-grid.com/guides/gantt/interaction/task-editing/
Source: src/content/docs/guides/gantt/interaction/task-editing.mdx
Description: Inline editing, the default task editor dialog, and scheduler-backed mutation flow.
Surface: gantt
Task edits route through mutation services and scheduler recomputation. Before applying task changes, Gantt emits cancelable `gantt-before-task-change`; dependency field edits emit `gantt-before-dependency-change`; assignee edits emit `gantt-before-assignment-change`.
Command/event surface is defined in:
- `packages/gantt/src/gantt/grid/gantt-events.ts`
- `packages/gantt/src/gantt/grid/gantt-plugin.ts`
For persistence and audit, listen to the unified `gridedit` event and inspect its typed `domainChanges`. Successful Gantt mutations are tagged with `gantt-task-change`, `gantt-dependency-change`, or `gantt-assignment-change`; rejected changes do not add Gantt domain entries. The Gantt before-change events remain the cancellation boundary.
## Inline Editing
Inline Gantt edits update the task through the Gantt mutation services, recompute the project, and emit history snapshots for user changes. Initial project hydration is not recorded as a history step.
Call `event.preventDefault()` in a before-change listener to reject a field edit before the task, dependency, or assignment store is mutated. Rejected predecessor/successor edits leave the old dependency rows in place; rejected assignee edits leave the old assignment rows in place.
### Edit validation boundary
`resolveTaskEdit()` is the stable validation entry point for inline task fields. It converts a raw editor value into a validated `TaskUpdate` patch or returns an `invalid-field`, `invalid-value`, or `readonly-derived-field` failure. It does not mutate task data itself. The task mutation service applies the accepted patch, and the scheduling engine then recomputes derived dates and project state.
Task-table text-entry editors follow spreadsheet-style keyboard entry, including text, numeric, percentage, date-text, Duration, Remaining Duration, and Work fields. With a cell selected, typing a printable character starts a new value and replaces the displayed value; for example, pressing `4` opens the editor with `4`. Press Enter or double-click instead when you want to open and modify the existing value. Choice, checkbox, and calendar controls keep their domain-specific input behavior.
The task table and Task Information dialog select controls from the same framework-neutral `TaskEditorFieldSchema.kind` contract. Duration, Remaining Duration, and Work use the explicit `duration` kind, so both surfaces share text input semantics, duration-field identification, formatting, and parsing. Grid adapters still own RevoGrid `EditorBase` lifecycle and keyboard seeds, while dialog adapters own controlled form state; this keeps behavior reusable without coupling modal controls to grid DOM lifecycle.
A zero Duration schedules the row as a milestone, but its Duration remains editable. Entering a positive Duration in either the grid or Task Information dialog converts it back to a regular task automatically. Summary Duration remains read-only because the scheduler derives it from the child-task span.
Dialog control rendering is decomposed by responsibility under `packages/gantt/src/gantt-task-editor-dialog/field-controls/`: input, date, select, switch, tags, textarea, and labels/resource controls each own one control category. The top-level `FieldControl` only dispatches by the shared field kind.
The implementation is split by value category under `packages/gantt/src/gantt/engine/task-edit-service/internal/`: date, duration, numeric, boolean, progress, name, status, task-mode, effort-mode, and constraint handlers each own their parsing, validation messages, and patch creation. Consumers link directly to `engine/task-edit-service/index`, whose only responsibility is dispatching to the appropriate handler. This keeps custom label handling and schedule-aware validation consistent without collecting every edit rule into one service file or exposing handler registries as public API.
The split is internal and does not change imports, event names, field behavior, or `TaskEditResult`. Existing integrations should continue importing Gantt APIs from `@revolist/gantt`.
## Task Editor Dialog
`GanttPlugin` installs the packaged task editor dialog by default, so every Gantt grid gets the row context-menu editor without registering an extra plugin. The same editor contract is also available as presentation-free helpers: render controls from `TASK_EDITOR_FIELD_SCHEMA`, seed them with `createTaskEditorFormValues()`, and turn submitted values into a `TaskUpdate` patch with `normalizeTaskEditorSubmit()`.
The packaged dialog is responsive for mobile use. On phone-width viewports it becomes a full-viewport dialog, switches task fields and dependency rows to a single column, keeps the form body scrollable, and uses larger controls for touch input. The same validation and submit flow runs on desktop, tablet, and phone layouts.
## How It Works
The task editor demo opens the editor from the Gantt row context menu and includes the packaged Gantt toolbar:
- The grid renders normal Gantt tasks, dependencies, calendars, resources, and assignments.
- Right-click a task row and choose **Edit...**.
- Use row context-menu **Add** to insert a `New task N` row without opening the editor, or toolbar **Task** to create a task and open the same editor for the new row.
- The auto-installed task editor dialog adds the row-menu edit item and owns the native HTML ``.
- New tasks open in the editor by default after the Gantt plugin emits its post-create lifecycle event. Set `ganttTaskEditorDialog.openOnCreate` to `false` when task creation should stay silent.
- The dialog renders a tabbed task information UI from `TASK_EDITOR_FIELD_SCHEMA`.
- **General** contains task name, status, task kind, dates, duration, and progress.
- **Advanced** contains effort mode, calendar, tags, constraints, manual scheduling, work, actual dates, inactive state, and leveling controls.
- **Predecessors** edits incoming dependency target, type, and lead/lag days. **Successors** remains available through customization but is hidden by default.
- **Resources** renders the resource assignment picker when resources and assignments are available.
- **Notes** provides a dedicated long-form notes editor.
- The patch preview calls `normalizeTaskEditorSubmit(task, values)` on every edit.
- The submit button applies the returned patch through the Gantt runtime/provider edit path, updates `grid.ganttAssignments` and `grid.ganttDependencies`, and refreshes the project.
```ts
grid.plugins = [GanttPlugin];
grid.ganttTaskEditorDialog = {
title: 'Task details',
description: 'Update task fields and save the changes to the project schedule.',
applyLabel: 'Save changes',
localeText: {
tabs: {
assignments: 'Team',
},
fields: {
effortMode: 'Task type',
},
resourcesSearch: {
placeholder: 'Find a resource',
},
},
};
const values = createTaskEditorFormValues(task, resources, assignments);
const result = normalizeTaskEditorSubmit(task, values);
if (result.ok) {
updateTask(task.id, result.patch);
}
```
## Fields
The schema includes editable task fields such as `name`, `startDate`, `endDate`, `duration`, `progressPercent`, constraints, deadlines, actual dates, work, effort mode, effort-driven scheduling, inactive state, leveling controls, manual scheduling, and `notes`. It also includes display fields such as `resourceLabels`.
Date fields open as date-only controls by default. Use the field-level **Time** toggle in the packaged dialog when you need minute-level scheduling; the dialog stores the submitted value as a UTC ISO datetime.
Set `gantt.dateFormats.editor` when the packaged dialog should show custom date text instead of native date inputs. In that mode, the dialog uses a text input, calls your parser as the user types, and still submits ISO dates to `normalizeTaskEditorSubmit()` and the Gantt mutation services:
```ts
grid.gantt = {
...project,
dateFormats: {
locale: 'en-GB',
timeZone: 'UTC',
table: { day: '2-digit', month: '2-digit', year: 'numeric' },
tooltip: { day: '2-digit', month: 'short', year: 'numeric' },
editor: {
options: { day: '2-digit', month: '2-digit', year: 'numeric' },
parser(value) {
const match = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(value.trim());
return match ? `${match[3]}-${match[2]}-${match[1]}` : null;
},
},
},
};
```
Custom date formats are presentation and input helpers only. Keep persisted tasks as `YYYY-MM-DD` dates or UTC datetimes ending in `Z`.
`resourceLabels` is read-only in the schema helper because `TaskUpdate` does not own assignment rows. The packaged dialog upgrades that field into a resource assignment picker and writes the selected resources to `grid.ganttAssignments`. Set `ganttTaskEditorDialog.resources` to `false` when you want the field to stay read-only.
Resource options show `ResourceEntity.avatarUrl` as a circular profile image. When no image is configured—or an image fails to load—the picker renders deterministic initials from the resource name, so every assignment remains visually identifiable without requiring avatar assets.
Entity panels include search controls: resource assignment searches by resource name and role, while predecessor and successor panels search task names. Resource lists, task dropdowns, dependency type dropdowns, and schema-backed select options are sorted A-Z by their displayed labels.
Use `ganttTaskEditorDialog.localeText` to translate or customize packaged dialog text. It covers tabs, field labels, field option labels, dependency type labels, empty states, search placeholders, switch labels, validation headings, save errors, and context-menu text. Existing top-level `title`, `description`, `applyLabel`, `resetLabel`, `closeLabel`, and `menuItemName` still work as direct overrides for those common labels.
## Customizing Tabs, Fields, and Controls
Use `ganttTaskEditorDialog.customization` to change the packaged editor without replacing it. Built-in tab ids are `details`, `predecessors`, `successors`, `assignments`, `advanced`, and `notes`. Set an item to `false` to remove it, use `hidden`, `title`, and `fields` to reconfigure it, and use `order` to place built-in and custom tabs. Nested customization takes precedence over the legacy top-level `fields` allow-list.
```ts
grid.ganttTaskEditorDialog = {
fields: ['name', 'status', 'type', 'startDate', 'endDate', 'calendarId', 'tags', 'notes'],
customization: {
controls: {
description: false,
reset: false,
},
tabs: {
order: ['details', 'successors', 'advanced', 'notes'],
items: {
details: { title: 'Main', fields: ['name', 'status', 'type', 'startDate', 'endDate'] },
successors: { hidden: false },
assignments: false,
},
},
},
};
```
The shell controls are `header`, `title`, `description`, `close`, `tabs`, `validation`, `preview`, `reset`, `apply`, and `footer`. Setting one to `false` hides only the packaged control; custom content can still call the corresponding controller method.
### Adding a Custom Page
Custom pages use a framework-neutral DOM lifecycle. `mount` runs once when the page is shown, `update` receives fresh task/project/draft context, and `destroy` runs when the page is removed. The controller can update built-in values, resources, dependencies, application-owned custom values, validation errors, and the reset/close/submit lifecycle.
```ts
const auditPage = {
mount(container, context) {
let currentContext = context;
const input = document.createElement('input');
input.placeholder = 'External approval reference';
container.appendChild(input);
const onInput = () => {
currentContext.controller.setCustomValue('approvalReference', input.value);
currentContext.controller.setValidationErrors(input.value.trim() ? [] : ['Approval reference is required.']);
};
input.addEventListener('input', onInput);
return {
update(next) {
currentContext = next;
input.value = String(next.state.customValues.approvalReference ?? '');
},
destroy() {
input.removeEventListener('input', onInput);
},
};
},
};
grid.ganttTaskEditorDialog = {
customization: {
tabs: {
order: ['details', 'audit', 'notes'],
items: { audit: { title: 'Audit', renderer: auditPage } },
},
},
async onSubmit({ result, customValues }) {
await saveTaskAndApproval(result.patch, customValues.approvalReference);
},
};
```
Custom values are passed to `onSubmit` but are never persisted implicitly. Custom validation errors block packaged submission until cleared.
### Replacing the Entire Editor
Set `customization.editor` to the same DOM renderer contract to replace the packaged dialog completely. The replacement receives the live project snapshot and shared draft controller, including `reset()`, `close()`, and `submit()`. Its lifecycle is destroyed when the editor closes or the plugin is removed.
### Calendar and Tags
`calendarId` is populated from the live Gantt calendars and validated before mutation. Changing it uses the normal task mutation, rescheduling, and history path. `tags` accepts comma-separated input; values are trimmed, blank entries are removed, and duplicates are removed while preserving their first-seen order.
Use `ganttTaskEditorDialog.openOnCreate = false` to keep toolbar or programmatic task creation from opening the dialog. The Gantt row context menu always creates silently through the default **Add** action when task creation is allowed, and the editor plugin prepends its **Edit...** item while preserving custom `rowContextMenu` or `contextMenu` items and `resolve` behavior. Set `gantt.contextMenu = false` to opt out of generated Gantt menu items, or set `ganttTaskEditorDialog.contextMenu` when only the packaged editor menu item should change.
## Task Colors
The Gantt row context menu and packaged task editor use the same shared color palette. Choosing a color updates the task through the normal Gantt mutation path, so source synchronization, cancelable before-change events, history, scheduling, and rendering stay consistent. **Default** clears the authored task color; `taskBarColorHook` remains authoritative when it returns a color.
Use `options` to replace the shared palette or `extendOptions` to add project colors. Any swatch can be disabled, as can the Default choice or the complete Color command:
```ts
grid.gantt = {
...project,
contextMenu: {
colorPalette: {
extendOptions: [
{ value: '#7c3aed', label: 'Project violet' },
{ value: '#0f766e', label: 'Reserved teal', disabled: true },
],
defaultOption: { label: 'Use project default' },
colorAriaLabel: (option) => `Task color: ${option.label}`,
},
disabled: {
delete: true,
},
},
};
```
Set `contextMenu.colorPalette` to `false`, `contextMenu.hidden.color` to `true`, or `contextMenu.disabled.color` to `true` to remove or disable this functionality. Set `colorPalette.defaultOption` to `false` to remove Default, or `colorPalette.disabled` to `true` to disable the whole submenu. The same `hidden` and `disabled` maps apply to the other built-in Gantt commands. Application commands supplied through RevoGrid's row-context-menu configuration continue to compose with the generated Gantt items.
The task editor inherits `gantt.contextMenu.colorPalette` by default. Use `ganttTaskEditorDialog.colorPalette` to configure its palette independently, or set it to `false` to remove the Color field from the editor. The editor-specific setting supports the same `options`, `extendOptions`, disabled choices, Default configuration, and accessible option-label resolver.
## Validation
`normalizeTaskEditorSubmit()` validates values before returning a patch:
- `name` cannot be empty.
- Date values must be ISO dates like `2026-05-12` or UTC datetimes ending in `Z`.
- `endDate` cannot be earlier than `startDate`.
- Numeric fields must be non-negative.
- `progressPercent` is clamped to `0..100`.
- Unsupported constraint values return field errors.
If both `startDate` and `endDate` are present, the helper recalculates `duration` from the date range. If values are unchanged, the default result omits them from the patch.
## Production Wiring
In production, keep the same flow but replace the demo's local source update with your persistence and project-state update path:
```ts
async function submitTaskEditor(task, values) {
const result = normalizeTaskEditorSubmit(task, values);
if (!result.ok) {
renderFieldErrors(result.errors);
return;
}
await api.updateTask(task.id, result.patch);
grid.source = grid.source.map((row) =>
row.id === task.id ? { ...row, ...result.patch } : row,
);
}
```
The Gantt plugin still owns timeline projection and scheduling when the refreshed task source is applied.
The packaged dialog defaults to applying task field patches through the Gantt runtime/provider edit path and replacing the edited task's assignment rows in `grid.ganttAssignments`. This keeps task editor saves visible to `HistoryPlugin` instead of bypassing grid edit tracking with direct `grid.source` replacement. To persist edits first, provide `ganttTaskEditorDialog.onSubmit`; return `false` from that callback when your application has already applied the task and assignment updates and the plugin should skip the default patch.
Dependency tabs also patch `grid.ganttDependencies` by replacing links that involve the edited task. Existing dependency ids are preserved, new rows get deterministic ids, and lag supports negative lead or positive lag days.
Set `preview: true` only for diagnostics or documentation when you want to show the generated `TaskUpdate` JSON.
---
# Gantt Quick Start — Angular
URL: https://pro.rv-grid.com/guides/gantt/introduction/angular/
Source: src/content/docs/guides/gantt/introduction/angular.mdx
Description: Build a RevoGrid Gantt project in Angular with the standalone wrapper, typed property bindings, and managed task columns.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) for Angular.
2. Create a standalone component and bind every Gantt collection as a property.
```ts
import { Component, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { GanttPlugin, createDefaultTaskTableColumn, type DependencyEntity, type GanttPluginConfig, type GanttTaskSourceRow } from '@revolist/gantt';
type CalendarEntity = NonNullable[number];
@Component({
selector: 'app-project-gantt',
standalone: true,
imports: [RevoGrid],
encapsulation: ViewEncapsulation.None,
styles: ['.project-gantt { display: block; width: 100%; height: 640px; }'],
template: `
`,
})
export class ProjectGanttComponent {
plugins = [GanttPlugin];
project: GanttPluginConfig = {
id: 'website-launch', name: 'Website launch', version: '1', currency: 'USD',
timeZone: 'UTC', primaryCalendarId: 'standard',
updatedAt: '2026-07-14T00:00:00.000Z', zoomPreset: 'day-week',
};
calendars: CalendarEntity[] = [{
id: 'standard', name: 'Standard', timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5], holidays: [], hoursPerDay: 8,
}];
tasks: GanttTaskSourceRow[] = [
{ id: 'design', name: 'Design', startDate: '2026-07-14', duration: 3, percentDone: 50, calendarId: 'standard' },
{ id: 'build', name: 'Build', startDate: '2026-07-17', duration: 5, percentDone: 0, calendarId: 'standard' },
];
dependencies: DependencyEntity[] = [{
id: 'design-to-build', predecessorTaskId: 'design', successorTaskId: 'build', type: 'FS', lagDays: 0,
}];
columns = [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startDate'),
createDefaultTaskTableColumn('duration'),
createDefaultTaskTableColumn('percentDone'),
createDefaultTaskTableColumn('predecessors'),
];
}
```
3. Import the Pro and Gantt CSS files in the application-wide stylesheet or build entry, not in an encapsulated child stylesheet.
Open the runnable Angular demo for the complete application source.
Continue with the [Gantt tutorial](/guides/gantt/tutorial/) and [application state guide](/guides/gantt/features/application-state/).
---
# Install RevoGrid Gantt
URL: https://pro.rv-grid.com/guides/gantt/introduction/installation/
Source: src/content/docs/guides/gantt/introduction/installation.mdx
Description: Install the standalone RevoGrid Gantt package, styles, and framework wrapper required to render a project.
Surface: gantt
## Live example
This is the mounted result of the installation and plugin-registration steps below. Expand a summary task and inspect its dependency arrows.
Install `@revolist/gantt` alongside the core grid and Pro. Add only the framework wrapper your application uses.
1. Install the packages.
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt
```
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt @revolist/react-datagrid
```
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt @revolist/vue3-datagrid
```
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/gantt @revolist/angular-datagrid
```
2. Import the product styles once in the application entry point.
```ts
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/gantt/styles.css';
```
3. Register `GanttPlugin` on the grid.
```ts
import { GanttPlugin } from '@revolist/gantt';
grid.plugins = [GanttPlugin];
```
4. Give the grid a visible size. Gantt needs both width and height to calculate its virtual viewport.
```css
.project-gantt {
display: block;
width: 100%;
height: 640px;
}
```
Evaluating Gantt? Visit the [RevoGrid trial page](https://rv-grid.com/trial?source=demo-page&demo=planning&demo_id=planning) and use `@revolist/gantt-trial` with its matching CSS path.
## Choose Your Framework
- [Vanilla TypeScript](/guides/gantt/introduction/vanilla-typescript/)
- [React](/guides/gantt/introduction/react/)
- [Vue 3](/guides/gantt/introduction/vue/)
- [Angular](/guides/gantt/introduction/angular/)
The general [Pro and product installation guide](/guides/installation/) covers private registry, pre-built, GitHub, source-build, and no-build distribution options.
---
# Gantt Quick Start — React
URL: https://pro.rv-grid.com/guides/gantt/introduction/react/
Source: src/content/docs/guides/gantt/introduction/react.mdx
Description: Build a typed RevoGrid Gantt project in React with memoized project collections, calendars, dependencies, and task columns.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) for React.
2. Create the component. Keep project collections and columns stable with `useMemo` so ordinary React renders do not replace Gantt state unnecessarily.
```tsx
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { GanttPlugin, createDefaultTaskTableColumn, type DependencyEntity, type GanttPluginConfig, type GanttTaskSourceRow } from '@revolist/gantt';
type CalendarEntity = NonNullable[number];
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/gantt/styles.css';
export default function ProjectGantt() {
const plugins = useMemo(() => [GanttPlugin], []);
const project = useMemo(() => ({
id: 'website-launch',
name: 'Website launch',
version: '1',
currency: 'USD',
timeZone: 'UTC',
primaryCalendarId: 'standard',
updatedAt: '2026-07-14T00:00:00.000Z',
zoomPreset: 'day-week',
}), []);
const calendars = useMemo(() => [{
id: 'standard', name: 'Standard', timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5], holidays: [], hoursPerDay: 8,
}], []);
const source = useMemo(() => [
{ id: 'design', name: 'Design', startDate: '2026-07-14', duration: 3, percentDone: 50, calendarId: 'standard' },
{ id: 'build', name: 'Build', startDate: '2026-07-17', duration: 5, percentDone: 0, calendarId: 'standard' },
], []);
const dependencies = useMemo(() => [{
id: 'design-to-build', predecessorTaskId: 'design', successorTaskId: 'build', type: 'FS', lagDays: 0,
}], []);
const columns = useMemo(() => [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startDate'),
createDefaultTaskTableColumn('duration'),
createDefaultTaskTableColumn('percentDone'),
createDefaultTaskTableColumn('predecessors'),
], []);
return (
);
}
```
3. Give `.project-gantt` a fixed or flex-derived height.
```css
.project-gantt { display: block; width: 100%; height: 640px; }
```
Open the runnable React demo for the complete application source.
Continue with the [Gantt tutorial](/guides/gantt/tutorial/) and [application state guide](/guides/gantt/features/application-state/).
---
# Gantt Quick Start — Vanilla TypeScript
URL: https://pro.rv-grid.com/guides/gantt/introduction/vanilla-typescript/
Source: src/content/docs/guides/gantt/introduction/vanilla-typescript.mdx
Description: Build a RevoGrid Gantt project with the custom element, typed project data, task columns, calendars, and dependencies.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) and add a mount element.
```html
```
2. Register the custom element and create the project.
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { GanttPlugin, createDefaultTaskTableColumn, type DependencyEntity, type GanttPluginConfig, type GanttTaskSourceRow } from '@revolist/gantt';
type CalendarEntity = NonNullable[number];
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/gantt/styles.css';
defineCustomElements();
const project: GanttPluginConfig = {
id: 'website-launch',
name: 'Website launch',
version: '1',
currency: 'USD',
timeZone: 'UTC',
primaryCalendarId: 'standard',
updatedAt: '2026-07-14T00:00:00.000Z',
zoomPreset: 'day-week',
};
const calendars: CalendarEntity[] = [{
id: 'standard',
name: 'Standard',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
holidays: [],
hoursPerDay: 8,
}];
const tasks: GanttTaskSourceRow[] = [
{
id: 'design',
name: 'Design',
startDate: '2026-07-14',
duration: 3,
percentDone: 50,
calendarId: 'standard',
},
{
id: 'build',
name: 'Build',
startDate: '2026-07-17',
duration: 5,
percentDone: 0,
calendarId: 'standard',
},
];
const dependencies: DependencyEntity[] = [{
id: 'design-to-build',
predecessorTaskId: 'design',
successorTaskId: 'build',
type: 'FS',
lagDays: 0,
}];
```
3. Configure and mount the grid. Set `source` after the element is connected.
```ts
const host = document.querySelector('#gantt');
if (!host) throw new Error('Missing #gantt');
const grid = document.createElement('revo-grid');
grid.className = 'project-gantt';
grid.plugins = [GanttPlugin];
grid.gantt = project;
grid.ganttCalendars = calendars;
grid.ganttDependencies = dependencies;
grid.columns = [
createDefaultTaskTableColumn('wbs'),
createDefaultTaskTableColumn('name'),
createDefaultTaskTableColumn('startDate'),
createDefaultTaskTableColumn('duration'),
createDefaultTaskTableColumn('percentDone'),
createDefaultTaskTableColumn('predecessors'),
];
host.appendChild(grid);
grid.source = tasks;
// Call when the hosting view is destroyed.
const dispose = () => grid.remove();
```
Open the runnable Vanilla TypeScript demo to inspect every source file.
Next, continue with the [Gantt tutorial](/guides/gantt/tutorial/) or read the [data model](/guides/gantt/concepts/data-model/).
---
# Gantt Quick Start — Vue 3
URL: https://pro.rv-grid.com/guides/gantt/introduction/vue/
Source: src/content/docs/guides/gantt/introduction/vue.mdx
Description: Build a reactive RevoGrid Gantt project in Vue 3 with the official wrapper, project collections, and managed task columns.
Surface: gantt
1. Complete [installation](/guides/gantt/introduction/installation/) for Vue 3.
2. Create a Vue component. Bind Gantt objects with Vue property bindings; do not serialize them as HTML attributes.
```vue
```
Open the runnable Vue demo for the complete application source.
Continue with the [Gantt tutorial](/guides/gantt/tutorial/) and [application state guide](/guides/gantt/features/application-state/).
---
# What Is A Gantt Chart?
URL: https://pro.rv-grid.com/guides/gantt/introduction/what-is-gantt/
Source: src/content/docs/guides/gantt/introduction/what-is-gantt.mdx
Description: Core concepts used by the standalone RevoGrid Gantt package.
Surface: gantt
> **Gantt = structured timeline for tasks with dependencies**
A **Gantt chart** is a **visual representation of a plan over time**. It’s a way to show tasks, their durations, and how they relate to each other on a timeline.
It answers only three things:
* **What** tasks exist
* **When** they happen
* **In what order / relation** they happen
---
## Core features of a pure Gantt
#### 1. Task model
Each task has:
* id
* start date
* end date (or duration)
* optional parent (hierarchy)
👉 This is the **data backbone**
---
#### 2. Timeline rendering
* Horizontal time axis
* Tasks rendered as bars
* Zoom levels (day / week / month)
👉 Without this, it’s not a Gantt
---
#### 3. Dependencies (defining feature)
Tasks can depend on each other:
* Finish → Start
* Start → Start
* Finish → Finish
👉 This is what makes it a **planning tool**, not just visualization
---
#### 4. Hierarchy (WBS — work breakdown structure)
* Parent / child tasks
* Expand / collapse
👉 Lets you represent real projects, not flat lists
---
#### 5. Direct manipulation
* Drag task → change start date
* Resize → change duration
👉 If this is missing → it's just a chart, not a planning tool
---
#### 6. Milestones
* Tasks with zero duration
* Represent key events
👉 Needed for any serious usage
---
#### 7. Progress (basic tracking)
* % completion per task
* Visual overlay on bars
👉 Not scheduling — just status
---
#### 8. Large data handling
* Thousands of tasks
* Smooth scroll
* Virtualization
👉 This is our competitive edge
---
## What is NOT part of Gantt (important boundary)
❌ Assigning people to time slots
❌ Calendar conflicts
❌ Shift planning
❌ Workforce shift-slot scheduling across personnel calendars
👉 That’s **scheduler territory** layered on top of Gantt fundamentals
---
## Mental model **“Grid-first Gantt”**
A Gantt is:
```text
Hierarchy (tree)
+ Timeline (x-axis)
+ Dependencies (graph edges)
```
* Left side = grid (tasks, editable)
* Right side = timeline (bars)
---
# Accessibility And Keyboard Navigation
URL: https://pro.rv-grid.com/guides/gantt/production/accessibility/
Source: src/content/docs/guides/gantt/production/accessibility.mdx
Description: Build accessible Gantt workflows with RevoGrid keyboard navigation, semantic task data, visible focus, and equivalent editing controls.
Surface: gantt
## Live example
Use Tab to move through the toolbar and task table, then test keyboard editing and focus after closing the task dialog. Accessibility support and remaining limits are described below.
Gantt inherits grid-cell focus, selection, editing, and keyboard navigation from RevoGrid. Timeline gestures supplement those workflows; they must not be the only way to change or inspect a task.
## Application Checklist
- Provide meaningful task, resource, column, and toolbar labels.
- Keep focus outlines visible and meet contrast requirements in custom task-bar colors.
- Expose move, resize, progress, dependency, and hierarchy actions through task-table fields, menus, or application controls in addition to pointer gestures.
- Keep the packaged task editor available when complex fields cannot be edited comfortably inline.
- Preserve expected Tab, Enter, Escape, and arrow-key behavior in custom editors.
- Announce validation errors next to the affected input and keep canceled edits focused.
- Do not place essential information only in color, hover tooltips, or dependency geometry.
## Custom Content
When using `taskBarContentHook` or custom cell templates, return concise visible content and keep the task name available in the task table. Custom toolbar buttons need native button semantics, accessible names, and pressed state where applicable.
Test Gantt with keyboard-only navigation, browser zoom, high contrast, and a screen reader in the frameworks your application supports. See [Task Editing](/guides/gantt/interaction/task-editing/) and [Visual Configuration](/guides/gantt/customization/visuals/).
---
# Localization And Date Formatting
URL: https://pro.rv-grid.com/guides/gantt/production/localization/
Source: src/content/docs/guides/gantt/production/localization.mdx
Description: Localize Gantt labels and consistently format task-table, tooltip, editor, and timeline dates.
Surface: gantt
## Live example
Enable Español dates to change labels and date presentation, then disable it to compare. The underlying project date values remain unchanged.
`gantt.labels` localizes packaged option labels, while `gantt.dateFormats` controls date display and parsing across the task table, tooltips, editor, and timeline.
```ts
grid.gantt = {
...project,
labels: {
taskMode: { auto: 'Automatisch', manual: 'Manuell' },
status: {
complete: 'Abgeschlossen',
'on-schedule': 'Im Plan',
late: 'Verspätet',
'future-task': 'Zukünftig',
},
workflowStatus: {
'not-started': 'Nicht begonnen',
'in-progress': 'In Arbeit',
done: 'Erledigt',
blocked: 'Blockiert',
},
},
dateFormats: {
locale: 'de-DE',
timeZone: 'Europe/Berlin',
table: { day: '2-digit', month: '2-digit', year: 'numeric' },
tooltip: { dateStyle: 'medium' },
},
};
```
Task values remain ISO date or UTC ISO date-time strings. Formatting changes presentation, not persisted data. When an editor uses a non-ISO text format, provide both a formatter and parser so submitted values return to the supported ISO contract.
Use one project time zone, explicit calendar time zones, and UTC date-time values when hour/minute precision matters. See [Configuration Reference](/guides/gantt/reference/configuration-reference/#date-formatting) for callback contexts and parsing rules.
---
# Performance And Large Projects
URL: https://pro.rv-grid.com/guides/gantt/production/performance/
Source: src/content/docs/guides/gantt/production/performance.mdx
Description: Keep large Gantt projects responsive by preserving virtualization, stable inputs, bounded visual layers, and measured scheduling work.
Surface: gantt
## Live example
Enable 750 tasks and scroll through the virtualized rows. Disable the switch to return to the dependency-rich project. This is a reproducible demo workload, not a maximum-capacity benchmark.
RevoGrid virtualizes the task table and timeline projection. Preserve that advantage by updating project data through public collections rather than rendering parallel task DOM or reading state back from cells.
## Recommendations
- Use stable task and linked-entity IDs.
- Replace only collections that changed and keep wrapper inputs memoized or reactive at the correct boundary.
- Batch related task, dependency, calendar, resource, and assignment changes before triggering a new schedule pass.
- Keep custom cell and task-bar renderers pure and inexpensive.
- Limit visible marker and time-range overlays to information users need in the active view.
- Prefer column presets over rendering every analytical field at once.
- Measure scheduling separately from browser rendering with the Gantt scheduler benchmark, and use the browser benchmark for end-to-end interaction evidence.
- Test representative hierarchy depth, dependency density, resource assignments, and timeline span—not only row count.
## Current Boundary
Virtual rendering is implemented. A server-side, date-windowed Gantt project model is not currently part of the public contract, so the complete linked project graph must be available to the client scheduler. See [Limitations](/guides/gantt/reference/limitations/) before designing very large remote projects.
## Two benchmark layers
The Gantt scheduler benchmark isolates scheduling-engine recalculation in Node.js. It does not measure browser rendering, scrolling, pointer interactions, hierarchy projection, memory, or mounted DOM.
Run the scheduler benchmark with:
```bash
pnpm --filter @revolist/gantt bench
```
For the reproducible browser suite, raw JSON/CSV, exact environment, screenshot, video, and live workload, open the [RevoGrid Gantt browser benchmark](https://rv-grid.com/benchmarks/gantt). Its published matrix covers 100, 1,000, 5,000, and 10,000 tasks across sparse, normal, and highly connected dependency graphs, with one warm-up and five measured runs per isolated scenario.
The browser suite leads with the measurable capacity claim “10,000 editable tasks and 19,796 dependencies in a live browser demo.” It does not make a “fastest” claim or compare numbers collected with a different harness.
---
# Responsive And Touch Interaction
URL: https://pro.rv-grid.com/guides/gantt/production/responsive-touch/
Source: src/content/docs/guides/gantt/production/responsive-touch.mdx
Description: Configure layouts that preserve Gantt scrolling, selection, editing, and task handles on phones, tablets, and narrow containers.
Surface: gantt
## Live example
Resize the page or open the example on a touch device. Scroll the task table and timeline, open toolbar actions, and inspect the task details dialog.
Gantt uses the same configuration on desktop and coarse-pointer devices. Touch interaction follows a select-then-edit model so ordinary swipes keep scrolling the task table or timeline.
- Tap a task bar to select it and reveal larger move, resize, progress, dependency, and split handles.
- Drag a selected bar or visible handle to edit.
- Keep the task table wide enough for at least the task name and one scheduling field.
- Allow the Gantt container to shrink with `min-width: 0` inside flex and grid layouts.
- Use a fixed, viewport-derived, or parent-derived height; do not rely on content height.
- The packaged task editor becomes single-column and full-viewport on narrow screens.
```css
.gantt-shell {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
min-width: 0;
height: min(760px, calc(100vh - 8rem));
}
.gantt-shell revo-grid {
display: block;
min-width: 0;
height: 100%;
}
```
Do not add page-level touch handlers that compete with Gantt pointer handling. Place application toolbars outside the scrollable grid container and verify edge swipes on unselected bars do not mutate tasks.
---
# SSR And Client Rendering
URL: https://pro.rv-grid.com/guides/gantt/production/ssr/
Source: src/content/docs/guides/gantt/production/ssr.mdx
Description: Integrate RevoGrid Gantt with server-rendered applications while keeping custom-element registration and browser APIs client-only.
Surface: gantt
## Live example
This page is rendered by Astro and mounts the Vue grid through a client-only boundary. Expand the source preview to inspect the framework component; the wrapper code below shows the SSR boundary.
The scheduling data model is framework-neutral, but the Gantt grid renders in the browser. Register custom elements, access `document` or `window`, and mount the grid only on the client.
## Integration Rules
- Render a stable-height placeholder during SSR to avoid layout shift.
- Dynamically import the wrapper or Gantt component with SSR disabled.
- Call `defineCustomElements()` only in a browser entry or client lifecycle.
- Keep project data serializable across the server/client boundary.
- Do not create the grid during module evaluation in code that also runs on the server.
- Clean up listeners and element instances when client-side routes unmount.
```ts
if (typeof window !== 'undefined') {
const { defineCustomElements } = await import('@revolist/revogrid/loader');
defineCustomElements();
}
```
For React meta-frameworks, dynamically load the component client-side. For Vue/Nuxt use a client-only boundary. For Angular SSR, guard browser-only setup with the platform check used by your application.
The [Vanilla quick start](/guides/gantt/introduction/vanilla-typescript/) shows explicit mount and cleanup; the wrapper quick starts show property binding after hydration.
```astro
---
import GanttBasic from '@revolist/revogrid-examples/components/gantt/GanttBasic.vue';
---
```
Copy the component into your own application; the private workspace examples package is used only by this documentation site.
---
# Configuration Reference
URL: https://pro.rv-grid.com/guides/gantt/reference/configuration-reference/
Source: src/content/docs/guides/gantt/reference/configuration-reference.mdx
Description: Human-readable map of public Gantt config objects.
Surface: gantt
Public config entry points:
- `GanttPluginConfig`
- `weekStartsOn?: 0 | 1` controls week header/tick alignment. Omit for Sunday (`0`); set `1` for Monday.
- `timelinePrecision?: 'day' | 'hour'` controls the default precision for timeline task create, move, and resize interactions. Use `'hour'` with `zoomPreset: 'hour-day'` or `zoom.defaultLevelId: 'hour-day'` for hour-scale planning.
- `snap?: { unit?: 'day' | 'hour'; workingTime?: boolean }` controls interaction snapping. `unit: 'hour'` keeps interactions on hour boundaries; `workingTime: true` or omitted skips holidays, closed weekdays, and closed `calendar.workingHours`.
- `calendars?: CalendarEntity[]` can include intraday `workingHours` such as `{ start: '09:00', end: '17:00' }` plus `hoursPerDay` for hour-mode snapping and scheduling.
- `durationSettings?: DurationSettings` controls the fixed/automatic display unit, compact display precision through `maximumFractionDigits` (default `1`), and conversions through `hoursPerDay`, `hoursPerWeek`, and `daysPerMonth`. See [Duration Units and Conversion](/guides/gantt/scheduling/durations/).
- `fields?: ViewFieldMap` maps shared view meanings to properties in each authored task row. See [Task source field mapping](#task-source-field-mapping).
- `taskWindow?: GanttTaskWindowConfig` selects scheduled tasks overlapping an explicit or relative date window while keeping their WBS ancestors. See [Task Windows and Lookahead](#task-windows-and-lookahead).
- `allowedTaskTypes?: TaskType[]` restricts the choices in packaged Task Type column and dialog editors. Omit it to allow `task`, `milestone`, and `summary`; use `['task']` to prevent users from converting tasks to milestones or summaries.
- `labels?: GanttLabelsConfig` customizes option labels, validation errors, scheduler warnings, and task-editor text from one project-level object. See [Labels, Errors, and Warnings](#labels-errors-and-warnings).
- `dateFormats?: GanttDateFormatConfig` controls task-table, tooltip, editor, and timeline date presentation and parsing. See [Date Formatting](#date-formatting).
- `visuals.percentDoneMode?: 'number' | 'bar' | 'circle'` controls the project default for the managed `% Done` task-table cell. It defaults to `bar`; `{ prop: 'percentDone', mode: 'circle' }` overrides it per column. See [Actuals and Progress](/guides/gantt/scheduling/progress/#percent-done-column-mode).
- `taskCreateRow?: boolean` shows an opt-in pinned bottom “Add new task” row. It requires `allowTaskCreate: true` and is hidden for read-only or resource-planning views. See [Add Task Row](/guides/gantt/interaction/add-task-row/) for `GanttAddTaskRowPlugin` behavior and `isGanttAddTaskRow()` usage.
- `GanttSchedulingConfig`
- `GanttResourcePlanningConfig`
- `GanttVisualConfig`
## Task Windows and Lookahead
Use `taskWindow` to coordinate the task list with a planning period. Tasks and milestones match when their scheduled dates overlap the inclusive window. Summary rows provide ancestor context; a summary spanning the window does not include unrelated children. The complete project remains available to the scheduler, so hidden predecessors still affect visible tasks.
For an explicit period:
```ts
grid.gantt = {
...projectConfig,
taskWindow: {
startDate: '2026-10-19',
endDate: '2026-11-08',
includeOverdue: true,
},
};
```
For a rolling three-week lookahead, reuse the shared relative-date expression:
```ts
grid.gantt = {
...projectConfig,
weekStartsOn: 1,
taskWindow: {
relative: {
version: 1,
mode: 'custom',
direction: 'next',
amount: 3,
unit: 'week',
rolling: true,
},
anchorDate: '2026-10-19',
includeOverdue: true,
},
};
```
Change `amount` to `2` or `6` for other lookahead lengths. With `rolling: true`, the window starts on the anchor date and contains exactly the requested number of calendar days or weeks. With `rolling: false`, weeks align to `weekStartsOn` and include the week containing the anchor. Without `anchorDate`, relative windows use `statusDate`, or today's calendar date in the project timezone when no status date is set. Relative windows resolve during configuration and data refreshes; applications that stay open overnight can reassign `grid.gantt` to refresh the current date.
`includeOverdue: true` also includes unfinished tasks ending before the window's first date. Actual-finished tasks, tasks at 100% progress, and tasks with workflow status `done` are excluded from this additional overdue set. Completed tasks still appear when they overlap the selected period.
The timeline follows a bounded task window by default. Set `syncTimeline: false` to filter task rows while retaining the task-derived timeline, or provide `timelineRange` to override the chart bounds explicitly. `timelineRange` on its own continues to control chart bounds without filtering rows. Invalid task-window dates or expressions leave task-window filtering disabled.
Window activation or a change of window reveals the ancestors of matching tasks. Users can subsequently collapse those rows. Search, resource filters, and other grid filters continue to apply. Task-window row filtering applies to the task view; resource-planning rows retain their resource/load semantics.
Clear the window through the same configuration property:
```ts
grid.gantt = { ...grid.gantt, taskWindow: undefined };
```
Use `resolveGanttTaskWindow(config)` and `selectGanttTaskWindowTasks(tasks, window)` from `@revolist/gantt` when an application needs the same resolved period and task selection for its own views. Trade, subcontractor, and location fields can remain in application records and use normal grid filtering.
## Task Source Field Mapping
Gantt uses the shared `ViewFieldMap` names also used by Scheduler and Kanban. The mapping values are property names in `grid.source`:
| Meaning | Gantt default |
| --- | --- |
| `id` | `id` |
| `title` | `name` |
| `status` | `workflowStatus` |
| `start` | `startDate` |
| `end` | `endDate` |
| `color` | `color` |
| `progress` | `percentDone` |
This lets one application record shape feed several views:
```ts
grid.gantt = {
...project,
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
};
```
The mapping changes property names only. Date and duration parsing, progress units, validation, and scheduling behavior stay the same. Gantt writes accepted task changes back with the authored property names and preserves unrelated row fields. `resourceId` is accepted so the mapping object can be shared, and its source value is preserved; Gantt resource scheduling continues to use `ganttAssignments`, where a task can have multiple assignments.
## Duration Settings
`durationSettings.defaultUnit` and `defaultWorkUnit` accept every `DurationUnit` or `'auto'`. Duration, Remaining Duration, and Work are stored as hours. `defaultUnit` controls task-span quantities while `defaultWorkUnit` independently controls Work; task `durationUnit` and `workUnit` win. `auto` displays sub-hour values as minutes, sub-day values as hours, and longer values as days without changing stored task data.
```ts
grid.gantt = {
...project,
durationSettings: {
defaultUnit: 'auto',
defaultWorkUnit: 'hour',
hoursPerDay: 8,
hoursPerWeek: 40,
daysPerMonth: 20,
},
};
```
See [Duration Units and Conversion](/guides/gantt/scheduling/durations/) for mixed task units, flexible input, elapsed durations, defaults, and conversion behavior.
## Labels, Errors, and Warnings
Use `gantt.labels` as the project-level localization and message catalog. Error and warning overrides can be strings or formatter functions. The plugin keeps the existing result contract—such as `{ field, code, message }`—and changes only the resolved `message`.
```ts
grid.gantt = {
...project,
labels: {
errors: {
taskEdit: {
invalidRemainingDuration:
'Remaining duration must be a value such as 5h, 2d, 3w, or 30m.',
},
taskEditor: {
invalidRemainingDuration: 'Enter remaining time in hours or days.',
},
dependency: {
taskReferenceNotFound: ({ value }) => `No task matches "${value}".`,
},
},
warnings: {
scheduler: {
deadlineMissed: ({ taskId, date }) =>
`Task ${taskId} missed its ${date} deadline.`,
},
},
taskEditor: {
applyLabel: 'Apply changes',
status: {
saveFailed: 'The task could not be saved.',
},
},
},
};
```
Message groups identify the runtime surface: `taskEdit` for managed grid-cell edits, `taskMutation` for task operations and timeline interactions, `assignment`, `dependency`, and `taskEditor`. Scheduler issues and conflicts use `warnings.scheduler`. The exported `DEFAULT_GANTT_ERROR_LABELS` and `DEFAULT_GANTT_WARNING_LABELS` constants contain every supported key and the default English text.
The task-editor dialog inherits `gantt.labels.taskEditor`. A dialog-specific `localeText` option remains supported and takes precedence over the inherited project labels, which allows one editor instance to override selected text without duplicating the rest of the project locale.
## Date Formatting
Managed Gantt date columns display a localized medium date by default (for example, `Apr 6, 2026` with the default `en-US` locale). This applies consistently to Start, End, Actual Start/Finish, Early/Late Start/Finish, Constraint Date, and Deadline. ISO date-time task values such as `2026-04-06T09:00:00.000Z` remain unchanged in your data; only their cell display is formatted, and the default table display intentionally omits the time.
Set `gantt.dateFormats.locale`, `timeZone`, and `table` to choose your own project-wide table format. `table` accepts standard `Intl.DateTimeFormatOptions`:
```ts
grid.gantt = {
...project,
dateFormats: {
locale: 'en-GB',
timeZone: 'Europe/London',
table: {
day: '2-digit',
month: '2-digit',
year: 'numeric',
},
},
};
```
For field-specific or application-defined output, provide a formatter callback. The context includes the managed field name, locale, time zone, and surface:
```ts
grid.gantt = {
...project,
dateFormats: {
table: (value, context) => {
const date = new Date(value.length === 10 ? `${value}T00:00:00Z` : value);
const prefix = context.field === 'deadlineDate' ? 'Due ' : '';
return `${prefix}${new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: 'short',
year: 'numeric',
timeZone: context.timeZone,
}).format(date)}`;
},
},
};
```
Use `dateFormats.tooltip` and `dateFormats.timeline` for those surfaces. If editor text must use a non-ISO format, pair `dateFormats.editor.formatter` with `dateFormats.editor.parser` (or the shared `dateFormats.parser`) so edits are converted back to ISO date or UTC ISO date-time values before scheduling.
## Adaptive Mobile Interactions
Gantt automatically adapts pointer interactions for phones, tablets, and other coarse-pointer devices. There is no mobile-only configuration flag; the same `GanttPluginConfig` works across desktop and touch devices.
Mobile behavior is designed around **select-then-edit**:
- Mouse and trackpad users keep direct desktop interactions for moving, resizing, progress updates, dependency drawing, timeline panning, and split-panel resizing.
- Touch users tap a task bar first to select it and reveal larger resize, progress, dependency, and split handles.
- Dragging a selected task bar moves the task. Dragging a visible handle resizes, updates progress, edits splits, or creates dependencies.
- Plain touch scrolling in the task table or timeline remains scroll-first and should not move, resize, or create tasks accidentally.
- Hidden coarse-pointer handles are not hit-testable until the task is selected or interaction-active, so an edge swipe on an unselected task still behaves like navigation.
- Task editor dialogs switch to a single-column, full-viewport layout on narrow screens with larger controls for touch input.
The Gantt E2E suite includes a dedicated mobile project using an iPhone viewport. It verifies that unselected task-bar swipes do not mutate dates, tapping selects a task and exposes handles, and the task editor dialog fits a phone-sized viewport.
Canonical definitions:
- `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`
For generated signatures, see [API: Gantt](/api/gantt/).
---
# Gantt Capability Coverage
URL: https://pro.rv-grid.com/guides/gantt/reference/coverage/
Source: src/content/docs/guides/gantt/reference/coverage.mdx
Description: Coverage matrix connecting supported Gantt capabilities to customer guides, public API areas, and runnable examples.
Surface: gantt
Use this matrix to move from a supported capability to its explanation, public contract, and—where behavior is visual or interactive—a runnable example. A missing example means the capability is configuration or integration guidance that does not benefit from a separate visual demo.
The matrix covers capabilities documented as supported by the current `@revolist/gantt` package. It deliberately does not imply support for capabilities that exist only in other Gantt products.
---
# Events And Methods
URL: https://pro.rv-grid.com/guides/gantt/reference/events-methods/
Source: src/content/docs/guides/gantt/reference/events-methods.mdx
Description: Use Gantt lifecycle events to validate changes and call public plugin methods for tasks, zoom, baselines, hierarchy, and snapshots.
Surface: gantt
## Live example
Move a task toward the configured freeze window to exercise a cancelable before-change event. The custom row actions dispatch application events without installing methods on the grid.
['Project lifecycle', 'applyGantt|clearGantt|getProjectSnapshot'],
['Zoom and range', 'zoomIn|zoomOut|setZoomLevel|getZoomLevel|canZoomIn|canZoomOut|getVisibleRange'],
['Tasks', 'createTask|updateTask|finalizeTaskCreate|discardTaskCreate'],
['Hierarchy', 'indentTask(s)|outdentTask(s)|deleteTask(s)|convertTask(s)ToMilestones'],
['Baselines', 'captureBaseline'],
];
['gantt-before-task-change', 'Before create, move, resize, edit, progress, split, indent, outdent, delete, or milestone conversion.'],
['gantt-before-dependency-change', 'Before dependency create, delete, edit, or set replacement.'],
['gantt-before-assignment-change', 'Before task assignments are edited.'],
];
## Get The Plugin
Outside plugin code, locate the runtime through the grid's plugin collection.
```ts
import { GanttPlugin } from '@revolist/gantt';
const plugins = await grid.getPlugins();
const gantt = plugins.find((plugin) => plugin instanceof GanttPlugin);
```
Do not install reverse callbacks or custom methods directly on the grid element.
## Public Method Groups
Use the [Gantt API](/api/gantt/) for exact signatures and return types.
## Cancelable Before-Change Events
Call `preventDefault()` to reject a proposed packaged mutation.
```ts
grid.addEventListener('gantt-before-task-change', (event) => {
if (!canEditTask(event.detail.taskId, event.detail.action)) {
event.preventDefault();
}
});
```
## Collect Committed Changes
Gantt publishes successful task, dependency, and assignment mutations through EventManager's existing `gridedit` event. This gives persistence and audit integrations one committed-change stream for grid edits and Gantt interactions such as moving, resizing, changing progress, editing hierarchy, and deleting tasks.
```ts
grid.addEventListener('gridedit', (event) => {
for (const change of event.detail.domainChanges ?? []) {
if (change.type === 'gantt-task') {
if (change.detail.task) saveTask(change.detail.task);
}
}
});
```
Use `event.detail.eventTypes` to detect `gantt-task-change`, `gantt-dependency-change`, or `gantt-assignment-change`. `domainChanges` preserves the committed changes in order and includes previous and current domain state. Bulk operations and scheduler cascades include every affected task.
`data`, `previousData`, and `models` keep the normal grid row/field shape. Stable task-row updates appear there exactly like other grid edits; dependencies, assignments, creates, deletes, and reorders that do not have a safe row coordinate stay in `domainChanges`. Replay-only project snapshots are stored separately in `history` and are never mixed into grid fields.
Cancellation belongs to the corresponding `gantt-before-*-change` event. A Gantt change in `gridedit` has already committed, so calling `preventDefault()` there does not roll it back. When EventManager collection is enabled, consecutive Gantt mutations are emitted as one collected transaction and one history step.
## Commands And Notifications
The grid also uses typed events for task creation/edit requests, hierarchy commands, zoom, search, dependency hover/selection, task-created notification, and panel resize. Prefer exported event constants when code must be refactor-safe.
```ts
import {
GANTT_TASK_CREATE_EVENT,
GANTT_ZOOM_IN_EVENT,
} from '@revolist/gantt';
grid.dispatchEvent(new CustomEvent(GANTT_TASK_CREATE_EVENT));
grid.dispatchEvent(new CustomEvent(GANTT_ZOOM_IN_EVENT));
```
Application listeners should update or validate project data, not query rendered task-bar geometry. See [Application State](/guides/gantt/features/application-state/) for persistence ownership.
---
# Limitations And MSP Differences
URL: https://pro.rv-grid.com/guides/gantt/reference/limitations/
Source: src/content/docs/guides/gantt/reference/limitations.mdx
Description: Current Gantt scheduling boundaries and intentional differences.
Surface: gantt
Implemented behavior is MSP-inspired, not full MSP parity.
Current boundaries:
- Auto-leveling is forward-schedule oriented; backward mode stays warning-only.
- Leveling is delay-based and does not auto-insert new split ranges.
- Constraint and validation coverage is deterministic but narrower than full MSP option matrix.
- Docs describe current code behavior in `packages/gantt/src/gantt`, not aspirational features.
---
# Troubleshooting
URL: https://pro.rv-grid.com/guides/gantt/reference/troubleshooting/
Source: src/content/docs/guides/gantt/reference/troubleshooting.mdx
Description: Common Gantt setup and scheduling issues.
Surface: gantt
Common checks:
- Missing `ganttCalendars` or mismatched `calendarId`.
- Duplicate IDs in tasks/dependencies/resources/assignments.
- Dependency points to missing task ID.
- A circular dependency chain such as `Setup load balancer -> Configure firewall -> Setup load balancer`. The scheduler reports this as `dependency-cycle`; inspect the issue's `taskIds` and `dependencyIds`, then remove or change one of the links.
- Invalid task date ranges or malformed constraints.
- No visible timeline because viewport/zoom span does not include task dates.
## Task Jumps While Dragging
If a task appears to jump during drag or resize, first check for dependency validation issues. Automatic tasks normally clamp to the earliest date allowed by their predecessors. A circular dependency cannot produce a valid predecessor order, so RevoGrid reports `dependency-cycle` and leaves dependency clamping disabled until the cycle is fixed.
Use the scheduling result passed to your diagnostics flow to locate the invalid links:
```ts
const cycleIssue = schedulerResult.issues.find((issue) => issue.code === 'dependency-cycle');
if (cycleIssue) {
console.table({
tasks: cycleIssue.taskIds.join(', '),
dependencies: cycleIssue.dependencyIds.join(', '),
});
}
```
Validation and diagnostics are produced by:
- `engine/scheduler-engine/validation.ts`
- `engine/scheduler-types.ts`
---
# Gantt Upgrades
URL: https://pro.rv-grid.com/guides/gantt/releases/
Source: src/content/docs/guides/gantt/releases/index.mdx
Description: Upgrade guidance for standalone RevoGrid Gantt integrations.
Surface: gantt
Use the [Gantt migration guide](./migration/) when upgrading `@revolist/gantt`.
It covers package alignment, public imports, scheduling regression fixtures, application events, persistence, task-table columns, and timeline visuals.
The current public contract is documented in the [Gantt API reference](/api/gantt/). This documentation does not maintain release snapshots or generated API comparisons.
---
# Gantt Migration Guide
URL: https://pro.rv-grid.com/guides/gantt/releases/migration/
Source: src/content/docs/guides/gantt/releases/migration.mdx
Description: Upgrade a standalone RevoGrid Gantt integration using stable imports, project data, events, and application-level regression checks.
Surface: gantt
Use this checklist when upgrading `@revolist/gantt`.
1. Upgrade Gantt, Pro, the framework wrapper, and RevoGrid together.
2. Run the application against the documented package version shown on this page.
3. Import supported runtime APIs from the public package barrel. Application recipes now live under `examples/components/src/components/gantt/recipes/`: copy the needed files into your app and replace former recipe imports from `@revolist/gantt` with relative `./recipes/...` imports.
4. Review the current [Gantt API reference](/api/gantt/) for exported contracts and deprecations.
5. Re-run schedule fixtures containing dependencies, calendars, constraints, resources, baselines, and manual tasks.
6. Verify cancelable `gantt-before-*` policies and persistence callbacks.
7. Check task-table columns and timeline visuals in each supported breakpoint.
```sh
pnpm up @revolist/gantt @revolist/revogrid-pro @revolist/revogrid
```
For a 1.x-to-2.x repository migration outside Gantt, also read the [general migrations guide](/guides/migrations/).
---
# Resource Planning View
URL: https://pro.rv-grid.com/guides/gantt/resources/resource-planning-view/
Source: src/content/docs/guides/gantt/resources/resource-planning-view.mdx
Description: Visualize resource-centric timelines and daily/weekly load bars.
Surface: gantt
The Resource Planning View switches the Gantt chart from a task-centric WBS to a resource-centric view. This allows managers to visualize daily or weekly load, capacity thresholds, and over-allocation hotspots.
Resource names include a compact avatar for faster scanning. Gantt derives a colored initials avatar from the resource name by default; supply `avatarUrl` on a resource to render its profile image instead.
## Enabling the Planning View
Use `gantt.resourcePlanning` to toggle the view and configure its behavior:
```typescript
grid.gantt = {
resourcePlanning: {
enabled: true,
visibleResourceIds: ['alex', 'nina', 'ravi'],
loadGranularity: 'day',
capacityDisplay: 'line',
overAllocationDisplay: 'highlight',
},
};
```
```typescript
grid.ganttResources = [{
id: 'alex',
name: 'Alex Morgan',
avatarUrl: '/images/team/alex-morgan.png',
role: 'Lead Engineer',
calendarId: 'standard',
allocationCapacity: 100,
hourlyCost: 120,
}];
```
### Configuration Options
| Option | Description |
|---|---|
| `enabled` | Toggle between task rows and resource load rows. |
| `visibleResourceIds` | Array of resource IDs to display in the planning view. |
| `loadGranularity` | `'day'` or `'week'`. The time bucket for load calculation. |
| `capacityDisplay` | `'line'` or `'none'`. Renders a capacity threshold on load bars. |
| `overAllocationDisplay` | `'highlight'` or `'none'`. Marks buckets exceeding capacity. |
## Resource Filtering
When you are in the normal task view, you can use `resourceFilterIds` to filter tasks assigned to specific resources while keeping ancestor summary tasks visible. This provides context-aware filtering within the project structure.
```typescript
grid.gantt = {
resourceFilterIds: ['alex'],
};
```
The Resource Planning View is highly dynamic. Changes to task dates or assignment units on the main grid are immediately reflected in the resource load bars.
---
# Resources and Assignments
URL: https://pro.rv-grid.com/guides/gantt/resources/resources-and-assignments/
Source: src/content/docs/guides/gantt/resources/resources-and-assignments.mdx
Description: Define resource capacity, assignments, and how they interact with task work.
Surface: gantt
Resources define the people, equipment, or facilities that perform work on your tasks. Assignments connect these resources to specific tasks with defined allocation units and work hours.
## Data Model
Resources and assignments are managed through two distinct collections:
```typescript
grid.ganttResources = [{
id: 'alex',
name: 'Alex',
role: 'Engineer',
calendarId: 'cal-standard',
allocationCapacity: 100, // 100% capacity
hourlyCost: 120,
}];
grid.ganttAssignments = [{
id: 'assign-1',
taskId: 'task-1',
resourceId: 'alex',
allocationUnits: 100, // 100% assignment
workHours: 40,
responsibility: 'Owner',
}];
```
### Capacity and Units
`allocationCapacity` and `allocationUnits` accept either a percent scale (`100`) or a decimal scale (`1.0`) for full capacity. The scheduling engine uses these values to calculate resource load and identify over-allocations.
## Resource Over-Allocation
When the `resourceLeveling` policy is set to `'warn'`, the scheduler calculates allocation by resource and working date. If assigned units exceed the resource's capacity on any given day, it reports a `resource-overallocation` conflict.
```typescript
grid.gantt = {
scheduling: { resourceLeveling: 'warn' },
};
```
### Example: Over-Allocation Warning
If one resource is assigned to multiple overlapping tasks at 100% each, a warning will be triggered:
```typescript
grid.ganttResources = [{
id: 'alex',
name: 'Alex',
allocationCapacity: 100,
}];
grid.ganttAssignments = [
{ taskId: 'build', resourceId: 'alex', allocationUnits: 100 },
{ taskId: 'docs', resourceId: 'alex', allocationUnits: 100 },
];
```
**Expected result**: On days where `build` and `docs` overlap, Alex is allocated at 200%. Each affected task receives a `resource-overallocation` warning in the grid and on the timeline.
---
## Next Steps
- **[Resource Planning View](../resource-planning-view)**: Visualize resource load on a dedicated timeline.
- **[Resource Leveling](../scheduling/leveling)**: Automatically resolve over-allocations by delaying tasks.
- **[Effort Modes](../scheduling/effort-modes)**: Control how assignments affect task duration.
---
# Calendars and Duration
URL: https://pro.rv-grid.com/guides/gantt/scheduling/calendars/
Source: src/content/docs/guides/gantt/scheduling/calendars.mdx
Description: Edit working weeks, holidays, and recurring exceptions in the working-calendar dialog, and see how they affect task schedules.
Surface: gantt
## Try the Working-Calendar Editor
Open **View → Working calendars** in the Gantt toolbar below to open the calendar editor. The live example includes the editor plugin and its opt-in toolbar action; expand **Source code** to see the integration in TypeScript, Vue, React, or Angular.
1. Select **Standard** in the calendar list. Use **New calendar** for a separate schedule or **Duplicate calendar** to start from an existing one. **Based on** selects a base calendar for inheritance; **Calendar name** and **Time zone** edit its metadata.
2. In **Working week**, choose **Custom hours** for a weekday to edit its start and finish times. Use **Add working hours** for split shifts, such as 09:00–12:00 and 13:00–17:00, or select **Non-working** to close the day. For a derived calendar, **Use inherited working week** controls whether it inherits its base week; individual weekdays can still override it.
3. In **Exceptions**, click **Add exception**, give it a name, and set **Start date** and **Finish date**. Choose **Non-working** for a holiday or shutdown, or **Custom hours** for a shortened or extra working day. **Repeat** supports daily, weekly, monthly, and yearly rules, with **Repeat interval** and an optional **Repeat until** date.
4. Navigate the month preview and select a date to inspect its effective working hours. The legend distinguishes non-working dates, local overrides, inherited exceptions, and the selected exception. Preview changes apply to the draft immediately.
5. Click **Save changes** to validate and apply the calendars and recalculate the schedule. **Cancel**, the close button, or **Escape** discards the draft. Enable **Read only** above the demo, then reopen **View → Working calendars** to inspect the same dialog with editing disabled.
The demo enables calendar-aware scheduling. To see a holiday affect task dates, add a non-working exception on a working date within a task's schedule, then save. Changes stay in the browser; the example does not write to a server. Creating a calendar does not assign it to tasks or resources automatically: set their `calendarId` to use it.
## Calendar-Aware Duration
`duration` is stored in hours and can be displayed or entered as minutes, hours, days, weeks, months, quarters, or years. Unit conversion uses `durationSettings` and the effective calendar; see [Duration Units and Conversion](/guides/gantt/scheduling/durations/). Enable calendar exclusion when weekends, holidays, and closed hours should be skipped:
```typescript
grid.gantt = {
scheduling: {
excludeHolidaysFromDuration: true,
},
};
```
## Defining Calendars
Calendars use ISO weekday numbers where Monday is `1` and Sunday is `7`:
```typescript
grid.ganttCalendars = [{
id: 'cal-standard',
name: 'Standard',
timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5],
holidays: ['2026-05-25'],
hoursPerDay: 8,
}];
```
Each task references a calendar through `calendarId`. Resource capacity checks use the resource calendar when a resource is assigned.
`CalendarEntity.hoursPerDay` is the default conversion for task durations on that calendar. Project `durationSettings.hoursPerDay` can override it globally. Conversion settings do not change `workingDays`, `holidays`, or `workingHours` automatically.
## Inherited Weeks and Exceptions
Use `baseCalendarId` to reuse a working week. A child inherits the base week by default; `inheritWorkingWeek: false` keeps its local week instead. `weekdayWorkingHours` overrides individual ISO weekdays. An empty interval list closes that weekday.
```typescript
grid.ganttCalendars = [
{
id: 'standard', name: 'Standard', timeZone: 'UTC',
workingDays: [1, 2, 3, 4, 5], holidays: [], hoursPerDay: 8,
workingHours: [{ start: '09:00', end: '17:00' }],
},
{
id: 'team', name: 'Team', timeZone: 'UTC',
baseCalendarId: 'standard',
workingDays: [], holidays: [], hoursPerDay: 8,
weekdayWorkingHours: {
1: [{ start: '10:00', end: '12:00' }, { start: '13:00', end: '16:00' }],
},
exceptions: [
{ id: 'shutdown', name: 'Shutdown', startDate: '2026-08-10', endDate: '2026-08-14', workingHours: [] },
{ id: 'annual-holiday', name: 'Annual holiday', startDate: '2026-12-25', workingHours: [], recurrence: { frequency: 'yearly' } },
{ id: 'short-day', name: 'Short day', startDate: '2026-12-24', workingHours: [{ start: '09:00', end: '12:00' }] },
],
},
];
```
Assign `calendarId: 'team'` to the relevant tasks or resources. Dated exceptions can close a date or supply replacement working intervals. Recurrence supports daily, weekly, monthly, and yearly rules with an optional positive `interval` and inclusive `until` date. A matching child exception takes precedence over its base calendar's exception. Calendar dates use civil `YYYY-MM-DD` values; working intervals belong to that calendar date.
Keep `scheduling.excludeHolidaysFromDuration: true` when task scheduling should skip closed dates and intervals. `hoursPerDay` remains the conversion factor for canonical task durations; it does not imply every weekday has that many available hours. For example, seven hours starting Monday at 10:00 on a 10:00–12:00 / 13:00–16:00 Monday finish after consuming another two available hours on a subsequent working date.
Base calendars must exist and inheritance must be acyclic. Bounded nonworking exceptions, including a recurrence’s `until` date, must span at most 3,653 inclusive days from their start so they fit within the working-date traversal horizon. Invalid ranges, recurrence rules, and calendars without an effective working week are rejected before editor changes are committed.
## Calendar Editor
Register the editor adapter alongside Gantt, then open it through its plugin instance:
```typescript
import {
defineGanttToolbar,
GanttPlugin,
GanttCalendarEditorPlugin,
} from '@revolist/gantt';
grid.plugins = [GanttPlugin, GanttCalendarEditorPlugin];
// toolbarElement is an application-owned container above the grid.
defineGanttToolbar(toolbarElement, {
grid,
controls: { calendars: true },
});
// Open the same dialog programmatically after configuring the project calendars.
const editor = (await grid.getPlugins()).find(
plugin => plugin instanceof GanttCalendarEditorPlugin,
);
editor?.open({ selectedCalendarId: 'team', referenceDate: '2026-12-24' });
```
The dialog edits a draft. **Cancel** or **Escape** discards that draft. **Save changes** validates the complete calendar collection, commits it once, and recalculates the effective task schedule. Read-only projects allow inspection while disabling changes.
For the **View → Working calendars** toolbar entry, enable `controls.calendars: true` in `defineGanttToolbar(...)` and keep `GanttCalendarEditorPlugin` registered. The control is opt-in and has the accessible name **Calendars**.
Applications can also call `gantt.setCalendars(nextCalendars)` on their `GanttPlugin` instance. It returns `false` for invalid calendars, broken task/resource references, read-only projects, or a canceled `gantt-before-calendar-change` event. Accepted changes appear in `gridedit` with the `gantt-calendar-change` event tag and a `gantt-calendar` domain change, and participate in Gantt undo/redo. To review or reject a proposed change:
```typescript
grid.addEventListener('gantt-before-calendar-change', event => {
if (!canEditProjectCalendars()) event.preventDefault();
});
```
The editor's `locale`, `localeText`, and `timeZones` options customize its presentation. These settings do not convert existing task timestamps to another zone. Calendar editing supports reusable working weeks and exceptions; it does not imply Microsoft Project parity for every task/resource calendar combination.
### Visibility and translations
Set persistent defaults on `grid.ganttCalendarEditor` (or `grid.additionalData.ganttCalendarEditor`). Pass per-open overrides to `editor.open(...)` or in the detail of `gantt-calendar-edit`. Controls, slots, and translation dictionaries merge by key; other options replace their configured value. Defaults are read on the next open, without discarding an open draft.
```typescript
grid.ganttCalendarEditor = {
locale: 'es-ES',
controls: {
preview: false,
timeZone: false,
deleteCalendar: false,
repeatUntil: false,
},
localeText: {
title: 'Calendarios laborales',
save: 'Guardar cambios',
cancel: 'Cancelar',
calendar: { previousMonth: 'Mes anterior', nextMonth: 'Mes siguiente' },
validation: { 'invalid-calendar-name': 'Escriba un nombre para el calendario.' },
},
};
// Keeps the configured translations and other visibility settings.
editor?.open({ controls: { preview: true } });
```
`locale` controls localized dates and weekday/month names. `localeText` controls visible labels, accessible names, placeholders, calendar navigation, and validation text. Unspecified keys use English defaults. `formatValidationIssue(issue, text)` and `formatSaveError(error, text)` can supply application-specific localized messages. Error objects are not shown verbatim by default. Calendar names and exception names are application data. `formatDate(value, locale)` and `formatTime(minutes, locale)` override display formatting. Native date-picker widgets follow the browser locale; use the corresponding field replacement slot when an application needs a different date control.
Use `dir` for per-editor text direction, `className` for the editor root, and `surfaceClass`/`overlayClass` with existing CSS variables for dialog styling. `draggable` configures the shared dialog drag behavior. The same customization options work with Pro's `CalendarEditor`, `createCalendarEditor`, and `CalendarEditorDialogPlugin`. Visibility is presentation configuration; hidden fields retain their values, and Gantt still validates the complete calendar collection on Save. Read-only project/grid/editor policies and pending saves also apply to custom controllers.
| Area | Visibility keys |
| --- | --- |
| Dialog | `editor`, `header`, `title`, `description`, `close`, `footer`, `cancel`, `save`, `validation` |
| Calendar selection | `sidebar`, `calendarList`, `calendarItem`, `calendarActions`, `newCalendar`, `duplicateCalendar`, `deleteCalendar` |
| Metadata | `calendarName`, `metadata`, `basedOn`, `timeZone` |
| Working week | `tabs`, `workingWeek`, `weekday`, `inheritWeek`, `weekdayType`, `workingHours`, `hourRow`, `startTime`, `finishTime`, `addHours`, `removeHours` |
| Exceptions | `exceptions`, `exceptionList`, `exceptionItem`, `exceptionDetails`, `addException`, `deleteException`, `exceptionName`, `exceptionStartDate`, `exceptionFinishDate`, `exceptionType`, `exceptionWorkingHours`, `repeat`, `repeatInterval`, `repeatUntil` |
| Preview | `preview`, `legend`, `previewDetail` |
`previewCalendar` configures the existing Pro Calendar's navigation, view controls, weekdays, outside days, Today action and date-label formatters. Its default remains the approved month preview; a per-open `previewCalendar` object replaces the configured object. Hiding the sidebar or preview lets the remaining content use the available space. Hiding a tab selects a remaining visible tab; hiding the tab bar keeps its current content accessible. If both tabs are hidden, their content is omitted.
### Custom elements and slots
Every visibility key is also a slot location: append `.before`, `.replace`, or `.after`. For example, `calendarName.after` adds custom metadata, `exceptionDetails.before` adds exception-specific content, and `save.replace` replaces the Save action. A hidden control suppresses its slots. A `null` replacement removes built-in content; omit the slot to retain the default.
Renderers use DOM containers so applications can mount Vanilla, Vue, React, or Angular content without depending on the editor's internal rendering library:
```typescript
import type { CalendarEditorDomRenderer } from '@revolist/gantt';
const calendarNote: CalendarEditorDomRenderer = {
mount(container, context) {
const note = container.ownerDocument.createElement('p');
const update = (next: typeof context) => {
note.textContent = next.selectedCalendar?.name ?? '';
};
container.append(note);
update(context);
return { update, destroy: () => note.remove() };
},
};
grid.ganttCalendarEditor = {
slots: { 'calendarName.after': calendarNote },
};
```
The renderer context contains detached draft calendars, the selected calendar/exception, localized text, errors, active tab, read-only and pending state. Repeated slots additionally receive the corresponding calendar, exception, weekday, or working-hour index. `context.renderDefault(container)` mounts the built-in content inside a replacement and keeps it updated, so a replacement can wrap an existing control.
Use `context.controller` to select calendars/exceptions/tabs, update the draft through `updateCalendar(id, patch)` or `setCalendars(calendars)`, request `save()`, or `close()`. Mutating the context snapshot itself never commits data. Draft mutation returns `false` while read-only or pending; `save()` resolves to `false` when saving is blocked, invalid, or rejected. Renderers receive `update` on draft/state changes and `destroy` when replaced or closed. Dispose framework roots and event listeners there. Escape and the controller remain close paths when the built-in close button is hidden.
## Working-Day Lag
Use working-day lag when dependency offsets should skip weekends and holidays:
```typescript
grid.gantt = {
scheduling: {
lagCalendar: 'working-days',
},
};
```
**Example**: A one-day lag after a Friday skips the weekend and a Monday holiday, landing on Tuesday.
Each task references a calendar via its `calendarId` field. Tasks on different calendars respect their own working days and holidays independently.
---
# MSP Compatibility
URL: https://pro.rv-grid.com/guides/gantt/scheduling/compatibility/
Source: src/content/docs/guides/gantt/scheduling/compatibility.mdx
Description: Understand how RevoGrid Gantt maps to Microsoft Project scheduling concepts and behavior.
Surface: gantt
The table below describes how RevoGrid Gantt maps to Microsoft Project (MSP) concepts. This ensures a familiar experience for planners transitioning from desktop project management tools.
| MSP concept | RevoGrid Gantt behavior |
|---|---|
| Manual vs automatic task mode | `taskMode: 'manual'` preserves authored dates and warns on dependency violations. Auto tasks are dependency-safe and are clamped to valid dates. |
| Scheduled Start / Finish | `startDate` and `endDate` in the rendered row are the scheduler's effective dates. For manual tasks, authored dates remain unless a warning is reported. |
| ASAP / ALAP | `scheduleFrom: 'project-start'` gives ASAP-style forward scheduling. `scheduleFrom: 'project-finish'` gives ALAP-style backward scheduling. |
| Dependencies | FS, SS, FF, and SF dependencies are supported. Positive `lagDays` delays a successor; negative `lagDays` is lead time. |
| Constraints | Six date constraints are supported: SNET, SNLT, FNET, FNLT, MSO, and MFO. |
| Deadlines | `deadlineDate` produces a warning indicator when a task finishes late. It does not move the task, matching MSP's deadline intent. |
| Resource over-allocation | `resourceLeveling: 'warn'` reports capacity violations. `resourceLeveling: 'auto'` performs deterministic delay-based leveling for eligible automatic tasks. |
| Effort/task type | `effortMode` supports `fixed-duration`, `fixed-work`, and `fixed-units`. Fixed-work and fixed-units can derive duration from work hours and assignment units. |
| Actuals and remaining duration | `actualFinishDate` pins completed work. `actualStartDate` and `remainingDuration` pin in-progress work when `progressRescheduling` is enabled. |
| Split tasks | `splitRanges` pause work inside a task span, render visual gaps, and extend the computed finish. |
| Critical path | Critical tasks, critical dependencies, early/late dates, and total slack are recomputed after all scheduling rules. |
Microsoft Project has more scheduling knobs than this implementation, including status-date rescheduling options, per-resource Can Level, elapsed duration syntax, and automatic split-based leveling. RevoGrid exposes a deterministic subset designed for web-driven scheduling.
## Reference Links
Useful Microsoft Project references for comparison:
- [Task Mode field](https://support.microsoft.com/en-gb/office/task-mode-task-field-3b185518-0e9a-4774-ba51-7b8d191bb84a)
- [Task constraints](https://support.microsoft.com/en-us/office/set-a-task-start-or-finish-date-constraint-for-a-task-3a7544fa-e992-4647-911b-54cdbe508784)
- [Predecessors field and lead/lag](https://support.microsoft.com/en-us/office/predecessors-task-field-5a5ea9c2-14e6-4be7-9a3d-d8b6cba10cab)
- [Deadline field](https://support.microsoft.com/en-us/office/deadline-task-field-8c6c4acd-1324-484a-a92b-9bd2eb313dfa)
- [Resource leveling](https://support.microsoft.com/en-us/office/resource-leveling-dialog-box-0d280b16-2753-4630-8cca-8c50915df9f5)
- [Task types and effort-driven scheduling](https://support.microsoft.com/en-us/office/change-the-effort-driven-setting-for-task-types-18efd7c7-d146-4b06-bdbe-b6a11564bdf3)
- [Actual Start and Actual Finish](https://support.microsoft.com/en-us/office/actual-start-fields-b1b833a7-07f5-4c4d-858d-f9261d1be7f7)
- [Split a task](https://support.microsoft.com/en-au/office/split-a-task-20c8581b-6266-45e3-af54-cc7c3b10deca)
- [Critical path](https://support.microsoft.com/en-us/office/show-the-critical-path-of-your-project-in-project-ad6e3b08-7748-4231-afc4-a2046207fd86)
---
# Task Constraints
URL: https://pro.rv-grid.com/guides/gantt/scheduling/constraints/
Source: src/content/docs/guides/gantt/scheduling/constraints.mdx
Description: Apply explicit date constraints like SNET, MSO, and MFO to control the scheduler's behavior.
Surface: gantt
Constraints are explicit fields on a task that override or refine the automatic forward-pass scheduling:
```typescript
{
id: 'integration',
name: 'External Integration',
constraintType: 'start-no-earlier-than',
constraintDate: '2026-05-04',
// other task fields
}
```
## Supported Constraint Types
| Constraint | MSP equivalent | Behavior |
|---|---|---|
| `start-no-earlier-than` | SNET | Start on or after the constraint date. |
| `start-no-later-than` | SNLT | Start on or before the constraint date. |
| `finish-no-earlier-than` | FNET | Finish on or after the constraint date. |
| `finish-no-later-than` | FNLT | Finish on or before the constraint date. |
| `must-start-on` | MSO | Start exactly on the constraint date. |
| `must-finish-on` | MFO | Finish exactly on the constraint date. |
## Soft vs. Hard Constraints
### Soft Constraints (SNET, SNLT, FNET, FNLT)
These constraints define a legal window for the task. If the dependency propagation moves the task outside this window, the scheduler will:
- Adjust the date to the nearest legal bound (if possible).
- Report a `constraint-window-conflict` if the dependency and constraint windows do not overlap.
### Hard Constraints (MSO, MFO)
Hard constraints are authoritative. They pin the task to an exact date, even if it violates dependencies.
- If a dependency requires a different date, the scheduler reports a `hard-constraint-conflict` or `dependency-constraint-conflict`.
- The task date is NOT moved to satisfy the dependency; the constraint wins.
Microsoft Project also exposes flexible ASAP and ALAP as constraint types. RevoGrid models those at project level with `scheduleFrom`, not as per-task `constraintType` values.
---
# Critical Path and Slack
URL: https://pro.rv-grid.com/guides/gantt/scheduling/critical-path/
Source: src/content/docs/guides/gantt/scheduling/critical-path.mdx
Description: Automatically compute and highlight the critical path — the longest chain of tasks that determines the project end date.
Surface: gantt
The **critical path** is the chain of tasks that controls the calculated project finish date.
In Microsoft Project terms, critical tasks usually have zero total slack: if one slips, the project finish slips.
The Gantt plugin computes this path from the final scheduled graph and highlights it visually.
---
## Enabling Critical Path
Set `showCriticalPath: true` in the `visuals` section of your project config:
```typescript
grid.gantt = {
visuals: {
showCriticalPath: true,
},
};
```
Critical tasks and the dependency arrows connecting them are highlighted in a distinct color (red by default).
## How It Works
The scheduler computes critical path after all scheduling rules (dependencies, constraints, actuals, etc.) have been applied.
It runs a backward pass over the graph to compute:
- **Early Start / Early Finish**: The earliest each task can begin and end.
- **Late Start / Late Finish**: The latest each task can begin and end without delaying the project.
- **Total Slack** = Late Start − Early Start.
| Field | Meaning |
|---|---|
| `earlyStartDate` | Effective scheduled start after all rules are applied. |
| `lateStartDate` | Latest date the task can occupy without delaying the project finish. |
| `totalSlackDays` | Working-day float between early and late dates. |
| `isCritical` | `true` when total slack is zero. |
Summary tasks are treated as critical when at least one child task is critical. Milestone tasks with no slack also appear on the critical path.
## Dependency Types
All four dependency types (FS, SS, FF, SF) participate in critical path analysis. Lags (`lagDays`) are also factored in. A positive lag delays the successor, while a negative lag (lead time) can reduce the path length.
## MSP Comparison
RevoGrid follows the Microsoft Project model: dependencies and final scheduled dates drive total slack, and tasks with zero slack become critical.
Deadlines and resource leveling also influence the critical path by shifting dates or changing risk profiles.
## Critical State Is Projected
Do not set `isCritical` in `grid.source`. Gantt source rows describe authored task data, while `isCritical`, slack fields, early/late dates, and critical dependency styling are projected by the scheduler on each render.
If an application needs to persist critical-path state, persist the scheduling inputs instead: tasks, dependencies, constraints, calendars, assignments, and scheduling options. RevoGrid will recompute the critical path from those inputs.
## CSS Customization
Critical path styling uses data attributes on bar and arrow elements:
```css
/* Critical task bar */
.gantt-bar[data-critical] {
--gantt-bar-color: #e53e3e;
}
/* Critical dependency arrow */
.gantt-dependency[data-critical] {
stroke: #e53e3e;
}
```
See the [Critical path analysis demo](https://demo.rv-grid.com/gantt-critical-path-analysis/ts) for a live example.
---
# Deadlines
URL: https://pro.rv-grid.com/guides/gantt/scheduling/deadlines/
Source: src/content/docs/guides/gantt/scheduling/deadlines.mdx
Description: Use deadlineDate to track target dates without constraining the scheduling engine.
Surface: gantt
Use `deadlineDate` for target dates that should warn but not lock the schedule:
```typescript
{
id: 'frontend',
name: 'Development',
deadlineDate: '2026-05-15',
// other task fields
}
```
## Behavior
If a task finishes after its `deadlineDate`, the scheduler reports a `deadline-missed` conflict. This warning appears in:
- The **Scheduling Warning** column.
- The task **tooltip**.
- The row/bar **warning state**.
- A visual **timeline indicator** (usually a down-arrow on the deadline date).
## Deadline vs. Finish Constraint
Unlike a `must-finish-on` or `finish-no-later-than` constraint, a deadline **never moves the task**.
- It is purely an analytical tool to measure performance against a target.
- It does not affect the critical path or slack calculations of other tasks.
This matches Microsoft Project's distinction between a deadline and an inflexible finish constraint: a deadline is an alert, not a scheduling rule.
---
# Scheduling Direction
URL: https://pro.rv-grid.com/guides/gantt/scheduling/direction/
Source: src/content/docs/guides/gantt/scheduling/direction.mdx
Description: Configure forward (ASAP) or backward (ALAP) scheduling for your Gantt project.
Surface: gantt
Forward scheduling is the default and behaves like Microsoft Project's ASAP (As Soon As Possible) direction. Automatic tasks are placed as early as their dependencies and constraints allow.
```typescript
grid.gantt = {
scheduling: {
scheduleFrom: 'project-start',
projectStartDate: '2026-04-06',
},
};
```
When `projectStartDate` is set, automatic root tasks start from that anchor unless a dependency or constraint moves them later.
## Backward Scheduling (ALAP)
Backward scheduling behaves like Microsoft Project's ALAP (As Late As Possible) project-finish direction:
```typescript
grid.gantt = {
scheduling: {
scheduleFrom: 'project-finish',
projectFinishDate: '2026-06-30',
},
};
```
Automatic tasks without successors are placed as late as possible against `projectFinishDate`; predecessors are pulled earlier to satisfy outgoing dependencies. If `projectFinishDate` is omitted, the scheduler uses the latest task finish in the current data.
## Example: Project Finish Scheduling
Use project-finish scheduling for ALAP-style plans:
```typescript
grid.gantt = {
scheduling: {
scheduleFrom: 'project-finish',
projectFinishDate: '2026-04-30',
},
};
grid.source = [
{
id: 'build',
name: 'Build',
duration: 3,
},
{
id: 'release',
name: 'Release',
duration: 1,
},
];
grid.ganttDependencies = [{
id: 'build-release',
predecessorTaskId: 'build',
successorTaskId: 'release',
type: 'finish-to-start',
lagDays: 0,
}];
```
**Expected result**: `release` is placed as late as possible against `2026-04-30`; `build` is pulled earlier so the FS relationship remains valid.
See the [Project-finish ALAP demo](https://demo.rv-grid.com/gantt-alap/ts) for a visible backward-scheduled chain anchored to a project finish date.
---
# Duration Units and Conversion
URL: https://pro.rv-grid.com/guides/gantt/scheduling/durations/
Source: src/content/docs/guides/gantt/scheduling/durations.mdx
Description: Enter, retain, format, and schedule task durations in minutes, hours, days, weeks, months, quarters, or years.
Surface: gantt
## Live example
Edit a task Duration using 30m, 5h, or 2d, then open Task Information to compare retained units. Summary durations remain derived and read-only.
Gantt stores `duration`, `remainingDuration`, and task `workHours` in one canonical unit: **hours**. `durationUnit` controls Duration, Remaining Duration, and Duration Variance; `workUnit` independently controls Work. These unit fields affect display and editing, not storage. Different tasks can therefore retain different display units in the same column:
```ts
grid.source = [
{ id: 'review', name: 'Review', startDate: '2026-04-06T09:00:00Z', duration: 5, durationUnit: 'hour', workHours: 16, workUnit: 'day' }, // 5 hours, 2 days Work
{ id: 'build', name: 'Build', startDate: '2026-04-07', duration: 16, durationUnit: 'day', workHours: '40h' }, // 2 days, 40 hours Work
{ id: 'release', name: 'Release train', startDate: '2026-04-09', duration: 120, durationUnit: 'week' }, // renders 3 weeks
{ id: 'handoff', name: 'Handoff', startDate: '2026-04-30T16:00:00Z', duration: 0.5, durationUnit: 'minute' }, // renders 30 minutes
];
```
The managed Duration, Remaining Duration, Duration Variance, Work, tooltip, task editor, and Excel export surfaces include the unit label, with singular/plural output such as `1 hour`, `5 hours`, `1 day`, and `2 days`.
## DurationUnit
```ts
type DurationUnit =
| 'minute'
| 'hour'
| 'day'
| 'week'
| 'month'
| 'quarter'
| 'year';
```
Minutes are supported. Persist new numeric data as canonical hours and include `durationUnit` when a particular display unit must be retained. At source hydration only, legacy unitless numeric values remain supported as day-based values (or the configured fixed default); hydrated task-store data is converted to hours.
When `durationUnit` is omitted and `defaultUnit` is `auto` (the default), the table chooses a readable display unit: values below one working hour use minutes, values below one working day use hours, and longer values use days. Work follows the same rule through `workUnit` and `defaultWorkUnit`. A unit entered by the user is retained, so `5 hrs` and `2 days` can coexist.
## Flexible input
The inline grid editor and Task Information editor accept a number followed by a short or long unit:
| Input | Meaning |
|---|---|
| `30m`, `30 min`, `30 mins` | 30 working minutes |
| `5h`, `5 hr`, `5 hrs` | 5 working hours |
| `2d`, `2 days` | 2 working days |
| `3w`, `3 wk`, `3 wks` | 3 working weeks |
| `2mo`, `2 months` | 2 working months |
| `1q`, `1 quarter` | 1 working quarter |
| `1y`, `1 year` | 1 working year |
A bare Duration or Remaining Duration edit uses the task's retained `durationUnit`, then `defaultUnit`, then days. A bare Work edit uses `workUnit`, then `defaultWorkUnit`, then hours. Every accepted edit is converted to hours before the task store is updated. Invalid or negative text is rejected. Work is person-time, so elapsed forms such as `2ed` are rejected for Work even though they are valid for task Duration.
## Conversion settings
Set project-wide conversion values with `gantt.durationSettings`:
```ts
grid.gantt = {
...project,
durationSettings: {
defaultUnit: 'auto', // or fix: 'minute' | 'hour' | 'day' | ...
defaultWorkUnit: 'auto', // independent fixed/automatic Work unit
hoursPerDay: 8,
hoursPerWeek: 40,
daysPerMonth: 20,
maximumFractionDigits: 1, // default MSP-style compact precision
},
};
```
With these settings:
- `1 day = 8 hours`
- `1 week = 40 hours = 5 working days`
- `1 month = 20 working days`
- `1 quarter = 3 working months`
- `1 year = 12 working months`
`defaultUnit` and `defaultWorkUnit` can be fixed independently, matching the distinction between task span and person-work. Both default to `auto`. `hoursPerDay` defaults to the effective task calendar's `hoursPerDay`, then 8. `hoursPerWeek` defaults to `hoursPerDay × workingDays.length`, commonly 40. `daysPerMonth` defaults to 20.
`maximumFractionDigits` controls rendered precision for Duration, Remaining Duration, Duration Variance, and Work. It defaults to `1`, trims trailing zeroes, and only affects presentation: `56.625 days` renders as `56.6 days`, while canonical stored hours are unchanged. Set it to `0` for whole units or a larger integer when a project needs more visible precision.
Changing `hoursPerDay` or `hoursPerWeek` changes conversion at edit, render, and scheduling boundaries. It does not rewrite stored hours and does not redefine the calendar's `workingDays`, holidays, or `workingHours`; configure those separately on `CalendarEntity`.
## Working time and elapsed time
Normal units consume working time. With an 8-hour day, `2d` is normally 16 working hours. When `scheduling.excludeHolidaysFromDuration` is enabled, the scheduler also skips closed weekdays, holidays, and configured intraday working-hour gaps.
Elapsed input uses an `e` prefix and runs continuously, ignoring the working calendar:
```ts
{ duration: 48, durationUnit: 'day', durationIsElapsed: true } // renders 2 elapsed days
```
The same value can be entered as `2ed` or `2 elapsed days`. `2d` means two working days; `2ed` means 48 continuous hours and can cross nights, weekends, and holidays. Elapsed aliases are supported for every `DurationUnit`, such as `30em`, `4eh`, and `1ew`.
`durationIsElapsed` affects scheduling, not only formatting. Use it only for work that genuinely continues while the project calendar is closed.
## Summary tasks
Summary duration is derived from the earliest scheduled child start to the latest scheduled child finish. It is a span, not the sum of child durations. Summary Duration cells remain read-only even when their children use mixed units.
## Milestones and zero duration
Setting a task's Duration to `0` schedules and renders it as a milestone. Unlike a derived summary Duration, the milestone Duration cell remains editable. Entering any positive value, such as `4h` or `2d`, stores the converted canonical hours and changes the task type back to `task`. The same reversible behavior applies in the Task Information dialog.
## Programmatic duration values
Store task duration as canonical hours and configure the project’s `durationSettings` for input and display units. Gantt uses a shared internal date package for parsing, conversion, and formatting across source hydration, scheduling, editors, and projection.
The former standalone duration utility exports have been removed. The shared `@revolist/dates` package is private to the workspace and is not available for separate installation. Custom integrations should use the task and project configuration contracts described above.
---
# Effort Modes
URL: https://pro.rv-grid.com/guides/gantt/scheduling/effort-modes/
Source: src/content/docs/guides/gantt/scheduling/effort-modes.mdx
Description: Model task types (Fixed Duration, Fixed Work, Fixed Units) to control how work and duration interact.
Surface: gantt
['fixed-duration', 'Preserve duration. Work and units do not recalculate duration.'],
['fixed-work', 'Preserve workHours and derive duration from assigned units and calendar.'],
['fixed-units', 'Preserve assignment units and derive duration from workHours and units.'],
];
Use `effortMode` to model Microsoft Project-style task types:
`workHours` is always stored as canonical hours. Set `workUnit` to retain a task-specific display unit, or enter Work as text such as `5h`, `2d`, or `3w`. Project `durationSettings.defaultWorkUnit` can fix the default Work unit independently from Duration.
## Formula
The duration formula for fixed-work/fixed-units is:
```text
duration = ceil(workHours / (hoursPerDay * totalAssignmentUnits))
```
## Example: Task Types
```typescript
grid.source = [
{
id: 'standup',
name: 'Standup',
effortMode: 'fixed-duration',
duration: 1,
workHours: 2,
},
{
id: 'implementation',
name: 'Implementation',
effortMode: 'fixed-work',
workHours: 32,
workUnit: 'hour',
},
];
grid.ganttAssignments = [
{
taskId: 'implementation',
resourceId: 'alex',
allocationUnits: 100,
},
];
```
**Expected result**: `standup` keeps its one-day duration. `implementation` becomes four days (32h / 8h/day) with one 100% assignment.
See the [Progress and work demo](https://demo.rv-grid.com/gantt-progress-work/ts) for more examples.
---
# Engine Logic
URL: https://pro.rv-grid.com/guides/gantt/scheduling/engine/
Source: src/content/docs/guides/gantt/scheduling/engine.mdx
Description: Deep dive into the RevoGrid Gantt scheduling engine pipeline and conflict resolution.
Surface: gantt
['High', 'Actuals', "actualStartDate and actualFinishDate pin the task; the engine won't move them."],
['High', 'Hard Constraints', 'MSO / MFO dates win over dependencies; conflict reported.'],
['Medium', 'Dependencies', 'Auto-tasks are clamped to satisfy predecessors.'],
['Medium', 'Soft Constraints', 'Dates are adjusted to bounds where possible; conflict reported if windows are disjoint.'],
['Low', 'Authored Dates', 'Used as the starting point for root tasks or manual tasks.'],
];
The RevoGrid Gantt scheduling engine follows a deterministic pipeline to compute effective dates for all tasks. It processes data changes in five distinct phases to ensure dependency safety and constraint satisfaction.
## The 5-Step Pipeline
### 1. Topological Sort
The engine first builds a dependency graph and performs a topological sort. This ensures that tasks are ordered such that every predecessor is processed before its successors.
- Summary tasks are handled separately to ensure they can roll up dates from their children.
- Circular dependencies are detected and reported as `dependency-cycle` issues with the involved task and dependency IDs.
When the active dependency graph contains a cycle, the engine treats the project as structurally invalid schedule data. It does not try to calculate a dependency-valid order through the loop, and task drag/resize does not clamp against predecessor links until the cycle is resolved.
### 2. Forward Pass (ASAP)
In the default `scheduleFrom: 'project-start'` mode, the engine performs a forward pass:
- Root tasks (those without predecessors) are anchored to the `projectStartDate`.
- Successors are shifted forward to satisfy their incoming dependencies (FS, SS, FF, SF) plus any `lagDays`.
- The engine uses the successor's calendar to compute working-day offsets if `lagCalendar: 'working-days'` is enabled.
### 3. Constraint Application
Once the dependency-driven dates are known, the engine applies per-task constraints:
- **Soft Constraints**: SNET, SNLT, etc., refine the start date if the dependency date falls outside the constraint bound.
- **Hard Constraints**: `must-start-on` and `must-finish-on` override the forward pass results. If a hard constraint violates a dependency, a conflict is reported, but the constraint date wins.
### 4. Summary Rollup
After all leaf tasks have their dates resolved, the engine rolls up the schedule to summary (parent) tasks:
- A summary task's `startDate` is the minimum of its children's `startDate`.
- A summary task's `endDate` is the maximum of its children's `endDate`.
- Rollup is recursive, traversing the entire WBS (Work Breakdown Structure) tree.
### 5. Critical Path Analysis
The final phase computes the project's early and late dates using a backward pass from the project finish date:
- **Total Slack**: The number of days a task can be delayed without delaying the project finish.
- **Critical Path**: Tasks with zero total slack are marked as `isCritical`.
- This analysis helps planners identify the "chain" of tasks that determines the total project duration.
## Conflict Resolution
The engine prioritizes user intent and data integrity when scheduling rules collide:
The scheduling engine runs synchronously on each render to provide immediate feedback during drag-and-drop or inline editing.
---
# Resource Leveling
URL: https://pro.rv-grid.com/guides/gantt/scheduling/leveling/
Source: src/content/docs/guides/gantt/scheduling/leveling.mdx
Description: Use automatic resource leveling to resolve over-allocations by delaying tasks.
Surface: gantt
Enable deterministic auto-leveling with:
```typescript
grid.gantt = {
scheduling: {
resourceLeveling: 'auto',
resourceLevelingWithinSlack: false,
},
};
```
Auto-leveling delays later overlapping automatic tasks until the resource is available, then reports `resource-leveling-adjustment`.
## Leveling Controls
Per-task leveling controls:
```typescript
const task = {
priority: 900, // 0-1000; higher priority is protected first
canLevel: true, // false keeps the task fixed during auto-leveling
levelingDelayDays: 2, // stored calendar-day delay applied by leveling
};
```
**Deterministic auto-leveling order**:
1. Non-levelable tasks first (fixed blockers).
2. Higher `priority` before lower `priority`.
3. Earlier start date, then task id.
## Constraints and Actuals
The leveling implementation avoids moving:
- Manually scheduled tasks.
- Tasks with `actualStartDate` or `actualFinishDate`.
- Summary tasks.
- Inactive tasks.
- Tasks pinned by `must-start-on` or `must-finish-on`.
Microsoft Project leveling can delay or split tasks. RevoGrid leveling is deterministic and delay-based; it supports priority and slack-limited leveling but does not split tasks automatically.
---
# Manual Scheduling
URL: https://pro.rv-grid.com/guides/gantt/scheduling/manual/
Source: src/content/docs/guides/gantt/scheduling/manual.mdx
Description: Use taskMode to lock dates and override automatic dependency propagation.
Surface: gantt
Set `taskMode` on each task, or use `taskModeDefault` for tasks that omit it:
```typescript
{
id: 'kickoff',
name: 'Fixed Kickoff Event',
taskMode: 'manual',
startDate: '2026-04-20',
endDate: '2026-04-20',
duration: 0,
// other task fields
}
```
Automatic tasks are dependency-safe. Dragging, resizing from the start edge, or editing an automatic successor before its predecessor allows will clamp the effective schedule to the dependency-valid date.
Manual tasks keep authored dates. If the manual start violates an active dependency, the scheduler reports `manual-dependency-violation` and the row/bar renders a scheduling warning indicator.
Microsoft Project's Task Mode field defaults new tasks to manually scheduled in some desktop versions. RevoGrid defaults to automatic scheduling because dependency safety is the default expectation for this Gantt plugin. Set `taskModeDefault: 'manual'` to make omitted task modes behave manually.
## Dependency Violations
When a manual task is placed at a date that violates an incoming dependency (e.g., starts before a FS predecessor finishes), the scheduler does not move the task. Instead, it:
1. Records a `manual-dependency-violation` conflict.
2. Displays a warning icon in the "Scheduling Warning" column.
3. Highlights the task bar with warning styling.
This allows planners to maintain "best guess" or "negotiated" dates while still seeing where the logical project structure is broken.
---
# Overview Scheduling
URL: https://pro.rv-grid.com/guides/gantt/scheduling/overview/
Source: src/content/docs/guides/gantt/scheduling/overview.mdx
Description: Configure MSP-style scheduling with task modes, dependencies, constraints, calendars, deadlines, resources, leveling, actuals, and critical path.
Surface: gantt
['manual-dependency-violation', 'A manual task starts before an active dependency allows.'],
['dependency-constraint-conflict', 'A dependency cannot be satisfied because an explicit constraint wins.'],
['hard-constraint-conflict', 'A must-start/must-finish constraint overrides dependencies.'],
['constraint-window-conflict', 'Soft constraint and dependency windows do not overlap.'],
['deadline-missed', 'A task finishes after deadlineDate.'],
['resource-overallocation', 'A resource exceeds capacity on a scheduled working date.'],
['resource-leveling-adjustment', 'Auto-leveling shifted a task to remove over-allocation.'],
];
The Gantt scheduler computes effective task dates from authored dates, duration, task mode, dependencies, lag/lead, calendars, constraints, deadlines, resources, actuals, split ranges, and optional resource leveling.
Its compatibility target is Microsoft Project scheduling behavior: automatic tasks are dependency-safe, manual tasks preserve authored dates but show warnings, deadlines warn without constraining, and leveling can delay eligible work.
---
## Scheduling Configuration
Configure scheduling policies under `grid.gantt.scheduling`:
```typescript
grid.gantt = {
scheduling: {
taskModeDefault: 'auto',
scheduleFrom: 'project-start',
projectStartDate: '2026-04-06',
excludeHolidaysFromDuration: true,
lagCalendar: 'working-days',
manualDependencyViolationBehavior: 'warn',
resourceLeveling: 'warn',
effortModeDefault: 'fixed-duration',
progressRescheduling: 'remaining-duration',
},
};
```
Definition source: `packages/gantt/src/gantt/grid/gantt-plugin.types.ts`.
## Scheduling Warnings
Warnings are reported as scheduler conflicts and projected into rows:
## Performance
Large-project scheduler performance is covered by the Gantt benchmark suite:
```bash
pnpm --filter @revolist/gantt bench
```
The benchmark exercises 1,000 and 3,000 task snapshots with dense dependencies and auto-leveling.
---
## Next Steps
Dive deeper into specific scheduling topics:
- **[MSP Compatibility](../compatibility)**: How RevoGrid maps to Microsoft Project.
- **[Engine Logic](../engine)**: The 5-step pipeline and conflict resolution.
- **[Scheduling Direction](../direction)**: Forward (ASAP) vs. Backward (ALAP).
- **[Calendars & Duration](../calendars)**: Working days, holidays, and lag calendars.
- **[Dependencies](../concepts/dependencies)**: FS, SS, FF, SF and lag/lead.
- **[Task Modes](../manual)**: Manual vs. Automatic scheduling.
- **[Constraints](../constraints)**: SNET, MSO, MFO and soft/hard rules.
- **[Deadlines](../deadlines)**: Target dates and missed deadline warnings.
- **[Resources & Assignments](../resources/resources-and-assignments)**: Resource capacity and assignments.
- **[Resource Planning View](../resources/resource-planning-view)**: Resource-centric timeline view.
- **[Resource Leveling](../leveling)**: Auto-leveling logic and priorities.
- **[Effort Modes](../effort-modes)**: Fixed work, duration, and units.
- **[Actuals & Progress](../progress)**: Tracking real-world status.
- **[Split Tasks](../split-tasks)**: Managing work gaps.
- **[Critical Path](../critical-path)**: Slack and schedule analysis.
---
# Actuals and Progress
URL: https://pro.rv-grid.com/guides/gantt/scheduling/progress/
Source: src/content/docs/guides/gantt/scheduling/progress.mdx
Description: Track project progress using actual start/finish dates and remaining duration.
Surface: gantt
Use actual fields to reflect progress:
```typescript
{
id: 'qa',
name: 'QA',
actualStartDate: '2026-04-08',
remainingDuration: 2,
percentDone: 50,
}
```
## Calculated Status and Status Date
Set `gantt.statusDate` to the reporting date used by the read-only **Status** and **Status Indicator** columns. It defaults to today's ISO date when omitted and affects reporting only; it does not move tasks or timeline lines.
```ts
grid.gantt = {
...project,
statusDate: '2026-04-08',
};
```
The calculated values are:
- `Complete` when `% Done` is 100 or Actual Finish exists.
- `Future Task` when the scheduled start is after the Status Date.
- `On Schedule` when linearly distributed reported completion covers the effective planned work through the day before the Status Date.
- `Late` when work has started or should have progressed but reported completion does not reach that boundary.
The comparison uses the effective task calendar, holidays, split ranges, and elapsed-duration rules. Incomplete milestones are Late before the Status Date, On Schedule on it, and Future Task after it. Summary tasks use rolled-up dates and progress. Because Gantt stores aggregate `% Done` rather than timephased actual work, completion is distributed linearly over the effective scheduled work span.
Use the editable **Workflow Status** field for `not-started`, `in-progress`, `done`, or `blocked`. Workflow Status never drives calculated Status or scheduling.
The default **Status**, **Status Indicator**, and **Workflow Status** columns use
the advanced selection filter. Their filter options reuse the same managed badge
or indicator template as the task-table cell, including projected row metadata.
## Percent Done column mode
The managed `% Done` column uses the Pro progress renderers and defaults to a horizontal `bar`. Choose the project default with `gantt.visuals.percentDoneMode`:
```ts
grid.gantt = {
...project,
visuals: {
percentDoneMode: 'circle',
},
};
```
Override that default for an individual authored column with its `mode` property:
```ts
grid.columns = [
{ prop: 'name', name: 'Task' },
{ prop: 'percentDone', name: '% Done', mode: 'number' },
];
```
| Mode | Cell display |
|---|---|
| `number` | Percentage text such as `40%`. |
| `bar` | Horizontal progress bar plus percentage. This is the default. |
| `circle` | Circular progress indicator with the percentage centered inside. |
The column `mode` takes precedence over `visuals.percentDoneMode`. Both change presentation only. Values remain numbers from `0` through `100`; sorting, filtering, editing, scheduling, task-bar progress, and Excel export continue to use `percentDone`. A custom `cellTemplate` supplied on the `% Done` grid column overrides either managed mode.
## Progress-Aware Scheduling
Enable progress-aware scheduling:
```typescript
grid.gantt = {
scheduling: {
progressRescheduling: 'remaining-duration',
},
};
```
When enabled:
- `actualStartDate` anchors in-progress work.
- `remainingDuration` replaces the planned duration for the remaining span.
- `actualFinishDate` pins completed work to the recorded finish.
- The schedule origin is shown as `Actual`.
## Example: Actuals and Remaining Duration
```typescript
grid.gantt = {
scheduling: { progressRescheduling: 'remaining-duration' },
};
grid.source = [
{
id: 'in-progress',
startDate: '2026-04-01',
duration: 10,
actualStartDate: '2026-04-03',
remainingDuration: 2,
},
{
id: 'complete',
startDate: '2026-04-01',
duration: 10,
actualStartDate: '2026-04-02',
actualFinishDate: '2026-04-04',
},
];
```
**Expected result**: `in-progress` is anchored on `2026-04-03` and schedules 2 remaining days. `complete` is pinned to its actual dates.
---
# Split Tasks
URL: https://pro.rv-grid.com/guides/gantt/scheduling/split-tasks/
Source: src/content/docs/guides/gantt/scheduling/split-tasks.mdx
Description: Pause work inside a task span using split ranges.
Surface: gantt
Use `splitRanges` to pause work inside a task:
```typescript
{
id: 'qa',
name: 'QA',
startDate: '2026-04-01',
duration: 4,
splitRanges: [{ startDate: '2026-04-03', endDate: '2026-04-04' }],
}
```
The scheduler extends the computed finish by the split duration so the task retains its planned working duration. Resource allocation checks skip split dates, and task bars render split ranges as visible gaps.
## Timeline Split Editing
- **Create**: Alt-drag inside a regular task bar.
- **Move**: Drag an existing split gap.
- **Remove**: Double-click a split gap.
## Example: Split Task with Resource Allocation
```typescript
grid.source = [{
id: 'qa',
startDate: '2026-04-01',
duration: 4,
splitRanges: [{ startDate: '2026-04-03', endDate: '2026-04-04' }],
}];
```
**Expected result**: the task keeps four working days of duration, but its computed finish extends. Resource allocation checks skip `2026-04-03` and `2026-04-04`.
---
# Build A Production Gantt Project
URL: https://pro.rv-grid.com/guides/gantt/tutorial/
Source: src/content/docs/guides/gantt/tutorial.mdx
Description: Extend the quick-start project with dependencies, editing, resources, critical path, baselines, persistence, and export.
Surface: gantt
This tutorial starts with any [framework quick start](/guides/gantt/) and builds toward the same data flow used by the full showcase.
The application recipes below live in the [examples source browser](/guides/gantt/examples/#copyable-application-recipes). Copy their `recipes/` files beside your code before using the relative imports; they are maintained examples rather than published Gantt exports.
1. **Add project hierarchy.** Keep task rows flat and connect children with `parentId`.
```ts
grid.source = [
{ id: 'delivery', name: 'Delivery', type: 'summary', startDate: '2026-07-14', duration: 8 },
{ id: 'design', parentId: 'delivery', name: 'Design', startDate: '2026-07-14', duration: 3 },
{ id: 'build', parentId: 'delivery', name: 'Build', startDate: '2026-07-17', duration: 5 },
{ id: 'launch', name: 'Launch', type: 'milestone', startDate: '2026-07-24', duration: 0 },
];
```
2. **Add dependencies.** Use stable task IDs and one of `FS`, `SS`, `FF`, or `SF`.
```ts
grid.ganttDependencies = [
{ id: 'd1', predecessorTaskId: 'design', successorTaskId: 'build', type: 'FS', lagDays: 0 },
{ id: 'd2', predecessorTaskId: 'build', successorTaskId: 'launch', type: 'FS', lagDays: 0 },
];
```
3. **Enable supported editing.** The managed task columns route edits through Gantt scheduling and validation.
```ts
grid.gantt = {
...grid.gantt,
allowTaskCreate: true,
taskCreateRow: true,
contextMenu: {},
};
```
Inline cell editing, timeline drag/resize/progress handles, dependency drawing, the task editor, and the context menu all use the same project state. See [Task Editing](/guides/gantt/interaction/task-editing/).
4. **Assign resources.** Resources and assignments are separate collections so multiple resources can work on one task.
```ts
grid.ganttResources = [{
id: 'designer', name: 'Avery', role: 'Designer', calendarId: 'standard',
allocationCapacity: 100, hourlyCost: 90,
}];
grid.ganttAssignments = [{
id: 'a1', taskId: 'design', resourceId: 'designer',
allocationUnits: 100, workHours: 24, responsibility: 'Visual design',
}];
```
5. **Show schedule analysis.** Critical path and baseline overlays are opt-in visual layers.
```ts
grid.gantt = {
...grid.gantt,
visuals: {
...grid.gantt?.visuals,
showCriticalPath: true,
showBaseline: true,
baselineId: 'approved',
},
};
grid.ganttBaselines = [{
id: 'approved',
name: 'Approved plan',
capturedAt: '2026-07-14T00:00:00.000Z',
tasks: [
{ taskId: 'design', startDate: '2026-07-14', endDate: '2026-07-16', duration: 24, progressPercent: 0 },
{ taskId: 'build', startDate: '2026-07-17', endDate: '2026-07-23', duration: 40, progressPercent: 0 },
],
}];
```
6. **Persist complete project snapshots.** Keep application state authoritative and replace grid collections after a successful save or server update.
```ts
import { exportProjectSnapshot, parseProjectSnapshotJson } from '@revolist/gantt';
const json = exportProjectSnapshot(snapshot, { space: 2 });
const restored = parseProjectSnapshotJson(json);
if (restored.ok) applyProject(restored.project);
```
7. **Add reporting actions.** Grid CSV/Excel export covers task-table data; the print recipe prepares a combined task-table/timeline report.
```ts
import { createPrintPdfExportRecipe } from './recipes/persistence-examples';
const print = createPrintPdfExportRecipe({ target: document.querySelector('#gantt')!, document, window });
print.exportProject(snapshot, { label: 'Delivery plan' });
```
Treat emitted or edited collections as application state. Do not scrape rendered task bars or cells to reconstruct project data.
Next, browse [all Gantt examples](/guides/gantt/examples/), review [configuration](/guides/gantt/reference/configuration-reference/), or open the [API reference](/api/gantt/).
---
# Smart Excel-Compatible Clipboard
URL: https://pro.rv-grid.com/guides/grid-utilities/clipboard-json/
Source: src/content/docs/guides/grid-utilities/clipboard-json.mdx
Description: Rich Excel and RevoGrid copy and paste with extensible clipboard formats.
`ClipboardPlugin` adds rich, Excel-compatible range copy and paste. It negotiates the
best browser-visible representation in this order: the lossless RevoGrid payload,
application handlers, Excel HTML, generic HTML tables, and TSV/plain text.
Use `DataGridFormattingPlugin` for the recommended setup. It installs one clipboard
owner automatically, so copied values, formulas, formats, merged cells, and explicit
row/column sizes share one canonical clipboard document:
```ts
import { DataGridFormattingPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin];
```
The clipboard codec only extracts browser-visible Excel declarations. Their
normalization into `DataGridCellFormat`, storage, rendering, and conversion back to
Excel HTML are owned by `DataGridFormattingPlugin`; clipboard code never applies
cell CSS directly.
For the complete formatting model, cell/range/row/column scopes, precedence,
Format Cells UI, state persistence, and workbook export, see
[Format Cells](/guides/data-grid/format-cells/).
## What is preserved
| Capability | RevoGrid → RevoGrid | Excel → RevoGrid | RevoGrid → Excel |
| ----------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| Values | Typed values through the rich payload, including serializable objects | Explicit numeric/boolean HTML values stay typed. When Excel supplies display-only Accounting, Percentage, or date text, the formatting plugin uses the accompanying number format to recover a canonical number or Excel serial date | Numbers, booleans, percentages, and dates are emitted as typed Excel HTML values; text remains text |
| Formulas | A1 source, plus the evaluated cached value when FormulaPlugin is available | Browser-visible A1 or R1C1 formulas normalize to FormulaPlugin-compatible A1. If Excel exposes only the displayed result, the formula source cannot be recovered | A1 source becomes Excel-relative R1C1 with a typed cached result when FormulaPlugin is available |
| Number and date formats | Exact canonical preset or custom-code format | `mso-number-format` becomes a canonical `{ kind: 'code' }` format | Presets and exact codes become Excel number-format declarations |
| Font and fill | Preserved through `DataGridCellFormat.appearance` | Allowlisted font, emphasis, text color, and fill declarations are converted | Canonical typography and colors become Excel-compatible HTML styles |
| Alignment and text layout | Horizontal/vertical alignment, wrap, indent, rotation, and shrink-to-fit | Supported Excel declarations are normalized into appearance options | Canonical options are serialized back to Excel HTML |
| Borders | Per-edge style, color, and width | Supported shorthand and longhand Excel borders are normalized | Per-edge borders are emitted; legacy uniform border options remain shorthand |
| Multiple ranges | Sparse offsets and gaps are lossless in the rich payload | Multiple browser-visible HTML tables are decoded as ranges | Each range is emitted as an HTML table; rich RevoGrid geometry remains available to RevoGrid |
| Merged cells | Merge spans are carried and applied when every destination is writable | `rowspan` and `colspan` become merge metadata | Merge metadata becomes `rowspan` and `colspan` |
| Row heights and column widths | Carried in the rich document; destination RevoGrid resizing is not applied currently | Parsed and retained, but destination RevoGrid resizing is not applied currently | Explicit sizes are emitted in the Excel HTML table |
| Application metadata | Namespaced serializable contributors are lossless | Available only when exposed by a registered handler | Written by compatible registered handlers |
Formatting is applied only after the existing provider-backed value paste is
accepted. Readonly cells, rejected validation targets, canceled edits, and
synthetic grouping rows do not receive formatting or merges.
Excel Desktop does not expose identical browser HTML on every platform. For
example, Excel for macOS may omit `x:num` and copy `$ 1,250.50`, `18.0%`, or
`8/20/26` as display text accompanied by Accounting, Percentage, or `Short Date`
format declarations. `DataGridFormattingPlugin` decodes Excel's CSS escapes and
named formats, converts those known display-only values to canonical numbers or
Excel serial dates, and stores the imported number format as a
`DataGridCellFormat`. Unknown or ambiguous text is left unchanged.
### Display-only Excel category coverage
Excel's browser HTML varies by platform, but the formatting owner covers every
documented cell number-format category when a usable format declaration is
present:
| Excel category | Display-only value recovery |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| General | Kept as text unless Excel supplies an explicit typed attribute; visual text alone is ambiguous |
| Number | Localized decimals, grouping, negative signs, parentheses, and scaling commas become numbers |
| Currency and Accounting | Locale/currency tokens and aligned currency displays become numbers; the exact format code remains canonical formatting |
| Percentage | The displayed percentage is converted back to its underlying decimal value once per `%` token |
| Fraction | Simple, mixed, fixed-denominator, and negative fraction displays become numbers |
| Scientific | Localized mantissas and signed `E` exponents become numbers |
| Date | Numeric and localized month-name dates become Excel serial dates using the format's token order and locale |
| Time and Date & time | 12/24-hour times, seconds, fractional seconds, and combined date-time displays become serial day values |
| Elapsed time | `[h]`, `[m]`, and `[s]` duration displays—including values beyond 24 hours—become serial day fractions |
| Text | Kept exactly as text, including leading zeros |
| Special | Postal, phone, social-security, and comparable placeholder formats recover their numeric value while retaining the exact format code |
| Custom | Numeric/date/time/text classification comes from the compiled code; four sections, conditions, colors, literals, spacing, fill, percentages, scaling, fractions, and scientific notation use the same engine as rendering |
Explicit `x:num` and boolean attributes always take precedence. Excel error
values and malformed, unsupported, or ambiguous display strings stay unchanged
instead of being guessed. These categories follow Microsoft's
[available number formats](https://support.microsoft.com/en-us/excel/get-started/available-number-formats-in-excel)
and [custom number-format rules](https://support.microsoft.com/en-us/excel/review-guidelines-for-customizing-a-number-format).
### Native Excel interoperability matrix
The following matrix was created as a real `.xlsx` workbook, opened in current
Excel Desktop for macOS, copied through the macOS system clipboard, pasted into
RevoGrid, and then copied from RevoGrid back into Excel. It is not an authored
HTML fixture.

### Quick support checklist
Use this list to compare an Excel range with its RevoGrid result. **Native
tested** means the format was created in an actual `.xlsx` workbook and copied
through Excel Desktop for macOS and the system clipboard—not merely passed to
the HTML parser in a test.
| Cell data or number format | Support | Native tested |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| General and plain numbers | Supported; an explicit Excel numeric value stays numeric. Ambiguous display-only General text stays text | Yes |
| Number | Supported, including decimals, grouping, signs, parentheses, and scaling commas | Yes |
| Currency and Accounting | Supported, including currency/locale tokens, aligned symbols, negative parentheses, and zero sections | Yes |
| Percentage | Supported; the displayed percentage is restored to its decimal value | Yes |
| Scientific | Supported, including signed exponents | Yes |
| Fraction | Supported, including simple, mixed, fixed-denominator, and negative fractions | Yes |
| Date | Supported for short, long, numeric, month-name, and localized dates | Yes |
| Time, DateTime, and elapsed time | Supported for 12/24-hour time, seconds, fractional seconds, combined values, and `[h]`/`[m]`/`[s]` durations | Yes |
| Text | Supported and emitted with Excel's Text format so leading zeros and number-like strings remain text | Yes |
| Special | Supported for numeric placeholder formats such as phone and postal masks | Phone mask tested |
| Custom | Supported for up to four sections, conditions, colors, literals, leading zeros, percentages, scaling, fractions, scientific notation, spacing, and fill | Positive/negative/zero sections, leading zeros, scaling, literals, and localized date tested |
| Boolean | Supported when Excel exposes Boolean typing; otherwise the displayed `TRUE`/`FALSE` is preserved as text | Conditional on Excel payload |
| Formula | Supported when the clipboard exposes A1/R1C1 source; otherwise only Excel's displayed/cached result can be preserved | RevoGrid → Excel tested; Excel for macOS omitted formula source |
| Appearance or structure | Support | Native tested |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| Font | Family, size, bold, italic, underline, strike-through, and text color | Family, size, bold, italic, and color tested |
| Fill | Solid browser-visible cell background color | Yes |
| Alignment and layout | Horizontal/vertical alignment, wrapping, indentation, text rotation, and shrink-to-fit | Alignment and wrapping tested |
| Borders | Per-edge style, color, and width | Double/per-edge border tested |
| Merged cells | Supported through HTML `rowspan`/`colspan` and the rich RevoGrid payload | Automated copy/paste tested |
| Multiple ranges | Lossless between RevoGrid instances; multiple Excel HTML tables are accepted | Automated copy/paste tested |
| Row heights and column widths | Parsed and copied back to Excel HTML; applying imported sizes to the destination RevoGrid is not implemented yet | Metadata emission tested |
| RevoGrid application metadata | Supported through namespaced, serializable contributors in the rich payload | Automated rich-payload tests |
### Unsupported or unavailable through browser clipboard
| Excel feature | Current behavior |
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Native BIFF/OLE clipboard records | Browsers do not expose them; RevoGrid uses HTML, plain text, and its versioned web custom format |
| Pictures, charts, shapes, and other floating objects | Not part of the cell-range clipboard contract |
| Comments, notes, threaded discussions, and attachments | Not copied |
| Data validation rules, dropdown definitions, and non-serializable validator callbacks | Not copied |
| Conditional-format rules, icon sets, data bars, and workbook themes | Rules are not copied; only browser-visible effective styles may be present in Excel HTML |
| Named ranges, tables, filters, pivot state, external links, and workbook relationships | Not copied |
| Macros, VBA, ActiveX, and embedded objects | Not copied or executed |
| Formula source omitted by Excel | Cannot be reconstructed safely; RevoGrid preserves the displayed/cached result and does not guess |
| Unsupported or malformed display-only values | Preserved as text instead of being coerced |
| Destination RevoGrid row/column resizing | Size metadata is retained, but the destination grid is not resized in the current version |
| Windows Excel native interoperability | Supported through the same browser-visible contract, but not yet manually verified in this matrix |
The pass deliberately records browser limits as limits. Excel for macOS exposed
`text/html`, `text/plain`, `text/rtf`, and `Files`, but its HTML omitted the raw
formula source and explicit numeric value for some display-only cells. RevoGrid
therefore preserves the displayed formula result in that direction and only
recovers a zero when the accompanying third number-format section identifies
the display literally. RevoGrid does not guess an unavailable formula. Native
Windows Excel remains a separate manual interoperability target because its
browser-visible clipboard payload can differ.
## How the representations work together
One copy operation writes several representations because the destination—not
the source—chooses which one it understands:
1. `application/x-revogrid-clipboard+json` version 2 is the lossless
RevoGrid-to-RevoGrid document.
2. Registered application formats can override built-ins by priority.
3. `text/html` carries the richest browser-visible Excel-compatible table.
4. `text/plain` keeps the selection useful in editors and applications that do
not understand structured clipboard formats.
On paste, RevoGrid negotiates in the same richest-first order. A malformed or
failing handler emits `clipboardformaterror` and falls through to the next
compatible representation instead of partially applying its result.
## Copying from RevoGrid to Excel
Copy writes an Excel-compatible HTML table alongside plain text and the lossless
RevoGrid payload. `DataGridFormattingPlugin` converts its canonical options back to
Excel number-format codes, font, fill, borders, alignment, wrapping, rotation, and
indentation. The clipboard owner also emits formulas with typed cached results,
merged-cell spans, and explicit row and column sizes.
Date and time application values remain unchanged in the grid. For Excel HTML only,
the formatting plugin converts valid date-like values to Excel 1900-system serials
and writes the matching number-format code. This lets Excel receive a real date cell
instead of display text while keeping the application's source model intact.
`ClipboardPlugin` can also be installed directly for values and custom formats. It
emits a one-time dependency warning and omits formatting metadata when no formatting
runtime is present.
## Browser and Excel boundary
Browsers expose `text/html`, `text/plain`, and permitted web custom formats; they do
not expose Excel's native BIFF/OLE clipboard records. RevoGrid therefore writes all
compatible browser representations and adds the versioned
`application/x-revogrid-clipboard+json` format for lossless grid-to-grid transfers.
Pictures, charts, comments, validation callbacks, and other native-only Excel objects
are outside the cell-range contract.
## Custom formats
Handlers have stable IDs, MIME types, priorities, detection, decoding, and optional
encoding. Higher priorities can override built-ins. A failing handler emits
`clipboardformaterror` and negotiation continues with the next compatible format.
```ts
import type { ClipboardFormatHandler } from '@revolist/revogrid-pro';
const handler: ClipboardFormatHandler = {
id: 'acme-cells',
mimeTypes: ['application/x-acme-cells+json'],
priority: 2_000,
canRead: ({ transfer }) =>
transfer.types.includes('application/x-acme-cells+json'),
read: ({ transfer }) =>
JSON.parse(transfer.getData('application/x-acme-cells+json')),
write: ({ document }) => [
{
type: 'application/x-acme-cells+json',
data: JSON.stringify(document),
},
],
};
grid.useClipboard = {
formats: [handler],
maxCells: 100_000,
maxBytes: 10 * 1024 * 1024,
};
```
Runtime registration is disposable:
```ts
const clipboard = grid
.getPlugins()
.find((plugin) => plugin instanceof ClipboardPlugin);
const unregister = clipboard?.registerFormat(handler);
```
Serializable application metadata can be carried in the rich payload through
`registerMetadataContributor`. Non-serializable editors, validators, and callbacks
are intentionally never copied.
## Migrating from ClipboardJsonPlugin
`ClipboardJsonPlugin`, its `legacyJsonText` option, and the `$rv-parse_` plain-text
marker were removed in v2.8. Use `ClipboardPlugin` directly, or install
`DataGridFormattingPlugin` to include formatting support. Object-valued cells are
preserved grid-to-grid through the versioned RevoGrid MIME payload; `text/plain`
remains readable JSON/text for other applications.
```ts
// Before
import { ClipboardJsonPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ClipboardJsonPlugin];
// After: values and custom clipboard formats
import { ClipboardPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ClipboardPlugin];
// After: recommended when cell formatting is enabled
import { DataGridFormattingPlugin } from '@revolist/revogrid-pro';
grid.plugins = [DataGridFormattingPlugin];
```
Applications that persisted or manually generated `$rv-parse_` strings must decode
them before upgrading. The smart clipboard intentionally does not interpret marker
text because ordinary user text must remain ordinary text.
## Example
---
# Dimension Animation
URL: https://pro.rv-grid.com/guides/grid-utilities/dimension-animation/
Source: src/content/docs/guides/grid-utilities/dimension-animation.mdx
Description: Animate row and column dimension sizes for custom collapse, expand, and resize flows.
`DimensionAnimationPlugin` is a plugin that animates grid dimension sizes. Use it when a feature needs rows or columns to collapse, expand, or move between explicit sizes without replacing the grid's own virtualization and dimension systems.
The plugin only animates sizes. It does not own row visibility, trimming, grouping, ordering, or business state. Callers decide what should become hidden or visible, then use the plugin to make the size transition smooth.
## When to use it
- Animate custom row or column collapse and expand flows.
- Animate explicit size changes when you already know the start and end sizes.
- Coordinate delayed state changes around animation completion.
- Support integrations such as tree rows, where `TreeDataPlugin` handles trim state and `DimensionAnimationPlugin` handles the visual transition.
## Installation
```typescript
import { DimensionAnimationPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [DimensionAnimationPlugin];
grid.dimensionAnimation = {
duration: 180,
easing: 'easeOutCubic',
types: ['rgRow'],
};
```
Configure the plugin through `grid.dimensionAnimation`. The older `additionalData.dimensionAnimation` path is still accepted for compatibility, but `grid.dimensionAnimation` is the supported API.
| Property | Default | Description |
| --- | --- | --- |
| `duration` | `180` | Animation duration in milliseconds. Use `0` to apply sizes synchronously. |
| `easing` | `'easeOutCubic'` | Easing used while interpolating sizes. Supports `'linear'`, `'easeOutCubic'`, or a custom `(progress) => number` function. |
| `types` | All row and column dimensions | Dimensions affected by requests that do not pass a specific `type`. |
## Controller API
After the plugin is registered, use its controller methods to animate indexes or explicit frames.
```typescript
const plugins = await grid.getPlugins();
const dimensionAnimation = plugins.find(
plugin => plugin instanceof DimensionAnimationPlugin,
);
await dimensionAnimation?.collapse([0, 1, 2], {
type: 'rgRow',
indexKind: 'visual',
});
await dimensionAnimation?.expand([0, 1, 2], {
type: 'rgRow',
indexKind: 'visual',
});
```
Use `indexKind: 'physical'` when indexes refer to source rows or columns. Use `indexKind: 'visual'` when indexes refer to the current rendered order after sorting, filtering, trimming, pinning, or other view changes.
### Explicit frames
Use `animate()` when you already know the visual indexes and target sizes.
```typescript
await dimensionAnimation?.animate([
{
type: 'rgCol',
visualIndex: 0,
from: 220,
to: 80,
},
]);
```
The plugin cancels any in-flight animation that targets the same dimension index before starting the next one. Call `cancel()` directly to stop matching animations, or omit arguments to cancel all dimension animations.
```typescript
dimensionAnimation?.cancel('rgRow', [0, 1]);
dimensionAnimation?.cancel();
```
## Event API
You can also request animations by dispatching the `dimensionanimate` event from the grid element. The plugin attaches `detail.done` synchronously, so event-driven integrations can wait before applying follow-up logic.
```typescript
const detail = {
action: 'collapse',
indexes: [0],
type: 'rgRow',
indexKind: 'visual',
};
grid.dispatchEvent(new CustomEvent('dimensionanimate', { detail }));
await detail.done;
```
Listen to `beforedimensionanimate` to cancel an operation before it starts. Listen to `afterdimensionanimate` to react after the animation completes or is cancelled.
```typescript
grid.addEventListener('beforedimensionanimate', event => {
if (event.detail.action === 'collapse' && shouldBlockCollapse()) {
event.preventDefault();
}
});
grid.addEventListener('afterdimensionanimate', event => {
console.log(event.detail.results);
});
```
## Tree integration
Tree rows can use dimension animation automatically. Enable `tree.animation`; `TreeDataPlugin` registers `DimensionAnimationPlugin` when it is not already present.
```typescript
import { TreeDataPlugin } from '@revolist/revogrid-pro';
grid.plugins = [TreeDataPlugin];
grid.tree = {
animation: true,
};
grid.dimensionAnimation = {
duration: 180,
};
```
In this flow, `TreeDataPlugin` owns tree trim state and expansion state. `DimensionAnimationPlugin` only animates the affected row sizes during collapse and expand.
## API reference
- [Dimension Animation API](/api/dimension-animation/)
- [Tree guide](/guides/data-manage/tree/)
---
# Overlay Nodes
URL: https://pro.rv-grid.com/guides/grid-utilities/overlay-nodes/
Source: src/content/docs/guides/grid-utilities/overlay-nodes.mdx
Description: A guide to using the Overlay Plugin to display notes on grid rows.
The **Overlay Plugin** in RevoGrid provides a powerful way to display additional content over the grid cells. This guide demonstrates how to use it to show notes on specific rows.
1. **Dynamic Overlay Positioning**: The Overlay Plugin automatically handles positioning and scrolling of overlay elements, ensuring they stay aligned with their corresponding grid rows.
2. **Performance Optimized**: Despite rendering additional content, the overlay layer remains virtualized, maintaining high performance even with many notes.
3. **Flexible Content**: You can display any type of content in the overlay, from simple notes to complex interactive components.
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Basic Setup](#basic-setup)
- [Adding Notes](#adding-notes)
- [Customizing Notes](#customizing-notes)
- [API Reference](#api-reference)
---
## Features
- **Row-Specific Notes**: Display notes on specific grid rows
- **Automatic Positioning**: Notes automatically position themselves relative to their rows
- **Scroll Synchronization**: Notes stay aligned with their rows during scrolling
- **Custom Styling**: Fully customizable note appearance and behavior
---
## Basic Setup
1. **Import the Overlay Plugin:**
```javascript
import { OverlayPlugin } from '@revolist/revogrid-pro';
```
2. **Initialize the Grid with the Plugin:**
```javascript
const grid = document.querySelector('revo-grid');
grid.plugins = [OverlayPlugin];
```
3. **Add Notes to Rows:**
```javascript
// Dispatch an event to add a note
grid.dispatchEvent(new CustomEvent('overlaynode', {
detail: {
nodeId: `note-${rowIndex}`,
vnode: h('div', { class: 'note' }, 'Your note text here')
}
}));
```
### Adding Notes
To add a note to a specific row, you need to:
1. Create a unique `nodeId` for the note
2. Create a virtual node (vnode) for the note content
3. Dispatch the `overlaynode` event with the note details
```javascript
const addNote = (rowIndex, noteText) => {
const noteNode = h('div', {
class: 'note',
style: {
position: 'absolute',
top: `${rowIndex * rowHeight}px`,
left: '0',
width: '100%',
padding: '8px',
backgroundColor: 'rgba(255, 255, 0, 0.2)',
border: '1px solid #ffd700'
}
}, noteText);
grid.dispatchEvent(new CustomEvent('overlaynode', {
detail: {
nodeId: `note-${rowIndex}`,
vnode: noteNode
}
}));
};
```
### Customizing Notes
You can customize the appearance and behavior of notes by:
1. Modifying the note's CSS styles
2. Adding interactive elements
3. Implementing custom positioning logic
```javascript
const createCustomNote = (rowIndex, noteData) => {
return h('div', {
class: 'custom-note',
style: {
position: 'absolute',
top: `${rowIndex * rowHeight}px`,
left: '0',
width: '100%',
padding: '12px',
backgroundColor: noteData.color || 'rgba(255, 255, 0, 0.2)',
border: `1px solid ${noteData.borderColor || '#ffd700'}`,
borderRadius: '4px'
}
}, [
h('div', { class: 'note-header' }, noteData.title),
h('div', { class: 'note-content' }, noteData.content)
]);
};
```
---
## API Reference
### OverlayPlugin Events
#### `overlaynode`
Dispatched to add or update a note in the overlay layer.
```typescript
interface OverlayNodeEvent {
nodeId: string;
vnode: VNode;
}
```
#### `overlayclearnode`
Dispatched to remove notes from the overlay layer.
```typescript
interface OverlayClearNodeEvent {
nodeIds: string[];
}
```
### Methods
#### `refresh()`
Manually refresh the overlay layer's positioning and content.
---
# Local Pagination
URL: https://pro.rv-grid.com/guides/grid-utilities/pagination/
Source: src/content/docs/guides/grid-utilities/pagination.mdx
Description: Manage large datasets by breaking them into smaller, more manageable pages.
Enable `PaginationPlugin` to efficiently manage large datasets by breaking them into smaller, more manageable pages, enhancing both performance and user experience.
Pagination allows you to divide your dataset into discrete pages, each containing a specified number of rows.
Users can then navigate through these pages rather than scrolling through a long list of data.
This is especially useful when dealing with large datasets where loading all rows at once could affect performance. It gives you:
- **Improved Performance**: By loading only a subset of data at a time, pagination helps maintain grid performance even with large datasets.
- **Enhanced User Experience**: Users can easily navigate through data without being overwhelmed by a large number of rows.
---
# Smart Panel
URL: https://pro.rv-grid.com/guides/grid-utilities/smart-panel/
Source: src/content/docs/guides/grid-utilities/smart-panel.mdx
Description: Add configurable, formula-powered row, selection, and application summaries beside any RevoGrid.
`SmartPanelPlugin` adds a compact status surface that stays synchronized with
RevoGrid. It reads selection, focus, row visibility, tree/group nodes, pinned
viewports, checkbox row selection, formulas, and source changes from grid
providers instead of inspecting rendered DOM cells.
Use the defaults for spreadsheet-style selection statistics, or replace the
complete item list with application-specific formulas and resolvers. The panel
can sit directly below the grid or render into an application-owned host.
The default panel adds row context on the left and selection statistics on
the right. Items appear only when their visibility conditions are met.
## Quick setup
Install the plugin and enable range selection:
```ts
import { SmartPanelPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid')!;
grid.range = true;
grid.plugins = [SmartPanelPlugin];
```
With no `smartPanel` configuration, the plugin creates a panel immediately
after ``. It always shows the number of main data rows. When a range
contains more than one cell it can also show:
- A1-style range address
- Selected cell and numeric-value counts
- Sum, average, minimum, and maximum
- Visible versus total rows when filtering, tree collapse, grouping, or other
trimming hides rows
The panel refreshes after edits, source and column updates, filtering, sorting,
trimming, pagination, row order, focus/range changes, multi-range changes,
checkbox row selection, and tree-state changes. Bursts are coalesced before
the provider snapshot is rebuilt.
## Configure panel items
Replace `items` to control exactly what appears:
```ts
grid.smartPanel = {
locale: 'en-US',
numberFormat: {
maximumFractionDigits: 0,
},
items: [
{
id: 'rows',
label: 'Rows',
metric: 'visibleRows',
align: 'left',
},
{
id: 'range',
label: 'Range',
metric: 'rangeAddress',
align: 'left',
visible: ({ snapshot }) => snapshot.selectedCells.length > 1,
},
{
id: 'revenue',
label: 'Selected revenue',
formula: '=SUM(SELECTION)',
align: 'right',
format: value => `$${Number(value).toLocaleString('en-US')}`,
},
],
};
```
Every item needs a stable `id` and one value source: `metric`, `formula`, or
`resolve`. If more than one is supplied, resolution uses `resolve`, then
`formula`, then `metric`.
### Built-in metrics
| Metric | Result |
| --- | --- |
| `selectedCells` | Unique selected cells across the active and multi-ranges |
| `selectedRows` | Checkbox-selected rows when `RowSelectPlugin` is installed; otherwise rows represented by the cell range |
| `visibleSelectedRows` | Selected rows that remain visible after trimming |
| `selectedColumns` | Unique columns represented by the selected cells |
| `visibleRows` / `totalRows` / `hiddenRows` | Main data-row counts; synthetic grouping rows are excluded |
| `visibleNodes` / `totalNodes` | Main row-store nodes, including synthetic tree or grouping nodes |
| `focusedCell` / `focusedColumn` | Current A1 address or column label |
| `rangeAddress` | One or more A1 ranges, with pinned viewport ownership when needed |
| `numericCount`, `sum`, `average`, `min`, `max` | Finite numeric values from selected cells |
| `distinct` | Unique selected values |
### Formula items
Formula items use the same evaluator as RevoGrid formulas. Two dynamic names
are available:
- `SELECTION` — all values in the current item scope
- `VALUES` — an equivalent alias that reads naturally for row/column formulas
```ts
grid.smartPanel = {
items: [
{ id: 'median', label: 'Median', formula: '=MEDIAN(SELECTION)' },
{ id: 'spread', label: 'Spread', formula: '=MAX(VALUES)-MIN(VALUES)' },
{
id: 'visible-arr',
label: 'Visible ARR',
formula: '=SUM(VALUES)',
scope: 'visibleRows',
column: 'arr',
},
{
id: 'all-arr',
label: 'All ARR',
formula: '=SUM(VALUES)',
scope: 'allRows',
column: 'arr',
},
],
};
```
`scope` defaults to `selection`. For `visibleRows` and `allRows`, set `column`
to evaluate one field. Without `column`, the panel flattens enumerable values
from every row in that scope.
Smart Panel formulas do not require `FormulaPlugin`. Add `FormulaPlugin` when
selected grid cells themselves contain formulas and the panel should aggregate
their evaluated results instead of their raw `=...` source strings.
## Application summaries
Custom resolvers receive the complete provider-backed snapshot and may be
synchronous or asynchronous:
```ts
grid.smartPanel = {
className: 'operations-panel',
items: [
{
id: 'visibility',
label: 'Visible accounts',
align: 'left',
resolve: ({ snapshot }) =>
`${snapshot.visibleRowCount} of ${snapshot.totalRowCount}`,
},
{
id: 'regions',
label: 'Regions',
align: 'center',
resolve: ({ snapshot }) =>
new Set(snapshot.visibleRows.map(row => row.region)).size,
},
{
id: 'pipeline',
label: 'Pipeline',
scope: 'visibleRows',
column: 'pipeline',
formula: '=SUM(VALUES)',
align: 'right',
format: value => `$${Number(value).toLocaleString('en-US')}`,
},
{
id: 'health',
label: 'Health',
align: 'right',
resolve: ({ snapshot }) =>
snapshot.visibleRows.every(row => row.health === 'On track')
? 'On track'
: 'Needs attention',
className: 'operations-panel__health',
},
],
};
```
Formula items and application resolvers can share the same left, center,
and right aligned panel.
The resolver context contains:
| Field | Description |
| --- | --- |
| `grid` | Host `HTMLRevoGridElement` |
| `providers` | RevoGrid plugin providers for advanced integrations |
| `snapshot` | One immutable-by-convention snapshot shared by every item in the refresh |
| `item` | Current item configuration |
| `values` | Values resolved from the item's `scope` and optional `column` |
`snapshot` includes ranges, selected cells and raw/evaluated values, checkbox
and range rows, selected columns, visible/all main rows, visible/all rows across
pinned stores, data-row and node counts, focus metadata, and a revision number.
## Item presentation
| Property | Default | Purpose |
| --- | --- | --- |
| `label` | none | Muted text before the value |
| `align` | `right` | Places the item in the `left`, `center`, or `right` group |
| `order` | declaration index | Orders items inside an alignment group |
| `priority` | `0` | Secondary sort and a `data-priority` styling hook |
| `visible` | `true` | Boolean or snapshot predicate controlling whether the item renders |
| `format` | panel formatter | Converts the resolved value into display text |
| `render` | none | Returns a safe `Node` or text string for custom presentation |
| `className` | none | Adds an application class to the item |
| `title` | none | Static string or context callback for the native title |
Strings returned from `render` or a resolver are inserted as text, never as raw
HTML. Return a DOM `Node` when richer content is required:
```ts
{
id: 'status',
render: value => {
const badge = document.createElement('strong');
badge.className = 'status-badge';
badge.textContent = String(value);
return badge;
},
resolve: ({ snapshot }) =>
snapshot.hiddenRowCount ? 'Filtered' : 'Complete',
}
```
## Render into a custom host
Pass an element or a callback when layout belongs to the application:
```html
```
```ts
grid.smartPanel = {
host: () => document.querySelector('#grid-insights'),
items,
};
grid.plugins = [SmartPanelPlugin];
```
The plugin never removes a user-owned host. Assign a new `smartPanel` object or
call `plugin.setHost(nextHost)` to move the existing component. Passing `null`
returns it to a plugin-owned host immediately after the grid.
## Panel configuration
| Property | Default | Description |
| --- | --- | --- |
| `host` | panel after grid | Element or callback providing the panel container |
| `items` | spreadsheet defaults | Complete ordered item configuration |
| `className` | none | Additional class on the panel host |
| `ariaLabel` | `Grid summary` | Accessible name for the status region |
| `hiddenWhenEmpty` | `false` | Hides the host when no configured items resolve visibly |
| `locale` | browser locale | Locale passed to `Intl.NumberFormat` |
| `numberFormat` | `{ maximumFractionDigits: 2 }` | Default numeric formatting options |
| `formatValue` | built-in formatter | Panel-wide value formatter used before item-level `format` |
| `onError` | none | Receives an isolated item/formula error |
| `schedule` | `microtask` | Use `animationFrame` to align refreshes with visual frame updates |
Assign `false` to hide the installed panel without removing the plugin:
```ts
grid.smartPanel = false;
```
Direct property assignments are observable. The legacy
`additionalData.smartPanel` configuration remains available for compatibility,
but new integrations should use `grid.smartPanel`.
## Runtime API and events
Retrieve the plugin instance through RevoGrid's plugin service:
```ts
const smartPanel = (await grid.getPlugins())
.find(plugin => plugin instanceof SmartPanelPlugin);
const snapshot = smartPanel?.getSnapshot();
const items = smartPanel?.getItems();
await smartPanel?.refresh();
smartPanel?.setHost(document.querySelector('#grid-insights'));
```
| Method | Purpose |
| --- | --- |
| `getSnapshot()` | Returns the last committed provider snapshot |
| `getItems()` | Returns a copy of the last resolved item list |
| `getComponent()` | Returns the presentation-only `SmartPanelComponent` |
| `refresh()` | Immediately rebuilds and renders the snapshot; resolves when committed |
| `setHost(host)` | Moves the panel to a user host or back to an owned host with `null` |
| Event | Detail | Purpose |
| --- | --- | --- |
| `smartpanelchange` | `{ snapshot, items }` | Fires after the latest asynchronous refresh commits |
| `smartpanelerror` | `{ error, item }` | Reports one failed item while healthy items continue rendering |
When async item resolvers overlap, only the newest refresh commits. Use
`onError` or `smartpanelerror` for telemetry without taking down the rest of
the panel.
## Styling
The Pro stylesheet includes responsive wrapping at `720px`. Customize the
panel through its public variables and stable classes:
```scss
.operations-panel {
--revo-smart-panel-background: #101615;
--revo-smart-panel-color: #d7e2de;
--revo-smart-panel-border: #2a3834;
--revo-smart-panel-gap: 24px;
border-radius: 12px;
&__health .revo-smart-panel__value {
color: #32d89b;
}
}
```
| Selector | Role |
| --- | --- |
| `.revo-smart-panel` | Host status region |
| `.revo-smart-panel__group--left` | Left item group |
| `.revo-smart-panel__group--center` | Center item group |
| `.revo-smart-panel__group--right` | Right item group |
| `.revo-smart-panel__item` | One resolved item |
| `.revo-smart-panel__label` | Item label |
| `.revo-smart-panel__value` | Text or custom rendered value |
## Composition patterns
Keep the defaults for range address, count, sum, average, min, and max.
Combine visible/total row metrics with formulas scoped to `visibleRows`.
Use node metrics beside data-row metrics to distinguish structural rows.
Mount into a dashboard shell and add async resolvers or custom Nodes.
The component uses `role="status"`, `aria-live="polite"`, and the configured
accessible label. Keep item labels concise so selection gestures do not create
overly verbose screen-reader announcements.
---
# Status Feedback And Badges
URL: https://pro.rv-grid.com/guides/grid-utilities/status-feedback/
Source: src/content/docs/guides/grid-utilities/status-feedback.mdx
Description: Show compact activity, pending states, errors, and actions with shared Pro status panels and badges.
Surface: grid
## Show an activity panel
`StatusFeedbackPanel` groups activity into one outlined surface with a heading,
item count, and divided rows. Each row accepts a title, description, optional
detail, semantic tone, loading indicator, and actions. The component renders the
supplied state; your application owns requests, retries, and state transitions.
```tsx
import { h, render } from 'preact';
import { StatusFeedbackPanel, StatusBadge } from '@revolist/revogrid-pro';
import '@revolist/revogrid-pro/styles.css';
render(
retrySave() },
{ id: 'server', label: 'Use server data', onClick: () => loadServerData() },
],
},
{
id: 'queued-save',
title: 'Save queued',
description: 'Waiting for the previous save.',
busy: true,
actions: [{ id: 'cancel', label: 'Cancel', appearance: 'ghost', onClick: () => cancelSave() }],
},
]} />,
document.querySelector('#activity')!,
);
render( , document.querySelector('#save-state')!);
```
Supply stable item and action IDs. An empty `items` array renders no panel.
Actions use native buttons and accept `disabled` and `ariaLabel`. Use
`appearance: 'primary'` for the main action, `'secondary'` for an outlined button,
and `'ghost'` for a quieter action. Secondary is the default.
## Render inside RevoGrid
Use `renderStatusFeedbackPanel(h, props)` or `renderStatusBadge(h, props)` inside a
RevoGrid template or registered overlay node.
Pass the template's own `h` function so the grid remains the renderer owner.
The same helpers back the Preact components.
Kanban uses the shared panel for its [persistence feedback](/guides/kanban/persistence/).
Its adapter supplies transaction messages and callbacks; the shared component
has no dependency on Kanban or its save implementation.
## Appearance and accessibility
Both components accept `tone: 'neutral' | 'warning' | 'danger' | 'success'`.
Set `busy: true` to show the loading indicator. Use a descriptive label or title
so state is communicated through text as well as color and icons.
The panel uses alert semantics for warning and danger rows, and status semantics
for other rows. Decorative icons are hidden from assistive technology. Buttons
retain native keyboard operation and visible focus, while panel interactions
are isolated from grid selection and keyboard shortcuts. Loading animation is
disabled when reduced motion is requested.
The panel follows shared `--rv-ui-*` surface, text, border, radius, button, and
focus tokens. Semantic colors can be customized with `--rv-status-warning`,
`--rv-status-danger`, and `--rv-status-success`. Light and dark themes follow
the same Pro theme selectors as other shared controls. Actions wrap below the
message when the panel's container is narrow; long text wraps within its row.
## Popover and compact mode
For a floating activity surface, create a `StatusFeedbackPopoverController` and
pass it as the panel's `popover` prop. Attach `controller.anchorRef` to the
bottom-right anchor element. Supply `onChange` to rerender your view, and call
`controller.destroy()` when the view unmounts. Place the anchor inside a positioned container. The controller uses the browser's
manual Popover API, with a positioned fallback when that API is unavailable.
It follows scrolling and resizing and keeps the surface within the viewport.
```ts
import { StatusFeedbackPopoverController } from '@revolist/revogrid-pro';
const activity = new StatusFeedbackPopoverController({
boundary: gridElement,
onChange: () => gridElement.refreshExtraElements(),
});
// In your grid overlay renderer:
h('div', {
ref: activity.anchorRef,
style: { position: 'absolute', right: '8px', bottom: '8px', width: '0', height: '0' },
},
renderStatusFeedbackPanel(h, { title: 'Sync activity', items, popover: activity }));
```
The disclosure button collapses the popup into a small bar showing its heading,
count, and state icon. Expand it to see messages and actions again. Escape also
collapses an expanded popup. Collapsing keeps requests running and preserves
all messages. Focus moves to the disclosure button before its content is hidden.
For an inline panel, supply `collapsed` and `onCollapsedChange` to control the
same presentation without floating positioning. Customize the shadow with
`--rv-status-shadow`.
## Translate controls and counts
Titles, item text, badges and action labels are supplied by your application.
Use `localeText` for the panel's own controls and count presentation:
```tsx
`${count} alterações`,
formatCount: count => new Intl.NumberFormat('pt-PT').format(count),
}}
/>
```
`collapse` and `expand` translate both the button tooltip and accessible name.
`countLabel` supplies the accessible count label; `formatCount` formats its
visible number. Existing `collapseLabel` and `expandLabel` props take precedence
when supplied. Partial `localeText` overrides retain the default presentation.
Floating panels animate on entrance, exit, collapse and expansion. Motion lasts
140–180 ms and respects `prefers-reduced-motion: reduce`. Interrupted transitions
are cancelled, and exit snapshots are inert and hidden from assistive technology.
Destroying the popover controller removes outstanding animations and snapshots.
---
# Tooltip
URL: https://pro.rv-grid.com/guides/grid-utilities/tooltip/
Source: src/content/docs/guides/grid-utilities/tooltip.mdx
Description: A comprehensive guide to using the RevoGrid Tooltip Plugin
The Tooltip Plugin is a Pro plugin that displays additional information when users hover over grid cells or other elements inside the grid. It creates a single managed tooltip element, reads content from cell attributes, and shows it after a short hover delay.
## Features
- Dynamic tooltip creation and positioning on `mouseover`, `mouseout`, and `mousemove`
- Automatic tooltip content from `data-tooltip` or `title` attributes
- Customizable tooltip appearance with `tooltip-type` and `tooltip-position` attributes
- Configurable show delay, animation, and cursor-following behavior
- Automatic mounting inside the active dialog/fullscreen container, with the document body as fallback
- Body fallback so grid and ancestor overflow containers cannot crop the tooltip
- Preserves the grid's effective LTR or RTL writing direction across the portal boundary
- Smart positioning that follows grid UI beyond the host while keeping tooltips within viewport boundaries
- Smooth show/hide transitions
- Support for different tooltip types (info, warning, error, etc.)
## Installation
The Tooltip Plugin is included in RevoGrid Pro. To use it, import and initialize it with your grid:
```typescript
import { TooltipPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [TooltipPlugin];
```
## Usage
### Basic Usage
The simplest way to use tooltips is by adding a `title` or `data-tooltip` attribute to your grid cells:
```typescript
grid.columns = [
{
prop: 'name',
name: 'Name',
cellTemplate: (createElement, props) => {
return createElement('div', {
title: 'User name',
'data-tooltip': 'Full user name',
}, props.model.name);
},
},
];
```
### Showing Behavior
Tooltips are shown only when the hovered element has a non-empty `data-tooltip` or `title` attribute. If both are present, `data-tooltip` is used. The plugin waits `500ms` before showing the tooltip by default, then hides it when the cursor leaves the tooltip target.
Use the `tooltip` grid property to control when and how the tooltip is shown:
```typescript
grid.tooltip = {
enabled: true,
showDelayMs: 250,
animation: true,
followCursor: true,
themeVariables: ['--app-tooltip-accent'],
appendTo: document.getElementById('tooltip-root') ?? document.body,
};
```
| Property | Default | Description |
| --- | --- | --- |
| `enabled` | `true` | Enables or disables tooltip hover behavior. Disabling it hides any open tooltip immediately. |
| `showDelayMs` | `500` | Delay before showing the tooltip. Negative values are normalized to `0`. |
| `animation` | `true` | Enables the tooltip entrance and exit transition. |
| `followCursor` | `true` | Keeps the tooltip attached to the cursor while it is open. Set to `false` to keep the tooltip at its opening position. |
| `themeVariables` | `[]` | Additional CSS custom properties copied from the grid to the portal-mounted tooltip. |
| `appendTo` | Automatic | Element that receives the managed tooltip node. Runtime changes move the existing tooltip to the new target. |
Without `appendTo`, the plugin uses the closest open native dialog or active fullscreen element containing the grid, then falls back to `document.body`. This keeps the tooltip in the browser's active top-layer subtree while the body fallback prevents `overflow: hidden` on the grid or its ancestors from cropping it. For a custom target, prefer a top-level overlay root without clipping or CSS transforms. A target inside an overflow-clipping container can crop the tooltip.
Plugins that own tooltip-specific theme variables can register them without mutating application configuration:
```typescript
const unregister = tooltipPlugin.registerThemeVariables([
'--my-plugin-tooltip-accent',
]);
// Call during plugin teardown.
unregister();
```
### Customizing Tooltip
You can control the tooltip position using the `tooltip-position` attribute and the `tooltip-type` attribute to change the tooltip type:
```typescript
createElement('div', {
'data-tooltip': 'Custom positioned tooltip',
'tooltip-position': 'right', // 'top', 'bottom', 'left', 'right'
'tooltip-type': 'info', // 'info', 'warning', 'error', etc.
}, content);
```
## Best Practices
1. Keep tooltip content concise and relevant
2. Use appropriate tooltip positions based on available space
3. Choose tooltip types that match the information context
4. Consider mobile users when designing tooltip interactions
5. Use tooltips to provide supplementary information, not critical data
---
# Removing Attribution
URL: https://pro.rv-grid.com/guides/hide-attribution/
Source: src/content/docs/guides/hide-attribution.md
Description: Removing Attribution from RevoGrid
As a Pro version user of RevoGrid, you can easily remove attribution from your projects. To do this, simply apply one of the following attributes:
### Method 1: Using `hideAttribution`
Add the `hideAttribution` attribute to your RevoGrid component:
```html
```
### Method 2: Using `hide-attribution`
Alternatively, you can use the `hide-attribution` attribute:
```html
```
### Conclusion
By applying either of these attributes, the attribution will be hidden from your RevoGrid component.
If you have any questions or need further assistance, feel free to reach out to the RevoGrid support team.
---
# Infinity Scroll
URL: https://pro.rv-grid.com/guides/infinity-scroll/
Source: src/content/docs/guides/infinity-scroll.mdx
Description: Efficiently handle large datasets with dynamic loading as users scroll through the grid.
The Infinity Scroll feature enables dynamic data loading as users scroll through the grid, efficiently managing large datasets by loading data in chunks and cleaning up unused data to optimize memory usage.
## Key Features
- Dynamic data loading based on scroll position
- Pre-loads data ahead of scroll direction
- Efficient memory management by cleaning up off-screen data
- Configurable chunk size and buffer sizes
- Support for both known and unknown total data sizes
- Compatible with pinned top and bottom rows
## Basic Setup
To enable infinity scroll in your grid:
```typescript
import { InfinityScrollPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [InfinityScrollPlugin];
grid.pinnedTopSource = [{ id: 'status', name: 'Pinned status row' }];
grid.pinnedBottomSource = [{ id: 'summary', name: 'Pinned summary row' }];
grid.infinityScroll = {
chunkSize: 50, // Number of rows per chunk
bufferSize: 100, // How many rows to keep in buffer
preloadThreshold: 0.75, // When to start loading more data (0-1)
total: 1000, // Optional: total number of rows
loadData: async (skip, limit, order, singleConditionFilters) => {
// Fetch data from your API
const response = await fetch(`/api/data?skip=${skip}&limit=${limit}&order=${JSON.stringify(order)}&filter=${JSON.stringify(singleConditionFilters)}`);
const data = await response.json();
return data; // You can return an array or { data, hasMore, total }
}
};
```
The configuration is an observable grid property, so assigning a new object to
`grid.infinityScroll` updates the plugin. In Vue templates, use the kebab-case
alias with Vue's `.prop` modifier so the object is assigned as an element
property:
```vue
```
`additionalData.infinityScroll` remains available as a deprecated compatibility
fallback for existing applications.
Pinned rows stay outside the remote loading lifecycle. Use the core
`pinnedTopSource` and `pinnedBottomSource` grid properties for static status,
summary, or action rows while `InfinityScrollPlugin` manages only the main
scrollable source.
## Configuration Options
Option
Type
Default
Description
chunkSize
number
undefined
Number of rows to load in each chunk. Can be set dynamically by the grid.
bufferSize
number
undefined
Number of rows to keep in the memory buffer. Can be set dynamically by the grid.
preloadThreshold
number
0.75
When to trigger loading the next chunk (0–1).
total
number
undefined
Total number of rows. If omitted, the plugin grows the source as you scroll.
loadData
function
Required
Function that loads data chunks. Return row[] or{' '}
{ data: row[], hasMore?: boolean, total?: number }.
### Remote advanced filtering
For a new backend integration, use the canonical `filterAst` and
`executionContext` arguments. The tree is JSON-safe and preserves nested
groups, cross-column logic, structured operators, and quick-filter composition.
The context captures the single reference instant, timezone, and serializable
blank-value policy used by relative-date rules, so the server can evaluate the
same request as the grid.
```typescript
loadData: async (
skip,
limit,
order,
_singleConditionFilters,
_multiConditionFilters,
_quickFilter,
_temporalContext,
filterAst,
executionContext,
) => {
const response = await fetch('/api/data', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(
{
page: { skip, limit },
order,
filter: filterAst ? { filterAst, executionContext } : undefined,
},
),
});
return response.json();
}
```
Register `AdvanceFilterPlugin` alongside `InfinityScrollPlugin` before using
this contract. Its `filterastchange` event and the `beforefilterapply` detail
expose the same canonical pair; see [Canonical Filter AST](/guides/data-filter/filter-ast/)
for the tree schema and [Filter Events](/guides/data-filter/filter-events/)
for a custom remote adapter.
#### Compatibility arguments
`loadData` keeps the established positional arguments for existing callers.
The fourth `singleConditionFilters` argument contains at most one condition per
column. The optional fifth `multiConditionFilters` argument contains every
advanced column condition and its relation; use it when an existing backend
supports multiple conditions for one column. The sixth argument is the
normalized global quick-filter payload, and the seventh is temporal transport
data for older date/datetime integrations. The eighth and ninth arguments are
the canonical `filterAst` and `executionContext` shown above.
`multiConditionFilters` can contain runtime-only values such as a selection
filter `Set`, so do not serialize it unchanged in new integrations. If an
existing backend requires it, normalize those values at that adapter boundary.
For example, if the Name column must contain `Remote` and must not contain
`Archived`, the fifth argument keeps both conditions and their relation:
```typescript
{
name: [
{ id: 0, type: 'contains', value: 'Remote', relation: 'and' },
{ id: 1, type: 'notContains', value: 'Archived', relation: 'and' },
],
}
```
The fourth argument remains compatible with single-condition backends and, for
this example, contains only `{ name: { type: 'contains', value: 'Remote' } }`.
Use `multiConditionFilters` whenever the backend needs the full expression.
### Using `hasMore` for unknown total
When your backend does not provide a fixed `total`, return `hasMore` from
`loadData`. This is the most reliable way to tell the plugin when to stop
requesting additional chunks.
```typescript
grid.infinityScroll = {
chunkSize: 100,
loadData: async (skip, limit, order, singleConditionFilters) => {
const response = await fetch('/api/data', {
method: 'POST',
body: JSON.stringify({ skip, limit, order, filter: singleConditionFilters }),
});
const result = await response.json();
return {
data: result.items,
hasMore: result.hasMore,
};
},
};
```
### Dynamic `total` from backend
You can also return `total` from `loadData`. When provided, the plugin updates
its internal total size and uses it for source sizing and end detection.
## Memory Management
The plugin automatically manages memory by:
- Loading new chunks of data as the user scrolls
- Maintaining a buffer of rows before and after the visible area
- Cleaning up data that's far from the current viewport
## Exporting to Excel
`ExportExcelPlugin` exports grid source rows, not rendered DOM rows. Ordinary
client-side virtualization therefore exports the entire source, including rows
currently outside the viewport.
Infinity scroll is different: it keeps only a moving client-side window and may
represent remote, unloaded positions with placeholders. Export does not call
`loadData` repeatedly and cannot discover records that have never been loaded.
To prevent a misleading partial workbook, direct export now fails with an
actionable error while `InfinityScrollPlugin` is active.
If you need a complete workbook, fetch the export dataset from the same backend
API and pass those rows to `ExportExcelPlugin` instead of relying on the visible
infinite-scroll grid. The demo export button uses a temporary hidden grid with
the same columns so the visible grid is not disturbed while the workbook is
generated.
## Best Practices
1. **Choose Appropriate Chunk Size**
- Smaller chunks mean more frequent loading but less memory usage
- Larger chunks mean fewer API calls but more memory usage
2. **Configure Buffer Size**
- Larger buffers provide smoother scrolling but use more memory
- Smaller buffers save memory but might cause more loading events
3. **Optimize Data Loading**
- Implement server-side pagination in your API
- Return only necessary data fields
- Consider data compression for large datasets
4. **Handle Loading States**
- Show loading indicators during data fetching
- Handle errors gracefully
- Consider implementing retry logic for failed requests
## Example with Loading State
```typescript
grid.infinityScroll = {
chunkSize: 50,
bufferSize: 100,
loadData: async (skip, limit, order, singleConditionFilters) => {
try {
// Show loading state
const response = await fetch(`/api/data?skip=${skip}&limit=${limit}&order=${JSON.stringify(order)}&filter=${JSON.stringify(singleConditionFilters)}`);
const data = await response.json();
// Hide loading state
return data;
} catch (error) {
console.error('Failed to load data:', error);
return []; // Return empty array on error
} finally {
// Hide loading state
}
},
};
```
This implementation provides efficient handling of large datasets while maintaining optimal performance and user experience.
---
# Installing RevoGrid Pro Products
URL: https://pro.rv-grid.com/guides/installation/
Source: src/content/docs/guides/installation.mdx
Description: Learn how to install RevoGrid Pro and standalone product packages using either a pre-built release or our private npm registry.
### Which Package Do I Need?
- Install `@revolist/revogrid-pro` when you need advanced Pro features (filters, editors, grouping, tree, export, etc.) without Pivot.
- Install `@revolist/pivot` for Pivot tables, Pivot Charts, and the Pivot configurator. Keep Pro installed alongside it.
- Install `@revolist/gantt` for project timelines and dependencies, `@revolist/scheduler` for event/resource scheduling, or `@revolist/kanban` for workflow boards. Each is independently installable alongside Pro.
```bash
npm install @revolist/revogrid-pro
npm install @revolist/pivot
```
Visit the [RevoGrid trial page](https://rv-grid.com/trial?source=demo-page&demo=planning&demo_id=planning) to get started with the trial packages.
If you are unsure, start with Pro and add only the standalone product package you need.
#### Next Steps
After installation, you can start using RevoGrid Pro and the standalone products in your project. Check out our other guides for:
- [Basic Usage](/guides)
- [Component API](/api/aggregations)
- [Simple Example](/guides/rows/row-odd/)
- [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git)
Each framework has its own set of components and examples that demonstrate how to use RevoGrid Pro products effectively in your chosen framework environment.
---
# Github Installation Guide
URL: https://pro.rv-grid.com/guides/installation-github/
Source: src/content/docs/guides/installation-github.mdx
Description: Learn how to install RevoGrid Pro products using GitHub Packages
[Private GitHub Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry)
1. Ensure you have access to the GitHub repository.
2. Authenticate your npm client with GitHub by creating a .npmrc file in your project directory or user home directory with the following content:
```json
@revolist:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=YOUR_PERSONAL_ACCESS_TOKEN
```
3. Replace `YOUR_PERSONAL_ACCESS_TOKEN` with a GitHub token that has `read:packages` and repo permissions.
4. **Install the packages**. Run the following commands to install the latest versions from the private package registry:
```bash
npm install @revolist/revogrid-pro@latest
npm install @revolist/pivot@latest
```
For additional guidance on installing from GitHub’s private registry, please refer to the [official GitHub Packages documentation](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry).
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
## Supply Chain Metadata
RevoGrid Pro product releases include SBOM files and Sigstore release signatures.
- SBOM files are generated in SPDX JSON and CycloneDX JSON formats.
- Package tarballs are signed with Sigstore Cosign keyless signing in the release workflow.
- The signed tarball is the same tarball published to GitHub Packages.
For verification commands and related policy links, see [Security and Supply Chain](/legal/security).
---
# Local Installation Guide
URL: https://pro.rv-grid.com/guides/installation-local/
Source: src/content/docs/guides/installation-local.mdx
Description: Learn how to install RevoGrid Pro products using local files
---
### Option 1: Directly Include in Builds
- Copy the into your project’s `assets` or `libs` folder.
- Link the necessary JavaScript and CSS files in your HTML or import them into your JavaScript/TypeScript files.
```html
```
Or in your module-based project:
```javascript
import 'path/to/revogrid-pro.js';
import 'path/to/revogrid-pro.css';
// If using build tools like Webpack or Vite:
// Add the ./release folder path to your resolve or alias configuration to reference the files.
resolve: {
alias: {
'@revogrid-pro': path.resolve(__dirname, 'path/to/release'),
},
},
```
This option is particularly suitable for quick testing or projects that don’t rely on npm for dependency management.
---
### Option 2: package.json Using Local Files
For projects where you want to use local versions of RevoGrid Pro products during development, you can reference the package files directly in your package.json.
This method is especially useful if you’re working with a cloned version of the repository or testing custom modifications.
Steps to Import Using Local Files:
1. Download files from the link above and extract them to a local folder.
2. Set Up Local References in package.json. In your project’s package.json file, reference the local folders containing Pro and the standalone products. For example:
```json
{
"dependencies": {
"@revolist/revogrid-pro": "file:./release/pro",
"@revolist/pivot": "file:./release/pivot"
}
}
```
3. Install Dependencies. Run the following command to install the local plugin:
```bash
npm install
```
4. NPM will create a symlink to the specified folders, allowing you to use the local files as if they were npm packages. Import each plugin from its owning package:
```js
import { ColumnStretchPlugin } from '@revolist/revogrid-pro';
import { PivotPlugin } from '@revolist/pivot';
```
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
---
# No-Build JavaScript Usage
URL: https://pro.rv-grid.com/guides/installation-no-build/
Source: src/content/docs/guides/installation-no-build.mdx
Description: Use a delivered RevoGrid Pro JavaScript build directly from the browser without a bundler.
Use this setup when you want to load a delivered RevoGrid Pro JavaScript build from a plain HTML file, a static page, or a server-rendered page that does not use a JavaScript build pipeline.
RevoGrid Pro is not published as a public browser CDN package. Use the JavaScript/CSS build delivered from the portal, or install the private npm package in projects that have a build step. The no-build example below assumes the delivered Pro `dist` files are copied into your app.
## Live Preview
This preview uses the same grid configuration as the no-build HTML example below.
1. Copy the delivered Pro build into your static assets.
Keep the whole Pro `dist` folder together because the ESM entry imports sibling chunk files.
```txt
public/
vendor/
revogrid-pro/
revogrid-pro.js
revogrid-pro.css
revogrid-pro*.js
```
2. Add the Pro stylesheet from your hosted asset path.
```html
```
3. Load the public RevoGrid Core web component from jsDelivr.
```html
```
4. Add an import map for Core package imports and the delivered Pro bundle.
The standalone Core script defines ``. The `@revolist/revogrid` import map entry is still required because the Pro ESM bundle imports Core helpers by package name.
```html
```
5. Import the Pro plugins from a module script.
```html
```
## Complete HTML Example
Save this as an HTML file and open it through any static web server. Do not use a `file://` URL; browser module imports should be served over HTTP.
The Pro ESM bundle imports `@revolist/revogrid` as a package name. Browsers do not resolve npm package names by themselves, so the import map tells the browser where each package entry points in your hosted assets.
---
# NPM Installation Guide
URL: https://pro.rv-grid.com/guides/installation-npm/
Source: src/content/docs/guides/installation-npm.mdx
Description: Learn how to install RevoGrid Pro and standalone product packages using npm
**1. Configure NPM Registry**
First, configure npm to use the RevoGrid registry:
```bash
npm config set "@revolist:registry=https://npm.rv-grid.com"
```
The command npm config set `@revolist:registry=https://npm.rv-grid.com` is used to configure npm
(Node Package Manager) to use a specific registry for packages that are scoped under the `@revolist`
namespace.
---
**2. Install RevoGrid Pro and the Products You Need**
Install the core grid and Pro, then add only the standalone product you need. For example, to add Gantt:
```bash
npm install @revolist/revogrid
npm install @revolist/revogrid-pro
npm install @revolist/gantt
```
Use `@revolist/scheduler`, `@revolist/kanban`, or `@revolist/pivot` instead of Gantt when that is the product you need.
---
**3. Framework Integration Packages**
For better integration with specific frameworks, you can install the corresponding framework package:
**React**
```bash
npm install @revolist/react-datagrid
```
**Angular**
```bash
npm install @revolist/angular-datagrid
```
**Vue**
```bash
npm install @revolist/vue3-datagrid
```
**Svelte**
```bash
npm install @revolist/svelte-datagrid
```
Read more about [RevoGrid Pro Tokens](/guides/installation-token).
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
---
# Trial Installation
URL: https://pro.rv-grid.com/guides/installation-npm-trial/
Source: src/content/docs/guides/installation-npm-trial.mdx
Description: Learn how to install RevoGrid Pro and standalone product trial packages using npm
No account, token, npm login, licence key, form submission, or approval email is
required. A new developer can follow this page immediately from a clean shell.
Trial access uses separate package names and asset paths from the full paid packages. After purchasing, switch each product trial to `@revolist/` and switch Pro trial to `@revolist/revogrid-pro`.
Clone or inspect the working trial example:
[github.com/revolist/revogrid-pro-trial](https://github.com/revolist/revogrid-pro-trial.git).
Trial packages use names such as `@revolist/rv-pro-trial` and
`@revolist/gantt-trial`, `@revolist/scheduler-trial`,
`@revolist/kanban-trial`, and `@revolist/pivot-trial`, with matching CSS filenames.
**1. Configure NPM Registry**
First, configure npm to use the public RevoGrid trial registry. No token or login is required:
```bash
npm config set "@revolist:registry=https://trial.rv-grid.com"
```
This command routes the `@revolist` scope through the public trial registry.
It does not change the default registry used for unscoped packages.
---
**2. Install RevoGrid Pro Trial**
Install the core grid and RevoGrid Pro trial package:
```bash
npm install @revolist/revogrid
npm install @revolist/rv-pro-trial
```
Use Pro exports from the trial package and import the trial CSS file:
```typescript
import '@revolist/rv-pro-trial/dist/rv-pro-trial.css';
import { RowOddPlugin } from '@revolist/rv-pro-trial';
```
Do not use the full package CSS path with the trial package:
```typescript
// Incorrect for trial packages
import '@revolist/rv-pro-trial/dist/revogrid-pro.css';
```
---
**3. Install a Standalone Product Trial**
Install only the product trial you need:
```bash
npm install @revolist/gantt-trial
# or: @revolist/scheduler-trial, @revolist/kanban-trial, @revolist/pivot-trial
```
Product trials build on Pro trial, so keep `@revolist/rv-pro-trial` installed.
For Pivot configuration, follow the
[First Working Pivot guide](/guides/pivot/#first-working-pivot).
When using the trial, replace `@revolist/revogrid-pro` imports with
`@revolist/rv-pro-trial` and `@revolist/pivot` imports with
`@revolist/pivot-trial`. Use the trial stylesheet paths
`@revolist/rv-pro-trial/dist/rv-pro-trial.css` and
`@revolist/pivot-trial/dist/pivot-trial.css`.
Collaborative Editing is separately installed so applications that do not need
realtime editing do not load Yjs:
```bash
npm install @revolist/revogrid-collaborative-editing-trial
```
Import its API from the trial package and keep the main Pro trial installed for
`EventManagerPlugin` and other grid integrations:
```typescript
import { EventManagerPlugin } from '@revolist/rv-pro-trial';
import { CollaborativeEditingPlugin } from '@revolist/revogrid-collaborative-editing-trial';
```
---
**4. Framework Integration Packages**
For better integration with specific frameworks, you can install the corresponding framework package:
**React**
```bash
npm install @revolist/react-datagrid
```
**Angular**
```bash
npm install @revolist/angular-datagrid
```
**Vue**
```bash
npm install @revolist/vue3-datagrid
```
**Svelte**
```bash
npm install @revolist/svelte-datagrid
```
---
**5. Verify a Complete Starter**
The maintained starter declares its supported Node and pnpm versions. From a
clean clone:
```bash
git clone https://github.com/revolist/revogrid-pro-trial.git
cd revogrid-pro-trial
corepack enable
corepack install
pnpm install --frozen-lockfile
pnpm build
pnpm dev
```
To run its Chromium smoke tests on a machine that has never used Playwright:
```bash
pnpm test:e2e:install
pnpm test:e2e
```
The starter includes Tree, Pivot, Gantt, and Scheduler examples. Kanban is
installable as `@revolist/kanban-trial` but is not mounted in this starter.
Collaborative Editing is separately installable as
`@revolist/revogrid-collaborative-editing-trial` but is not mounted in this
starter. See the complete
[trial feature inventory](/legal/trial/#trial-feature-inventory).
---
# Build from Source
URL: https://pro.rv-grid.com/guides/installation-source/
Source: src/content/docs/guides/installation-source.mdx
Description: Learn how to build RevoGrid Pro and standalone products from source
For maximum control, you can build RevoGrid Pro and the standalone product packages from source.
This allows you to customize the build process if needed.
1. Clone the repository and navigate to the project directory.
```bash
git clone https://github.com/revolist/revogrid-pro.git
cd revogrid-pro
```
2. Run the build commands for Pro and the products you need.
```bash
pnpm build:pro
pnpm build:pivot
pnpm build:gantt
pnpm build:scheduler
pnpm build:kanban
```
3. After the build completes, the generated files are available in each package's `dist` folder, for example `/packages/pro/dist`, `/packages/pivot/dist`, and `/packages/gantt/dist`.
Choose the method that best suits your workflow, and install only the RevoGrid Pro products your application needs.
---
# Access Token Installation Guide
URL: https://pro.rv-grid.com/guides/installation-token/
Source: src/content/docs/guides/installation-token.mdx
Description: Learn how to use access tokens with RevoGrid Pro NPM Registry
Access tokens provide a secure way to authenticate with the RevoGrid Pro repository, especially useful for CI/CD environments or multi-user repository access. Instead of using password authentication, you can create and manage tokens for secure authorization.
### Prerequisites
Before creating access tokens, you must first configure and login to the npm server. Please follow the [NPM Installation Guide](/guides/installation-npm) instructions first.
### Creating an Access Token
To create a new token using the command line, run:
```bash
npm token create --registry=https://npm.rv-grid.com
```
You will be prompted for your password. After entering it, a new token will be displayed in the console:
```bash
┌──────────┬─────────────────────────┐
│ token │ Ihj... │
├──────────┼─────────────────────────┤
│ user │ your-username │
├──────────┼─────────────────────────┤
│ cidr │ │
├──────────┼─────────────────────────┤
│ readonly │ false │
├──────────┼─────────────────────────┤
│ created │ 2025-03-21T01:02:03.00Z │
└──────────┴─────────────────────────┘
```
Make sure to copy and securely store the token value.
### Viewing Access Tokens
To view all your available tokens:
```bash
npm token list --registry=https://npm.rv-grid.com
```
This will display a list of all your active tokens:
```bash
┌────────┬─────────┬────────────┬──────────┬────────────────┐
│ id │ token │ created │ readonly │ CIDR whitelist │
├────────┼─────────┼────────────┼──────────┼────────────────┤
│ b54f12 │ eyJhb.… │ 2024-03-21 │ no │ │
└────────┴─────────┴────────────┴──────────┴────────────────┘
```
### Removing an Access Token
To remove a token, use its ID from the token list:
```bash
npm token delete tokenId --registry=https://npm.rv-grid.com
```
### Using Tokens in Your Project
You can use access tokens by creating a `.npmrc` file in your project directory with the following content:
```ini
@revolist:registry=https://npm.rv-grid.com
//npm.rv-grid.com/:_authToken=YOUR-AUTH-TOKEN
```
Replace `YOUR-AUTH-TOKEN` with the token value you received when creating the token.
### Token File Locations
The `.npmrc` file can be stored in several locations, which npm checks in the following order:
1. Project-specific: `/path/to/your/project/.npmrc`
2. User-specific: `~/.npmrc`
3. Global: `$PREFIX/etc/npmrc`
4. npm built-in: `/path/to/npm/npmrc`
### CI/CD Integration
For Continuous Integration/Continuous Delivery (CI/CD) systems, you can use access tokens stored in `.npmrc`. Add the following to your CI/CD environment's `.npmrc` file:
```ini
@revolist:registry=https://npm.rv-grid.com
//npm.rv-grid.com/:_authToken=YOUR-AUTH-TOKEN
```
### Multi-user Access
For teams working on the same project, you have two options for repository access:
1. **Individual Logins**: Each developer can use their own credentials to access the repository
2. **Shared Tokens**: Create and share access tokens among team members
### Troubleshooting
If you encounter issues with token authentication:
1. Verify your token is valid by running `npm token list`
2. Ensure your `.npmrc` file is properly formatted
3. Check that you're using the correct registry URL
4. Verify your network can access the npm registry
For additional support or questions, please contact our support team.
To jump start your project with all major frameworks, check out our [Framework Boilerplates](https://github.com/revolist/revogrid-pro-boilerplate.git).
---
# RevoGrid Kanban
URL: https://pro.rv-grid.com/guides/kanban/
Source: src/content/docs/guides/kanban/index.mdx
Description: Render the same RevoGrid source as an accessible, virtualized Kanban board with workflow columns, swimlanes, WIP rules, and customizable cards.
Surface: kanban
`KanbanPlugin` turns canonical `grid.source` rows into workflow cards without creating a second data model. Configured workflow columns become RevoGrid columns and optional swimlanes expand into native card rows. Every workflow cell contains at most one card, so RevoGrid owns both horizontal and vertical virtualization.
## Install
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/kanban
```
```ts
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/kanban/styles.css';
```
Keep cards in `grid.source`. Set `grid.kanban` to show the board and set it to `false` to restore the latest cards and table columns.
RevoGrid providers own projected data, columns, dimensions, focus, viewport state, and plugin dependencies.
Reorder cards, move across columns or lanes, drag a multi-selection, validate transitions, and enforce WIP limits.
Replace card, header, lane, empty-state, and drag-preview content while the managed shell preserves interaction and accessibility.
## Minimal setup
{/* kanban-starter:vanilla */}
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { KanbanPlugin, type KanbanConfig } from '@revolist/kanban';
import '@revolist/revogrid-pro/styles.css';
import '@revolist/kanban/styles.css';
defineCustomElements();
await customElements.whenDefined('revo-grid');
const kanban: KanbanConfig = {
columns: [
{ prop: 'todo', name: 'To do' },
{ prop: 'doing', name: 'In progress', wipLimit: 3 },
{ prop: 'done', name: 'Done' },
],
card: { titleField: 'title', descriptionField: 'description' },
};
const grid = document.createElement('revo-grid');
grid.style.cssText = 'display:block;height:600px;width:100%';
grid.plugins = [KanbanPlugin];
grid.kanban = kanban;
document.body.append(grid);
grid.source = [
{ id: 'card-1', title: 'First card', status: 'todo', order: 1000 },
];
// Call when the host view unmounts.
export const destroy = () => grid.remove();
```
{/* /kanban-starter */}
The packaged editor is installed automatically. Double-click a card to edit it;
set `grid.kanbanCardEditorDialog = false` only when supplying your own editor.
## Add packaged controls
Install [`KanbanToolbarPlugin`](/guides/kanban/toolbar/) alongside `KanbanPlugin` for
card actions, search, field filters, undo/redo and board/table switching. The toolbar
uses the same editor, canonical cards and persistence adapter as other interactions.
## React starter
Add `react`, `react-dom`, their TypeScript types and `@revolist/react-datagrid`.
Include the following JSX augmentation to type the `kanban` property in your
application.
{/* kanban-starter:react */}
```tsx
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { KanbanPlugin, type KanbanConfig } from '@revolist/kanban';
import '@revolist/revogrid-pro/styles.css';
import '@revolist/kanban/styles.css';
declare module '@revolist/revogrid' {
namespace JSX {
interface RevoGrid {
kanban?: KanbanConfig | false;
}
}
}
type Card = { id: string; title: string; status: string; order: number };
export default function Board({ rows }: { rows: Card[] }) {
const plugins = useMemo(() => [KanbanPlugin], []);
const kanban = useMemo(() => ({
columns: [{ prop: 'todo', name: 'To do' }, { prop: 'done', name: 'Done' }],
card: { titleField: 'title' },
}), []);
return ;
}
```
{/* /kanban-starter */}
Pass `rows={[]}` for an empty board; replace `rows` when application data arrives.
React owns mounting and unmounting the grid. The standalone
[Kanban showcase](https://github.com/revolist/kanban) also includes Vue and Angular
examples.
The default fields are `id`, `status`, and `order`. Configure `idField`, `columnField`, or `orderField` when your application uses other names.
`columns` is optional so Kanban can be enabled before workflow metadata or data
loads. An empty configuration and empty source render a stable empty board. If
cards arrive before columns, they remain visible in the managed **Unmapped**
column until the workflow configuration is supplied.
## How the projection works
```txt
canonical grid.source cards
+ KanbanConfig
│
▼
validated card and bucket indexes
│
▼
RevoGrid columns + expandable swimlane card rows
```
Your application supplies card records and a save endpoint. Kanban manages projection, interaction, validation, and local updates. Configure the [persistence adapter](/guides/kanban/persistence/) to save canonical changes with approval, rollback, retry, and conflict handling.
## Recommended path
1. Define stable card IDs and explicit [workflow columns](/guides/kanban/data-model/).
2. Add [swimlanes, WIP limits, and card rules](/guides/kanban/swimlanes-wip-rules/) if the workflow needs capacity or policy controls.
3. Configure the [packaged card editor or a replacement UI](/guides/kanban/card-editor/).
4. Configure [async approval and persistence](/guides/kanban/persistence/) for server-backed changes, and use [events and public methods](/guides/kanban/events-methods/) to connect application controls.
5. Add [custom card content and translations](/guides/kanban/customization-i18n/).
6. Verify [accessibility and performance](/guides/kanban/accessibility-performance/) with production-sized data.
Kanban is a presentation plugin rather than an application toolbar. Your application controls the table/Kanban switch by setting `grid.kanban`.
---
# Accessibility And Performance
URL: https://pro.rv-grid.com/guides/kanban/accessibility-performance/
Source: src/content/docs/guides/kanban/accessibility-performance.mdx
Description: Operate Kanban without a pointer and keep large boards responsive through native row virtualization and stable card identity.
Surface: kanban
## Accessibility contract
- The managed card shell exposes selection, read-only, invalid, and drag state without depending on custom card content.
- Each selectable card shell is keyboard focusable. Space picks up or drops, arrows move the proposed target, and Escape cancels. Enter requests the card-edit workflow and Shift+F10 opens the card context menu.
- Ctrl+Space or Cmd+Space toggles the focused card in the managed multi-selection. Shift+Space extends the selection from the current anchor. Keyboard pickup first synchronizes selection when the focused card was not already selected.
- A live region announces selection, pickup, target position, validation failures, completion, and cancellation using `KanbanLabels`.
- Cards expose checkbox selection state, read-only and invalid identity state, plus their supported keyboard shortcuts. Focus uses a persistent high-contrast outline that is distinct from the selected-card treatment.
- Column and swimlane controls have localized expand/collapse names.
- Motion used for card hover and disclosure controls is removed when the operating system requests reduced motion.
- Custom content should use meaningful headings and text, mark nested interactive controls so they do not initiate card movement, and maintain sufficient contrast for rule states.
Check custom cards at 200% and 400% zoom, in forced-colors/high-contrast mode, with keyboard-only navigation, and with the screen reader/browser combinations your application supports.
## Native row virtualization
RevoGrid virtualizes workflow columns and the projected card rows. A cell contains at most one card, avoiding nested virtual scrollers and keeping card focus, drag targets, and row ownership aligned with the grid provider model.
Use `cardRowHeight` to reserve equal space for every card row. Choose a value that fits the number of content bands in your card template; individual swimlane definitions can override it with `height`. RevoGrid virtualizes those rows directly, while card identity and canonical rank remain independent of the rendered row index.
```ts
grid.kanban = {
columns,
cardRowHeight: 176,
customization: { cardContent },
};
```
Card content is contained within its reserved row, so an undersized custom template cannot overlap the next card. Increase `cardRowHeight` when adding metadata rows rather than relying on content overflow.
## Large-board guidance
- Use stable primitive IDs and finite numeric ranks.
- Keep render hooks pure and avoid creating network requests, timers, or global listeners inside templates.
- Put expensive derived values on the card record or memoize them outside the template.
- Configure `cardRowHeight` to fit the complete managed or custom card shell while keeping every projected row uniform.
- For known large server datasets, configure `kanban.remote.total` and `placeholder`; unloaded card rows use the same indexed skeleton pattern as Infinity Scroll.
- In placeholder mode, known positions participate in unfiltered visible counts and total/WIP counts, while placeholders remain non-interactive. Local filters still evaluate the records currently available to the board.
- Indexed page replacement and unfiltered card moves patch affected projection buckets, keeping interaction work independent from a full 100K projection rebuild.
- Persist committed move batches rather than rewriting the entire dataset for every pointer update.
Virtualization limits rendered card elements to the viewport. Card identity and drag targets remain tied to canonical records as the board scrolls.
---
# Kanban Card Editor
URL: https://pro.rv-grid.com/guides/kanban/card-editor/
Source: src/content/docs/guides/kanban/card-editor.mdx
Description: Add the packaged Kanban CRUD dialog, map default or arbitrary fields, and replace the editor with application UI.
Surface: kanban
`KanbanCardEditorDialogPlugin` is a separate companion plugin that `KanbanPlugin` installs by default. It listens to cancelable create, edit, and delete request events, then uses `KanbanPlugin`'s canonical mutation methods and history integration. The separate Open request remains application-owned for products that provide a distinct read-only detail view.
```ts
import { KanbanPlugin } from '@revolist/kanban';
grid.plugins = [KanbanPlugin];
grid.kanban = kanban;
grid.kanbanCardEditorDialog = {
fields: [{
id: 'points', label: 'Story points', kind: 'number', field: 'points',
validate: (value) => Number(value) < 0 ? 'Story points cannot be negative.' : undefined,
}],
};
```
The packaged form includes title, description, workflow status, swimlane, priority, tags, assignees, progress, start/end schedule, due date, and color. Matching schema IDs override those fields, `hiddenFields` removes them, and `fieldOrder` sets layout order. Additional schemas can map any `ColumnProp`, or use `read` and `write` for composite application records.
Field schemas support text, textarea, number, select, multiselect, tags, checkbox, date, datetime, color, assignees, and custom DOM controls. They can also provide icons, options, placeholders, defaults, parsing, formatting, readonly/required state, synchronous or asynchronous validation, and renderer cleanup lifecycles.
Assignee fields use the same resource picker as Scheduler and Gantt. Supply
schema `options` when your application owns a people directory. If the packaged
field has no explicit options, the editor derives a unique picker catalog from
the canonical Kanban cards through `card.assignees`, or `assigneeField` plus
`assigneeAvatarField`. Explicit options remain authoritative, and existing
mapped selections stay visible even before a directory is loaded.
## Validation
Each visible field runs its required check and synchronous or asynchronous
`validate` callback. Use the editor-level `validate` callback for cross-field or
remote rules; return a form message or a record keyed by field ID.
```ts
grid.kanbanCardEditorDialog = {
fields: [{
id: 'points', field: 'points', kind: 'number', required: true,
validate: async (value) => await api.pointsError(value),
}],
validate: ({ draft }) => draft.startDate > draft.dueDate
? { dueDate: 'Due date must follow the start date.' }
: undefined,
};
```
The validation lifecycle is observable through
`beforekanbancardeditorvalidate`, `beforekanbancardeditorfieldvalidate`,
`kanbancardeditorfieldvalidate`, `kanbancardeditorvalidate`, and
`kanbancardeditorinvalid`. The two `before...` events are cancelable. Their
details expose `setError`, allowing application validators to add field or form
errors without replacing editor persistence. Cancelled or invalid validation
always keeps the editor open and blocks the Kanban mutation.
Replacement editors call `context.controller.validate()` to run the same rules
and events as the packaged dialog. The plugin instance also exposes `validate()`
for an open editor.
## Persistence and identifiers
By default, Save calls `createCard` or `updateCard`, Delete calls `deleteCards`, and every successful operation is undoable. The fallback create ID is a collision-checked string. Use `createCardId` for numeric or domain IDs.
Configure [`kanban.persistence`](/guides/kanban/persistence/) to save editor changes with rollback, retry, and conflict handling. Use `onSubmit` and `onDelete` for application-owned submit and delete workflows; returning `false` tells the plugin that the application handled the mutation. Errors thrown by async hooks remain visible without closing the dialog. When the persistence adapter saves changes, do not save them again from these hooks.
## Replace or disable the editor
Set `grid.kanbanCardEditorDialog = false` or cancel the request event to retain an application-owned workflow. Explicitly registering `KanbanCardEditorDialogPlugin` remains supported and the Kanban dependency lifecycle reuses that instance. A full `customization.editor` DOM renderer receives the draft state and controller, so React, Vue, Angular, and other application UI can mount in place of the packaged modal.
```ts
grid.kanbanCardEditorDialog = {
customization: {
editor: {
mount(container, context) {
return mountApplicationEditor(container, context.controller);
},
},
},
};
```
## Dates and storage representations
Use `YYYY-MM-DD` for date-only values such as `dueDate`; a date-only value stays
on the authored day in every timezone. Do not use yearless strings such as `Aug 12`.
Use offset-qualified ISO strings, `Date` objects or epoch milliseconds for instants.
Set `card.dateTimeZone` to an IANA zone such as `Europe/Warsaw` to display and edit
instants in that zone. If omitted, the browser timezone is used. Date-only due
values ignore that zone. An untouched editor save preserves the original value,
type, offset representation and sub-minute precision. Edited instants retain the
original Date/number/string representation; new datetime values use UTC ISO strings.
Clearing a date removes its value.
Invalid calendar values and nonexistent DST times fail validation. For a date
field stored as an instant, this includes days whose midnight is skipped in the
configured timezone. Date-only calendar values remain valid on those days. An unchanged
repeated hour keeps its original instant; a newly entered repeated hour uses the
earlier instant. Custom `parse`, `format`, `read` and `write` callbacks should use
a matching representation; they remain application policy.
---
# Customization And i18n
URL: https://pro.rv-grid.com/guides/kanban/customization-i18n/
Source: src/content/docs/guides/kanban/customization-i18n.mdx
Description: Customize Kanban cards, headers, drag previews, empty states, CSS variables, labels, and locale-aware output.
Surface: kanban
## Managed card shell
Kanban owns the outer card shell, focus target, selection state, whole-card drag behavior, rule classes, and accessibility attributes. `customization.cardContent` replaces everything visible inside it.
```ts
customization: {
cardContent: (h, { card, selected, badges }) =>
h('div', { class: 'issue-card' }, [
h('strong', null, card.summary),
h('small', null, `${card.owner} · ${card.points} points`),
selected ? h('span', null, 'Selected') : null,
...badges.map((badge) => h('span', { class: badge.className }, badge.label)),
]),
cardProps: ({ card }) => ({
class: card.blocked ? 'issue-card-shell--blocked' : '',
title: card.summary,
}),
}
```
The same customization object supports `dragPreview`, `swimlaneHeader`,
`swimlaneColumnHeader`, and `emptyState` templates. The swimlane-header context
reports `swimlaneColumnCollapsed`; compact mode keeps a managed vertical title
badge so labels remain readable. Template contexts use canonical cards and
projected metadata; do not query card DOM to recover product state.
## Swimlane header templates
Set `swimlaneTemplate` on an explicit swimlane when that lane needs to own its
complete header layout. This works in both `column` and `top` swimlane layouts:
```ts
grid.kanban = {
...config,
swimlaneField: 'workstream',
swimlaneLayout: 'top',
swimlanes: [
{
id: 'product',
title: 'Product experience',
collapsible: true,
swimlaneTemplate: (h, context) => h('div', { class: 'workstream-header' }, [
h('span', { class: 'workstream-header__icon', 'aria-hidden': 'true' }, '◈'),
h('span', { class: 'workstream-header__kicker' }, 'WORKSTREAM'),
h('strong', null, context.swimlane.title),
h('span', null, `${context.visibleCount} / ${context.totalCount}`),
h('button', {
type: 'button',
onClick: context.toggleCollapsed,
}, context.swimlane.collapsed ? 'Expand' : 'Collapse'),
]),
},
],
};
```
`swimlaneTemplate` fully replaces the managed title, count, WIP badges, and
collapse button for that lane. Render the pieces your product needs and call
`context.toggleCollapsed()` when the custom header exposes collapse. The action
is guarded by the lane's `collapsible` setting. Kanban continues to own the
outer group row, lane identity, configured style, and accessibility label.
A definition-owned template takes precedence over
`customization.swimlaneHeader`. Lanes without one keep the board-wide custom
content or the default header.
## Workflow header templates
Set `columnTemplate` and `columnProperties` directly on a workflow definition,
using the same shape as a RevoGrid column:
```ts
grid.kanban = {
...config,
columns: [
{
prop: 'active',
name: 'Active',
size: 280,
wipLimit: 4,
columnProperties: () => ({
class: 'workflow-header-cell',
style: { backgroundColor: 'var(--active-header)' },
}),
columnTemplate: (h, props, additionalData) => {
const { column, visibleCount, totalCount, overWipLimit, toggleCollapsed } = props.kanban;
const name = column.name ?? String(column.prop);
return h('div', { class: overWipLimit ? 'workflow-header workflow-header--over' : 'workflow-header' }, [
h('strong', null, name),
h('span', null, `${visibleCount} / ${totalCount}`),
h('button', { type: 'button', onClick: toggleCollapsed }, 'Toggle'),
additionalData?.workspaceName
? h('small', null, additionalData.workspaceName)
: null,
]);
},
},
],
};
```
`columnTemplate` fully replaces the inner managed workflow header. Kanban does
not append its default name, count, WIP badge, or collapse button, so render the
pieces your product needs and call `props.kanban.toggleCollapsed()` when the
custom header exposes collapse. Application `additionalData` is passed through
unchanged. RevoGrid's outer resizable header remains managed.
`columnProperties` is composed with Kanban's required header properties through
the shared Pro merging behavior: classes and styles combine, while required
workflow identity and interaction attributes win conflicts. Collapsed body
rails deliberately render `name` (or `String(prop)`) instead of invoking the
header template.
## Default content
Configure `card.titleField` and `card.descriptionField`, or provide formatter callbacks. The title defaults to the `title` field and falls back to the card ID.
Default cards can also render shared progress and assignee content without a
custom template:
```ts
grid.kanban = {
...config,
card: {
titleField: 'name',
startDateField: 'startDate',
endDateField: 'endDate',
dateTimeZone: 'UTC',
progressField: 'percentDone',
assigneeField: 'owners',
assigneeAvatarField: 'ownerAvatars',
},
};
```
Assignee and avatar fields accept either scalar values or parallel arrays. For
The default date range appears directly after the card title and uses the
configured Kanban locale and optional `dateTimeZone`. For fully custom cards,
the managed shell continues to own focus, selection, accessibility, and drag
behavior.
## Labels and locale
English labels are bundled. Pass a partial `labels` object to replace every visible or assistive string used by your product:
```ts
grid.kanban = {
...config,
locale: 'pt-PT',
labels: {
emptyColumn: 'Sem cartões',
addCard: (column, lane) => `Adicionar cartão em ${column}${lane ? `, ${lane}` : ''}`,
newCard: 'Novo cartão',
editCard: 'Editar cartão',
addCardHere: 'Adicionar cartão aqui',
deleteCard: 'Eliminar cartão',
cardContextMenu: 'Ações do cartão Kanban',
cardCount: (visible, total) => `${visible} de ${total} cartões`,
moveBlocked: (reason) => `Movimento bloqueado: ${reason}`,
},
};
```
`locale` controls locale-aware query normalization for built-in card search and
the default date-range component. Other number and product-specific display
formatting remains template-owned. Kanban does not automatically download a
translation pack.
## CSS
Import the standalone Kanban stylesheet once:
```ts
import '@revolist/kanban/styles.css';
```
Prefer Kanban CSS variables and classes returned by card rules or customization hooks. Avoid changing managed-shell focus, selection, and drag-state elements in ways that hide their state.
Kanban follows the grid's `material`, `compact`, `darkMaterial`, and
`darkCompact` themes automatically. Its defaults derive from RevoGrid's public
`--revo-grid-*` tokens, while the semantic `--kanban-board-background`,
`--kanban-surface`,
`--kanban-card-background`, `--kanban-border`, `--kanban-text`,
`--kanban-muted`, `--kanban-accent`, and `--kanban-accent-strong` variables let
the host tune either theme without replacing component styles. Drag previews
copy the resolved Kanban tokens, so a body-mounted preview retains the active
dark theme.
---
# Cards, Columns, And Ordering
URL: https://pro.rv-grid.com/guides/kanban/data-model/
Source: src/content/docs/guides/kanban/data-model.mdx
Description: Define canonical Kanban cards, workflow columns, stable ranks, diagnostics, and board projection behavior.
Surface: kanban
## Canonical cards
Kanban reads and updates ordinary RevoGrid source rows. A card needs a stable ID, workflow-column value, and preferably a numeric order:
```ts
grid.source = [
{
workId: 'PAY-104',
summary: 'Retry declined payments',
stage: 'active',
rank: 1000,
team: 'billing',
},
];
grid.kanban = {
idField: 'workId',
columnField: 'stage',
orderField: 'rank',
swimlaneField: 'team',
columns: [
{ prop: 'queued', name: 'Queued' },
{ prop: 'active', name: 'Active' },
{ prop: 'done', name: 'Done' },
],
};
```
When a card has no rank, source order is used. The first move assigns fractional ranks between neighboring cards. If the available numeric gap becomes unsafe, Kanban rebalances only the destination bucket and reports the affected IDs in `kanbancardmove`.
## Shared view fields
Use `kanban.fields` when the same application records also feed Scheduler or
Gantt. The keys describe field meaning and the values name properties in your
source rows:
```ts
grid.kanban = {
fields: {
id: 'id',
title: 'name',
status: 'workflowStatus',
start: 'startDate',
end: 'endDate',
color: 'color',
progress: 'percentDone',
resourceId: 'owner',
},
columns: [
{ prop: 'todo', name: 'To do' },
{ prop: 'doing', name: 'In progress' },
{ prop: 'done', name: 'Done' },
],
};
```
Kanban defaults these meanings to `id`, `title`, `status`, `startDate`,
`endDate`, `color`, `progress`, and `assignees`. An explicitly mapped shared
key takes precedence over the corresponding `idField`, `columnField`, or
`card.*Field` option. Use those individual options to map fields without a shared
`fields` object.
The mapping only selects properties. It does not convert dates or progress
units. `resourceId` represents one resource identifier; `card.assigneeField`
also accepts an array of assignees. Fields that Kanban does not change, including
application metadata, remain on the original card. Moves, editor changes, remote updates, and undo/redo preserve authored
property names and do not add internal Kanban aliases to source rows.
## Remote card pages
Use `kanban.remote` when the canonical collection should arrive from one
whole-board offset/limit stream:
```ts
const loadCards = async (skip: number, limit: number) => {
const response = await fetch(`/api/cards?skip=${skip}&limit=${limit}`);
const result = await response.json();
return {
data: result.cards,
total: result.total,
};
};
grid.kanban = {
columns,
remote: {
loadData: loadCards,
total: 100_000,
chunkSize: 100,
preloadThreshold: 0.75,
placeholder: (index) => ({
id: `remote-${index}`,
status: statuses[index % statuses.length],
order: Math.floor(index / statuses.length),
}),
},
};
```
With `total` and `placeholder`, Kanban works like Infinity Scroll: it creates the
known number of lightweight positions, renders an animated skeleton for each
unloaded card row, and replaces the requested 100-card slice in place. The
placeholder only needs a unique temporary `id`, workflow column, swimlane when
configured, and rank fields. Its ID may differ from the returned server card ID;
the source offset owns replacement identity. The workflow, swimlane, and rank
values must match the corresponding server card. Scrolling directly to an
unloaded area requests that area's chunk; approaching a loaded chunk boundary
preloads the next one.
Without `placeholder`, remote mode retains append paging: page zero replaces
the current cards and later pages append as the board nears its scroll boundary.
Both modes preserve loaded cards on errors and expose the same inline retry
action.
The result can be a card array or `{ data, total?, hasMore? }`. In append mode,
a returned `total` takes precedence over `hasMore`; otherwise an explicit
`hasMore` wins, then the configured or previously returned total is consulted,
and only then does a short page end the stream. Indexed mode treats the
configured `total` as its fixed layout: every request must return its complete
requested slice, and response `total` or `hasMore` values must agree with that
layout. A mismatch keeps the slice retryable and emits the remote error event.
Local update and move operations preserve indexed server positions. Create and
delete are rejected in indexed mode because they would shift every later
offset; perform those operations on the server and call `refreshRemote()`.
Replacing `grid.source` externally restarts indexed loading from page zero. In
append mode, local CRUD does not rewind the cursor and an external source
replacement adopts its length as the next offset. Both behaviors invalidate a
late response.
The loader type and indexed chunk behavior are shared with
`InfinityScrollPlugin`, so the same application adapter can power a table or
Kanban view. Kanban needs the additional placeholder factory because an
unloaded card's workflow column and optional swimlane cannot be inferred from a
global total. The two plugins should not own one grid simultaneously because
Kanban projects cards into its own virtual rows.
## Workflow columns
`kanban.columns` is the workflow schema; `grid.columns` remains the canonical
table/card-field schema. A card belongs to a workflow column when
`card[columnField] === column.prop`.
Workflow definitions reuse the safe RevoGrid header and sizing subset and add
Kanban behavior such as WIP limits, collapse state, transition rules, metadata,
and card filtering.
The array is optional and defaults to `[]`. This supports loading workflow
metadata and cards independently: no columns plus no cards is an empty board,
while cards without configured columns use the managed **Unmapped** column by
default. Supplying workflow columns later rebuilds the projection from the same
canonical `grid.source` records.
```ts
columns: [
{ prop: 'queued', name: 'Queued', size: 260 },
{
prop: 'active',
name: 'Active',
minSize: 240,
maxSize: 360,
wipLimit: 4,
allowedFrom: ['queued', 'active'],
collapsible: true,
},
]
```
Projected workflow columns keep the exact authored `prop`; no synthetic grid
property is introduced. If `name` is omitted, Kanban displays `String(prop)`.
The default `size` is 288 and `minSize` is 220. The core column-move plugin
handles header dragging; `kanbancolumnorderchange` exposes the resulting prop
order for persistence.
Every workflow column is collapsible by default. Set `collapsible: false` to lock a column open, or use `collapsed: true` for its initial state. At runtime, `setColumnCollapsed(prop, collapsed)` changes one column and `getCollapsedColumnProps()` returns the active collapsed set. A collapsed column remains visible as a narrow rail with a vertical resolved-name badge, so users can restore it without losing board context.
## Unknown and invalid data
- Unknown statuses render in a localized **Unmapped** column by default. Set `unmappedColumn: false` only when the host deliberately hides them.
- Missing or duplicate card IDs produce blocking diagnostics. Those cards remain visible but cannot be moved.
- Duplicate column props produce a blocking configuration diagnostic.
- Invalid numeric ranks fall back to source order and produce a diagnostic.
Listen to `kanbandiagnostics` or call `getDiagnostics()` on the plugin during development and after remote data refreshes.
## Counts
Column and swimlane headers distinguish:
- **visible count**: cards passing RevoGrid filters, Kanban search, and the optional predicate;
- **total count**: all canonical cards in that bucket.
WIP limits always use total counts so a filter cannot accidentally permit over-capacity work.
In append mode, both counts describe the cards loaded so far. In indexed mode,
known placeholder positions participate in total and visible counts, so loading
a page does not change an unfiltered header count. Placeholders remain
non-interactive, and active filters can exclude positions that cannot match.
Enforce server-side rules in the application whenever Kanban does not have the
authoritative records needed for the decision.
---
# Events And Methods
URL: https://pro.rv-grid.com/guides/kanban/events-methods/
Source: src/content/docs/guides/kanban/events-methods.mdx
Description: Validate and persist Kanban moves, observe selection and diagnostics, request CRUD flows, and call public plugin methods.
Surface: kanban
['gridedit', 'Unified committed card changes from EventManager; inspect kanban-card-change and typed domainChanges.'],
['beforekanbancardmove', 'Cancelable proposal with cards, previous locations, target, reason, and proposed records.'],
['kanbancardmove', 'Committed moved cards, every changed canonical record, locations, and destination-bucket rebalance IDs.'],
['kanbanselectionchange', 'Selected card IDs/records and replace, toggle, range, clear, or API reason.'],
['kanbancolumnorderchange', 'Runtime workflow column order after native header drag or API movement.'],
['kanbandropstatechange', 'Current valid, invalid, or order-preserving no-op target state and localized reason.'],
['kanbandiagnostics', 'Current source/config diagnostics.'],
['kanbancardcreaterequest', 'Host-owned create workflow request.'],
['kanbancardopenrequest', 'Host-owned card detail request; the packaged editor does not consume it.'],
['kanbancardeditrequest', 'Host-owned card editor request.'],
['kanbancarddeleterequest', 'Host-owned delete workflow request.'],
['beforekanbancardcreate / kanbancardcreate', 'Cancelable create proposal and committed canonical card.'],
['beforekanbancardupdate / kanbancardupdate', 'Cancelable update proposal and committed card with previous record.'],
['beforekanbancarddelete / kanbancarddelete', 'Cancelable atomic delete proposal and committed card batch.'],
['beforekanbancardeditorvalidate', 'Cancelable start of packaged or replacement-editor validation; listeners can add errors.'],
['beforekanbancardeditorfieldvalidate', 'Cancelable per-field validation proposal with value and setError.'],
['kanbancardeditorfieldvalidate', 'Committed valid, invalid, skipped, or cancelled result for one editor field.'],
['kanbancardeditorvalidate', 'Completed editor validation result with valid, cancelled, and errors.'],
['kanbancardeditorinvalid', 'Validation completion that blocked submission because errors or cancellation remain.'],
['kanbanremoteloadstart', 'A whole-board page request started with skip, limit, reason, and current remote state.'],
['kanbanremoteloadsuccess', 'A page was committed to canonical cards; detail includes the returned cards and updated state.'],
['kanbanremoteloaderror', 'A page failed without removing loaded cards; detail includes the error and retryable state.'],
];
## Get the plugin
```ts
import { KanbanPlugin } from '@revolist/kanban';
const plugins = await grid.getPlugins();
const kanban = plugins.find((plugin) => plugin instanceof KanbanPlugin);
```
Public methods are `getCards`, `getConfig`, `getProjection`, `createCard`, `updateCard`, `deleteCards`, `moveCards`, `getSelectedCardIds`, `setSelectedCardIds`, `revealCard`, `getColumnOrder`, `setColumnOrder`, `getCollapsedColumnProps`, `setColumnCollapsed`, `getSwimlaneColumnCollapsed`, `setSwimlaneColumnCollapsed`, `refresh`, `getDiagnostics`, `getRemoteState`, `loadMoreCards`, `refreshRemote`, `requestCreateCard`, `requestOpenCard`, `requestEditCard`, and `requestDeleteCards`. Obtain the plugin from RevoGrid rather than installing reverse callbacks on the grid element.
`await kanban.revealCard(cardId)` scrolls a currently rendered card's native
grid row and workflow column into view. It resolves to `false` when the card is
missing, filtered, or hidden by collapsed presentation state.
## Event reference
Event constants such as `BEFORE_KANBAN_CARD_MOVE_EVENT` and `KANBAN_CARD_MOVE_EVENT` are exported for refactor-safe listeners.
Every successful create, update, delete, or move also reaches the shared
`gridedit` stream with `kanban-card-change` in `eventTypes`. Its ordered
`domainChanges` contains one `kanban-card` entry per changed canonical card,
including cards re-ranked during a move. Each entry provides the action, card
ID, previous card, and committed card. Create uses a null previous card and
delete uses a null committed card.
`data`, `previousData`, and `models` contain only ordinary row/field patches for
cards that retain a stable canonical source index. Creates, deletes, and
position changes without a safe grid coordinate remain in `domainChanges`;
compact undo/redo state lives separately in `history`.
The Kanban `beforekanban*` events remain the cancellation boundary. A Kanban
`gridedit` has `sourceMutation: 'producer'`, is observational, and calling
`preventDefault()` does not roll back the already committed mutation.
Passing `skipHistory: true` to a mutation still publishes this event with
`recordHistory: false`, but History deliberately creates no undo entry.
Remote loading exports `KANBAN_REMOTE_LOAD_START_EVENT`,
`KANBAN_REMOTE_LOAD_SUCCESS_EVENT`, and `KANBAN_REMOTE_LOAD_ERROR_EVENT`.
Call `getRemoteState()` for `loading`, `nextOffset`, `hasMore`, `total`, and
`error`; `loadMoreCards()` requests the next page and `refreshRemote()` clears
cards and history, or reinitializes configured placeholders, before loading
page zero. Both loading methods return a
promise and deduplicate concurrent requests.
Editor validation constants are exported with
`KanbanCardEditorDialogPlugin`. `beforekanbancardeditorvalidate` and
`beforekanbancardeditorfieldvalidate` are cancelable; all other validation
events report committed results. Cancelling validation always blocks submit.
## Cancel and persist a move
Use this pattern when managing persistence directly without `kanban.persistence`.
```ts
grid.addEventListener(BEFORE_KANBAN_CARD_MOVE_EVENT, (event) => {
if (!permissions.canMove(event.detail.cardIds, event.detail.target)) {
event.preventDefault();
}
});
grid.addEventListener(KANBAN_CARD_MOVE_EVENT, async (event) => {
await api.saveCards(event.detail.changedCards);
});
```
Before-move listeners must make their cancel decision synchronously. For remote authorization, configure `persistence.prepareMove` and use `moveCardsAsync` as described in the [persistence guide](/guides/kanban/persistence/).
CRUD request events remain the stable integration point. With `KanbanCardEditorDialogPlugin` installed they open the packaged workflow; without it, when it is disabled, or when a listener cancels the request, the application remains responsible. Committed mutation events are suitable for synchronizing remote state.
## Optional persistence lifecycle
With `kanban.persistence` enabled, `moveCardsAsync` awaits approval and the final
save receipt. `getPersistenceState`, `retryPersistence`, `cancelPersistence` and
`resolvePersistenceConflict` expose the transaction state without accessing grid DOM.
`kanbanpersistencechange` reports operation ID, affected card IDs, attempt, status
and a message. Existing CRUD/move events describe local canonical changes; the
persistence event describes server acceptance or failure. See the
[persistence guide](/guides/kanban/persistence/) before attaching save listeners.
---
# Kanban Examples
URL: https://pro.rv-grid.com/guides/kanban/examples/
Source: src/content/docs/guides/kanban/examples.mdx
Description: Live RevoGrid Kanban examples for a basic board, swimlanes and WIP, custom cards and i18n, and persistence events.
Surface: kanban
## Basic workflow
The source rows remain ordinary application cards. Drag between columns or reorder within a workflow column.
## Swimlanes, rules, and blocking WIP
This example uses explicit team lanes, per-lane WIP limits, allowed origins, and an urgent-card rule.
## Custom cards and labels
The managed shell retains selection and drag behavior while the content, properties, and Portuguese labels are application-defined.
## Cancelable moves and persistence payloads
The locked card is rejected before mutation. Successful moves show the payload a persistence adapter receives.
These focused portal examples complement the full `kanban-board` demo, which is available in the examples catalog for TypeScript, React, Vue 3, and Angular. All variants use the same grid properties and `@revolist/kanban` imports.
---
# Filtering, History, And Application State
URL: https://pro.rv-grid.com/guides/kanban/filtering-history-state/
Source: src/content/docs/guides/kanban/filtering-history-state.mdx
Description: Combine RevoGrid filters with Kanban search, undo card moves, persist board state, and restore the table view.
Surface: kanban
Use the [packaged toolbar](/guides/kanban/toolbar/) for search, field filters, history
buttons and board/table switching, or wire the APIs below into your own controls.
## Filtering and search
Core RevoGrid filters are evaluated against canonical cards and the host's captured table columns. Kanban then composes its lightweight search and predicate:
```ts
grid.kanban = {
...config,
filter: {
query: searchInput.value,
fields: ['title', 'owner', 'labels'],
predicate: (card) => !card.archived,
},
};
```
A card must pass all three layers: RevoGrid filters, query matching, and the predicate. Search defaults to the configured card title field.
Header counts expose visible and total cards when filters differ. WIP limits always count all canonical cards.
## Undo and redo
Kanban coordinates with `EventManagerPlugin` and `HistoryPlugin`. Each successful move or editor CRUD action contributes normal stable row patches plus typed card changes to the shared `gridedit` stream. Replay-only card state is kept in separate keyed `history` metadata. With EventManager collection enabled, consecutive operations merge by card ID into one transaction and one history step while preserving each card's earliest prior state and latest committed state. Undo/redo is replayed through Kanban rather than against projected lane rows, including source-length changes.
History is not cleared merely because filtering, sorting, or board projection refreshes. Replacing `grid.source` with an externally authored dataset clears Kanban history so an old move cannot overwrite newer host data.
## Persistence ownership
Use the optional [async approval and persistence adapter](/guides/kanban/persistence/)
for atomic versioned writes, precise rollback, retries and server-approved history.
It captures canonical CRUD, moves and history once; do not add a second save listener.
For manually managed integrations without the adapter, `kanbancardmove` exposes
`changedCards`, including every rank-rebalanced neighbor. Persist them atomically.
`beforekanbancardmove` remains a synchronous veto; use `prepareMove` and
`moveCardsAsync` when approval needs to await a server.
## Switching views
```ts
// Table to Kanban
grid.kanban = kanbanConfig;
// Kanban to table
grid.kanban = false;
```
Disabling Kanban restores the latest canonical source, latest host column definitions, and relevant table state. Sorting is not used for card order while the board is active; `orderField` remains authoritative. Re-enabling projects the current source again.
---
# Drag, Drop, And Selection
URL: https://pro.rv-grid.com/guides/kanban/interaction/
Source: src/content/docs/guides/kanban/interaction.mdx
Description: Move and select Kanban cards with pointer, touch, keyboard, auto-scroll, and atomic multi-card operations.
Surface: kanban
## Card movement
Cards can be reordered within a workflow column or moved across columns and swimlanes. The complete non-interactive card surface starts a pointer drag and the card itself is the keyboard pickup target. Pointer capture keeps the drag active outside a card, and drop indicators plus localized announcements expose the proposed target.
During a drag, Kanban auto-scrolls the board horizontally and RevoGrid vertically. Filtered cards remain in the canonical bucket; visible cards act as insertion anchors so hidden records keep deterministic ranks.
### Touch and mobile behavior
Kanban uses Pointer Events for mouse, pen, and touch rather than browser-native HTML drag-and-drop. A tap selects the card without creating a drag preview. Once touch movement crosses the drag threshold, pointer capture keeps the operation active and horizontal edge auto-scroll exposes off-screen workflow columns. Browser `pointercancel` events remove the preview and drop feedback without committing a move.
Draggable cards use `touch-action: pan-y`, so a vertical swipe can continue to scroll a long board from the card surface. A primarily horizontal gesture starts cross-column movement. Because mobile browsers arbitrate vertical gestures before application code, use the context menu, keyboard, or `moveCards()` API when an exact vertical reorder gesture is claimed by native scrolling.
Keep the grid sized to its container on phones; workflow columns retain their configured widths and scroll inside RevoGrid rather than compressing card content. Validate production integrations on physical iOS Safari and Android Chrome in addition to automated mobile emulation.
Empty alignment cells are not drop zones. A column exposes targets on its existing cards and once in the first empty cell immediately after its final visible card, preventing repeated empty placeholders across the swimlane.
## Multiple selection
- Click replaces the selection.
- Cmd/Ctrl-click toggles one card.
- Shift-click selects a contiguous range in board reading order.
- Dragging a selected card moves the entire selection.
- Dragging an unselected card first replaces the selection with that card.
A selection can span several columns and swimlanes. The move is atomic and preserves the selected cards' prior board reading order.
When filters change, selection is reduced to visible cards. Removed source rows are always pruned.
## Keyboard movement
Focus the card, then:
| Key | Action |
| --- | --- |
| Space | Pick up or drop the selected card group. |
| Arrow keys | Move the proposed target through positions, columns, and lanes. |
| Escape | Cancel the active keyboard drag. |
The live region announces pickup, target changes, validation failures, cancelation, and completion. Application card content remains inside the managed shell so custom templates cannot remove these interactions.
## Card context menu
Right-click a card or focus it and press the Context Menu key/Shift+F10. Kanban uses the same shared context-menu surface as Scheduler and Gantt, including Font Awesome SVG icons, keyboard navigation, nested menus, dividers, viewport-aware positioning, and theme colors.
The built-in menu provides:
- A single Edit request action.
- Move destinations grouped by workflow column and swimlane. If the invoked card belongs to the current multi-selection, the complete selection moves atomically.
- Add card here, scoped to the current column and swimlane.
- A visually separated destructive Delete request.
Move commands use the same rules, transitions, WIP checks, history entry, and persistence events as drag-and-drop. CRUD entries always emit the matching cancelable request event. The automatically installed `KanbanCardEditorDialogPlugin` handles those requests with the packaged schema-driven editor. Set `grid.kanbanCardEditorDialog = false` when supplying your own dialogs. The public Open request is still available for an application-owned detail view, but it is not duplicated in the packaged menu or editor. Hide any request action your application does not handle so the menu advertises only working capabilities.
Disable the generated entries or hide individual commands without replacing an application row menu:
```ts
grid.kanban = {
...config,
contextMenu: {
includeHostItems: true,
hidden: {
edit: ({ card }) => !permissions.canEdit(card),
create: ({ column, swimlane }) => !permissions.canCreate(column.prop, swimlane.id),
delete: ({ cardIds }) => !permissions.canDelete(cardIds),
},
},
};
```
Each `hidden` entry accepts a boolean or a predicate evaluated when the menu opens. The predicate receives the primary `card`, invoked `cardIds`, `column`, and `swimlane`. Set `contextMenu: false` to remove Kanban actions entirely. Visible strings remain configurable through `labels`, while `includeHostItems` composes application `rowContextMenu` entries. Host-owned `ContextMenuPlugin` instances are preserved across activation and teardown.
## Programmatic movement
Locate the plugin through RevoGrid's plugin collection and call `moveCards`:
```ts
const plugins = await grid.getPlugins();
const kanban = plugins.find((plugin) => plugin instanceof KanbanPlugin);
await kanban?.moveCards(
['CARD-12', 'CARD-19'],
{ columnProp: 'review', swimlaneId: 'platform', index: 0 },
{ reason: 'api' },
);
```
Programmatic moves use the same validation, events, provider update, ranking, and history path as pointer and keyboard moves. For [asynchronous approval or persistence](/guides/kanban/persistence/), call `await kanban.moveCardsAsync(cardIds, target)` to wait for server acceptance.
---
# Kanban Limitations
URL: https://pro.rv-grid.com/guides/kanban/limitations/
Source: src/content/docs/guides/kanban/limitations.mdx
Description: Understand editing, remote data, dragging, view switching, sorting, and localization constraints.
Surface: kanban
Kanban manages board projection and interaction. Your application supplies authorization rules and the backend used by the persistence adapter.
| Area | Behavior |
| --- | --- |
| Toolbar and views | Install [`KanbanToolbarPlugin`](/guides/kanban/toolbar/) for card actions, search, field filters, history and board/table controls. Hosts can also set `grid.kanban` directly. |
| Card forms | The packaged editor is installed automatically. Configure its fields, intercept requests, or set `grid.kanbanCardEditorDialog = false` to supply your own UI. Use [`kanban.persistence`](/guides/kanban/persistence/) to save canonical changes. |
| Remote data | `kanban.remote` loads one whole-board offset/limit stream. A known `total` plus placement `placeholder` enables fixed Infinity-style indexed skeleton chunks; otherwise pages append. Indexed create/delete must happen on the server followed by `refreshRemote()` so offsets stay stable. Filtering stays local, and the application owns persistence and server-wide validation. |
| External drag | Cross-board, cross-window, file, and third-party drag targets are not supported. |
| Swimlane order | Configuration-owned; swimlanes cannot be drag-reordered. |
| Column order | Uses RevoGrid's core column move behavior. Pointer/touch capabilities follow that plugin; programmatic ordering remains available. |
| Sorting | Table sorting does not reorder board cards. `orderField` is authoritative while Kanban is active. |
| Translation packs | English is bundled; applications provide partial or complete translated labels. |
| Asynchronous validation | `beforekanbancardmove` cancellation is synchronous. For remote approval, configure [`persistence.prepareMove`](/guides/kanban/persistence/) and call `moveCardsAsync`; pointer, keyboard, and context-menu moves use that path automatically. |
Card pointer interaction supports mouse, pen, and touch. Keyboard card movement uses the focusable managed card shell and does not depend on browser-native HTML drag-and-drop.
Unknown workflow values use the **Unmapped** column by default, while invalid or duplicate card identities remain visible but immovable. Treat diagnostics as data-quality errors rather than hiding affected records.
Kanban remote loading and `InfinityScrollPlugin` share the same generic loader
contract and fixed indexed chunks. Kanban additionally needs a lightweight
placement placeholder because a global total cannot identify an unloaded
card's workflow column or swimlane. Do not enable both loaders on the same
Kanban grid: Kanban must retain ownership of the projected row provider while
its remote controller owns canonical cards. If multiple row-source owners are
configured, the first active owner remains enabled and the later plugin
disables itself with a console warning.
---
# Async Approval And Persistence
URL: https://pro.rv-grid.com/guides/kanban/persistence/
Source: src/content/docs/guides/kanban/persistence.mdx
Description: Save canonical Kanban cards atomically with version checks, precise rollback, explicit retry and server-approved history.
Surface: kanban
## Configure one save adapter
Configure `persistence` to save canonical card changes through an asynchronous adapter.
Load the complete canonical dataset and its server versions before enabling persistence.
Partial/indexed `remote` loading cannot be combined with this adapter.
```ts
import { KanbanPlugin, type KanbanPersistenceOptions } from '@revolist/kanban';
type Card = { id: string; title: string; status: string; order: number };
const loaded: { cardId: string; card: Card; version: string | number }[] =
await fetch('/api/kanban').then(response => response.json());
const initialVersions = new Map(loaded.map(row => [row.cardId, row.version]));
const persistence: KanbanPersistenceOptions = {
optimistic: true,
getVersion: (_card, id) => initialVersions.get(String(id)) ?? null,
prepareMove: async ({ cards, target, signal }) => {
const response = await fetch('/api/kanban/approve-move', {
method: 'POST', signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ cardIds: cards.map(card => card.id), target }),
});
if (!response.ok) return 'Move approval was denied.';
return true;
},
commit: async ({ signal, ...request }) => {
const response = await fetch('/api/kanban', {
method: 'POST', signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
// A network error, timeout or non-2xx response may happen AFTER a server write.
// Throwing retains the operation for an idempotent retry; it does not assume rollback.
if (!response.ok) throw new Error(`Save returned HTTP ${response.status}`);
return response.json();
},
};
grid.plugins = [KanbanPlugin];
grid.source = loaded.map(row => row.card);
grid.kanban = {
columns: [{ prop: 'todo', name: 'To do' }, { prop: 'doing', name: 'Doing' }],
persistence,
};
```
`prepareMove` is optional. The runnable reference API described below implements
`GET/POST /cards`; omit that approval hook when running the reference unchanged.
A real approval endpoint can refresh permissions, but the final write endpoint
must enforce authorization, WIP and transition rules again atomically.
The existing editor and create/update/delete methods capture one canonical save
automatically. Do not also save from `kanbancardmove`, editor callbacks or `gridedit`:
that would duplicate writes. A batch contains all changed canonical cards, including
rank-rebalanced neighbors; projected grid rows are never persisted.
## Await a move
```ts
const plugins = await grid.getPlugins();
const kanban = plugins.find(plugin => plugin instanceof KanbanPlugin)!;
const committed = await kanban.moveCardsAsync(['a'], { columnProp: 'doing' });
```
The promise resolves after remote acceptance or a definitive rejection/cancellation.
While a network outcome or version conflict is unresolved it remains pending until
the user reconciles it. Pointer, keyboard and context-menu moves use this path
automatically when persistence is configured. Rules and ranks are recomputed after
approval against current cards. With `prepareMove` configured, synchronous direct
`moveCards` returns `false`; use `moveCardsAsync` to await approval. Without the
adapter, `moveCards` retains its boolean contract and fast path.
## Atomic server receipts
Every request has an `operationId`, an `action`, and `changes`. Each change includes
`cardId`, detached `previousCard`/`card` snapshots, `sourceIndex`, and `expectedVersion`.
A null `card` deletes a card; a null expected version means the identity is absent.
Versions are opaque strings or numbers, with null reserved for absence.
Return exactly one of these correlated results:
```ts
// The batch was durably committed. Include a version for every changed identity.
{ operationId, status: 'accepted', versions: [{ cardId: 'a', version: 42 }] }
// Definitive policy/validation rejection: no part of the batch was written.
{ operationId, status: 'rejected', message: 'Doing is at capacity.' }
// Compare-and-swap failed: no part was written. Include ALL affected identities.
{ operationId, status: 'conflict', message: 'Card changed elsewhere.',
cards: [{ cardId: 'a', card: latestCard, version: 43 }] }
```
The reference serves all three result envelopes with HTTP 200. If your endpoint
uses HTTP 409 for a conflict, parse and return its correlated conflict envelope
in `commit` instead of throwing for that known response. Unstructured HTTP failures
still have an unknown outcome.
Cache the receipt by operation ID in the same database transaction as the writes.
The same ID/body must return the original receipt even after a response is lost.
Use a new ID only for a new logical action; the client does this when rebasing a
known conflict. Never report a timeout as a definitive rejection.
## Pending, errors and conflict choices
The board exposes non-blocking status, Retry save, Cancel (before submission),
Use server data, Retry changes, and Dismiss controls. Hosts can also
listen to `kanbanpersistencechange` and query `getPersistenceState()`. The shared
[status panel and badges](/guides/grid-utilities/status-feedback/) follow Pro theme
tokens and keep actions usable in narrow containers.
```ts
kanban.retryPersistence(operationId); // Same ID and body after unknown failure
kanban.cancelPersistence(operationId); // Cancels unsent work; submitted work becomes unknown
kanban.resolvePersistenceConflict(operationId, 'server'); // Load authoritative fields, retain later local edits
kanban.resolvePersistenceConflict(operationId, 'retry'); // Rebase this delta on the latest version with a new ID
```
Writes touching the same identity serialize; unrelated identities can save concurrently.
Queued payloads are rebuilt from acknowledged state plus their own field changes.
A rejected move therefore cannot reappear inside a later title-save payload.
Rollback reprojects only the affected fields and retains later edits and unrelated cards.
A conflict pauses overlapping work until an explicit choice. An error after submission
keeps optimistic state and ordering until the same operation is retried.
Set `optimistic: false` to leave proposed changes off the board until accepted.
## History and lifecycle
Only accepted writes enter undo history. Undo/redo waits for a server-approved write
before shared history changes its stacks; a rejected replay leaves both cards and
history intact. Unresolved ordinary writes/approvals block replay. Hosts with their
own history controls should also disable those controls while persistence is busy.
Replacing `grid.source`, replacing the adapter, disabling Kanban or destroying the
board invalidates old callbacks. A response cannot overwrite the replacement dataset.
Aborting a submitted request does not prove that the server cancelled it: reload
authoritative data before starting a new board/session after abandonment.
Keep card identities stable. For server-assigned IDs, use the packaged editor's
existing asynchronous `createCardId` callback to allocate the ID before creation.
Response-time identity remapping is not supported. Extra server-normalized card fields
must arrive through a subsequent authoritative reload; receipts advance revisions
and do not overwrite cards with arbitrary whole-object server snapshots.
## Runnable reference
The source checkout contains `packages/kanban/server-reference/server.ts`:
```sh
pnpm exec tsx packages/kanban/server-reference/server.ts
```
It binds to `127.0.0.1:4042`, serves `GET/POST /cards`, applies atomic version checks,
caches operation receipts, and enforces a sample Doing capacity of two. Proxy your
frontend's `/api/kanban` route to `/cards`. Its process-local storage is a reference;
production storage, durable idempotency, authentication and tenant isolation belong
to the host. The adjacent README describes that boundary and server-ID allocation.
### Collapse activity
The activity popup floats above the board using the browser's Popover API and a
subtle shadow. Use **Collapse activity** to reduce it to a compact bar with the
number of pending messages. Use **Expand activity** to restore the details and
actions. Escape also collapses it. Requests continue while collapsed, and new
activity updates the count without expanding the popup.
### Translate activity feedback
Use `kanban.labels` to translate the activity popup, just like other Kanban
controls. `kanban.locale` formats its numeric count. Translate both visible
labels and accessible control/count labels:
```ts
grid.kanban = {
...grid.kanban,
locale: 'pt-PT',
labels: {
...grid.kanban?.labels,
persistenceTitle: 'Atividade',
persistencePreparing: 'A aguardar aprovação',
persistenceQueued: 'Gravação em espera',
persistenceSaving: 'A guardar alterações',
persistenceError: 'Não foi possível confirmar a gravação',
persistenceConflict: 'Rever alterações',
persistenceCommitted: 'Alterações guardadas',
persistenceRejected: 'Alteração recusada',
persistenceCancelled: 'Pedido cancelado',
persistencePreparingDescription: 'A aguardar aprovação do movimento.',
persistenceQueuedDescription: 'A aguardar a gravação anterior.',
persistenceSavingDescription: 'As suas alterações estão a ser guardadas.',
persistenceConflictDescription: 'Este cartão foi atualizado por outra pessoa.',
persistenceNewerEdits: 'As suas edições mais recentes são preservadas.',
persistenceRetryChanges: 'Tentar novamente',
persistenceUseServer: 'Usar dados remotos',
persistenceRetrySave: 'Repetir gravação',
persistenceCancel: 'Cancelar',
persistenceDismiss: 'Dispensar',
persistenceCollapse: 'Recolher atividade',
persistenceExpand: 'Expandir atividade',
persistenceActivityCount: count => `${count} alterações`,
},
};
```
The `persistence*` entries in `KanbanLabels` also cover internally generated
notices, including denied approval, cancellation, changed card existence,
invalid receipts, and retry instructions. `persistenceInitialReadError(message)`
and `persistenceRetryMessage(message)` format diagnostic details.
Messages returned by your persistence adapter remain application-owned. Return
localized messages from `commit` and `prepareMove`, or provide
`labels.persistenceMessage(state)` to map them to translated UI text. Return
`undefined` to use the translated status description instead. Label changes
apply to the mounted board without replacing the persistence adapter.
The popup animates its entrance, exit, and compact/expanded transitions. These
short transitions are skipped when the user requests reduced motion. Closing
activity never delays or changes the outcome of a save.
---
# Swimlanes, WIP, And Rules
URL: https://pro.rv-grid.com/guides/kanban/swimlanes-wip-rules/
Source: src/content/docs/guides/kanban/swimlanes-wip-rules.mdx
Description: Group cards into lanes, define capacity policies, validate transitions, and apply ordered visual and permission rules.
Surface: kanban
Use swimlanes when each workflow column must be split by team, class of service, release, product area, or another card field.
## Swimlane placement
Swimlane labels render in a dedicated row-header column by default. Use
`swimlaneLayout: 'top'` to render a compact header across the workflow columns
before each lane instead. Both layouts keep counts, custom header content, and
per-lane collapse controls.
```ts
grid.kanban = {
columns,
swimlaneField: 'team',
swimlaneLayout: 'top', // use 'column' for the dedicated label column
swimlanes: [
{
id: 'product',
title: 'Product team',
collapsible: true,
swimlaneTemplate: (h, context) => h('div', { class: 'product-lane' }, [
h('strong', null, context.swimlane.title),
h('span', null, `${context.visibleCount} / ${context.totalCount}`),
h('button', { type: 'button', onClick: context.toggleCollapsed }, 'Toggle'),
]),
},
{ id: 'platform', title: 'Platform team', collapsible: true },
],
};
```
`column` is the default. `swimlaneColumn` width and collapse
settings apply only to that dedicated-column layout. A lane's
`swimlaneTemplate` fully controls its header content in either layout while
Kanban keeps the outer lane row and identity semantics.
## Derived or explicit lanes
Setting only `swimlaneField` derives lanes in first-appearance order. Provide definitions when labels, order, heights, collapse controls, styles, or lane-specific WIP limits matter.
```ts
grid.kanban = {
columns,
swimlaneField: 'team',
swimlanes: [
{ id: 'web', title: 'Web team', wipLimits: { active: 2 } },
{ id: 'api', title: 'API team', collapsed: false },
],
};
```
Set swimlane order through the configuration array. Swimlanes cannot be drag-reordered.
For a board that does not use swimlanes, disable the row-header presentation
directly in Kanban configuration:
```ts
grid.kanban = {
columns,
swimlaneColumn: false,
};
```
Cards still use the internal implicit lane for ordering and virtualization, but
no swimlane label or lane-collapse control is rendered.
## Collapsible swimlane labels
The swimlane row-header column can collapse independently from the lanes. In
its compact state it shrinks to a narrow rail and renders lane titles as
vertical badges; cards and lane heights are unchanged.
```ts
grid.kanban = {
columns,
swimlaneField: 'team',
swimlaneColumn: {
title: 'Teams',
width: 176,
collapsedWidth: 52,
collapsible: true,
collapsed: false,
},
};
```
Use `getSwimlaneColumnCollapsed()` and `setSwimlaneColumnCollapsed(...)` on the
plugin instance when an application toolbar also needs to control the state.
This is separate from each swimlane definition's `collapsed` state, which
reduces that lane's projected row height.
## WIP behavior
Set a column `wipLimit`, or a lane `wipLimits` entry for a more specific bucket limit. `wipBehavior` defaults to `warn`:
- `warn` permits the move and renders/announces the over-limit state.
- `block` rejects the entire proposed move.
Multi-card moves validate the final batch count, not each card independently. A failed card or capacity check cancels the full move.
## Movement permissions
Use `allowedFrom` for simple workflow transitions and `rules` for application policy:
```ts
rules: {
canDrag: (card) => card.locked ? 'Locked by another user' : true,
canSelect: (card) => !card.archived,
canEdit: (card) => card.permissions.includes('edit'),
canDrop: ({ cards, targetColumn }) =>
cards.some((card) => card.requiresApproval) && targetColumn.prop === 'done'
? 'Approval is required'
: true,
}
```
Return `false` or a localized reason string to reject an action.
## Visual card rules
`cardRules` is an ordered declarative layer for badges, classes, styles, and permissions. Matching classes and badges accumulate, later styles override earlier properties, and any explicit permission denial wins.
```ts
cardRules: [
{
id: 'urgent',
when: ({ card }) => card.priority === 'urgent',
result: {
className: 'card--urgent',
badges: [{ label: 'Urgent' }],
style: { '--kanban-card-accent': '#dc2626' },
},
},
]
```
---
# Kanban Toolbar
URL: https://pro.rv-grid.com/guides/kanban/toolbar/
Source: src/content/docs/guides/kanban/toolbar.mdx
Description: Add packaged card actions, search, field filters, history controls, and board/table switching.
Surface: kanban
## Add the toolbar
Install `KanbanToolbarPlugin` alongside `KanbanPlugin`. The toolbar renders in its
own grid header slot and preserves other header content. Without the plugin,
Kanban does not render a toolbar.
```ts
import { KanbanPlugin, KanbanToolbarPlugin } from '@revolist/kanban';
import '@revolist/revogrid-pro/styles.css';
import '@revolist/kanban/styles.css';
grid.plugins = [KanbanPlugin, KanbanToolbarPlugin];
grid.kanban = {
columns: [
{ prop: 'todo', name: 'To do' },
{ prop: 'doing', name: 'In progress' },
{ prop: 'done', name: 'Done' },
],
card: { titleField: 'title', priorityField: 'priority' },
};
grid.kanbanToolbar = {
filters: [{ id: 'priority', field: 'priority', label: 'Priority' }],
};
```
The compact command bar includes icon buttons for **Add card**, **Filters**,
**Undo/Redo**, and **Board/Table**, alongside search and a card count. **Edit** and
**Delete** appear when cards are selected. Buttons use Font Awesome SVGs with
localized tooltips and accessible names. The view selector uses the shared Pro
`SegmentedSwitch` component and its theme styles.
An empty board includes **Create first card**, using the same packaged editor.
## Card actions and permissions
Actions call Kanban's existing request APIs, so configured editor fields, request
interception, delete confirmation, rules and persistence behavior still apply.
Edit requires one editable selection. Delete requires every selected card to be
editable. Read-only boards disable mutation actions. Indexed remote loading
also disables create/delete because those operations require server refresh.
Undo and redo use the installed shared history service. Their availability tracks
its current state, including pending replay. If you disable the packaged editor,
handle card request events in your application.
## Search and filters
Search uses Kanban's configured query fields, defaulting to the title field.
Open **Filters** to choose a workflow or field value. Changes apply immediately;
use **Done**, Escape, or a click outside to close the popover. The workflow selector
filters by the canonical workflow field. Additional filters
accept a canonical `field` and either explicit options or options derived from
the loaded cards. Array-valued fields match when they contain the selected value.
Numbers, strings and booleans retain their identity.
```ts
grid.kanbanToolbar = {
filters: [
{
id: 'priority', field: 'priority', label: 'Priority',
options: [
{ value: 'high', label: 'High' },
{ value: 'low', label: 'Low' },
],
},
{ id: 'owner', field: 'assigneeIds', label: 'Assignee' },
],
};
```
**Clear filters** clears the toolbar query and field choices. The application's
existing predicate and core RevoGrid filters continue to apply. Search updates
projection without resetting column order, collapsed groups, history or pending
saves. Filtered-out cards are removed from the active selection.
For application-controlled search, call `kanbanPlugin.setFilter(filter)`; the
mounted toolbar reflects that filter. With remote loading, filtering and derived
options apply to loaded cards. They do not perform server-wide queries.
## Switch views
The view buttons set `grid.kanban` using the current board configuration. Returning
to the board restores its order, collapsed columns/swimlanes, filters and selected
cards while using the current canonical source, including edits made in table view.
Card actions and board filters are disabled while table view is active; shared
history controls remain available.
View switching is disabled while saves or approvals are unsettled, a conflict or
unknown outcome needs resolution, or a history replay is pending. Resolve or retry
that activity before leaving the board. Searching remains available during saves.
## Customize controls and translations
```ts
grid.kanbanToolbar = {
actions: ['add', 'edit', 'undo', 'redo'],
workflowFilter: false,
viewSwitcher: true,
labels: {
toolbar: 'Barra Kanban',
add: 'Adicionar cartão', edit: 'Editar cartão',
search: 'Pesquisar cartões', clear: 'Limpar filtros',
board: 'Quadro', table: 'Tabela',
count: (visible, total) => `${visible} de ${total} cartões`,
},
};
```
All toolbar text is exposed through `KanbanToolbarLabels`, including control/group
names, counts, selection, first-card guidance and pending-save text. Translate
custom filter labels and options in their definitions. Count callbacks can use
`Intl.NumberFormat` and application plural rules.
Set `search`, `workflowFilter` or `viewSwitcher` to `false` to hide that control.
Set `actions: []` to hide card and history action buttons. Set `grid.kanbanToolbar = false` to
hide the entire toolbar; it does not clear active filters or change the view.
Assign an options object to show it again.
Controls use native buttons, search input and selects with accessible names,
visible focus and normal keyboard operation. Toolbar interactions are isolated
from grid shortcuts. Controls wrap within narrow headers and follow shared Pro
light/dark theme tokens.
## Customize the panel
`KanbanToolbarPlugin` is installed separately from `KanbanPlugin`. Its TSX
components own presentation; board APIs continue to own card changes and history.
You can replace the toolbar with your own UI by omitting this optional plugin.
The default is icon-only. Enable `showLabels` to show translated action and view
names beside icons. Both modes retain tooltips and accessible names from `labels`.
Partial label overrides fall back to the defaults; count and selection labels
accept functions for your language's plural rules.
```tsx
import { h } from 'preact';
grid.kanbanToolbar = {
showLabels: true,
className: 'project-toolbar',
count: false,
emptyState: false,
actions: ['add', 'edit', 'delete'],
labels: {
add: 'New task',
board: 'Cards',
table: 'Rows',
selection: count => `${count} selected`,
},
renderIcon: name => ,
renderStart: state => {state.visible} visible tasks ,
renderEnd: state => ,
};
```
`ProjectIcon` and `ProjectActions` are your own components. Icon renderers return
decorative Preact content; the toolbar supplies the accessible button name.
Start/end renderers receive current `active`, `pending`, `selected`, `visible`,
`total`, and `enabled` action flags. These are presentation snapshots; use the
existing Kanban plugin APIs to change board data.
Replace `grid.kanbanToolbar` with a new options object to update these settings
without reinstalling the plugin. `search`, `workflowFilter`, and `viewSwitcher`
can each be disabled independently. `filters` configure labeled fields/options;
`actions` controls action order and visibility. Excluding `add` also removes the
first-card button. Shared Pro tokens style controls; `className` scopes your own
panel styling.
## Add a card inside a column
The plus button at the end of each column/swimlane opens the card editor with
that location selected. It works independently of the toolbar plugin.
```ts
grid.kanban = {
...grid.kanban,
addCard: { mode: 'dialog' },
};
```
To create immediately, use `mode: 'create'`. The default card receives a unique
ID and the localized `labels.newCard` title. Supply additional values with
`createCard`; the clicked column and swimlane determine the final placement.
```ts
grid.kanban = {
...grid.kanban,
addCard: {
mode: 'create',
createCard: ({ draft, columnProp, swimlaneId }) => ({
...draft,
title: 'New task',
priority: 'medium',
}),
},
labels: {
addCard: (column, lane) => `Add task to ${column}${lane ? `, ${lane}` : ''}`,
newCard: 'New task',
},
};
```
Set `addCard: false` to hide the plus button. Read-only boards and indexed remote
loading hide it automatically. Creation uses the existing rules, WIP limits,
cancellable create event, and history. A custom `customization.emptyState`
renderer still owns empty buckets when provided. Drag targets remain available
when the plus button is hidden.
---
# Migrations
URL: https://pro.rv-grid.com/guides/migrations/
Source: src/content/docs/guides/migrations.mdx
Description: Breaking changes and upgrade steps for RevoGrid Pro and its standalone product packages.
This guide covers breaking changes in RevoGrid Pro and the standalone product
packages. Start with the section for your target version, then review any earlier
versions you are skipping.
Major upgrades can change package imports, plugin configuration, CSS setup, and
public APIs. Review the migration checklist before updating your application.
---
## Quick migration checklist
1. Use the Core version required by your target Pro release; 3.0 requires `@revolist/revogrid` `^4.29.0`
2. Replace all sub-path imports with flat imports from `@revolist/revogrid-pro`
3. Replace hardcoded event strings with named constants from `@revolist/revogrid-pro`
4. Import `revogrid-pro.css` in your application entry point
5. Move plugin configuration from `grid.additionalData` to direct grid properties
6. Update custom plugins to extend `CorePlugin` instead of `BasePlugin`
7. Keep `@revolist/revogrid-pro` alongside grid-based products; standalone Charts does not require Pro
8. Replace the Enterprise umbrella with direct Pivot, Gantt, Kanban, Scheduler, and Charts packages
9. Update Excel export integrations that relied on bundled SheetJS or bundled workbook import parsing when upgrading from `2.0.8`
10. Rename authored Gantt task `status` fields to `workflowStatus` and treat the Status column as calculated/read-only
11. When upgrading from 2.7 to 2.8, replace model-backed formatting selections and version 1 formatting state with physical coordinates and state version 2
12. Replace `ClipboardJsonPlugin` and `$rv-parse_` clipboard text with `ClipboardPlugin` and the smart clipboard document
13. Move Event Scheduler event arrays from `eventSchedulerEvents` to `source` when convenient; the legacy property remains a deprecated compatibility alias
14. Remove `LicensePlugin` imports and manual trial plugin registration
---
## 3.0 breaking changes
### Enterprise umbrella root removed
`@revolist/revogrid-enterprise` and its trial counterpart
`@revolist/rv-enterprise-trial` are retired. Replace the umbrella dependency,
root imports, combined stylesheet, and UMD script with the product packages your
application uses. The legacy workspace package has also been removed; browser tests import the
standalone products directly.
| Capability | Package | Stylesheet |
| --- | --- | --- |
| Shared grid plugins and UI | `@revolist/revogrid-pro` | `@revolist/revogrid-pro/styles.css` |
| Pivot, configurator, and Pivot Charts | `@revolist/pivot` | `@revolist/pivot/styles.css` |
| Gantt | `@revolist/gantt` | `@revolist/gantt/styles.css` |
| Kanban | `@revolist/kanban` | `@revolist/kanban/styles.css` |
| Event Scheduler | `@revolist/scheduler` | `@revolist/scheduler/styles.css` |
| Standalone Charts | `@revolist/charts` | No separate stylesheet |
```ts
import { PivotPlugin, GanttPlugin } from '@revolist/revogrid-enterprise';
import '@revolist/revogrid-enterprise/dist/revogrid-enterprise.css';
```
```ts
import { PivotPlugin } from '@revolist/pivot';
import { GanttPlugin } from '@revolist/gantt';
import '@revolist/revogrid-pro/styles.css';
import '@revolist/pivot/styles.css';
import '@revolist/gantt/styles.css';
```
Install only the required products and their declared dependencies. Shared APIs
such as `CorePlugin`, `DEFAULT_CALENDAR`, and `SharedResourcePicker` come from
`@revolist/revogrid-pro`. For UMD integrations, load the corresponding product
scripts and use their product globals instead of `RevoGridEnterprise`.
Trial applications use the corresponding direct trial packages, such as
`@revolist/rv-pro-trial`, `@revolist/pivot-trial`, `@revolist/gantt-trial`,
`@revolist/kanban-trial`, `@revolist/scheduler-trial`, and
`@revolist/charts-trial`.
### LicensePlugin removed; trial registration is automatic
`LicensePlugin` is no longer a public export. Remove its import and any manual
entry in `grid.plugins`; keep the normal product plugins your grid uses. The
current trial behavior remains the watermark shown after a random 1–5 minute
delay, with its timer and notice cleaned up when the grid or chart is destroyed.
Trial builds inject a private registration call into the compiled `CorePlugin`
constructor. The private trial package owns registration, deduplication, and the
watermark lifecycle. Charts trial builds inject lifecycle wrappers into compiled
standalone factories and native renderers. Charts source contains no trial
placeholder or hooks. Regular builds skip injection and contain no trial
registration or watermark implementation.
No application setup or separate trial-control installation is required. The
private registration function is not a supported application API.
---
## 2.7 → 2.8 breaking changes
### Event Scheduler uses `source` for canonical events
Put the event entity array in the standard RevoGrid `source` property. The
deprecated `eventSchedulerEvents` property is still accepted and copies its
value into `source`, so existing integrations can migrate incrementally:
```ts
// Legacy compatibility alias
grid.eventSchedulerEvents = events;
// Preferred canonical API
grid.source = events;
```
Generated calendar slots and resource rows are internal projection data and no
longer replace the public property. After an accepted create, edit, delete,
undo/redo, or remote result, `grid.source` contains the latest canonical event
list. Existing scheduler mutation events still provide `detail.events`; use
those events to update React, Vue, or Angular application state and to persist
changes.
### DataGridFormattingPlugin is coordinate-only
v2.8 removes the formatting compatibility layer completely. The following 2.7
contracts are no longer accepted as formatting operation or state inputs:
- model-backed `{ scope, rows, columns, cells }` selections
- `getFormat(model, prop, rowType)`
- `getRowKey` and `rowKeyProp`
- formatting runtime state version 1 and row-key preset state
Cell operations now use inclusive, zero-based physical coordinates in the
selected row and column `source` stores:
```ts
plugin.apply({
scope: 'cells',
rows: [invoice],
columns: [amountColumn],
cells: [{ model: invoice, column: amountColumn, rowType: 'rgRow' }],
}, moneyFormat);
const format = plugin.getFormat(invoice, 'amount', 'rgRow');
```
```ts
plugin.apply({
start: { row: 4, column: 2 },
rowType: 'rgRow',
colType: 'rgCol',
}, moneyFormat);
const format = plugin.getFormat({ row: 4, column: 2 });
```
Complete-column operations use a separate coordinate target so their format
also applies to future rows:
```ts
plugin.apply({
scope: 'columns',
ranges: { start: 2, end: 4, colType: 'rgCol' },
}, moneyFormat);
const format = plugin.getColumnFormat({ column: 2, colType: 'rgCol' });
```
Complete-row operations are symmetrical and also apply to columns added later:
```ts
plugin.apply({
scope: 'rows',
ranges: { start: 4, end: 6, rowType: 'rgRow' },
}, moneyFormat);
const format = plugin.getRowFormat({ row: 4, rowType: 'rgRow' });
```
`DataGridFormattingRuntimeState` now accepts only `version: 3`. v2.8 does not
read or infer formatting from version 1 or version 2 state. Applications must migrate
persisted state before assigning it to `grid.dataGridFormatting` or calling
`plugin.setState()`. The v3 format stores `row`, `column`, `rowType`, and
`colType` directly; `getRowKey` and `rowKeyProp` are unnecessary because
formatting remains attached to the physical coordinate when source objects are
replaced.
Value formats are now a strict discriminated union. Built-in formats use
`{ kind: 'preset', preset: 'currency', ... }`; exact Excel-compatible codes use
`{ kind: 'code', formatCode: '#,##0.00' }`. The ambiguous legacy
`{ preset, formatCode }` object is rejected. The RevoGrid rich clipboard payload
is correspondingly version 2; version 1 payloads are not inferred as v2.
`DataGridFormattingPlugin` no longer auto-installs `EventManagerPlugin`. Add it
explicitly when formatting mutations, managed clipboard paste, or accepted
autofill formatting must flow through the cancelable `gridedit` event. Add
`AutoFillPlugin` alongside Event Manager when fill-handle gestures should copy
formatting as well as values. `HistoryPlugin` continues to install Event Manager
automatically for undo and redo.
Bold cell formatting now uses the standard CSS `700` weight. This also makes
bold formatting imported from Excel visually match the source workbook.
### ClipboardJsonPlugin was replaced by ClipboardPlugin
v2.8 removes `ClipboardJsonPlugin`, `legacyJsonText`, and automatic decoding of
the `$rv-parse_` text marker. The marker overloaded `text/plain`, exposed an
internal serialization convention to external applications, and duplicated the
versioned smart clipboard document.
```ts
import { ClipboardJsonPlugin } from '@revolist/revogrid-pro';
grid.plugins = [ClipboardJsonPlugin];
```
```ts
import {
ClipboardPlugin,
DataGridFormattingPlugin,
} from '@revolist/revogrid-pro';
// Values and application-defined clipboard formats:
grid.plugins = [ClipboardPlugin];
// Or the recommended rich-formatting setup:
grid.plugins = [DataGridFormattingPlugin];
```
The smart plugin preserves objects, formulas, formatting, structures, and sparse
multi-range geometry in `application/x-revogrid-clipboard+json`, with HTML and
plain-text fallbacks for Excel and other applications. Decode any stored
`$rv-parse_` values in application code before assigning or pasting them; v2.8
treats marker text literally.
v2.8 also removes the clipboard implementation markers
`SMART_CLIPBOARD_PLUGIN`, `SmartClipboardRuntime`, and
`isSmartClipboardRuntime`. Application code that needs the installed plugin
instance should use the plugin class instead:
```ts
const clipboard = (await grid.getPlugins())
.find(plugin => plugin instanceof ClipboardPlugin);
```
Custom Pro plugins can use the equivalent provider-backed lookup:
`providers.plugins.getByClass(ClipboardPlugin)`.
---
## 1. Core dependency version
v2.0 requires `@revolist/revogrid` **4.21.4 or later**. Earlier core versions are not supported.
```bash
npm install @revolist/revogrid@^4.21.4
# or
pnpm add @revolist/revogrid@^4.21.4
```
---
## 2. Import paths — flat surface only
v1.x supported sub-path imports that pointed to internal modules. These paths are **removed** in v2.0.
All exports are now available from the single package entry point.
```ts
import { commonAggregators } from '@revolist/revogrid-pro/aggregations';
import { CorePlugin } from '@revolist/revogrid-pro/core';
import { FOCUS_APPLY_EVENT } from '@revolist/revogrid-pro/events';
import { createMergedData } from '@revolist/revogrid-pro/utils';
```
```ts
import {
commonAggregators,
CorePlugin,
FOCUS_APPLY_EVENT,
createMergedData,
} from '@revolist/revogrid-pro';
```
---
## 3. Event strings → named constants
v2.0 exports more than 135 named event constants. Replace every hardcoded event string with the
corresponding export to benefit from type checking and rename refactoring.
```ts
grid.addEventListener('beforecellfocus', handler);
grid.addEventListener('beforefocuslost', handler);
grid.addEventListener('beforekeydown', handler);
grid.addEventListener('aftersourceset', handler);
```
```ts
import {
EVENT_BEFORE_CELL_FOCUS,
EVENT_BEFORE_FOCUS_LOST,
BEFORE_KEYDOWN_EVENT,
AFTER_SOURCE_SET_EVENT,
} from '@revolist/revogrid-pro';
grid.addEventListener(EVENT_BEFORE_CELL_FOCUS, handler);
grid.addEventListener(EVENT_BEFORE_FOCUS_LOST, handler);
grid.addEventListener(BEFORE_KEYDOWN_EVENT, handler);
grid.addEventListener(AFTER_SOURCE_SET_EVENT, handler);
```
The full list of available constants is exported from `@revolist/revogrid-pro`.
---
## 4. CSS — required import
Starting in v2.0, the Pro plugin styles are **not** injected automatically. You must import the
stylesheet once in your application entry point.
```ts
// main.ts (or your app entry)
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
```
```html
```
Import the stylesheet for each standalone product you use:
```ts
import '@revolist/pivot/styles.css';
import '@revolist/gantt/styles.css';
```
---
## 5. Plugin configuration — direct grid properties
v2.0 introduces a direct property system for Pro and standalone product plugin configuration. Plugin options
should now be assigned to their own grid property instead of being grouped under
`grid.additionalData`.
`additionalData` is deprecated for plugin configuration. It is still read as a legacy fallback during
the v2 migration window, but new code should use direct properties.
This is a breaking change because plugins no longer treat one large shared object as the primary
configuration surface. The old pattern made `additionalData` grow too quickly: unrelated plugins
shared the same object, and changing one key could notify every plugin observing `additionalData`.
That could cause unrelated state updates and unnecessary redraws. Direct properties make each plugin
observe only the configuration it owns.
```ts
grid.additionalData = {
pivot,
pagination,
tree: {
expandedRowIds,
},
rowExpand: {
expandedRows,
},
rowAutoSize: {
mode: 'auto',
},
eventManager: {
applyEventsToSource: true,
},
};
```
```ts
grid.pivot = pivot;
grid.pagination = pagination;
grid.tree = {
expandedRowIds,
};
grid.rowExpand = {
expandedRows,
};
grid.rowAutoSize = {
mode: 'auto',
};
grid.eventManager = {
applyEventsToSource: true,
};
```
For framework wrappers, bind the plugin property directly:
```tsx
```
```vue
```
```ts
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
@Component({
selector: 'my-grid',
standalone: true,
imports: [RevoGrid],
// Allows Angular demos to bind RevoGrid plugin props that are not wrapper inputs.
schemas: [NO_ERRORS_SCHEMA],
template: `
`,
})
export class MyGridComponent {}
```
Common replacements:
| v1.x `additionalData` key | v2.0 property |
|---|---|
| `additionalData.pivot` | `grid.pivot` |
| `additionalData.pagination` | `grid.pagination` |
| `additionalData.tree` | `grid.tree` |
| `additionalData.rowExpand` | `grid.rowExpand` |
| `additionalData.rowAutoSize` | `grid.rowAutoSize` |
| `additionalData.masterRow` | `grid.masterRow` |
| `additionalData.transpose` | `grid.transpose` |
| `additionalData.rowContextMenu` | `grid.rowContextMenu` |
| `additionalData.columnContextMenu` | `grid.columnContextMenu` |
| `additionalData.eventManager` | `grid.eventManager` |
| `additionalData.infinityScroll` | `grid.infinityScroll` |
| `additionalData.cellMerge` | `grid.cellMerge` |
| `additionalData.excel` | `grid.excel` |
| `additionalData.hiddenColumns` | `grid.hideColumns` |
Core grid inputs such as `source`, `columns`, `plugins`, `theme`, `stretch`, `rowDefinitions`, and
`rowHeaders` are already direct RevoGrid properties. Keep binding those directly as before.
---
## 6. Custom plugins — extend `CorePlugin`
In v2.0 the recommended base class for all Pro plugins changed from `BasePlugin` (re-exported from
`@revolist/revogrid`) to `CorePlugin` (exported from `@revolist/revogrid-pro`). `CorePlugin` adds:
- `observeAttribute(name, callback)` — reacts to DOM attribute mutations
- `observeProperty(name, callback)` — reacts to direct JS property assignments
```ts
import { BasePlugin } from '@revolist/revogrid';
export class MyPlugin extends BasePlugin {
constructor(grid, providers) {
super(grid, providers);
// plugin setup
}
}
```
```ts
import { CorePlugin } from '@revolist/revogrid-pro';
export class MyPlugin extends CorePlugin {
constructor(grid, providers) {
super(grid, providers); // license banner injected here automatically
// plugin setup
this.observeProperty('myProp', (value) => {
// react to grid.myProp = value
});
}
}
```
---
## 7. Standalone products — install Pro as a dependency
Pivot, Gantt, Scheduler, and Kanban require `@revolist/revogrid-pro` as a runtime dependency.
Install Pro explicitly alongside the products you use.
```bash
npm install @revolist/revogrid-pro @revolist/pivot @revolist/gantt
# or
pnpm add @revolist/revogrid-pro @revolist/pivot @revolist/gantt
```
Keep Pro and the standalone products on the **same version** (they are released together).
---
## 8. Pivot and Gantt use standalone packages
In v1.x, Pivot and Gantt plugins were part of `@revolist/revogrid-pro`. They now live in their
own `@revolist/pivot` and `@revolist/gantt` packages.
```ts
import { PivotPlugin, PivotConfig } from '@revolist/revogrid-pro';
import { GanttPlugin } from '@revolist/revogrid-pro';
```
```ts
import { PivotPlugin, type PivotConfig } from '@revolist/pivot';
import { GanttPlugin } from '@revolist/gantt';
```
---
## 9. Excel export provider changes since 2.0.8
This is a breaking change for applications upgrading from `@revolist/revogrid-pro` `2.0.8` or earlier if they relied on bundled SheetJS, non-`.xlsx` default export output, or bundled `.xlsx` / `.xls` import parsing.
`ExportExcelPlugin`, `grid.exportExcel`, `export-excel`, `excel-before-import`, and `excel-before-set` keep their public names. The implementation contract changed so workbook-library choices stay app-owned and the default package is lighter.
| Area | In `2.0.8` and earlier | After this change |
| --- | --- | --- |
| Bundled export library | SheetJS `xlsx` | `write-excel-file` |
| Default export format | SheetJS-supported formats | `.xlsx` only |
| Default import | Workbook parsing through bundled dependency | CSV-only import |
| XLSX import | Plugin-owned | App-owned parser |
| Provider integration | Mostly implicit | Explicit provider objects or factories |
| Formatted workbook output | Reconstructed by provider | Prepared in `context.cellRows` |
### Default export
Use the bundled `.xlsx` provider for normal grid exports:
```ts
grid.plugins = [ExportExcelPlugin];
await excel.export({
workbookName: 'report.xlsx',
sheetName: 'Report',
});
```
The bundled provider now rejects non-`.xlsx` output:
```ts
await excel.export({
workbookName: 'report.xlsb',
});
await excel.export({
writingOptions: { bookType: 'xlsb' },
});
```
Use SheetJS, ExcelJS, workbook-module, or a custom provider when another workbook format is required.
### App-owned SheetJS
If an app depended on SheetJS output formats, install `xlsx` in the app and pass the module object into the SheetJS adapter:
```sh
pnpm add xlsx
```
```ts
import * as XLSX from 'xlsx';
import { createSheetJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createSheetJsExcelExportProvider(XLSX),
};
```
The SheetJS adapter writes plain `context.rows` data. It is the compatibility path for apps that want SheetJS-owned workbook writing.
### App-owned ExcelJS
Use ExcelJS when migration requires formatted `cellRows`, merge spans, row heights, freeze panes, or richer workbook style mapping:
```sh
pnpm add exceljs
```
```ts
import ExcelJS from 'exceljs';
import { createExcelJsExcelExportProvider } from '@revolist/revogrid-pro';
grid.exportExcel = {
exportProvider: createExcelJsExcelExportProvider(ExcelJS),
};
```
The ExcelJS adapter does not add ExcelJS to RevoGrid Pro dependencies. Your application owns the dependency and bundle cost.
### Custom providers
Custom providers should consume the prepared provider context instead of reading rendered grid rows:
```ts
const provider = {
id: 'custom',
async export(context) {
await writeWorkbook(context.cellRows, {
fileName: context.workbookName,
sheetName: context.sheetName,
metadata: context.exportMetadata,
});
},
};
```
Use `context.cellRows` when the provider needs formatted cells, grouped headers, formulas, or merge spans. Use `context.rows` only for plain row-object output.
### CSV-only default import
Default import now accepts CSV files. It reads files with `FileReader.readAsText`, dispatches `excel-before-import`, maps rows, dispatches `excel-before-set`, then updates the grid source unless either event is prevented.
`.xlsx` and `.xls` parsing is no longer bundled. For workbook uploads:
1. Install a parser such as SheetJS, ExcelJS, or read-excel-file.
2. Parse the file in app code.
3. Normalize workbook data into `columns` and `rows`.
4. Assign `grid.columns`, then `grid.source`.
See [Excel Import and Export Libraries](/guides/excel-export/excel-import-export-libraries/) for parser tradeoffs and examples.
### Feature-owned helpers
Feature-owned export helpers are still exported from Pro:
- Formula helper for exporting workbook formulas.
- Cell merge and grouped column transforms run automatically.
- Date, numeral, and select helpers are explicit utilities for optional column packages.
- `exportTransformers` are available on both `grid.exportExcel` and individual `plugin.export()` calls.
Provider contexts now include `cellRows`, `sourceRows`, `headerRowsCount`, `gridDerivedOptions`, and `exportMetadata`. Custom providers that need richer export output should consume those fields instead of reconstructing workbook state from `rows`.
When migrating custom providers, keep these rules in mind:
- Export data is prepared from data stores and grid source fallbacks, not rendered DOM rows.
- `sourceRows` preserves pinned segment semantics through `rowType` (`rowPinStart`, `rgRow`, `rowPinEnd`).
- `cellRows` includes generated headers and formatted provider cells; its data rows stay in pinned-top, body, pinned-bottom order.
- Missing pinned sources are empty. If a body data store is not initialized, export falls back to `grid.source`.
---
## Gantt Status is now calculated
The Gantt `Status` field now follows Microsoft Project-style reporting semantics and is read-only. Existing workflow values move to `workflowStatus`:
```ts
// Before
{ id: 'task-1', status: 'in-progress' }
// After
{ id: 'task-1', workflowStatus: 'in-progress' }
```
Legacy source and project JSON ingestion maps `status` to `workflowStatus` only when `workflowStatus` is absent. Serialization writes only `workflowStatus`. Remove code that edits the Status column and use the editable Workflow Status column or task-form field instead.
Set a project reporting date when deterministic results matter:
```ts
grid.gantt = {
...project,
statusDate: '2026-04-08',
};
```
When omitted, `statusDate` defaults to today. Excel export now contains separate `Status` and `Workflow Status` columns, and label overrides are split between `labels.status` and `labels.workflowStatus`.
---
## Versioning strategy
Pro and the standalone product packages are released with the **same version number** (lockstep semver).
| Change type | Version bump |
|---|---|
| Breaking API/behavior change | **Major** (`2.0.0 → 3.0.0`) |
| New plugin or feature (backward-compatible) | **Minor** (`2.0.0 → 2.1.0`) |
| Bug fix | **Patch** (`2.0.0 → 2.0.1`) |
The RevoGrid core package (`@revolist/revogrid`) versions independently — always check its
changelog when upgrading core alongside the Pro product packages.
## Pivot release migration
Pivot helps applications keep analytical meaning consistent while users drill, rank, sort, export and restore reports. It adds explicit execution policies and bounded failure paths so a rejected update can leave the last committed result usable.
:::note[Plan your integration]
Review [verified compatibility, workload limits and deployment responsibilities](/guides/pivot/compatibility/). The original 100,000-fact warm-refresh target was not met; million-fact and browser workload qualification remain incomplete.
:::
### Analytical behavior to verify
| Customer problem | Current behavior | Start here |
| --- | --- | --- |
| A total average differs from the displayed child averages | Each total aggregates its own original facts; explicit strict policies distinguish missing values from zero | [Analytical semantics](/guides/pivot/concepts/analytical-semantics/) |
| A formula needs a percentage rather than its underlying sum | Explicit raw/calculated references select the intended operand | [Calculations](/guides/pivot/concepts/analytical-semantics/#calculations-and-formula-references) |
| A long member list obscures the important groups | Contextual Top/Bottom ranks complete peers; Other reaggregates omitted facts | [Ranking and sorting](/guides/pivot/concepts/ranking-and-summary-sorting/) |
| Hierarchy order changes when paging | Canonical summary sorting scores complete prefixes against the opposite grand total before windows | [Supported sort scope](/guides/pivot/concepts/ranking-and-summary-sorting/#summary-sort-scope) |
| A large local update needs cancellation or refusal | Cooperative execution yields; runtime limits reject excess work and preserve the committed view | [Execution and limits](/guides/pivot/concepts/execution-and-limits/) |
| Exporting a server viewport loses groups | Complete export requests a bounded atomic export-time snapshot | [Exports](/guides/pivot/concepts/exporting-reports/) |
| Reports must survive reload without carrying secrets or callbacks | Portable reports retain analytical intent; the host restores runtime bindings and storage | [Persistence](/guides/pivot/concepts/report-persistence/) |
### Choose the right semantics
**Legacy local** uses `PivotPlugin`/`PivotConfig` and existing numeric coercion and clicked-header sorting. **Canonical/headless** uses portable reports and `createPivotController`; V1 preserves legacy policies, while V2 explicitly admits derived fields and declared aggregate operations. **Server execution** uses authorized adapters and their advertised capabilities. An execution pin prevents accidental local/server rebinding; it does not make arithmetic or null policies identical.
The shipped SDK provides controllers, plugin integration, remote contracts, validation and export writers. The executable Node/SQLite backend is repository reference code, excluded from the published package. Authentication, databases, deployment, durable storage and operational limits remain application responsibilities.
### Validate your integration
Follow [Getting started](/guides/pivot/getting-started/) for a grid and an executable headless example. Then explore [hierarchies and totals](/guides/pivot/concepts/analytical-semantics/#hierarchies-expansion-and-totals), [remote integration](/guides/pivot/server-side/adapters-and-security/) and the [runtime API tables](/guides/pivot/concepts/execution-and-limits/).
### Calculation policies and saved reports
For new local reports, use V2 numeric/scalar declarations from
`createPivotStrictAggregators` and persist `aggregationSemantics: 'local'`.
Numeric input skips null/missing values and fails invalid values by default;
scalar input keeps empty strings and typed identity. The helper returns V2
field declarations, not native callbacks. See the complete
[aggregation configuration](/guides/pivot/concepts/aggregations/).
For an existing saved report, retain its aggregate declarations and runtime
bindings while adding the pin for the actual execution owner. Do not silently
replace callbacks, change count meaning, discard invalid facts or convert missing
amounts to zero during configuration. Default numeric AVG includes null as zero;
a numeric preset skips null. `[10, null]` therefore has supported results 5 and
10 under those two different policies. Compare known KPIs before an intentional
policy change or engine switch.
This complete example adds a local pin to an existing V2 report without replacing
its default AVG declaration:
```ts
// docs-check: complete
import {
createPivotController, parsePivotReport, serializePivotReport,
type PivotReportV2,
} from '@revolist/pivot/headless';
const saved: PivotReportV2 = {
kind: 'revogrid:pivot.report', version: 2,
fields: [{ id: 'amount', source: { kind: 'physical', prop: 'amount' } }],
rows: [], columns: [], filters: [],
measures: [{ id: 'average', fieldId: 'amount', aggregator: 'avg' }],
totals: { grandTotal: true },
};
const savedJson = serializePivotReport(saved);
const current = parsePivotReport(savedJson, { version: 2 });
const pinned: PivotReportV2 = { ...current, aggregationSemantics: 'local' };
const controller = createPivotController({
reportVersion: 2, report: pinned,
source: [{ amount: 10 }, { amount: null }],
});
try {
const outcome = await controller.refresh();
if (outcome.status !== 'committed') throw new Error('Keep the accepted saved report');
console.table(controller.getSnapshot().view?.model.pinnedBottomSource);
// AVG remains 5. Review this known result before persisting the candidate.
const candidateJson = controller.exportReportJson();
console.log(candidateJson); // Application storage writes only after acceptance.
} finally {
controller.dispose();
}
```
V1 uses `parsePivotReport(json)` and its existing runtime registries. V2 requires
explicit V2 parsing/controller/plugin options; the V1 `savePivotReport`,
`loadPivotReport` and saved-report envelope helpers reject V2. Store V2 JSON
through application-owned storage. Use current matching readers and writers:
a closed schema without `aggregationSemantics` rejects a pinned report.
Plugin/controller preparation and compilation/migration reject a mismatched pin
before adapter loading. `local` versus `server` records the execution path,
**not identical results across engines**. Server mode, an adapter or a remote
store selects the server path during preparation. Direct local adapter/model and
cooperative-work entry points enforce pins against actual local execution before
reading facts. Standard remote load and full-export forwarding reject versioned
local operations and unknown descriptor properties; independent summary policies
are also rejected by server preparation. Backends independently validate policies. Test your actual
server's null, count, collation, totals and precision contract before changing
execution owner. See [Remote API](/guides/pivot/server-side/remote-api-reference/)
and [Report specifications](/guides/pivot/concepts/report-specifications/).
---
# Pagination Remote
URL: https://pro.rv-grid.com/guides/pagination-remote/
Source: src/content/docs/guides/pagination-remote.mdx
Description: Manage large datasets by breaking them into smaller, more manageable pages.
This example shows how to use pagination with remote data in the grid. To enable pagination you'll need to use the `PaginationPlugin`.
This plugin provides the necessary functionality to paginate data within the grid.
## Complete remote filter state
`loadData` keeps the original fourth argument for compatibility and passes the
complete advanced filter model as an optional fifth argument:
```ts
grid.pagination = {
itemsPerPage: 50,
initialPage: 0,
total: 0,
loadData: async (
skip,
take,
order,
singleConditionFilters,
multiConditionFilters,
) => {
return fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({
skip,
take,
order,
filters: multiConditionFilters,
}),
}).then(response => response.json());
},
};
```
Use `multiConditionFilters` for new integrations because it preserves every
condition and same-column relation. `singleConditionFilters` contains only the
first condition for each column.
```javascript
// fixing render for multiframework
```
---
# Pivot Overview
URL: https://pro.rv-grid.com/guides/pivot/
Source: src/content/docs/guides/pivot/index.mdx
Description: Learn the Pivot mental model, when to use it, and how to build your first RevoGrid Pivot.
:::tip[Explore Pivot]
Start with [Pivot migration guidance](/guides/migrations/#pivot-release-migration) and [Getting started](/guides/pivot/getting-started/). Review [compatibility, measured limits and deployment](/guides/pivot/compatibility/) before adopting Pivot.
Explore [hierarchies and calculations](/guides/pivot/concepts/analytical-semantics/), [ranking and summary sorting](/guides/pivot/concepts/ranking-and-summary-sorting/), [runtime limits](/guides/pivot/concepts/execution-and-limits/), [exports](/guides/pivot/concepts/exporting-reports/), [secure adapters](/guides/pivot/server-side/adapters-and-security/) and [report persistence](/guides/pivot/concepts/report-persistence/).
:::
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](/api/pivot/) and [Pivot Config API](/api/pivot-config/). Use this guide set for behavior, defaults, and how the parts work together.
## Choose Your Integration
| What you need | API to use | Guide |
| --- | --- | --- |
| A Pivot table in RevoGrid | `PivotPlugin` with `grid.pivot` | [Configuration](./concepts/configuration-reference/) |
| Your own constructor or toolbar for an installed Pivot | `pivotPlugin.getAuthoringController()` | [Custom integrations](./concepts/custom-integrations/) |
| Report calculation and editing without a grid | `createPivotController` from `@revolist/pivot/headless` | [Headless controller](./concepts/headless-controller/) |
| Charts linked to the table | `PivotChartsPlugin` from `@revolist/pivot` | [Charts](/guides/pivot-charts/) |
| Charts over your own data | Standalone dataset builder and chart controller from `@revolist/pivot/charts` | [Standalone charts](/guides/pivot-charts/#use-charts-without-a-grid) |
| Server aggregation and paging | An application-owned engine adapter or remote store | [Remote contract](./server-side/remote-api-reference/) |
Start with `PivotConfig` for grid setup. Use a portable report document for saved
analytical intent and keep runtime bindings separate. The installed plugin owns
its execution; use its authoring controller for a custom UI. A separately created
headless controller owns a separate report session.
Open the full demo and framework source.
## When To Use Pivot
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.
## Core Vocabulary
- `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.
## Mental Model
Client-side Pivot follows this shape:
```txt
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:
```txt
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.
## Conceptual Foundations
If you are new to Pivot tables or Business Intelligence (BI) concepts, we recommend starting with these conceptual guides:
- [What Is Pivot?](/guides/pivot/introduction/what-is-pivot/): The basic mental model of transforming flat data.
- [What is an OLAP Cube?](/guides/pivot/introduction/olap-cube-concepts/): Understanding multidimensional analysis and core operations.
- [Data Modeling for Pivot](/guides/pivot/introduction/data-modeling/): How to structure your facts and dimensions for the best results.
For new numeric reports, use the [explicit numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/#recommended-local-configuration). The introductory grid setup below uses a compiled numeric callback with the same skip-null/error-invalid policy. Default callbacks remain available with their documented coercion/count behavior.
## First Working Pivot
1. Install Pro and the standalone Pivot package.
```bash
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/pivot
```
2. Import the plugin and any aggregators you want to expose.
```ts
import {
PivotPlugin,
type PivotConfig,
} from '@revolist/pivot';
import { compileAggregateOperation } from '@revolist/revogrid-pro/aggregations';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/pivot/styles.css';
```
3. Register `PivotPlugin` on the grid.
```ts
grid.plugins = [PivotPlugin];
```
4. Define a `PivotConfig`.
```ts
const pivot: PivotConfig = {
aggregationSemantics: 'local',
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'sales',
name: 'Sales',
aggregators: {
sum: compileAggregateOperation({ builtin: 'sum', operation: {
version: 1, input: { type: 'finite-number' }, nulls: 'skip',
invalid: 'error', output: { type: 'finite-number' },
} }),
},
},
],
rows: ['region'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
};
```
5. Apply it through the direct `pivot` binding.
```ts
grid.pivot = pivot;
```
`additionalData.pivot` is also observed; bind `pivot` directly for this configuration.
## How To Read A PivotConfig
```ts
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.
## What RevoGrid Pivot Owns
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.
## Feature Map
- 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/pivot/src`, including Pivot core, Pivot Charts, and Pivot Config.
## Recommended Reading Order
1. [What Is Pivot?](/guides/pivot/introduction/what-is-pivot/)
2. [What is an OLAP Cube?](/guides/pivot/introduction/olap-cube-concepts/)
3. [Data Modeling for Pivot](/guides/pivot/introduction/data-modeling/)
4. [Dimensions](/guides/pivot/concepts/dimensions/)
5. [Aggregations](/guides/pivot/concepts/aggregations/)
6. [Configuration Reference](/guides/pivot/concepts/configuration-reference/)
7. [Totals](/guides/pivot/concepts/totals/)
8. [Values On Rows](/guides/pivot/concepts/values-on-rows/)
9. [Drill-Down](/guides/pivot/concepts/drilldown/)
10. [Sorting And Filtering](/guides/pivot/concepts/sorting-filtering/)
11. [Pivot Charts](/guides/pivot-charts/)
12. [Plugin Lifecycle](/guides/pivot/concepts/plugin-lifecycle/)
13. [Configurator](/guides/pivot/configurator/)
14. [Field Panel](/guides/pivot/concepts/field-panel/)
15. [Server-Side Pivot](/guides/pivot/server-side/)
16. [Caching And Cache Keys](/guides/pivot/server-side/caching/)
17. [OLAP Best Practices](/guides/pivot/server-side/olap-best-practices/)
---
# Pivot Charts
URL: https://pro.rv-grid.com/guides/pivot-charts/
Source: src/content/docs/guides/pivot-charts/index.mdx
Description: Turn a committed Pivot result into interactive, linked analytical charts with a built-in renderer or a custom headless integration.
Pivot Charts turn the current Pivot result into an interactive analytical
view. Pivot includes the renderer-neutral chart lifecycle, a first-party
SVG renderer, a searchable chart gallery, semantic drill-down, a current-frame
data table, persistence, export, localization, and server-side contracts. No
separate charting dependency is required.
`PivotChartsPlugin` owns chart data, lifecycle, persistence, drill,
synchronization, and rendering. The optional `PivotChartsUiPlugin` adds the
built-in controls for embedded workspaces, the modeless dialog, and the
**Pivot Chart…** context-menu action. Applications that only need a chart or
their own controls can install only the headless plugin.
Pivot Charts consume the report's committed aggregate values. Configure numeric/scalar eligibility, counts and execution pins in the [Pivot aggregation contract](/guides/pivot/concepts/aggregations/). Chart normalization, formatting or a shared dataset shape does not establish identical local/server results or increase arithmetic precision. A remote provider must supply complete aggregates under its backend policy, not averages or distinct totals reconstructed from a visible page.
## Choose a guide
| Guide | What it covers |
| --- | --- |
| [Chart types and compatibility](./chart-types/) | Built-in chart types, required data channels, recommendations, and compatibility rules. |
| [Data and bindings](./data-and-bindings/) | Analytical frames, series and channel selection, totals, linking, data view, and limits. |
| [Drill-down and interactions](./drilldown-and-interactions/) | Cell-aware creation, breadcrumbs, synchronization modes, tooltips, legends, keyboard behavior, and events. |
| [Analysis tools](/guides/charts/analysis/#use-the-same-tools-in-pivot) | Shared calculations, confidence/prediction intervals, comparisons, facets and source-record access. |
| [Customization and export](./customization-and-export/) | Presentation, chart options, renderer styling, localization, PNG/PDF, and image encoders. |
| [Headless API and persistence](./headless-api-and-persistence/) | Plugin methods, `PivotChartRef`, model restore, custom renderers, and lifecycle events. |
| [Server data and performance](./server-data-and-performance/) | Complete-data providers, semantic scopes, cancellation, validation, dense data, and geographic capabilities. |
## Register Pivot Charts
For a chart-only integration, register the Pivot and headless chart plugins:
```ts
import {
PivotChartsPlugin,
PivotPlugin,
createPivotChartsRenderer,
} from '@revolist/pivot';
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
};
grid.plugins = [PivotPlugin, PivotChartsPlugin];
```
`grid.pivotCharts` is observable. Replacing the configuration updates locale
text and defaults, invalidates cached chart data, and refreshes linked charts.
This is where renderer options, a remote data provider, defaults, limits,
localization, and drill synchronization belong. The application container is
supplied later, when it exists.
## Use charts without a grid
For new chart-only applications, use the independent `@revolist/charts` package.
See the [Standalone Charts guide and live example](/guides/charts/).
`@revolist/pivot/charts` provides a standalone controller for an ordinary DOM
container. It creates no ``, Pivot plugin, source query or editor.
```ts
import { createStandalonePivotChart, createStandalonePivotChartDataset } from '@revolist/pivot/charts';
import '@revolist/pivot/styles.css';
const container = document.createElement('div');
container.style.cssText = 'width: 640px; height: 420px';
document.body.append(container);
const dataset = createStandalonePivotChartDataset(
[{ month: 'January', revenue: 120 }, { month: 'February', revenue: null }],
{ category: { field: 'month' }, series: [{ field: 'revenue', label: 'Revenue' }] },
);
const chart = await createStandalonePivotChart({ container, dataset, chartType: 'line' });
await chart.update({ presentation: { title: 'Monthly revenue' } });
const csv = chart.getCSV();
const saved = JSON.stringify(chart.model);
chart.destroy(); // Call when the application removes the host.
const restored = await createStandalonePivotChart({ container, model: JSON.parse(saved) });
restored.destroy();
```
The builder maps each row to one category and each explicit numeric field to one
series. Values must be finite numbers or explicit `null`. Typed category keys
must be unique; duplicate keys are rejected, never aggregated. Its `identity`
measure metadata is descriptive. Use the headless Pivot controller when source
aggregation is needed.
`category.label` names a source field containing each row's string display
label; `series.label` supplies a literal series caption. For example, this
keeps `'jan'` as the category identity while displaying `'January'`:
```ts
const labeled = createStandalonePivotChartDataset(
[{ month: 'jan', monthName: 'January', revenue: 120 }],
{ category: { field: 'month', label: 'monthName' }, series: [{ field: 'revenue', label: 'Revenue' }] },
);
```
Omit `category.label` to display the category key. When provided, the named
field must contain a string in every row. Rename the data-table category header
with `localeText.dataTable.categoryHeader`.
The controller owns data/settings snapshots. Updates run in order and capture
caller patches immediately. `undefined` preserves an owner; `{}` resets it;
arrays replace. Presentation is a replacement owner, so include titles you want
to retain. Dataset replacement requires a complete canonical dataset. Save an
unlinked `chart.model` and supply `model` instead of dataset/settings to restore.
A creation `signal` cancels pending creation and disposes the chart on later
abort. `destroy()` is idempotent; other operations reject after disposal.
`interaction.mode: 'select'` commits local selection; `'event'` calls
`onSelectionRequest` without committing. Applications commit with `setSelection`
or `clearSelection`. Pivot drill/filter/query and peer synchronization require
the grid plugin and are rejected here. Native navigation and legend visibility
callbacks update controller state. Geographic charts retain their separate
provider pack API, without controller/export parity.
The creation-time renderer is fixed. SVG is the default; an injected ECharts
renderer supports its documented three types and PNG, plus controller CSV.
`capabilities` intersects advertised export support with actual methods. Export
operations share the update queue and capture the committed state when their
turn begins. `onBeforeDownload` is synchronous; returning `false` cancels. Hooks
receive owned snapshots. Custom renderer failures trigger an update rollback;
if both fail the promise reports an `AggregateError` rather than claiming the
old scene survived. Optional methods fail clearly when unavailable.
The low-level renderer remains available for applications owning their own
state. Its `update` takes a complete render input, with caller-owned mutable data;
use fresh inputs and callbacks deliberately. The chart facade preserves root
module identities and does not promise a dependency-isolated bundle or SSR
rendering. DOM-free imports and data building are supported.
The chart entrypoint also exports the existing types used by these integrations:
`PivotChartCsvOptions` and `PivotChartImageOptions` for reusable export settings,
`PivotChartNodeClick` for callbacks, renderer theme/label/tooltip/PDF types, and
the ECharts, vector-PDF, and geographic provider contracts. Import them with
`import type` from `@revolist/pivot/charts`; the root package exports the same
types.
## Render a chart outside the Pivot grid
This grid-linked API still requires a RevoGrid and Pivot plugins, even though
the chart container is outside the grid.
`mountPivotChart()` renders into any application-owned `HTMLElement`.
| Experience | Plugins | Mount option |
| --- | --- | --- |
| Chart only | `PivotPlugin` + `PivotChartsPlugin` | Omit `controls` |
| Chart with your own controls | `PivotPlugin` + `PivotChartsPlugin` | Omit `controls`, then call `chart.update()` |
| Chart with built-in controls | Add `PivotChartsUiPlugin` | `controls: 'standard'` |
All three render in your container. None creates a popup.
### Chart only
```html
```
```ts
import {
PivotChartsPlugin,
PivotPlugin,
createPivotChartsRenderer,
mountPivotChart,
} from '@revolist/pivot';
const grid = document.querySelector('#pivot')!;
grid.pivotCharts = { renderer: createPivotChartsRenderer() };
grid.plugins = [PivotPlugin, PivotChartsPlugin];
const chart = await mountPivotChart(grid, {
container: document.querySelector('#sales-chart')!,
presentation: { title: 'Sales by region' },
});
```
That is the complete integration. The chart defaults to grouped columns and
stays linked to the committed Pivot result.
### Chart with built-in controls
Add `PivotChartsUiPlugin`, then request the standard controls at mount time:
```ts
import {
PivotChartsPlugin,
PivotChartsUiPlugin,
PivotPlugin,
mountPivotChart,
} from '@revolist/pivot';
grid.plugins = [PivotPlugin, PivotChartsPlugin, PivotChartsUiPlugin];
const chart = await mountPivotChart(grid, {
container: document.querySelector('#chart-workspace')!,
controls: 'standard',
});
```
This embeds the same chart gallery, Data and Customize tabs, drill
breadcrumbs, current-frame table, and export actions used by the standard
dialog. The application still owns the outer container.
### Chart with your own controls
Use the chart-only mount and connect only the controls your product needs:
```ts
const chart = await mountPivotChart(grid, { container });
typeSelect.addEventListener('change', () => {
void chart.update({
chartType: typeSelect.value as PivotChartType,
});
});
```
Use `chart.update()` for selected series, bindings, totals, link state,
presentation, theme, or chart-specific options. You do not need the UI plugin
for application-built controls.
## Mount after the DOM exists
The container must exist when `mountPivotChart()` runs. In Vue, React, and
other component frameworks, mount the chart in the framework's mounted effect
and destroy it during cleanup.
```vue
```
Use the equivalent mounted effect for React. In an Astro portal, call
`mountPivotChart()` immediately after the portal target has been appended.
An existing container may start hidden and become visible later: the built-in
renderer uses `ResizeObserver` and redraws when its size changes. Give it a
non-zero height when visible. Do not place a container, selector, or ref in
`grid.pivotCharts` or `grid.pivotChartsUi`.
## Open the modeless dialog
`PivotChartsUiPlugin.openChart()` remains available for an application toolbar
or the **Pivot Chart…** context-menu action. A grid owns at most one open
dialog, but it may also own any number of embedded charts.
Right-clicking a Pivot analytical cell creates a cell-aware initial scope:
- a group value opens its immediate children;
- a leaf value opens its siblings under the same parent;
- subtotal and grand-total cells choose the nearest valid breakdown;
- the clicked column path and measure become the initial series;
- values-on-rows preserve the clicked row measure.
The scope comes from committed Pivot row and column metadata, not DOM text or
a stale grid selection.
## What stays synchronized
Linked charts refresh after committed Pivot changes, filtering, sorting,
group expansion, source replacement, and theme changes. Unlinking stores a
bounded normalized dataset in the chart model; the snapshot then remains
stable until the chart is relinked.
Use the **Data** tab to choose totals, relationship bindings, drill
synchronization, and link state. Use **Customize** for presentation and
chart-specific options. **View data** switches the plot to the accessible
table for the current drill frame without mutating the Pivot or chart model.
## Demo and API
- Open the [live Pivot demo](/demo/pivot/) for the complete analytical
workspace. The screenshot states in this guide come from the registered
**Pivot Fields Panel** example, which also runs in all four framework
wrappers.
- Use the [Pivot Charts API reference](/api/pivot-charts/) for exact generated
interfaces and signatures.
- Continue with [Chart types and compatibility](./chart-types/).
---
# Chart Types and Compatibility
URL: https://pro.rv-grid.com/guides/pivot-charts/chart-types/
Source: src/content/docs/guides/pivot-charts/chart-types.mdx
Description: Choose among all 68 built-in Pivot chart types and understand their data-channel and compatibility requirements.
The built-in catalog is defined by `PIVOT_CHART_DEFINITIONS`. Each definition
owns its stable type ID, family, data view, required semantic channels,
constraints, interactions, presentation capabilities, preview dataset, option
schema, renderer, localized labels, and documentation slug.
The standard dialog evaluates every definition against the current dataset
before selection. Each chart type appears once in its family. Incompatible
cards stay visible and explain the missing channel or semantic requirement
instead of failing after selection. Every gallery card uses the real renderer.
Pivot Charts consume the report's committed aggregate values. Configure numeric/scalar eligibility, counts and execution pins in the [Pivot aggregation contract](/guides/pivot/concepts/aggregations/). Chart normalization, formatting or a shared dataset shape does not establish identical local/server results or increase arithmetic precision. A remote provider must supply complete aggregates under its backend policy, not averages or distinct totals reconstructed from a visible page.
## Optional data labels
`getPivotChartDefinition(type).interactions.labels` advertises support for optional
`presentation.dataLabels` text. The Customize checkbox is available only when
this capability is true. Switching to a chart without optional labels clears
`dataLabels: true` while preserving other compatible presentation settings.
Unsupported chart types reject `dataLabels: true` with `INVALID_DATA_SHAPE` in
the model parser, plugin, standalone API, and native renderer. `false` and omitted
values remain valid. This also applies to saved models: clear an unsupported
`true` value before restoring an older model; restoration does not silently
rewrite it. Intrinsic category labels and primary KPI values remain visible
independently of optional data labels. Supported labels remain subject to the
chart's density and available-space policies.
Customize the rejection text with
`localeText.renderer.dataLabelsUnsupported(chartType)`. When parsing a model
directly, pass a resolved locale as the optional second argument to
`parsePivotChartModel(model, createPivotChartsLocaleText(overrides))`; locale
callbacks are never persisted inside the model. Direct renderer calls also reject non-boolean
`dataLabels` values; customize that diagnostic with
`localeText.renderer.invalidDataLabels()`. Model parsing retains its structural
validation error for malformed presentation values.
## Gallery behavior
Select **Open chart workspace** to browse chart types and edit data or appearance
with room for both the chart and its controls. Your changes stay in the embedded
preview when you close the workspace.
- Search matches localized type names and descriptions.
- Categories follow the catalog family and order.
- Arrow keys move among cards, while Enter and Space
select a compatible type.
- Preview charts are decorative, tooltip-free renderings of catalog fixtures.
- Near-viewport mounting defers preview work. SVG snapshots are cached by type,
renderer, theme, and locale; Canvas previews recreate their live engine when
remounted because cloning a canvas does not preserve its bitmap.
- Lazy mounting does not cap the number of active previews after scrolling
through the entire gallery.
- Recommended cards carry a badge within their family.
- A missing-field card offers **Configure data** when the chart query editor
can resolve the requirement using configured measures. Otherwise its reason
directs you to the Pivot configuration or data provider. Unsupported renderer
cards explain the backend limitation.
- A chart type is validated again by the headless plugin, so a custom picker
cannot bypass compatibility rules.
## Comparison charts
| Type | Best for | Required data and constraints |
| --- | --- | --- |
| `groupedColumn` | Comparing multiple series across categories. | Category, value, and one or more series; signed values are supported. |
| `stackedColumn` | Comparing totals and their series contribution. | Category, value, and series; stacks signed values. |
| `normalizedColumn` | Comparing percentage composition across categories. | Category, value, and series; normalizes each category to 100%. |
| `rangeColumn` | Showing vertical low-to-high intervals. | Explicit low/high groups, with a shared channel or matched parallel channels; each high measure owns its axes. |
| `lollipop` | A lighter single-series category ranking. | Category, value, and exactly one selected series. |
| `groupedBar` | Category comparison when labels are long. | Category, value, and series; signed values are supported. |
| `stackedBar` | Horizontal totals with composition. | Category, value, and series. |
| `normalizedBar` | Horizontal percentage composition. | Category, value, and series; normalizes to 100%. |
| `rangeBar` | Horizontal low-to-high intervals. | Explicit low/high groups with shared or matched parallel channels. |
## Trend and area charts
| Type | Best for | Required data and constraints |
| --- | --- | --- |
| `line` | Trends across ordered categories. | Category, value, and series. |
| `smoothLine` | Visually continuous trends. | Same channels as `line`; smoothing changes geometry, not data. |
| `steppedLine` | State or price changes that hold until the next category. | Category, value, and series. |
| `smallMultiples` | Comparing many trends without overlapping lines. | Category and value; up to 12 visible panels, with fewer in smaller containers. Omitted panels remain in canonical data and CSV. |
| `annotatedTimeSeries` | Trends with emphasized analytical points. | Category, value, and series; date-like categories are recommended. |
| `area` | Trend magnitude with filled areas. | Category, value, and series. |
| `steppedArea` | Stepwise magnitude changes. | Category, value, and series. |
| `stackedArea` | Total trend plus series contribution. | Category, value, and series. |
| `normalizedArea` | Percentage composition over an ordered axis. | Category, value, and series; normalizes to 100%. |
| `rangeArea` | Low/high bands over categories. | Explicit low/high groups with shared or matched parallel channels. |
| `confidenceBand` | Uncertainty envelopes around trends. | Explicit lower/upper groups through `low` and `high`, with shared or matched parallel channels. |
## Part-to-whole charts
All part-to-whole types require non-negative values and select the first
available series when more than one series exists.
| Type | Best for | Required data |
| --- | --- | --- |
| `pie` | A small set of category shares. | Category, value, and exactly one series. |
| `donut` | Shares with a central visual summary area. | Category, value, and exactly one series. |
| `semiDonut` | A compact half-ring composition. | Category, value, and exactly one series. |
| `waffle` | Discrete-looking proportions in a 100-cell grid. | Category, value, and exactly one series. |
## Relationship charts
| Type | Best for | Required data |
| --- | --- | --- |
| `scatter` | Correlation between measures. | Explicit X/Y groups with single-channel broadcast or matched parallel bindings; incomplete points are omitted. |
| `bubble` | Correlation with a third magnitude. | Explicit X/Y/size groups with broadcast or matched parallel bindings; sizes must be non-negative. |
| `quadrant` | Positioning observations against two authored thresholds. | Explicit X/Y groups; optional bubble sizing and numeric axis/split configuration. |
| `densityScatter` | Dense relationships where individual points overlap. | One `x` and one `y`; dense geometry is aggregated for rendering and accessibility. |
| `connectedScatter` | Showing the path or sequence between X/Y observations. | One `x` and one `y`; category order defines the connection order. |
The Data panel infers distinct X/Y bindings when possible. A measure already
used by one required relationship channel is disabled for the other.
## Polar charts
Polar charts require non-negative values.
| Type | Best for | Required data |
| --- | --- | --- |
| `rose` | Comparing magnitudes by angle and radius. | Category, value, and series. |
| `radarLine` | Comparing multivariate profiles. | At least three categories, non-negative values, and series. |
| `radarArea` | Comparing filled multivariate profiles. | At least three categories, non-negative values, and series. |
| `polarColumn` | Radial category comparison. | Category, value, and series. |
| `radialColumn` | Rectangular radial columns for category comparison. | Non-negative category/value data and one or more series. |
| `stackedPolarColumn` | Radial totals and composition. | Category, value, and series. |
| `normalizedPolarColumn` | Radial percentage composition. | Category, value, and series. |
| `radialBar` | A single-series circular progress comparison. | Category, value, and exactly one series. |
| `multiRingRadialBar` | Comparing several series as concentric rings. | Category, value, and series. |
## Distribution and statistical charts
| Type | Best for | Required data |
| --- | --- | --- |
| `histogram` | Numeric sample distributions with count, sum or mean metrics. | First measure by default, or explicit sample collections through X/value bindings; complete global bins and loaded raw rows retain distinct semantics. |
| `stripDot` | Showing individual observations along a value axis. | Category/value input and one series. |
| `boxPlot` | Median, quartiles, spread, and outliers. | Original numeric samples or a typed precomputed summary for selected series. |
| `violin` | Distribution shape and density. | Original numeric samples, or a typed summary with density, for selected series. |
| `errorInterval` | Actual values with lower and upper error bounds. | One `actual`, one `low`, and one `high` measure. |
| `rangeInterval` | Comparing statistical intervals within assigned value-axis groups. | Category and value with two or more series. |
## Hierarchy charts
Treemap, sunburst, icicle and circle packing require row-hierarchy metadata
and select one non-negative weight series. Organization cards use their own
parent-ID or dimension-path hierarchy contract and can omit numeric weights.
| Type | Best for | Required data |
| --- | --- | --- |
| `treemap` | Space-efficient nested proportions. | `hierarchy` plus one `weight` measure. |
| `sunburst` | Concentric hierarchy levels. | `hierarchy` plus one `weight` measure. |
| `icicle` | Depth-oriented rectangular hierarchy. | `hierarchy` plus one `weight` measure. |
| `circlePacking` | Nested hierarchy represented by packed circles. | `hierarchy` plus one `weight` measure. |
| `organization` | Parent/child reporting structures with cards and connectors. | Stable category/parent-ID or dimension-path hierarchy; numeric weights are optional. Missing parents and cycles reject. |
Treemap includes a category legend so names remain identifiable when a tile is
too narrow for its header. Legend colors match the tiles, including custom
renderer palettes. Use `presentation.legendPosition` to position it
or set `'none'` to hide it, and the renderer's `labels` API to translate or style
legend text. Very compact or dense legends retain the shared overflow indicator;
each tile still exposes its full name through hover and keyboard focus.
## Matrix and specialized charts
| Type | Best for | Required data |
| --- | --- | --- |
| `heatmap` | Comparing magnitude across category-by-series cells. | Category, value, and series. |
| `calendarHeatmap` | Activity intensity by date. | A date-semantic category, value, and exactly one series. |
| `waterfall` | Sequential positive and negative contribution to a result. | Category, value, and exactly one series. |
| `timelineRange` | Start/end intervals by category. | Exactly one `start` and one `end` measure. |
## Process, flow, and financial charts
| Type | Best for | Required data |
| --- | --- | --- |
| `funnel` | Stage-by-stage conversion or attrition. | Non-negative category/value data and one series. |
| `coneFunnel` | Proportional stage widths connected by tapered segments. | Non-negative category/value data and one selected series. |
| `pyramid` | Hierarchical-looking stage magnitude. | Non-negative category/value data and one series. |
| `sankey` | Weighted directional flows. | `source`, `target`, and non-negative `weight` channels. |
| `chord` | Weighted relationships in a circular flow matrix. | `source`, `target`, and non-negative `weight` channels. |
| `candlestick` | Open/high/low/close price movement. | One or more explicit OHLC groups bound to `open`, `high`, `low`, and `close`; each close field owns a group. |
| `ohlc` | Compact open/high/low/close price ticks. | One or more explicit OHLC groups with distinct close owners. |
| `volumePrice` | OHLC price movement plus traded volume. | One or more paired OHLC/volume groups with distinct close and volume owners. |
## Combination and KPI charts
| Type | Best for | Required data |
| --- | --- | --- |
| `columnLine` | Comparing measures that benefit from column and line geometry. | Category/value data with at least two series. |
| `areaLine` | Comparing a filled magnitude with a trend. | Category/value data with at least two series. |
| `bullet` | Actual versus target in a compact linear view. | One `actual` and one `target` measure. |
| `gauge` | Actual versus target on a dial. | One `actual` and one `target` measure. |
| `kpi` | A headline value with target and optional variance. | One `actual`, one `target`, and optionally one `variance` measure. |
| `progressGauge` | Progress toward a target. | One `actual` and one `target` measure. |
| `linearGauge` | Actual versus target on a horizontal or vertical track. | One `actual` and one `target` measure; authored limits and segmentation are optional. |
Both combination types also accept explicit bubble and histogram layers. See [Cartesian sample layers](/guides/charts/tools/cartesian-sample-layers/) for channel ownership, category/sample X identity, named axes, layer order and complete-bin semantics. This does not enable arbitrary chart-family composition.
## Compatibility rules
`chartOptions.showGridLines` controls Cartesian grid lines and the cell
separators in `heatmap` and `calendarHeatmap`. Matrix charts keep their existing
separators by default; `false` draws contiguous cells without separator strokes.
Sankey and chord charts have no grid and no longer advertise the previously
ignored option. Remove `showGridLines` from older saved flow-chart
`chartOptions`, or reset that owner to `{}`, before restoring them;
the strict option parser rejects keys outside the active chart's schema.
`evaluatePivotChartCatalog` and `evaluatePivotChartCompatibility` return
`recommended`, `compatible`, `needsFields`, or `unsupported` results with
structured reasons and an optional `inferredPatch`.
Compatibility checks cover:
- renderer support for the requested type;
- minimum and maximum channel counts;
- distinct required bindings such as X and Y or low and high;
- non-negative value constraints;
- hierarchy and semantic date requirements;
- actual statistical distributions or eligible native source samples.
The headless plugin separately validates category, series, and point limits,
authored options, and the renderer's complete candidate input before committing.
When a compatible relationship chart is missing explicit bindings, the
catalog may return an inferred binding patch. Persisted models still retain
the resolved explicit bindings.
Recommendations use supplied measure identities, rather than guessing units
from captions or numeric magnitudes. Distinct measures favor small multiples.
Combination charts receive a mixed-unit preference only when different units
and primary/secondary series assignments are already authored. Recommendations
do not invent those assignments or change the chart automatically.
## Optional geographic pack
`createPivotChartGeographicCapabilityPack(provider)` creates a standalone host
for provider-owned `choropleth`, `proportionalSymbolMap`, and
`geographicHeatmap` views. Render these explicitly with `pack.render(...)`.
They are intentionally outside `PIVOT_CHART_TYPES` and the standard gallery.
The provider owns map assets, projected geometry, and licensing; Pivot
does not bundle a map library or geographic data.
[Previous: Pivot Charts overview](../) ·
[Next: Data and bindings](../data-and-bindings/) ·
[API reference](/api/pivot-charts/)
---
# Customization and Export
URL: https://pro.rv-grid.com/guides/pivot-charts/customization-and-export/
Source: src/content/docs/guides/pivot-charts/customization-and-export.mdx
Description: Customize chart presentation, semantic labels and tooltips, themes, localization, and PNG, SVG, CSV, and PDF export.
Pivot Charts separate portable model presentation from renderer-wide styling.
Use `presentation` and `chartOptions` when the choice must persist with one
chart. Configure `createPivotChartsRenderer` when an application needs a
shared palette, typography, tooltip, or export pipeline.
The Data and Customize inspectors group related controls into collapsible
sections. Data starts with ordered row and column fields and checkbox measure
selection; sorting, ranking, member filters, and the Pivot connection are
separate sections. Valid query edits update automatically. Customize
starts with labels and legend, with appearance, axes, colors, navigation,
interaction, and references available in their own sections. Collapsing a
section preserves its draft edits. Switching tabs also preserves Customize
drafts and expanded sections. Presentation, colors, annotations and query edits
update automatically. Incomplete numeric or reference edits stay local with
validation feedback until valid. Color controls use the shared Pro palette and
native color chooser, without a free-text color field. **Automatic** restores
the default color where supported. Reset colors preserves combination layer
and axis assignments, including changes still being processed.
The picker captions and swatch names are customizable through
`localeText.colors.picker.customColor` and `localeText.colors.picker.colorLabels`.
Each existing color control policy continues to hide or disable its picker.
Apply policy and translation keys are accepted;
the inspector no longer renders those actions.
Rename section titles with `localeText.inspector`. Hide or disable a whole
section with `pivotChartsUi.controls['inspector.']`; existing leaf
control policies continue to apply. Section IDs are `source`, `fields`, `sort`,
`filters`, `connection`, `presentation`, `appearance`, `axes`, `colors`,
`navigation`, `interaction`, and `annotations`.
```ts
grid.pivotChartsUi = {
localeText: {
inspector: { fields: 'Fields to compare', annotations: 'Notes and targets' },
},
controls: {
'inspector.navigation': 'hidden',
'inspector.axes': 'disabled',
},
};
```
Existing `chartOptions.heading`, `colors.heading`, `annotations.heading`,
`navigation.heading`, and `interaction.heading` overrides remain section-title
fallbacks. An explicit `inspector` title takes precedence.
Pivot Charts consume the report's committed aggregate values. Configure numeric/scalar eligibility, counts and execution pins in the [Pivot aggregation contract](/guides/pivot/concepts/aggregations/). Chart normalization, formatting or a shared dataset shape does not establish identical local/server results or increase arithmetic precision. A remote provider must supply complete aggregates under its backend policy, not averages or distinct totals reconstructed from a visible page.
## Portable presentation
```ts
await chart.update({
presentation: {
title: 'Quarterly sales',
subtitle: 'Committed orders only',
legendPosition: 'bottom',
dataLabels: true,
xAxisTitle: 'Region',
yAxisTitle: 'Revenue',
},
theme: 'material',
});
```
`legendPosition` accepts `top`, `right`, `bottom`, `left`, or `none`.
`theme` accepts `auto` or a renderer-supported theme string. The standard UI
offers follow-grid, default, material, compact, dark material, and dark compact
choices, plus high contrast and dark high contrast.
## Chart-specific options
The catalog generates a typed option schema for every chart:
| Option | Availability | Valid values |
| --- | --- | --- |
| `showGridLines` | Charts with axes. | Boolean. |
| `markOpacity` | Every built-in type. | `0.2`–`1` in `0.05` steps. |
| `strokeWidth` | Line, area, combination, radar line, and radar area. | `1`–`6` in `0.5` steps. |
| `fillOpacity` | Area-family, area-line, radar area, confidence band, and range area. | `0.1`–`0.9` in `0.01` steps. |
| `facetScaleMode` | Small multiples. | `independent` (default) or `shared`. |
```ts
await chart.update({
chartOptions: {
showGridLines: false,
markOpacity: 0.9,
strokeWidth: 2.5,
},
});
```
Unsupported keys, out-of-range values, and values that do not align with the
declared step are rejected during update and restore.
Small multiples use independent value domains by default. This emphasizes each
series' trend; equal panel heights can represent very different values. Choose
shared scales when comparing magnitudes:
```ts
await chart.update({
chartType: 'smallMultiples',
chartOptions: { facetScaleMode: 'shared' },
});
```
Panels show their scale mode and value ticks. Shared domains use the complete
visible series data, including values omitted by path sampling or outside the
current viewport. Independent domains use each series' complete values.
Changing visibility can change these domains. Reference lines and notes follow
the same panel scales as the plotted data.
## Semantic colors and visibility
Portable `colors` settings complement the renderer palette. The default
`paletteMode: 'index'` preserves palette order; opt into `'semantic'` to assign
colors by measure and field-qualified member identity across reordering and
refresh. Labels do not determine identity. Semantic mode requires field metadata
for nonempty member paths; providers supply it through hierarchy definitions
or selection fields. Missing metadata is rejected instead of reverting to an
unstable index color.
Configured measure IDs keep semantic colors stable when measures are reordered.
Without a configured ID, measure identity includes its original configured
index to distinguish repeated measures; reordering that configuration can
therefore change its palette assignment.
```ts
await chart.update({
colors: {
paletteMode: 'semantic',
measures: [{
measure: { prop: 'sales', aggregator: 'sum', valueIndex: 0 },
color: '#2563eb',
}],
members: [{
axis: 'row',
path: [{ field: 'region', value: 'North' }],
color: '#f97316',
pattern: 'diagonal',
}],
},
});
```
Member overrides match the exact field-qualified path; they do not implicitly
color descendants or synthetic Other members with a matching label. Authored
colors accept `#RGB` or `#RRGGBB`. Unavailable identities remain dormant so
styling can return when a member or series reappears. When measure selectors
overlap, the first matching measure-color entry wins. Duplicate exact member
paths or measure selectors are rejected.
Supported discrete marks can map their analytical value to color:
```ts
await chart.update({
colors: {
valueMap: {
type: 'diverging',
min: -100,
midpoint: 0,
max: 100,
colors: ['#2563eb', '#ffffff', '#dc2626'],
},
},
});
```
`continuous` maps between two colors. `conditional` applies the first matching
numeric `gt`, `lt`, `equal`, or inclusive `between` rule, with an optional
fallback color. Mappings use analytical values, preserve missing observations,
and clamp outside explicit domains. Automatic domains use the visible series'
full data, independently of viewport sampling.
Numeric mappings display a separate color key even when the ordinary legend is
hidden. Gradient keys show their domain; conditional keys show a bounded set of
swatches and the number of additional rules. The Customize controls retain the
complete rule list.
Value mapping takes precedence over explicit series/member colors, followed
by measure colors and the palette. Mapping is supported for grouped bar and
column, heatmap/calendar heatmap, and pie/donut/semi-donut charts. Line colors
represent series; numeric mapping of an entire line is unsupported. Aggregated
heatmap buckets cannot receive canonical member or value mappings.
For independently visible series, `seriesOverrides` can persist `color` and
`visible`. Line-bearing charts also support `lineDash` and `marker`:
```ts
await chart.update({
seriesOverrides: {
[forecastSeriesId]: {
color: '#475569',
visible: true,
lineDash: 'dash',
marker: 'diamond',
},
},
presentation: { legendAction: 'toggleVisibility' },
});
```
Visibility removes a series from plotted geometry, scales, stacks, tooltips,
and rendered exports while retaining the canonical chart dataset. Hidden
series remain available in the legend and Customize controls. Hiding every
series leaves a recoverable empty plot. Appearance and visibility changes do
not request new provider data or change the shared Pivot.
Series visibility is supported by bar/column and line/area families,
combination charts, annotated time series, and small multiples. Required
relationship or financial channels cannot be hidden as independent series.
Unsupported authored settings are rejected according to chart capabilities.
`colors` and `seriesOverrides` updates replace their whole authored objects;
omit them to preserve settings or pass `{}` to reset. Custom renderers must
opt into the new appearance and visibility capabilities.
## Reference lines, bands, and notes
Persist thresholds and notes in `annotations`. References use logical axes:
`category`, `primary`, or `secondary`. On horizontal bars, the primary value
axis runs horizontally. Secondary references require a combination chart.
```ts
await chart.update({
annotations: {
references: [
{
id: 'target',
kind: 'line',
axis: 'primary',
value: 2500,
label: 'Sales target',
color: '#dc2626',
lineDash: 'dash',
},
{
id: 'expected-range',
kind: 'band',
axis: 'primary',
min: 1000,
max: 2000,
color: '#2563eb',
opacity: 0.2,
},
],
notes: [{
id: 'launch',
target: {
row: [{ field: 'region', value: 'North' }],
column: [],
measure: { prop: 'sales', aggregator: 'sum' },
},
text: 'New product launch',
placement: 'above',
}],
},
});
```
Category lines use `at`; category bands use `from` and `to`. Each coordinate is
either `{ scale: 'category', path: [{ field, value }], measure? }` or
`{ scale: 'time', value: utcEpochMilliseconds }`. Time coordinates require an
actual time axis. Category bands span the centers of their semantic endpoints
in the current order. They do not create extra category buckets.
References use the existing plot domain: off-domain lines disappear and bands
are clipped. They never stretch the scale. Numeric bounds must be finite and
ordered; references on logarithmic axes must be positive. In small multiples,
a primary reference applies the same authored value to each panel's own scale.
Notes match exact field-qualified row and column paths plus a measure, rather
than labels or array indexes. Use a configured measure ID to distinguish repeated
measures; an explicit `valueIndex` further constrains the match. Group and
collapsed aggregate points can have notes. Subtotals, grand totals, synthetic
Other members, and ambiguous targets are unavailable.
A missing, hidden, null, off-screen, or omitted-panel target keeps its authored
note dormant.
When that exact observation returns, the note follows its current value.
Drilling to children never reassigns a parent's note. Nonempty paths require
provider field metadata. Notes can anchor observations omitted by visual
sampling, and do not intercept chart selection or drill actions.
The Customize tab applies valid edits automatically and provides Clear controls for editing
references and notes. Annotation edits preserve provider data and survive
refresh, unlink, and JSON restoration. Pass `annotations: {}` to clear them;
omit the field to preserve them. Updates replace the whole annotation object.
Each array accepts up to 32 entries, IDs up to 128 characters, reference labels
up to 256 characters, and nonblank note text up to 512 characters.
Supported families are grouped bar/column, unstacked line/area variants,
combination charts, annotated time series, and small multiples. Unsupported
families reject authored annotations. Custom renderers opt in through
`capabilities.annotations`. Existing automatic extrema labels are independent
of authored notes. Rendered references and notes follow the chart's label,
theme, and image/document export pipeline. The label formatter receives
`annotationId` and `annotationKind` (`reference` or `note`) for authored labels.
Notes retain their canonical datum context; numeric reference labels expose
the threshold value without pretending to be a data observation. Long labels
wrap within the panel, with complete text retained in accessible titles.
## Renderer styling
```ts
const renderer = createPivotChartsRenderer({
className: 'sales-chart',
fontFamily: '"IBM Plex Sans", sans-serif',
categorySeparator: ' / ',
palette: ['#2563eb', '#f97316', '#16a34a'],
theme: ({ theme }) => ({
...theme,
background: '#fffdf8',
foreground: '#292524',
grid: '#e7e5e4',
}),
});
```
The theme callback receives the resolved light/dark base theme and current
render input. Renderer classes, theme colors, palette, fonts, labels, and
visible chart options are retained in image and document exports.
## Semantic label pipeline
Every visible label passes through one formatter. Kinds are `title`,
`subtitle`, `axisTitle`, `axisTick`, `category`, `legend`, `data`,
`annotation`, `summary`, and `specialized`.
```ts
const renderer = createPivotChartsRenderer({
labels: {
styles: {
title: { fontSize: 20, fontWeight: 700 },
category: { fill: '#475569', fontSize: 11 },
data: { fontWeight: 600 },
},
formatter(context) {
if (context.kind === 'legend') {
return `Measure: ${context.defaultText}`;
}
if (context.kind === 'axisTick' && context.axis === 'y') {
return null;
}
if (context.kind === 'data' && context.formattedValue) {
return {
text: context.formattedValue,
ariaLabel:
`${context.category?.label}: ${context.formattedValue}`,
className: 'sales-chart-value',
};
}
return undefined;
},
},
});
```
Return a string, a structured result, `null` to suppress one label, or
`undefined` to retain the default. Structured results can override text,
accessible text, classes, safe SVG typography, opacity, and visibility.
Annotated time series automatically arranges endpoint and extrema labels inside
the plot after applying your formatter and typography. Labels move apart with
leaders pointing to their observations. If no clear position fits, the optional
label is omitted; the full value remains available on the point's tooltip and
accessible label. Zooming uses the visible observations. Return `null` for
`context.kind === 'annotation'` to hide these labels and their leaders.
## Tooltip customization
```ts
const renderer = createPivotChartsRenderer({
tooltip: {
showDelayMs: 100,
hideDelayMs: 60,
followPointer: true,
className: 'sales-chart-tooltip',
formatter(context, defaultContent) {
return {
...defaultContent,
footer: context.drillable
? 'Open the next sales level'
: 'Leaf value',
};
},
},
});
```
The formatter receives raw and formatted values, category and series metadata,
relationship channels, mark color, and drillability. Return structured
content, plain text, an `HTMLElement`, or `null` to suppress one tooltip. Set
`tooltip: false` to disable tooltips.
## Full localization
`pivotCharts.localeText` supports deep partial overrides for dialog actions,
status and errors, breadcrumbs, gallery tabs/families, all chart names and
descriptions, compatibility reasons, chart options, data controls and table,
legend overflow, tooltips, accessibility text, context actions, themes,
totals, and number formatting.
```ts
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
localeText: {
actions: {
exportChart: 'Exportar',
downloadPdf: 'Transferir PDF',
viewData: 'Ver dados',
viewChart: 'Ver gráfico',
},
chartTypes: {
groupedColumn: 'Colunas agrupadas',
boxPlot: 'Diagrama de caixa',
},
renderer: {
formatNumber: (value) =>
new Intl.NumberFormat('pt-PT').format(value),
},
},
};
```
## Chart graphics and data export
The built-in SVG renderer exports PNG, SVG, and PDF. CSV is provided by the
chart plugin independently of the graphics backend. Use
`charts.getChartDownloadCapabilities(chart.id)` to discover the formats available
for an existing chart; custom renderers advertise only the formats they
implement. The Canvas adapter provides PNG graphics and chart-data CSV.
Graphics capture the committed rendered chart, including its resolved theme,
viewport, series visibility, labels, and annotations. SVG remains vector output.
Font-family styles are captured, but font files are not embedded in SVG, PNG,
or the default raster PDF. A web font loaded only into the page may be unavailable
when the browser rasterizes the exported SVG, causing a fallback font. Use a
system font or an application-supplied `imageEncoder` that embeds the needed
fonts when exact raster typography is required.
See the browser's [SVG image restrictions](https://developer.mozilla.org/en-US/docs/Web/SVG/Guides/SVG_as_an_image#restrictions).
The default PDF contains a raster image; an optional local vector encoder is
shown below. `pdfEncoding` reports `raster`, `vector`, or `custom` when known.
Programmatic downloads default to PNG:
```ts
await charts.downloadChart(chart.id, {
fileName: 'quarterly-sales',
fileFormat: 'pdf',
width: 1_600,
height: 900,
});
const dataUrl = await charts.getChartImageDataURL(chart.id, {
width: 1_200,
height: 675,
});
```
### Chart-data CSV
```ts
await charts.downloadChart(chart.id, {
fileName: 'quarterly-sales',
fileFormat: 'csv',
dataScope: 'current',
});
// Include hidden series and categories outside the current viewport.
const csv = charts.getChartCSV(chart.id, {
dataScope: 'all',
valueFormat: 'raw',
});
```
`current` exports every canonical value in the current viewport for visible
series. Sampling and View data virtualization do not reduce the CSV. `all` exports
the complete committed chart frame, including hidden series and categories
outside the viewport. Both scopes retain the chart's query, drill frame, and
selected measures; neither fetches original transactions or reloads the provider.
The data table's temporary sort does not reorder the export.
CSV uses numeric values and blank missing cells by default. Choose
`valueFormat: 'formatted'` to use display values and localized number formatting.
Hierarchy labels and series labels form the headers; they are presentation text,
not a lossless identity interchange format. CSV preserves Unicode and quotes
commas, newlines, and double quotes. `escapeFormulas: true` prefixes spreadsheet
formula-like strings with an apostrophe while preserving numeric negative
values. This option defaults to false. Comma and CRLF are the default delimiters; `columnDelimiter`, `rowDelimiter`,
and `bom` can override the output. The BOM is omitted by default. Width and
height are graphics options and are rejected for CSV.
### Optional vector PDF
Install and import `jspdf` and `svg2pdf.js` in your application, then inject a
local encoder. Pivot does not fetch these libraries or fonts automatically.
```ts
import { jsPDF } from 'jspdf';
import 'svg2pdf.js';
import {
createPivotChartsRenderer,
createPivotChartsVectorPdfEncoder,
} from '@revolist/pivot';
const pdfEncoder = createPivotChartsVectorPdfEncoder({
createDocument: options => new jsPDF({
...options,
format: [...options.format],
}),
}, {
fonts: [
{
fileName: 'Report-Regular.ttf',
fontFamily: 'Report',
fontStyle: 'normal',
dataBase64: reportRegularTtfBase64,
},
{
fileName: 'Report-Bold.ttf',
fontFamily: 'Report',
fontStyle: 'bold',
dataBase64: reportBoldTtfBase64,
},
],
});
const renderer = createPivotChartsRenderer({
fontFamily: 'Report',
pdfEncoder,
});
```
Supply the base64 contents of licensed TTF files whose glyphs cover your chart's
text, and register the font styles your labels use. The vector helper maps CSS
weights and oblique styles to the registered normal, bold, italic, or bold-italic
PDF style and matches family names without regard to case. Registered family
names may contain spaces and commas; quotes, backslashes, and control characters
are rejected because the converter cannot reliably parse them. SVG and PNG keep
the original CSS styles. Standard PDF fonts do not
provide general Unicode coverage. Language shaping, font coverage, and converter
support determine the result; this is not a universal Unicode or PDF/UA guarantee.
The helper creates a fresh document per export and converts the captured SVG to
vector PDF locally. It preserves the SVG's drawing, including supported patterns
and clipping, and converts CSS pixels to PDF points. Conversion errors propagate
without falling back to a raster image or a remote server. An arbitrary custom
`pdfEncoder` reports `custom`; the first-party vector helper reports `vector`.
Graphics dimensions must be positive and finite. Output is limited to 16,384
pixels per dimension and 64 × 1,024 × 1,024 pixels in total; omitted dimensions use the
rendered chart size.
### Download lifecycle
Listen to `pivot-chart-before-download` and call `preventDefault()` to cancel a
download before encoding. The detail contains the chart ID, format, operation
ID, and owned model/options snapshots. Changing those snapshots does not update
the chart or alter the download. `pivot-chart-downloaded` reports completion of
the download action; it cannot confirm that the user saved the file.
`pivot-chart-download-error` reports failures and aborted operations.
Pass an `AbortSignal` in download options to cancel an operation. Graphics are
captured before asynchronous encoding, and the renderer checks cancellation and
disposal again before starting a download. Updating the chart while encoding
does not change the captured graphics. Injected encoders may finish their own
work after cancellation, but the renderer will not start a later download. A canceled before-download event produces
no completion event. Invalid options are rejected before a download operation is
created, so they do not emit download lifecycle events.
The first-party renderer also accepts `imageEncoder` and `pdfEncoder` callbacks
for applications that need a different rasterization or PDF pipeline. These
callbacks receive an owned SVG snapshot. They must return the encoded data URL
and propagate failures; the plugin does not send chart data to an export server.
[Previous: Drill-down and interactions](../drilldown-and-interactions/) ·
[Next: Headless API and persistence](../headless-api-and-persistence/) ·
[API reference](/api/pivot-charts/)
## Localize and configure the chart UI
`pivotCharts.localeText` supplies shared chart and renderer text.
`pivotChartsUi.localeText` supplies UI overrides, merged deeply over that text.
Both properties are reactive: replacing either configuration updates existing
chart dialogs and embedded chart panels. Partial overrides retain all other
labels, descriptions, option labels, accessible names, and formatter functions.
```ts
grid.pivotCharts = {
renderer,
localeText: {
controls: { totals: 'Totales' },
dataTable: { categoryHeader: 'Categoría', emptyValue: 'Sin valor' },
renderer: { formatNumber: value => numberFormatter.format(value) },
},
};
grid.pivotChartsUi = {
localeText: {
context: {
heading: 'Detalles de los datos',
close: 'Cerrar detalles',
rows: 'Filas',
columns: 'Columnas',
frame: (categories, series) => `${categories} categorías · ${series} métricas`,
formatFilterValue: value => String(value),
},
actions: { chartLabel: 'Gráfico', dataLabel: 'Tabla', viewData: 'Ver datos', viewChart: 'Ver gráfico' },
chartOptions: { labels: { strokeWidth: 'Grosor de línea' } },
gallery: { tabs: { chart: 'Gráfico', data: 'Datos', customize: 'Estilo' } },
},
controls: {
'context.note': 'hidden',
'controls.keepLinked': 'hidden',
'controls.totals': 'disabled',
'tabs.chart': 'hidden',
'chartTypes.pie': 'hidden',
'chartOptions.showGridLines': 'hidden',
'chartOptions.strokeWidth': 'disabled',
annotations: 'hidden',
'export.pdf': 'disabled',
},
dataGrid: {
sorting: true,
range: true,
resize: true,
columnGrouping: true,
rowSize: 36,
categoryColumn: { name: 'Categoría', size: 220 },
seriesColumn: series => ({ name: series.measure?.label ?? series.label, size: 180 }),
filter: false,
},
};
```
`controls` accepts `enabled`, `disabled`, or `hidden`. Unspecified controls are
enabled when their data and renderer capabilities permit them. Hidden controls
are not rendered; disabled controls remain visible and cannot be activated.
A parent restriction wins over an enabled child. For example, disabling
`navigation` also disables `navigation.enabled`, even if the child is explicitly
enabled. A UI policy never enables an unsupported renderer capability or removes
a loading/read-only restriction.
The close policy also gates Escape, and `drill.navigate` gates Alt+Left and Home
in the chart area. Re-enabling these controls restores their shortcuts.
For the transient data-details popup, `context.close` controls its close button;
Escape and clicking outside still dismiss the popup. Restrict `context` or
`context.heading` to disable access. Restricting the details or export trigger
also closes its already-open popup or menu. Disabled tabs stay visible; if no
tab is enabled, no settings panel is active. Hiding every tab removes the sidebar.
Visible numeric option values can be localized independently of the numeric API:
```ts
grid.pivotChartsUi = {
localeText: {
chartOptions: {
formatValue: (value, key, step) => new Intl.NumberFormat('de-DE', {
maximumFractionDigits: step < 1 ? 2 : 0,
}).format(value),
},
},
};
```
`formatValue(value, key, step)` affects range-value captions. Option updates still
use numbers. Omit it to retain the default step-based decimal formatting.
The policy changes available UI actions. It does not rewrite the chart model,
disable model-defined rendering behavior, or restrict programmatic API calls.
Use `ref.update(...)` to configure chart behavior, and the UI policy to control
which settings users may change. Disable reset actions too when their broader
scope is inappropriate for your application.
### Native chart navigation policies
For a chart type whose catalog definition advertises `navigation`, configure
its rendered toolbar and plot gestures through the chart model:
```ts
ref.update({
navigation: {
enabled: true,
navigator: true,
crosshair: true,
tooltipMode: 'shared',
actions: {
zoomIn: 'enabled',
zoomOut: 'disabled',
reset: 'hidden',
},
gestures: { wheel: false, pan: true, pinch: false },
},
});
```
Each action accepts `enabled`, `disabled`, or `hidden`; omitted actions default
to enabled. Disabled actions cannot activate through pointer or keyboard input.
Plot gestures require `enabled: true`, and each omitted gesture defaults to
allowed. Navigator handles and its range band remain independent of that flag.
Action policies alone do not create a toolbar; toolbar availability still
follows the `enabled`, `navigator`, and viewport configuration.
A supplied `navigation` record replaces the previous record, including its
nested action and gesture policies. Omitting it from an update preserves the
current configuration; supplying `{}` restores defaults. These policies survive
model serialization and restoration. Unsupported chart types reject a nonempty
navigation record with a localizable error.
Use `localeText.navigation` to rename toolbar actions, navigator accessible
names, instructions, and range summaries. Visible button text is independent of
its accessible action name:
```ts
const localeText = {
navigation: {
zoomInButtonText: 'Zoom in',
zoomOutButtonText: 'Zoom out',
panLeftButtonText: 'Earlier',
panRightButtonText: 'Later',
resetViewportButtonText: 'Reset',
formatNavigatorValue: (value, scale, handle, defaultText) =>
`${handle}: ${defaultText}`,
},
};
```
Translated text is measured and shortened with an ellipsis when space is limited.
If no text prefix fits, the button shows its distinct default action glyph instead.
The full text remains in the button title, and accessible action names remain
independently configurable. Default glyph buttons retain their compact geometry.
`formatNavigatorValue` customizes slider `aria-valuetext`; `value` matches numeric
`aria-valuenow` (epoch milliseconds for time, a boundary ordinal for categories).
The category end boundary is exclusive, while its `defaultText` names the last
included category. Numeric slider bounds and behavior remain unchanged.
`localeText.renderer.invalidNavigation`
and `navigationUnsupported(chartType)` customize validation messages. These
renderer/model policies also work through the standalone charts API, without a
RevoGrid host.
| ID or prefix | Controlled UI |
| --- | --- |
| `settings` | Entire settings sidebar; hiding it gives the chart the full width |
| `tabs.chart`, `tabs.data`, `tabs.customize` | Individual settings tabs; selection and arrow navigation skip unavailable tabs |
| `inspector`, `inspector.` | All collapsible inspector sections, or one section listed above; restrictions cascade to its controls |
| `gallery`, `chartTypes.` | Gallery and individual chart types, including recommendations |
| `data`, `query`, `relationship.x/y/size` | Data settings, independent query editor, and relationship bindings |
| `query.rows/columns/filters` | Hierarchy and member-filter editors |
| `controls.` | Individual presentation/data controls: title, subtitle, description, legend, dataLabels, xAxisTitle, yAxisTitle, totals, drillSyncMode, keepLinked, valueSeries, theme |
| `presentation`, `customize` | Presentation controls or all customization settings |
| `chartOptions.` | Each catalog option, including its reset action |
| `chartOptions.resetAll` | Reset all chart options |
| `axes.y.`, `axes.ySecondary.` | Primary/secondary axis scale, min, max, title, unit, notation and precision |
| `axes.seriesLayer`, `axes.seriesAxis` | Combination series layers and axis assignment |
| `navigation`, `interaction`, `annotations`, `colors` | Whole feature groups; individual controls use the same prefix |
| `actions.viewData`, `dataView` | Chart/data switch or the data view itself; hiding an active data view returns to the chart |
| `dataView.sorting/selection/resize/filtering` | Native data-grid interaction features |
| `context`, `context.heading`, `context.close` | Data details popup, trigger and close action |
| `context.overview/rows/columns/totals/filters/localFilters/note` | Individual information groups within the popup |
| `export`, `export.png/pdf/svg/csv`, `actions.exportChart` | Export menu, formats and trigger |
| `drill`, `drill.navigate`, `drill.retry` | Drill navigation controls |
| `actions.retry`, `actions.close` | Chart retry and close actions |
Import `PIVOT_CHART_UI_CONTROLS`, `PivotChartUiControlId`,
`PivotChartUiControlState`, `PivotChartsUiConfig`, and `PivotChartDataGridConfig`
from `@revolist/pivot` or `@revolist/pivot/charts` for the concrete control
inventory and public types. Each rendered control also exposes its stable ID in
`data-pivot-chart-control`; IDs are independent of translated labels.
### Native data-grid configuration
The current-frame RevoGrid is always read-only. Its source and formatting stay
owned by the chart. `dataGrid` exposes sorting, selection (`range`), resizing,
focus, clipboard, row/column sizes, theme, and category/series column names,
sizes, bounds, sortability, and pinning. Global sorting restrictions win over a
column's `sortable: true` setting.
Column-axis members use native grouped headers: `Q1 › Revenue` becomes a `Q1`
group with a `Revenue` child column. Nested paths and multiple measures share
their adjacent parent groups without changing series order. Standalone series
without a column path retain their flat names. Values-on-rows layouts use the
last column member as the leaf name. `columnGrouping: false` restores full flat
series labels; `groupLabel(label, series, level)` overrides group names, and
`seriesColumn` overrides leaf names. Groups use semantic member keys, so equal
display names do not merge distinct members. Labels come from the dataset;
display text is never split to infer a hierarchy.
Sorting and Pro filtering are enabled by default. Category columns offer text
conditions and series columns offer numeric conditions against raw values,
independent of formatted cell text. The native filter popup is supplied by
`AdvanceFilterPlugin`. Pass RevoGrid's `ColumnFilterConfig` through `dataGrid.filter`, including its
`localization` captions and filter names. Setting `filter: false`, or disabling
`dataView.filtering`, removes filtering. RevoGrid's native filter-localization
contract is retained rather than introducing a second set of filter strings.
`categoryColumn.filter` and `seriesColumn(...).filter` can select Pro filter
types (such as `['selection']`) or disable individual column filters. Global
filtering and sorting restrictions win over column overrides. Sorting/filtering
affect only the current data grid; they do not change the chart dataset, Pivot
report, or exports. They refresh against new frame values and reset when the
series schema changes.
The data view uses `localeText.dataTable` for its accessible name, category
header, missing values, and sort actions/announcements. Series names come from
the dataset and may be overridden with `seriesColumn`. Numbers use the supplied
formatted dataset values or `localeText.renderer.formatNumber`.
### Formatters and standalone charts
Function overrides cover dynamic strings as well as fixed labels. In addition
to number and data-table formatting, `context.frame`, `context.more`, and
`context.formatFilterValue` customize data details. Annotation target/path text
and the UTC input hint use `annotations.targetLabel`, `annotations.pathLabel`,
and `annotations.utcPlaceholder`.
For a stacked-axis tick whose mathematical value exceeds JavaScript's finite
number range, `renderer.formatScaledNumber(value, multiplier)` formats the
value without requiring their product to fit in a `number`. The default uses
scientific notation, such as `2.697 × 10^308`. Override this callback through
`pivotCharts.localeText` to control the complete plotted label. Ordinary
representable ticks continue to use `renderer.formatNumber`. Shared renderer
formatters belong in `pivotCharts.localeText`; `pivotChartsUi.localeText`
overrides inspector and data-view UI text without replacing the plot/export
locale.
Radar's category prerequisite message is customizable with
`renderer.minimumCategoriesRequired(chartTypeLabel, minimum)`. Both radar
types require three non-total categories; empty frames retain the no-data view.
Use `query.formatMemberValue` for member-picker values and
`context.formatFieldLabel` for row and column dimension headings and filter
headings in data details. The latter receives
the configured dimension label when available, otherwise the field name. Their
defaults are `String(value)` and the unchanged resolved field label.
Generated inspector labels also expose formatter callbacks:
`query.formatMeasureLabel(prop, aggregator)` controls the fallback caption when
a measure has no configured label, `colors.measureLabel(prop, aggregator)`
controls measure-color captions, `colors.memberPathLabel(path)` formats member
paths, and `annotations.coordinateLabel(label, path)` formats category reference
choices. Configured field and measure names remain application data; rename
those in the Pivot configuration.
```ts
grid.pivotChartsUi = {
localeText: {
query: { formatMemberValue: value => `Member: ${String(value)}` },
context: { formatFieldLabel: label => `Field: ${label}` },
},
};
```
A zero-grid chart created with `createStandalonePivotChart` accepts the same
`localeText` options for its renderer and export text. It does not render the
settings sidebar or data-details popup, so UI policies belong to
`PivotChartsUiPlugin`; standalone behavior is configured through the controller's
model/update API and renderer capabilities.
Numeric color-map keys expose `localeText.colors.formatValueMapValue(value, role)`
for the complete displayed number. `role` is `minimum`, `midpoint`, or `maximum`.
An explicit callback is used unchanged, including localized small or long values.
When omitted, keys use `renderer.formatNumber` with the existing compact scientific
fallback for small values or long labels. For example:
```ts
import type { PivotChartsLocaleTextOptions } from '@revolist/pivot';
const localeText: PivotChartsLocaleTextOptions = {
colors: {
formatValueMapValue: (value, role) => `${role}: ${new Intl.NumberFormat('de-DE').format(value)}`,
},
};
```
The toolbar's short captions use `actions.chartLabel` and `actions.dataLabel`;
`actions.viewChart` and `actions.viewData` name their accessible actions.
Configure the buttons independently with `controls['actions.viewChart']` and
`controls['actions.viewData']`. `dataView` controls the entire view switch.
`gallery.tabs.chart/data` caption overrides remain fallbacks when the
corresponding short caption is not supplied.
Every native dropdown choice supports `.options.`, for example:
```ts
grid.pivotChartsUi = {
controls: {
'controls.totals.options.all': 'hidden',
'controls.totals.options.grand': 'disabled',
'query.sortDirection.options.desc': 'disabled',
},
};
```
These policies configure the editor, without rewriting the chart model. A hidden
current choice retains its selected value and label but cannot be chosen from the
menu. Parent restrictions and existing capability restrictions still apply.
For dynamic choices, use the option's native `value`, exposed alongside its full
ID in `data-pivot-chart-control`.
Automatic category grouping and authored axis formatting are also translatable:
```ts
grid.pivotCharts = {
renderer,
localeText: {
renderer: {
otherCategoryLabel: 'Autres',
groupedCategoryLabel: (label, index) => `${label} (groupe ${index})`,
formatAxisTick: (value, format) => new Intl.NumberFormat('fr-FR', format).format(value),
formatTimeAxisTick: (epochMilliseconds, dateOnly) =>
new Intl.DateTimeFormat('fr-FR', dateOnly
? { dateStyle: 'medium', timeZone: 'UTC' }
: { dateStyle: 'short', timeStyle: 'short', timeZone: 'UTC' }).format(epochMilliseconds),
},
},
};
```
Axis formatter callbacks also receive the built-in text as their last argument,
so an application can selectively override formats. User-authored titles, field
and series labels, units and notes remain application data; use their existing
model, column-label and formatter APIs to translate them. Backend diagnostic text
can be mapped through `status.formatError` and `drill.formatError`.
## Shared axis and flow options
Pivot uses the shared [axis contract](/guides/charts/tools/axes/), including signed scales, numeric ticks, grouped category labels, named X/Y assignments and measured gutters. Customize → Axes authors named X definitions and their navigation/brush targets. Declared named Y axes appear as editable value forms. Horizontal bars expose primary value/category forms plus horizontal secondary/named forms; gallery transitions clear incompatible positions and unsupported assignments atomically. Direct API transitions remain strict.
Sankey layout/sort/color/label/highlight fields come from the [shared schema](/guides/charts/types/sankey/#general-graphs-configuration-and-runtime-controls). Supply runtime `sankey` callbacks or `categoryAxis.groups` callbacks to `createPivotChartsRenderer`; reattach callbacks on restore. Heatmap [row/column navigation](/guides/charts/types/heatmap/#two-dimensional-navigation) uses the same canonical IDs and export scope as standalone Charts.
All grouped/stacked/normalized bars and columns also use the shared named-X authoring controls. Their category axes accept path levels, ID-based label mappings, grouped rows, reversal and independent viewport/brush targets. Additional bar category axes use left/right positions; columns use top/bottom. See [bar/column category assignments](/guides/charts/tools/axes/#multiple-category-axes-on-bars-and-columns).
Scatter, bubble and quadrant charts also accept `axes.ySecondary`, `axes.yAxes[id]` and `seriesOverrides[boundYId].yAxis`. The Pivot inspector limits assignment controls to bound Y measures. Scale/domain/reversal/ticks, error bars, value references, semantic notes and regression overlays follow that Y axis. Y navigation and synchronization remain primary-only; named/secondary hover shares X only. See [all relationship axis options and limits](/guides/charts/tools/axes/#multiple-value-axes-on-relationship-charts).
Relationship groups also support `axes.xAxes[id]`, `seriesOverrides[boundYId].xAxis`, `viewport.xAxes[id]`, `navigation.xAxis` and `rangeSelection.xAxis`. The bound Y measure owns both assignments; `bindings.x` supplies numeric X values. Named X forms default to linear and omit category mapping controls. Named X scales, errors, annotations, brushes, navigator, synchronization and async full-domain metadata use that projection. See [the full relationship X contract](/guides/charts/tools/axes/#independent-numeric-x-axes-on-relationship-charts).
Candlestick and OHLC charts expose the shared primary price-axis form: `axes.y` supports linear/log/symlog/sqrt, bounds, reversal, vertical placement, title/unit/colors, numeric ticks and tick formatting. Price marks and annotations use the same clipped projection, and styled labels use measured gutters. See [all financial price options and limits](/guides/charts/tools/axes/#candlestick-and-ohlc-price-axes). Volume-price uses fixed price/volume controls described below; additional financial axis collections remain unsupported.
`volumePrice` shares category/time/numeric X navigation across aligned panes. `axes.y` configures prices and `axes.ySecondary` configures volume, including independent transforms, bounds, reversal, placement, units, formatting and ticks. Fixed roles preserve canonical data and reject swaps. Pivot exposes both forms. Notes/references/drawings use their pane's scale; full-source/synchronized Y metadata remains price-only. See [all volume-price options and limits](/guides/charts/tools/axes/#volume-price-pane-axes).
Bound range columns/bars/areas, confidence bands and error intervals also support primary/secondary/named value axes and named X projections. The bound high measure owns each range group; low and actual fields follow it. Pivot exposes assignment controls on high measures. Scales, bounds, reversal, formatting, ticks, measured labels, clipping, annotations, navigator and brushes use the shared projections. See [all range-axis options and limits](/guides/charts/tools/axes/#range-group-axes-and-navigation).
Box plots, violins and statistical intervals also expose primary `axes.y`, secondary `axes.ySecondary` and named `axes.yAxes` numeric controls. Actual distributions own box/violin projections, and bound matrix observations own statistical intervals. Clipping, measured labels and annotations share those projections. Horizontal boxes retain physical value/category forms in Pivot. See [all statistical-axis options and limits](/guides/charts/tools/axes/#statistical-primary-value-axes).
Statistical charts also support category windows, reversal/grouped labels, navigator/brush controls and primary-domain/category synchronization. Box/violin overviews and shared hover use displayed medians; canonical CSV cells remain unchanged. See [statistical navigation options and boundaries](/guides/charts/tools/axes/#statistical-categorical-navigation).
Statistical primary X also supports time and all four numeric scales, including horizontal boxes. Raw key spacing, continuous brushes, annotations, navigator, source snapshots and remote detail/overview requests share that projection. See [all statistical continuous-X options and physical-axis roles](/guides/charts/tools/axes/#statistical-time-and-numeric-x).
Histograms also expose primary aggregate-Y controls and optional numeric bin-boundary X scales. X comes from bound sample values; Y configures counts, sums or means. Bounds clip bins without rebucketing, and canonical CSV/observation identities remain intact. See [all histogram-axis options and boundaries](/guides/charts/tools/axes/#histogram-bin-and-metric-axes).
Histograms support local raw-sample X windows, zoom/pan, a bin-metric navigator, independent numeric brushes and compatible domain/window synchronization. Bins remain stable while the window clips their rectangles. See [all histogram navigation options and boundaries](/guides/charts/tools/axes/#histogram-sample-navigation). Histograms also support [shared/crosshair/peer bin readouts and typed formatter metadata](/guides/charts/tools/axes/#histogram-shared-hover-and-crosshair). Remote histogram loading uses [complete-bin source summaries](/guides/charts/tools/axes/#complete-histogram-source-frames); typed metadata keeps global metrics separate from loaded raw rows.
Histogram Data views provide native Samples rows with numeric bin membership columns and a separate complete Bins inspection table. See [all shared snapshot fields, scopes and localization](/guides/charts/tools/axes/#histogram-sample-and-bin-data-views).
### Path sampling
**Customize → Appearance → Path sampling** controls the shared Charts
`chartOptions.sampling` setting for line/area and combination charts. Choices are
**Automatic**, **None**, **M4**, **Minimum / maximum**, and **Largest triangle
(LTTB)**; resetting appearance returns to automatic. The exported
`PivotChartSampling` type names these values. Models retain the choice, including
server rendering. Read [path sampling](/guides/charts/tools/axes/#path-sampling)
for supported types, stack alignment, raw-data guarantees, marker handling, and
budgets.
### Step endpoints
**Customize → Axes → [series] Step endpoints** offers **Automatic**,
**Expanded bands**, and **Strict points** for line/area layers. It writes
`seriesOverrides[id].strictStepCurve` (`false` by default). Expansion applies to
stepped categorical combination bands; native point and continuous X layouts
retain exact geometry. The flag persists in models and SVG/SSR. Switching to a
column/scatter/range layer or resetting appearance clears it. Read the
[step endpoint policy](/guides/charts/tools/axes/#step-endpoint-policy) for gaps,
reversal, sampled paths, and source identity.
### Category placement
**Customize → Axes → [axis] Category placement** writes
`axes.x.categoryPlacement` or `axes.xAxes[id].categoryPlacement` independently.
Choose **Automatic**, **Point positions**, or **Centered bands** on supported
line/area and combination categorical axes. Native defaults stay points;
combinations default to bands. Scale/gallery transitions clear incompatible
settings while preserving sibling axes and compatible category-ID ranges. The
`PivotChartCategoryPlacement` type names `'point' | 'band'`. Read
[category placement](/guides/charts/tools/axes/#category-point-and-band-placement)
for step expansion, column margins, grouped labels, navigator behavior and saved
models.
### Transient point highlights
**Chart appearance → Disable point highlights** writes
`chartOptions.disableLineItemHighlight`. **Axes → [series] Point highlights**
writes `seriesOverrides[id].disableHighlight` using Automatic/Enabled/Disabled.
Both booleans default to false and share the standalone Charts implementation.
They suppress the transient glyph while retaining source inspection and regular
markers. See [transient highlights](/guides/charts/tools/axes/#transient-source-point-highlights)
for saved endpoint substitution, tooltips, keyboard traversal and clean
export snapshots.
### Statistical value-axis collections
`boxPlot`, `violin` and `rangeInterval` share `axes.y`, `axes.ySecondary`, named `axes.yAxes` and `seriesOverrides[id].yAxis` in Charts and Pivot. Numeric scale, bounds, reversal, units, ticks and formatting apply independently to each assigned group. Named IDs must be declared; `secondary` can be implicit. Box/violin domains use genuine distribution samples/summaries; violin supports multiple selected distributions, series color and visibility, while keeping its single-series category palette by default. Statistical intervals accept two or more measures without a three-measure cap and aggregate only within each assigned value-axis group. A singleton group preserves source activation; aggregates remain read-only inspection targets.
Vertical extra axes use left/right/none; horizontal box extra axes use top/bottom/none. Horizontal boxes retain their primary physical aliases (`axes.x` for value appearance, numeric settings in `axes.y`). Pivot reconciles extra-axis positions on orientation/type changes. Invalid direct updates are atomic. References, shapes, semantic notes, model persistence and SVG export use the assigned projection. Primary runtime Y hints do not fix secondary/named scales: author their bounds as needed. Named statistical X collections are described below.
### Named statistical X axes
Box plots, violins and statistical intervals accept named `axes.xAxes` and per-series `seriesOverrides[id].xAxis`, plus primary/named `data` and `level` X mappings. Each X frame has its own category/time/numeric scale and `viewport.xAxes[id]`; `navigation.xAxis` and `rangeSelection.xAxis` select interaction targets. Statistical intervals partition observations by the X/Y assignment pair, while each Y domain includes its full assigned source statistics across X frames. Named X decorations use top/bottom/none vertically, left/right/none for horizontal boxes. Pivot shares the controls and reconciles positions on orientation changes. References/shapes/notes, save/SSR, activation and canonical data use the assigned projections. See the axes guide for fields, defaults and limits.
### Financial numeric X
Candlestick, OHLC and volume-price charts accept primary `axes.x.scale: 'linear' | 'log' | 'symlog' | 'sqrt'` plus `data`/`level` X mappings, numeric bounds, reversal, units, tick formatting/policy and physical styles/placement. Both volume-price panes share raw X coordinates and navigation; glyph widths follow the nearest distinct visible numeric gap. Source activation, annotations, model/SSR and canonical CSV retain their existing contracts. Pivot exposes primary mapping/numeric controls, with named X on all financial collections and named Y on candlestick/OHLC. See the axes guide for defaults, field limits and remaining collection/pane capabilities.
### Candlestick/OHLC collections
Candlestick and OHLC accept parallel open/high/low/close binding arrays. Close fields uniquely own groups and their `seriesOverrides[id].xAxis`/`yAxis` assignments; open/high/low helpers can broadcast and inherit their owners. Use `axes.xAxes`, `axes.ySecondary` and `axes.yAxes` for independent scale/style/window controls. Groups keep canonical source indexes, stable slots and independent value domains; missing rows omit only the incomplete group. CSV windows include every owned OHLC field, and primary runtime Y hints exclude secondary/named price groups. Pivot preserves explicit grouped bindings and exposes axis selectors on close owners. See the axes guide for channel rules, limits, annotations and source contracts. Volume-price paired collections retain one shared price pane and one shared volume pane, as described below.
### Paired volume-price collections
`volumePrice` supports parallel OHLC/volume binding arrays, distinct paired close/volume owners and named X axes. Each close owns X for its prices and volume; Y remains primary for prices and secondary for all volumes. Both panes share aligned group slots, windows and raw source identity. Missing volume preserves complete prices; incomplete OHLC observations omit both sides of that pair. `chartOptions.volumePaneRatio` accepts `0.1`–`0.8` of post-gap height; unset/reset retains automatic 24% sizing and its small-pane policy. Pivot exposes close-owner X selectors, fixed Y forms and the ratio control. See the axes guide for pairing, mappings, helper transitions, hover, annotations, exports, source/SSR and remaining additional-pane/Y limits.
### Source keyboard inspection
Pivot uses the shared Charts source traversal for native line/area charts and
combination line/area layers. Hidden or sampled-out glyphs remain keyboard
reachable with raw source tooltips and activation; derived/synthetic values stay
inspection-only. The temporary focus ring is independent of visual-highlight
suppression and excluded from export snapshots. No Pivot-specific option is
required. See [keys, eligibility, order and focus restoration](/guides/charts/tools/axes/#source-point-keyboard-traversal).
### Pie slice geometry
Pie, donut and semi-donut expose saved `outerRadius`, `paddingAngle`,
`cornerRadius`, `arcLabelRadius` and `arcLabelMinAngle` under **Customize →
Appearance**. The shared renderer preserves adaptive ring placement when label
radius is omitted; the editor preset is `0.7`. Fields persist through models and
exports. See [units, defaults, ranges and limits](/guides/charts/customization/#pie-arc-geometry).
### Stack baselines
**Customize → Appearance → Stack baseline** selects `diverging`, `none` or
`expand`. The per-series selector under **Axes and scales** overrides its actual
stack partition. Automatic removes the override; conflicting member policies
reject. See [default behavior, signed values and normalization precedence](/guides/charts/customization/#stack-baseline-policies).
### Native line and area groups
Native paths expose a per-series **stack group** field under **Axes and scales**,
plus group order and baseline policies. Multiple groups and independent raw
paths use the same Charts engine; no composition adapter changes their public
chart type. See [defaults, normalized groups, baseline conflicts and sampling](/guides/charts/customization/#native-line-and-area-stack-groups).
## Compact sparklines
Enable **Customize → Chart appearance → Sparkline** on a native line/area or grouped-column chart. It uses the shared persisted `chartOptions.sparkline` mode, keeps raw source identity and keyboard/tooltip inspection, and hides chart furniture while fitting the host. Turn it off to restore authored headings, axes, legend and navigation. See [sparklines](/guides/charts/tools/sparklines/) for supported types, defaults and sizing/export rules.
## Histogram collections
Explicit histogram sample bindings can use shared or independent axes and per-sample paint/visibility. In Data, **Sample series** switches between each distribution's raw records and complete bins. Existing axis controls target the samples, while weighted helper measures stay out of the legend. See [histogram collections](/guides/charts/types/histogram/#histogram-collections-and-axes) for bindings, all options and complete remote-source contracts.
## Pie highlighting and fading
Shared `pieHighlight` and `pieFade` appearance controls select item/ring scope and peer fading. Saved `highlightedArc`/`fadedArc` styles and `highlightedPieItem` controlled identity configure geometry and paint. See [all fields, units, defaults, callbacks and snapshot behavior](/guides/charts/tools/pie-highlighting/).
## Radar metric axes
Radar line and area share independent numeric metric domains/scales, reversible projection, authored grid bands and value labels through `axes.radar` and radar appearance options. See [radar axis contracts and Pivot editing](/guides/charts/tools/radar-axes/).
Polar columns and radial bars share saved [polar geometry controls](/guides/charts/tools/polar-geometry/) in Customize → Appearance, including rotation, partial sweeps, direction, radius ratios and category/series gaps.
The six polar/radial-bar types share [polar numeric value axes](/guides/charts/tools/polar-value-axes/) in Customize → Axes and scales, including scale/bounds, reversal, numeric ticks, units and independent value/category/grid visibility.
---
# Data and Bindings
URL: https://pro.rv-grid.com/guides/pivot-charts/data-and-bindings/
Source: src/content/docs/guides/pivot-charts/data-and-bindings.mdx
Description: Control the Pivot data projected into a chart, semantic channel bindings, totals, link state, and safety limits.
Pivot Charts project the committed Pivot result into a renderer-neutral
`PivotChartDataset`. The standard matrix is efficient for category/series
rendering, while the normalized analytical frame supplies stable fields and
observations for relationship, hierarchy, range, flow, financial, KPI, and
other specialized chart types.
Pivot Charts consume the report's committed aggregate values. Configure numeric/scalar eligibility, counts and execution pins in the [Pivot aggregation contract](/guides/pivot/concepts/aggregations/). Chart normalization, formatting or a shared dataset shape does not establish identical local/server results or increase arithmetic precision. A remote provider must supply complete aggregates under its backend policy, not averages or distinct totals reconstructed from a visible page.
## Dataset shape
```ts
interface PivotChartDataset {
revision: number;
source: 'client' | 'server';
categories: PivotChartCategory[];
series: PivotChartSeries[];
values: Array>;
displayValues?: Array>;
hierarchies?: {
row?: PivotChartHierarchyDefinition;
column?: PivotChartHierarchyDefinition;
};
analyticalFrame?: PivotChartAnalyticalFrame;
}
```
`values` is row-major: `values[categoryIndex][seriesIndex]`. Optional
`displayValues` preserve the Pivot's formatted values for chart labels,
tooltips, data tables, and accessibility text. Categories and series retain
stable IDs, member paths, labels, member kinds, measures, and drill hierarchy
metadata.
The dataset revision identifies the committed Pivot snapshot that produced the
frame. Linked refreshes and drill requests use it to reject stale work.
## Normalized analytical frame
```ts
interface PivotChartAnalyticalFrame {
fields: PivotChartFieldDescriptor[];
observations: PivotChartObservation[];
matrixCells?: Array<{
categoryId: string;
seriesId: string;
observationId: string;
measureFieldId: string;
}>;
hierarchies?: PivotChartDataset['hierarchies'];
derived?: {
bins?: PivotChartJsonValue[];
quantiles?: PivotChartJsonValue[];
distributions?: PivotChartDistribution[];
ranges?: PivotChartJsonValue[];
flowEdges?: PivotChartJsonValue[];
financial?: PivotChartJsonValue[];
geographicFeatures?: PivotChartJsonValue[];
};
}
```
Fields declare a stable ID, label, dimension/measure role, semantic type, source
field, aggregator, and optional format. Each observation stores dimension
values separately from numeric measures and optional display measures.
Providers and custom renderers can use this metadata independently of visible
labels. Built-in statistical charts use the typed distribution views; built-in
measure-channel bindings resolve matrix series identities.
### Provider-owned analytical identities
Use `matrixCells` when supplied observations or measure fields have their own
IDs. Each entry maps one matrix cell to one measure on a tidy observation. A
single observation can therefore carry several measures:
```ts
dataset.analyticalFrame = {
fields: [
{ id: 'region-code', label: 'Region', role: 'dimension', semanticType: 'identifier' },
{ id: 'revenue', label: 'Revenue', role: 'measure', semanticType: 'number' },
{ id: 'margin', label: 'Margin', role: 'measure', semanticType: 'number' },
],
observations: [{
id: 'report-row-42',
dimensions: { 'region-code': { value: 'north', label: 'North' } },
measures: { revenue: 120, margin: 18 },
}],
matrixCells: [
{ categoryId: 'north', seriesId: 'sales-series', observationId: 'report-row-42', measureFieldId: 'revenue' },
{ categoryId: 'north', seriesId: 'margin-series', observationId: 'report-row-42', measureFieldId: 'margin' },
],
};
// The corresponding matrix must contain [[120, 18]], in those series positions.
```
Map every matrix cell once, including null cells, and reference every supplied
observation. IDs must exist, referenced fields must be measures, and each
observation/measure pair maps to one cell. Matrix values remain authoritative;
conflicting numbers or display strings are rejected. Extra dimension metadata
and ancillary fields remain metadata; they do not redefine matrix aggregation.
Projection retains mapped observation IDs and metadata for surviving cells.
Removing a selected series removes its mapped measure, rather than retaining
that excluded value in a stale analytical frame. Unlinking and JSON restoration
retain the explicit mapping. Existing generated canonical frames remain
supported; arbitrary supplied IDs without a mapping are rejected with an
actionable error. Observation IDs are opaque identities, so applications should
not construct or split generated IDs.
## Semantic bindings
`PivotChartBindings` declares renderer channels. For the built-in renderer,
measure channels refer to IDs from `dataset.series`, and the flow dimensions
use `pivot:category` and `pivot:series`. Empty and omitted channels are equivalent.
| Channel group | Channels | Used by |
| --- | --- | --- |
| Value selection | `value` | Statistical distributions, histogram, strip/dot, waffle, calendar, and annotated time series. |
| Relationship | `x`, `y`, `size` | Scatter, bubble, density, and connected-scatter views. |
| Flow | `source`, `target`, `weight` | Sankey and chord. |
| Range | `start`, `end`, `low`, `high` | Timeline, range, confidence, and error views. |
| Financial | `open`, `high`, `low`, `close`, `volume` | Candlestick, OHLC, and volume-price. |
| KPI | `actual`, `target`, `variance` | Bullet, gauge, KPI, and progress gauge. |
Other typed channels are extension points for custom renderers. The built-in
renderer derives ordinary categories, hierarchy, polar layout, and facets from
the matrix; `facetRow`, `facetColumn`, `label`, `tooltip`, `shape`, `angle`, and
`radius` do not configure these native views. Calendar accepts `color` as a
fallback value-series binding. Use `colors` and the label/tooltip formatter APIs
for native appearance. Preserving an extra analytical field does not make it a
native rendering channel.
The chart gallery checks required channel counts before selection. For
relationship charts, the Data panel infers distinct X/Y measures when possible
and prevents the same field from satisfying both required channels.
```ts
await chart.update({
chartType: 'bubble',
bindings: {
x: ['measure:cost:sum'],
y: ['measure:revenue:sum'],
size: ['measure:units:sum'],
},
});
```
Use matrix series IDs for these native measure bindings, not visible labels or
arbitrary analytical-frame field IDs. Some specialized channels can infer an
unambiguous semantic alias such as `low` or `close`; explicit IDs are preferable
when captions are localized or several candidates match.
## Series and values-on-rows
`seriesIds` selects a subset of dataset series. Types whose catalog definition
uses `firstAvailable` select one series even when the Pivot exposes several.
Types marked `all` retain the compatible series selection.
When the Pivot places values on rows, the category carries its measure.
`categoryMeasure` selects the required `{ prop, aggregator }` pair without
rewriting row paths or pretending the measure is a column series.
```ts
const dataset = await charts.getChartData({
chartType: 'line',
seriesIds: ['series:2026'],
categoryMeasure: {
prop: 'revenue',
aggregator: 'sum',
},
});
```
## Totals
`totals` accepts four modes:
| Mode | Included members |
| --- | --- |
| `none` | Leaf and group members only. |
| `grand` | Include grand totals. |
| `subtotals` | Include subtotals. |
| `all` | Include grand totals and subtotals. |
The default is `none`. Totals keep their semantic member kind, so charts,
tooltips, data tables, drill logic, and persisted models can distinguish them
from ordinary categories. Totals select the generated members available in the
current Pivot scope; they do not enable totals disabled in the Pivot configuration.
At a semantic root, parent aggregates already represent the immediate members,
so selecting subtotals need not add separate row categories.
## Chart-local queries
A linked chart can author its own `query` without changing the shared Pivot
layout or adding a shared filter contribution. A query can select registered
row and column fields, select existing configured measures, add member filters,
sort categories, and rank immediate row members with Top/Bottom N.
```ts
await chart.update({
query: {
slice: { rows: ['region'], columns: ['year'] },
filters: { status: ['Open'] },
topBottom: {
direction: 'top',
count: 5,
measure: { prop: 'sales', aggregator: 'avg', valueIndex: 0 },
otherLabel: 'Other regions',
},
},
});
// Reset a linked chart to the current Pivot query.
await chart.update({ query: {} });
```
The fields and measure identities must exist in the committed Pivot
configuration. Use `id` or the original configured `valueIndex` when the same
source measure appears more than once. Queries cannot define new aggregators
or calculated measures.
`query` updates replace the whole object. Omit it to preserve its authored
settings; pass `{}` to reset a linked chart. Chart-local member filters
intersect inherited member selections and chart-owned shared filters, including
when both constrain the same field.
In chart-query member selections, `null` denotes both missing and null source
values, and signed zeros use the same member identity.
Top/Bottom N ranks the immediate children of the current row scope. Ranking
uses the selected raw-measure aggregator within the requested column scope.
Ties retain source order and missing scores rank last. Without an explicit
sort, retained members follow ranking order and Other follows them.
Other contains the excluded source transactions, which are aggregated again
by Pivot. This preserves averages, distinct counts, custom aggregators, and
statistical samples. Ranking changes member presentation order while preserving
source transaction order for custom aggregators and samples. It never adds
displayed averages or distinct counts.
Without Other, excluded members also leave the query's totals; with Other,
their transactions remain. Other is a synthetic member and cannot drill into
fabricated children, filter the shared source, or identify a peer-chart member.
`charts.getDataShapingCapabilities()` reports current support and reasons.
Unsupported inherited ranking,
aggregate filters, or opaque filter execution fail with a reason instead of
discarding those constraints. Ranking or sorting by a calculated measure is
unsupported. Server queries require an opted-in complete-data provider.
The native query path conservatively rejects Advanced Filter and custom Core
filter evaluators, including when they appear inactive, because their public
state does not establish an exact source predicate for an isolated query.
Changing the slice layout resets chart drill and viewport state. A chart with
an independent layout uses chart-local or application-controlled drill;
Pivot-synchronized drill requires the shared layout. A sort preserves semantic
viewport endpoints. Filtering and ranking retain valid scopes or fall back to
a valid ancestor.
Unlinking freezes the shaped dataset and its descriptive query. Recalculating
or resetting that query requires relinking to the source.
In the **Data** tab, valid field, measure, sort, rank, and member-filter edits
update automatically. Incomplete or invalid edits remain local with validation
feedback. Drafts survive tab changes and earlier asynchronous acknowledgments.
**Reset query** clears local and applied query settings. Series/binding and
Pivot connection controls also apply immediately.
Fields already used on either axis cannot be added again. Independent layouts
show a drill-synchronization hint and require **Chart only** or **Application
event** for independent query edits. Calculated measures remain available for projection,
while sort and ranking selectors disable them.
Member lookup searches and pages in batches of 50, retains selected raw values
across pages, cancels obsolete requests, and provides loading, empty, error, and
retry states. Labels and summaries use `localeText.query.formatMemberValue`;
formatting never changes the predicate's raw value. Query status and recovery
text are customizable through `localeText.query.description`,
`immediateChanges`, `independentLayoutSync`, `memberLoading`, `memberEmpty`, and
`retryMembers`. UI policies can hide or disable
`query.independentLayoutSync`, `query.memberStatus`, `query.retryMembers`, and
`controls.connectionHint`, alongside the existing query and connection controls.
## Linked and unlinked data
A linked chart follows the current committed Pivot. Data, filters, sorting,
group expansion, source, Pivot model, and relevant theme changes schedule
coalesced refreshes.
Turning off **Keep linked to Pivot** stores the current bounded normalized
dataset in `model.dataset`. Later Pivot changes do not affect that chart.
Relinking removes the stored snapshot and projects the latest committed Pivot
state. Restore validates both linked models and embedded unlinked datasets.
The dialog's context strip identifies the linked chart or saved snapshot,
current category and series counts, totals policy, hierarchy, and available
Pivot/chart member filters. It also states when additional provider or Pivot
filters may apply; missing metadata is not treated as proof of an unfiltered
source. Unlinked charts retain their captured source context.
The **Data details** button opens a separate popup with the current data size,
link status, row and column hierarchy, totals policy, and member filters. Opening
it leaves the plot size unchanged. Close it with Escape, the close button, or a
click outside the popup.
## Current-frame data table
The header's **View data** action replaces the plot with a read-only RevoGrid
for the active drill frame. It:
- uses the same category order, series, and formatted display values as the
chart;
- represents missing observations consistently;
- virtualizes rows and columns, supports selection and column resizing, and
provides keyboard-operable sorting with sort announcements;
- does not alter the chart model, link state, Pivot, or drill state;
- switches back through **View chart**.
## Scope and limits
`getChartData` can request the immediate children of a semantic parent:
```ts
const dataset = await charts.getChartData({
chartType: 'groupedColumn',
totals: 'none',
scope: {
rowPath: ['Europe'],
columnPath: [2026],
},
});
```
Chart creation and `getChartData()` use default acquisition limits of 10,000
categories, 250 series, and 50,000 data points. The raw projector uses a
1,000-category default when no chart type is supplied. Plugin and chart-authored
limits override these defaults; see [server data and performance](../server-data-and-performance/#dense-data-behavior)
for precedence and rendering policies.
The plugin validates limits, actual counts, matrix dimensions, observations,
bindings, and chart compatibility. Unsafe projections fail with a
`PivotChartError` rather than silently changing persisted data.
[Previous: Chart types and compatibility](../chart-types/) ·
[Next: Drill-down and interactions](../drilldown-and-interactions/) ·
[API reference](/api/pivot-charts/)
## Source distributions
Box plots and violin charts use original numeric observations for one category
and measure. They never treat unrelated aggregated measure columns as samples.
Client Pivot queries the committed, filtered original source lazily when a
statistical chart is selected; calculated measures cannot supply raw samples.
The sample allocation is bounded by `maxDataPoints` and cached per data revision
and chart scope. No observations are silently sampled or truncated.
Server providers and standalone datasets can supply distributions. Preserve any existing `matrixCells`, fields, and observations when adding derived statistics:
```ts
dataset.analyticalFrame = {
...dataset.analyticalFrame,
fields: dataset.analyticalFrame?.fields ?? [],
observations: dataset.analyticalFrame?.observations ?? [],
derived: {
...dataset.analyticalFrame?.derived,
distributions: [{
kind: 'samples',
categoryId: dataset.categories[0].id,
seriesId: dataset.series[0].id,
samples: [1, 2, 3, 4, 100],
}],
},
};
```
Each category/series pair must be unique and refer to existing matrix members.
Samples must be finite numbers. Empty samples omit that mark; singleton and
constant samples remain valid. Quartiles use linear interpolation and whiskers
use the outermost observations within 1.5 interquartile ranges. Outliers retain
the actual observation value for tooltips and click events.
A provider can instead supply a `kind: 'summary'` entry with `count`, `minimum`,
`lowerQuartile`, `median`, `upperQuartile`, `maximum`, `lowerWhisker`, and
`upperWhisker`, plus optional `outliers` and `mean`. Violin charts additionally
require `density: [{ value, density }]`, ordered by strictly increasing value,
with finite nonnegative density and at least one positive density. Precomputed
summaries draw the supplied statistics; the renderer does not invent samples.
Derived distributions survive selection, caching, unlinking, and JSON model
save/restore. The older opaque `derived.quantiles` field remains accepted as
metadata but does not enable statistical charts. Range interval charts retain
their separate min/max semantics across bound matrix series.
Canonical field descriptors retain their semantic type and format through
selection and model persistence. Generated observation IDs are opaque; do not
construct or split them. For provider-owned observation IDs and fields, supply
the complete [`matrixCells` mapping](#provider-owned-analytical-identities).
The matrix remains authoritative for measures and display strings; mapped
metadata survives selection and model persistence.
## Category and time axes
`annotatedTimeSeries`, `smallMultiples`, `columnLine`, and `areaLine` accept the typed policy
`axes: { x: { scale: 'category' | 'time' } }`. Category spacing is the default.
Select **Customize → X-axis scale → Time** to position points by actual elapsed
time. Time input comes from the last semantic category path member, never its
localized display label. Accepted values are valid ISO calendar dates (UTC),
ISO timestamps with an explicit zone, and numeric epoch milliseconds.
Ambiguous civil timestamps, impossible dates, and empty total-member paths fail
with `INVALID_DATA_SHAPE`; exclude totals when using a time axis.
Time coordinates sort chronologically with stable original ordering for equal
instants. Tooltips and drill events retain the original category and value.
Every small-multiple panel shares the same exact X domain. Singleton/equal
instants are centered. Tick formatting uses UTC and available panel width.
Missing, null, or nonfinite Y values split paths before point sampling, so
sampled lines cannot connect across missing observations.
Axis settings survive JSON model persistence and linked refreshes. An axes
update replaces the axes configuration; `axes: {}` restores defaults. A
programmatic time policy on an unsupported chart type is rejected. The gallery
clears incompatible policies when switching chart types.
## Combination value axes and series overrides
`columnLine` and `areaLine` support independent primary (`axes.y`) and secondary
(`axes.ySecondary`) value axes. Each accepts `scale: 'linear' | 'log'`, finite
`min`/`max` bounds, `title`, a tick-only `unit`, and a serializable `tickFormat`:
```ts
await chart.update({
axes: {
y: { min: 0, max: 100_000, title: 'Revenue', unit: 'USD',
tickFormat: { notation: 'compact', maximumFractionDigits: 1 } },
ySecondary: { scale: 'log', min: 1, max: 1_000, title: 'Orders' },
},
seriesOverrides: {
[revenueSeriesId]: { type: 'column', yAxis: 'primary' },
[ordersSeriesId]: { type: 'line', yAxis: 'secondary' },
},
});
```
Overrides use stable dataset series IDs and accept `type: 'column' | 'line' |
'area'` and `yAxis: 'primary' | 'secondary'`. Unspecified series retain the
combination defaults: the first series is column/area, the remaining
series are lines, and every series uses the primary axis. Dormant IDs remain
in the model and apply when those series return. Multiple columns are grouped
side by side; areas draw before columns and lines. Time columns reserve space
at the endpoint groups and use bounded widths based on distinct instant gaps.
A log axis requires positive observations and bounds, and supports line series
only. Invalid assignments or domains fail with `INVALID_DATA_SHAPE` before
rendering. Explicit domains clip geometry without changing canonical values,
tooltips, or exports; fully clipped observations have no focus targets. Linear
column/area baselines use zero clamped to the visible domain. A secondary axis
reserves space only when an assigned active series has finite observations.
The primary title falls back to `presentation.yAxisTitle`; the secondary title
is independent. Units are never inferred. `tickFormat.notation` is `standard`,
`compact`, or `scientific`; optional minimum/maximum fraction digits are integers
from 0 to 20 with minimum no larger than maximum. Authored formatting uses the
runtime Intl locale; without it, ticks retain the locale text number formatter.
Runtime label customization receives axis identity `y` or `ySecondary`.
Axes and overrides are independently replaced on update. Omit a property or use
`undefined` to retain it; use `{}` to reset it. JSON restore, linked refresh,
and drill preserve both. Other chart types reject unsupported authored value
axes/assignments; clear these properties when switching programmatically.
Candlestick and OHLC role bindings can contain parallel arrays for multiple groups. Open/high/low helpers can broadcast, while each close field uniquely owns its group and X/Y assignments. See [candlestick and OHLC collections](/guides/charts/tools/axes/#candlestick-and-ohlc-collections). Volume-price adds one distinct paired volume measure per close owner and keeps shared price/volume Y roles.
---
# Drill-down and Interactions
URL: https://pro.rv-grid.com/guides/pivot-charts/drilldown-and-interactions/
Source: src/content/docs/guides/pivot-charts/drilldown-and-interactions.mdx
Description: Navigate Pivot hierarchies from chart marks and synchronize chart drill state with the Pivot or application.
Pivot Charts carry row and column hierarchy metadata into every compatible
dataset. A mark can therefore identify the exact semantic category, series,
paths, measure, aggregator, and child state that produced it.
Pivot Charts consume the report's committed aggregate values. Configure numeric/scalar eligibility, counts and execution pins in the [Pivot aggregation contract](/guides/pivot/concepts/aggregations/). Chart normalization, formatting or a shared dataset shape does not establish identical local/server results or increase arithmetic precision. A remote provider must supply complete aggregates under its backend policy, not averages or distinct totals reconstructed from a visible page.
## Cell-aware creation
With `pivotChartsUi.contextMenu: true` and `ContextMenuPlugin` installed,
right-clicking a Pivot analytical body cell contributes **Pivot Chart…**.
Creation resolves scope from committed Pivot metadata:
- group values open their immediate children;
- leaf values open siblings under the same parent;
- subtotal and grand-total cells use the nearest valid breakdown;
- the clicked column member and measure seed the series selection;
- values-on-rows retain the clicked category measure.
The context action composes with other menu contributions. It does not replace
application items or depend on visible DOM text.
## Mark activation
With the default `interaction.mode: 'drill'`, pointer click and keyboard
activation first look for an automatic semantic drill target. When the category or series can drill, activation starts the
appropriate row, column, or two-axis drill action and does not emit
`pivot-chart-node-click`. The event is emitted only when no automatic drill
target exists.
The chart ref supports row, column, and two-axis targets:
```ts
if (chart.canDrillDown({ axis: 'row', category })) {
await chart.drillDown({ axis: 'row', category });
}
await chart.drillDown({ axis: 'column', series });
await chart.drillDown({ axis: 'both', category, series });
```
Use `canDrillDown` before offering an application-owned drill control. It
checks hierarchy identity, level, child state, and the current active scope.
## Breadcrumb navigation
Each successful frame is stored in `PivotChartDrillState.frames`. The compact
breadcrumb shows the active row and column labels:
```ts
await chart.drillUp();
await chart.drillTo(1);
await chart.resetDrill();
```
Selecting **All data** resets to the root. Selecting an ancestor calls
`drillTo` directly; it does not replay every intermediate click. After a
successful UI drill, focus moves to the current breadcrumb item.
## Loading, cancellation, and retry
Drill loads are abortable and revision-checked:
- the last successful frame stays visible while the next frame loads;
- a newer action aborts or supersedes older work;
- stale provider completions are ignored;
- rendering failures roll back model and dataset state;
- successful scopes are cached;
- failed drill metadata is retained for `retryDrill()`.
The dialog displays the pending target, failure message, and retry action
without discarding the previous chart.
## Drill synchronization
`drillSyncMode` controls the relationship between chart navigation and the
Pivot frontier:
| Mode | Behavior |
| --- | --- |
| `chart` | Navigate only in the chart. |
| `pivot` | Synchronize the Pivot frontier and let the linked Pivot refresh drive the chart. |
| `both` | Commit the chart frame and synchronize the Pivot. |
| `event` | Emit `pivot-chart-drill-sync`; the application owns synchronization. |
For `pivot` and `both`, the active `PivotPlugin` applies the semantic frontier
unless `pivotCharts.synchronizeDrill` is provided. The custom adapter receives
the event detail and an `AbortSignal`.
```ts
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
defaultDrillSyncMode: 'event',
async synchronizeDrill(detail, { signal }) {
await saveAnalyticsRoute(detail.to, { signal });
},
};
```
## Persistent selection and cross-filtering
`interaction.mode` chooses what mark activation does. It is independent of
`drillSyncMode`:
| Mode | Behavior |
| --- | --- |
| `drill` | Use the existing automatic hierarchy navigation. |
| `select` | Commit a persistent semantic selection. |
| `filter` | Commit selection and apply its member predicates through Pivot. |
| `event` | Emit a selection proposal for the application without committing or drilling. |
These modes are supported by grouped columns/bars, line, column-line,
area-line, pie/donut, annotated time series, and small multiples. Other chart
families reject unsupported modes. Check the chart definition's
`interactionModes` before offering custom controls.
```ts
await chart.update({
interaction: {
mode: 'select',
selectionMode: 'multiple',
crossHighlight: true,
},
});
```
Ordinary activation replaces the selection, or clears an already selected
single target. In multiple mode, Ctrl /Meta activation
adds or removes a target. Pointer clicks and Enter /Space
use the same rules. Selected marks have a visible outline and `aria-pressed`
state. Use the clear action or Escape to clear selection. In Event
mode, these user actions request clearing; the application commits the change.
In Customize, compose a selection in Select mode, then choose **Apply selection
as filter**. The immediate Filter mode narrows the source chart as well as the
linked Pivot. Choose **Edit selection** to release that chart's filter while
keeping its selection, revise the selected members, and apply again. Other
charts' contributions and user filters remain active.
### Semantic identity and persistence
Selection targets store row and column `{ field, value }` members and an
optional measure identity. They do not store visible indexes or display labels:
```ts
await chart.setSelection([
{
row: [{ field: 'region', value: 'North' }],
column: [{ field: 'year', value: 2026 }],
measure: { prop: 'sales', aggregator: 'sum' },
},
]);
await chart.clearSelection();
```
Linked projections supply their committed layout fields. Standalone datasets
must provide hierarchy level fields or explicit
`selectionFields: { row: ['region'], column: ['year'] }`, aligned with category
and series paths. Missing or ambiguous mappings fail rather than interpreting
labels as source fields. Measure carriers in values-on-rows layouts never
become raw member predicates.
Strings, finite numbers, booleans, and null retain their typed identity.
Missing/null source members share the canonical blank value `null`; negative
zero is normalized to zero. Refresh, drill, reordering, and temporarily absent
members preserve semantic selection. Rendering only matches current members;
it does not replay a filter or emit activation events.
`interaction` updates replace the authored object: omit it to retain it or
use `{}` to reset. `selection: []` clears selection. Save/restore includes both
fields. Unlinking releases that chart's native filter; relinking or restoring a
linked Filter chart applies its persisted contribution once.
### Native filters and peer highlighting
A chart's selected tuples combine with OR, and fields inside a tuple combine
with AND. Selecting `(North, 2026)` and `(South, 2027)` therefore excludes
`(North, 2027)` and `(South, 2026)`. Independent chart owners, ordinary member
filters, Core source filters, and result filters combine with AND.
Pivot owns these contributions. Clearing, editing, unlinking, or destroying a
chart removes only its contribution. Contributions are ephemeral in Pivot
configuration; the chart model owns their persistence. If a layout change
removes a predicate field, Pivot drops the entire affected contribution after
a successful commit rather than weakening its tuples. Prevented, failed, or
superseded selection operations retain the committed chart model. If rendering
fails after filtering and restoring the previous native contribution also fails,
Pivot retires that owner and reports a selection error. Future queries omit the
owner; the last committed remote view remains until a successful refresh.
Destroying a chart disposes its filter owner even when ordinary configuration
updates are prevented. If the server cannot refresh during disposal, a selection
error reports the cleanup failure and the last committed remote view remains
visible. The disposed contribution is removed from future queries; it is not
retained as a hidden filter after its chart is gone.
Native filtering requires a linked active Pivot and meaningful source member
predicates. Dimensionless totals, synthetic Top/Bottom Other members, and
ambiguous field mappings cannot be used as raw filters. A server adapter that
explicitly advertises `filter: false` cannot apply a native chart filter.
Peer highlighting requires `crossHighlight: true` on both participating charts.
It compares shared dimension fields across layouts and leaves unrelated charts
unaffected. Measure identity distinguishes local selected cells; peer matching
uses dimensions. Highlight updates are silent and are not copied into each
peer's persisted selection.
### Application-controlled selection
In Event mode, listen for `pivot-chart-selection-request`, inspect its semantic
proposal, and explicitly call `setSelection` when the application accepts it:
```ts
grid.addEventListener('pivot-chart-selection-request', async event => {
const { chartId, proposed } = event.detail;
if (await acceptSelection(proposed)) {
await charts.setChartSelection(chartId, proposed);
}
});
```
`pivot-chart-before-selection` is preventable. Committed changes emit
`pivot-chart-selection-changed`; failures emit `pivot-chart-selection-error`.
Details include chart ID, operation ID, revision, origin, interaction mode,
proposed selection, and current selection. Received highlighting and redraws
never emit these selection actions.
Custom user Clear controls should call
`charts.requestChartSelectionClear(chartId, origin)` so Event mode can delegate
the request. `chart.clearSelection()` and `charts.clearChartSelection(chartId)`
are explicit application commits, including in Event mode.
## Zoom, pan, and the navigator
Navigation controls share one persisted category/time viewport (`viewport.x`).
They change the visible
window while retaining the complete chart dataset, Pivot filters, and semantic
selection. Payload limits still apply to the complete dataset.
The built-in renderer supports category navigation for line and area charts
(including smooth, stepped, stacked, and 100% stacked variants), grouped,
stacked, and 100% stacked columns and bars, plus annotated time series, small
multiples, column-line, and area-line. Horizontal bars navigate along their
vertical category axis; their navigator remains horizontal. Explicit zoom,
navigator, or viewport settings replace automatic dense category scrolling
while active. The ordinary line, area, column, and bar types retain a category
axis; time navigation remains available on annotated time series, small
multiples, and the two combination types. Check the chart definition's `navigation`
capability and the configured renderer's `capabilities.navigation` before
offering custom controls. Unsupported authored settings are rejected.
```ts
await chart.update({
navigation: {
enabled: true,
navigator: true,
crosshair: true,
tooltipMode: 'shared',
},
viewport: {
x: { scale: 'category', startId: firstCategory.id, endId: lastCategory.id },
},
});
// Restore the full X domain, retaining navigation controls.
await chart.update({ viewport: {} });
```
For a time axis, use absolute UTC epoch milliseconds within the supported date
range:
```ts
await chart.update({
axes: { x: { scale: 'time' } },
viewport: { x: { scale: 'time', min: Date.UTC(2026, 0, 1), max: Date.UTC(2026, 2, 1) } },
});
```
The viewport scale must match the chart's category/time axis policy. Both `navigation` and `viewport` use
replacement updates: omit them to preserve their values, or pass `{}` to reset
them. Navigation is opt-in; it does not change the default interaction mode.
With navigation enabled, wheel input zooms around the pointer, dragging pans the
window, and touch supports category-axis pan and pinch zoom. Horizontal bars
pan vertically and leave horizontal touch scrolling available; other charts
pan horizontally and leave vertical touch scrolling available. A claimed drag suppresses its activation click;
ordinary clicks keep the configured Drill, Select, Filter, or Event behavior.
Escape cancels an active gesture.
The navigator's start and end handles resize the same window; dragging its
selected band pans it. Focus a handle or the band to use arrow keys, with
Shift for a larger step and Home /End for a
boundary. Separate zoom, pan, and reset buttons support keyboard operation
without changing the arrow-key navigation of chart observations.
Category windows store semantic category IDs. Reordering resolves the inclusive
interval between the current positions of both endpoints. If either endpoint
disappears, the authored window remains dormant and the full domain is shown
until both return. Time windows retain their absolute bounds; a window outside
the current domain displays an empty plot with a reset action.
Refresh, unlink, relink, and saved-state restore retain the viewport when the
semantic scope survives. Successful drill navigation or fallback to a valid
ancestor scope resets it; failed navigation retains it. The
automatic Y domain continues to use the full dataset, so panning does not change
the apparent magnitude of values. Explicit Y bounds remain authoritative.
## Crosshair and shared tooltips
`navigation.crosshair` follows the nearest visible category. With
`tooltipMode: 'shared'`, the existing tooltip card shows the active series for
that category, including missing values and their display formatting. Matching
uses category identity, so identical labels do not merge distinct members.
Crosshair and shared tooltip state is transient and does not select, filter,
drill, or broadcast highlighting to another chart.
Shared cards show up to 20 series and announce the omitted count. Custom tooltip
formatters retain the existing primary datum context and receive optional
`sharedItems` and `omittedSharedItemCount`; each shared item includes its original
category/series index, value, display value, color, and authored axis unit.
Keyboard focus follows the focused member, including distinct categories with
the same timestamp. Pointer lookup uses a deterministic member for tied times
and can inspect a visible category omitted by dense mark sampling.
## Legend focus and visibility
Series legends are keyboard-accessible guides. By default, activating
a legend item focuses that series, emphasizes matching marks, and mutes the
others. Activating it again or selecting the chart background clears the
focus. When a dense legend cannot fit, it shows a deterministic subset and a
localized overflow count.
On charts supporting independent series visibility, set
`presentation.legendAction: 'toggleVisibility'` to persist show/hide changes
instead. Enter, Space, and pointer activation share the same action. Hidden
series remain available for restoration; the canonical dataset and shared
Pivot remain unchanged. The Customize controls provide the full series list
when the SVG legend overflows.
## Semantic tooltips
Interactive marks use an application-owned tooltip card rather than native SVG
`title` text. The default tooltip includes:
- the complete category path;
- series identity and formatted value;
- the resolved mark color;
- X, Y, and size rows for relationship charts;
- a drill hint when the datum has children.
Pointer tooltips stay inside the viewport. Keyboard focus opens the same card
immediately and connects it with `aria-describedby`. Escape closes
the tooltip without closing the chart dialog; selection modes also clear the
selection, or request clearing in application-controlled Event mode. Gallery previews intentionally
do not create tooltips.
## Keyboard and accessibility
- The modeless dialog has a stable accessible name and restores focus when it
closes. Tab can leave the chart to reach the grid. A menu or chart control that
handles Escape keeps the outer dialog open.
- Gallery tabs, cards, breadcrumbs, legend items, chart marks, export actions,
and the data table are keyboard operable.
- Marks have localized accessible names built from category, series, formatted
value, relationship channels, and drillability.
- View data uses a read-only RevoGrid with a descriptive label, keyboard sorting,
cell selection, and sort announcements.
- Status regions announce loading, drill success, failure, and data-table sort
changes without replacing visible content.
Charts use one Tab stop for observation and aggregate inspection at every dataset
size. Tab and Shift+Tab enter or leave inspection; arrow keys move between
displayed targets, and Home /End reach the first/last.
Enter or Space activates a real datum according to the
interaction mode. Aggregate bins, analytical overlays, forecasts and synthetic
Others are inspection-only; activating them never selects a representative
source observation. Focus follows a retained observation through updates.
For the facts behind a selected observation, enable the shared
[underlying-records action](/guides/charts/tools/underlying-records/).
It uses Pivot's existing paginated drillthrough API, rather than treating the
chart's aggregated data table as a list of source transactions.
Aggregate tooltips use localized inspection text. They do not call the datum
tooltip formatter, whose context requires an individual category and series.
The range-interval chart's mean across several measure values is also an
inspection target, rather than a cell belonging to one of those measures.
Box and violin descriptions include their distribution statistics and retain
drill hints where applicable. A precomputed summary without a mean does not
announce an estimated mean.
Every chart provides a localized description of its data dimensions and actual
inspection targets. Annotated time series and small multiples also describe
finite source and displayed observations and sampling omissions. Navigation
visits displayed targets; sampling never creates
additional observations. Redraws preserve the focused semantic datum where
possible, use a nearby displayed datum when it disappears, and leave focus
outside the chart untouched. Empty charts have no observation navigation
instructions, and gallery previews remain decorative.
Provide additional context with `presentation.description`, a plain-text string
of up to 4096 UTF-16 code units. It is saved with the chart and linked to the chart's
accessible description. An empty string clears it; an omitted or `undefined`
update preserves it. Description edits do not reload provider data.
The existing `highContrast` and `highContrastDark` themes are available alongside
the other chart themes. Forced-color styling preserves focus and selected-control
boundaries. Use supported marker shapes, line dashes, or member patterns when
series need differentiation beyond color, and **View data** to inspect exact
values in a table.
## Drill and interaction events
| Event | Purpose |
| --- | --- |
| `pivot-chart-node-click` | Reports the activated semantic datum and its paths/measure when no automatic drill target exists. |
| `pivot-chart-before-selection` | Cancelable proposal before a selection commit. |
| `pivot-chart-selection-changed` | Reports committed semantic selection. |
| `pivot-chart-selection-request` | Delegates Event-mode selection to the application. |
| `pivot-chart-selection-error` | Reports a failed selection/filter transition. |
| `pivot-chart-before-drill` | Cancelable event before a drill is committed. |
| `pivot-chart-drill-start` | Announces a pending drill frame. |
| `pivot-chart-drilled` | Supplies the committed model and dataset. |
| `pivot-chart-drill-error` | Reports the failed transition and error. |
| `pivot-chart-drill-reset` | Reports a successful reset to root. |
| `pivot-chart-drill-sync` | Delegates `pivot`, `both`, or `event` synchronization. |
Event payloads clone JSON-safe semantic state. See
[Headless API and persistence](../headless-api-and-persistence/) for the full
lifecycle event list.
[Previous: Data and bindings](../data-and-bindings/) ·
[Next: Customization and export](../customization-and-export/) ·
[API reference](/api/pivot-charts/)
---
# Headless API and Persistence
URL: https://pro.rv-grid.com/guides/pivot-charts/headless-api-and-persistence/
Source: src/content/docs/guides/pivot-charts/headless-api-and-persistence.mdx
Description: Create, update, drill, export, save, restore, and destroy Pivot charts through the plugin API.
`PivotChartsPlugin` is the source of truth for chart data and lifecycle.
`PivotChartsUiPlugin` is a client of that API; applications can build their
own gallery, settings, containers, or workspace without replacing the data
model.
For normal application-owned charts, use `mountPivotChart()`:
```ts
import { mountPivotChart } from '@revolist/pivot';
const chart = await mountPivotChart(grid, {
container: document.querySelector('#dashboard-chart')!,
});
```
Pivot Charts consume the report's committed aggregate values. Configure numeric/scalar eligibility, counts and execution pins in the [Pivot aggregation contract](/guides/pivot/concepts/aggregations/). Chart normalization, formatting or a shared dataset shape does not establish identical local/server results or increase arithmetic precision. A remote provider must supply complete aggregates under its backend policy, not averages or distinct totals reconstructed from a visible page.
## Application-owned containers
The container does not need to be inside the grid. The default
`controls: 'none'` path renders only the chart and requires no UI plugin.
Request `controls: 'standard'` when the same container should include the
built-in configuration experience:
```ts
const chart = await mountPivotChart(grid, {
container: document.querySelector('#chart-workspace')!,
controls: 'standard',
});
```
`controls: 'standard'` requires `PivotChartsUiPlugin`; chart-only mounting does
not. Both return the same `PivotChartRef`. Give the mount container an explicit
height. The built-in renderer fills the available size and observes later
layout changes, including a mounted container that becomes visible later.
Treat the container as an empty chart mount point: mounting replaces its
children, and `chart.destroy()` leaves it empty.
Configuration controls are optional application UI. Each control can call the
same chart ref without recreating the renderer:
```ts
chartTypeSelect.addEventListener('change', async () => {
await chart.update({
chartType: chartTypeSelect.value as PivotChartType,
});
});
dataLabelsCheckbox.addEventListener('change', async () => {
await chart.update({
presentation: { dataLabels: dataLabelsCheckbox.checked },
});
});
```
See the [Pivot Charts overview](../) for complete minimal markup and setup for
chart-only, standard-control, and component-lifecycle integrations.
## Direct plugin access
Use the plugin instance when an advanced workflow needs collection-wide
methods such as restore, `getChartModels()`, or `destroyAllCharts()`:
```ts
const charts = (await grid.getPlugins()).find(
(plugin) => plugin instanceof PivotChartsPlugin,
) as PivotChartsPlugin;
```
## Plugin methods
| Method | Purpose |
| --- | --- |
| `getChartData(options?)` | Project and validate a cached immutable dataset for the current Pivot revision and optional semantic scope. |
| `createChart(params)` | Create a model, resolve data, mount a renderer instance, and return `PivotChartRef`. |
| `updateChart(chartId, patch)` | Validate and queue a model/data/presentation update. |
| `setChartSelection(chartId, selection)` | Commit owned semantic selection and apply Filter-mode predicates. |
| `clearChartSelection(chartId)` | Clear selection and release only this chart's filter contribution. |
| `requestChartSelectionClear(chartId, origin?)` | Handle a custom UI clear action: request an empty selection in Event mode, or commit clearing in other modes. |
| `getChartRendererCapabilities(chartId)` | Inspect the creation-time backend's supported types, interaction modes, options, and graphics features. |
| `getChartRenderer(chartId)` | Obtain the creation-time renderer factory for matching previews or other advanced integrations. |
| `getChartDownloadCapabilities(chartId)` | Discover CSV and the existing renderer instance's graphics formats and PDF encoding. |
| `getChartCSV(chartId, options?)` | Encode committed chart data without downloading or loading a provider. |
| `refreshChart(chartId)` | Reproject a linked chart from the current committed Pivot. |
| `canDrillDown(chartId, target)` | Test a semantic row, column, or two-axis drill target. |
| `drillDownChart(chartId, target)` | Load and commit a child frame. |
| `drillUpChart(chartId)` | Navigate to the parent frame. |
| `drillToChart(chartId, frameIndex)` | Jump to a stored breadcrumb frame. |
| `resetChartDrill(chartId)` | Return to the chart's root frame. |
| `retryChartDrill(chartId)` | Retry the last failed drill transition. |
| `getChartModels()` | Return cloned serializable models for every active chart. |
| `restoreChart(model, options)` | Validate a saved model and recreate it in a supplied container. |
| `downloadChart(chartId, options?)` | Download advertised PNG/SVG/PDF graphics or plugin-owned chart-data CSV. |
| `getChartImageDataURL(chartId, options?)` | Request an image data URL from a capable renderer. |
| `destroyChart(chartId)` | Destroy one renderer and release its state. |
| `destroyAllCharts()` | Destroy every chart owned by the plugin. |
All updates for a chart are serialized. A later linked refresh cannot
partially overwrite an earlier options update.
## Create and update
```ts
const chart = await charts.createChart({
container: document.querySelector('#sales-chart')!,
chartType: 'groupedColumn',
linked: true,
totals: 'none',
bindings: {},
initialScope: {
rowPath: ['Europe'],
rowLabels: ['Europe'],
columnPath: [2026],
columnLabels: ['2026'],
},
presentation: {
title: 'European sales',
legendPosition: 'bottom',
},
theme: 'auto',
chartOptions: {
showGridLines: true,
markOpacity: 0.9,
},
drillSyncMode: 'chart',
});
await chart.update({
chartType: 'stackedColumn',
totals: 'grand',
presentation: { subtitle: 'Including grand total' },
});
```
Data-affecting patches reproject or validate a snapshot. Presentation, theme,
and chart-option changes reuse the current dataset. Failed renderer updates
roll back to the previous model and rendered input.
Patch values are owned before entering the chart's queue. Omitted properties
preserve their current value. `presentation` merges its supplied fields; axes,
bindings, chart options, query, navigation, viewport, colors, annotations, and
series overrides replace their authored objects. Pass `{}` to reset those
objects. `selection` replaces the selection array, and `seriesIds: []` selects
all configured series where the chart supports them. Changing chart type clears
chart options unless the same patch supplies options for the new type.
Render-only changes, including axes, viewport, presentation, and theme, preserve
pending fresh data and drill caches. When that refresh completes, the chart uses
the fresh data with its latest render settings. A source-changing query
deliberately supersedes earlier source work; if the new operation fails, the
previous committed chart remains available.
## `PivotChartRef`
The ref scopes common operations to one chart:
```ts
chart.id;
chart.container;
chart.model;
await chart.update({ linked: false });
chart.canDrillDown({ axis: 'row', category });
await chart.drillDown({ axis: 'row', category });
await chart.drillUp();
await chart.drillTo(0);
await chart.resetDrill();
await chart.retryDrill();
chart.getDrillState();
await chart.setSelection(selection);
await chart.clearSelection();
chart.focus();
chart.destroy();
```
`model` and `getDrillState()` return clones. Mutating them does not update the
active chart; call `update` or a drill method.
## Model and persistence
`PivotChartModel` version 2 contains:
- stable `chartId` and `chartType`;
- link state and an optional unlinked `dataset` snapshot;
- totals, series selection, values-on-rows measure, and semantic bindings;
- presentation, renderer theme, and validated chart options;
- authored axes, per-series overrides, interaction mode, and semantic selection;
- navigation controls and the authored category/time viewport;
- the authored chart-local query, including its slice, filters, sort, and ranking;
- breadcrumb/drill scope and drill synchronization mode.
```ts
const saved = charts.getChartModels();
localStorage.setItem('pivot-charts', JSON.stringify(saved));
const restoredModels = JSON.parse(
localStorage.getItem('pivot-charts') ?? '[]',
);
for (const model of restoredModels) {
await charts.restoreChart(model, {
container: createWorkspacePanel(model.chartId),
});
}
```
Restore parses the current model version, checks chart identity, renderer
support, options, bindings, selected series, hierarchy paths, dataset shape,
limits, and linked/unlinked rules. Optional `model.limits` contains the authored
partial budget overrides and survives JSON save/restore. Missing limits in an existing version 2 model inherit
plugin configuration. Restoring captured data validates it against the effective
budget before renderer creation. Older model versions are rejected rather
than implicitly migrated. Supply `chartId` in `PivotChartRestoreOptions` only
when the application intentionally replaces the persisted identity.
## Custom renderer contract
```ts
const renderer: PivotChartRenderer = {
capabilities: {
supportedTypes: ['groupedColumn', 'line'],
download: true,
downloadFormats: ['png'],
imageDataUrl: true,
},
create(input) {
const instance = mountApplicationChart(input);
return {
update(nextInput) {
instance.update(nextInput);
},
focus() {
instance.focus();
},
async download(options) {
await instance.downloadPng(options);
},
async getImageDataURL(options) {
return instance.getPngDataUrl(options);
},
destroy() {
instance.destroy();
},
};
},
};
```
`PivotChartRenderInput` contains the chart ID, container, chart type, validated
dataset, bindings, presentation, chart options, axes, series overrides, theme,
locale text, and `chart`/`preview` render mode. It also supplies interaction mode,
persistent selection, derived peer `highlightSelections`, and activation and
clear callbacks. Call `onNodeClick` for semantic mark activation and
`onSelectionClear` for a user clear action; the plugin dispatches these according
to the interaction mode. A custom renderer must not mutate the supplied dataset
or model state, or persist peer highlights as local selection.
A renderer can implement optional synchronous `validate(input)` preflight.
Throw when the complete candidate input requests a feature the backend cannot
preserve. The plugin runs this hook before renderer creation/update and native
selection-filter effects; the hook must be side-effect-free. Existing renderers
can omit it. Direct renderer consumers must also receive the same validation
from the renderer's own `create` and `update` methods.
Configure the renderer before creating a chart. Renderer injection is a runtime
configuration, and is not serialized in the chart model. Replacing that
configuration does not migrate an existing chart instance to another backend.
Each chart retains its captured factory, validator, and capability contract.
Changing `pivotCharts.renderer` applies to newly created charts. Existing chart
controls, previews, updates, and exports continue using their original backend.
The [optional ECharts Canvas adapter](../server-data-and-performance/#optional-echarts-canvas-renderer)
uses this boundary with an explicit supported feature set.
Navigation-capable renderers receive the canonical dataset, authored `navigation`
and `viewport`, and `onViewportChange`. Request a semantic window through that
callback instead of modifying Pivot filters or the supplied dataset. Window-only
updates use the chart's update queue and do not load another provider dataset.
Custom renderers must advertise `capabilities.navigation: true` to accept
authored navigation or viewport settings. Omitting that capability preserves
existing renderer integrations and rejects unsupported navigation requests.
The UI intersects the chart catalog with the existing backend's capabilities.
`interactionModes` and `chartOptionKeys` can restrict modes and option controls;
omitting them uses the catalog defaults. An empty `interactionModes`
list disables authored interaction. A backend's `validate` hook remains
responsible for additional option combinations and data-specific restrictions.
Download and image actions are available only when the renderer advertises and
implements them.
## Standard UI API
`PivotChartsUiPlugin.openChart(params)` accepts every
`PivotChartCreateParams` field except `container`; the UI owns its chart
container. `pivotChartsUi.dialogParent` can move the modeless host into an
application overlay root. Closing the dialog destroys its chart.
`PivotChartsUiPlugin.mountChart(params)` is the lower-level equivalent of
`mountPivotChart(..., { controls: 'standard' })`. It embeds the existing
gallery, Data, Customize, current-frame table, export, and drill controls in
the supplied application container. Prefer `mountPivotChart()` unless the UI
plugin instance is already part of a larger plugin-management workflow.
## Lifecycle events
| Event | Detail |
| --- | --- |
| `pivot-chart-created` | Chart ID and `PivotChartRef`. |
| `pivot-chart-updated` | Chart ID, update reason, and cloned model. |
| `pivot-chart-destroyed` | Chart ID. |
| `pivot-chart-node-click` | Semantic category/series, paths, measure, and aggregator. |
| `pivot-chart-error` | Optional chart ID and normalized `Error`. |
| `pivot-chart-before-download` | Cancelable download proposal with owned model/options, format, and operation ID. |
| `pivot-chart-downloaded` | The download action completed; this does not confirm that the browser saved a file. |
| `pivot-chart-download-error` | A download failed or was aborted, with its operation ID and error. |
| `pivot-chart-before-selection` | Cancelable semantic selection proposal. |
| `pivot-chart-selection-changed` | Committed selection with origin, operation ID, and revision. |
| `pivot-chart-selection-request` | Event-mode proposal for explicit application acceptance. |
| `pivot-chart-selection-error` | Failed selection/filter transition or cleanup. |
| `pivot-chart-before-drill` | Cancelable transition with from/to drill state. |
| `pivot-chart-drill-start` | Pending drill transition. |
| `pivot-chart-drilled` | Successful transition with model and dataset. |
| `pivot-chart-drill-error` | Failed transition and error. |
| `pivot-chart-drill-reset` | Successful reset transition. |
| `pivot-chart-drill-sync` | Synchronization request and selected mode. |
Update reasons are `data`, `options`, `theme`, `relink`, and `drill`.
[Previous: Customization and export](../customization-and-export/) ·
[Next: Server data and performance](../server-data-and-performance/) ·
[API reference](/api/pivot-charts/)
---
# Server Data and Performance
URL: https://pro.rv-grid.com/guides/pivot-charts/server-data-and-performance/
Source: src/content/docs/guides/pivot-charts/server-data-and-performance.mdx
Description: Supply complete semantic chart frames for remote Pivots and understand validation, cancellation, caching, limits, and dense rendering.
A remote Pivot viewport is only a window into the analytical result. Pivot
Charts never treat the visible rows or columns as the complete dataset.
Configure `PivotChartDataProvider` to return the complete immediate-child frame
for every requested semantic scope.
Pivot Charts consume the report's committed aggregate values. Configure numeric/scalar eligibility, counts and execution pins in the [Pivot aggregation contract](/guides/pivot/concepts/aggregations/). Chart normalization, formatting or a shared dataset shape does not establish identical local/server results or increase arithmetic precision. A remote provider must supply complete aggregates under its backend policy, not averages or distinct totals reconstructed from a visible page.
## Configure a provider
```ts
grid.pivotCharts = {
renderer: createPivotChartsRenderer(),
dataProvider: {
async load(request, { signal }) {
const response = await fetch('/api/pivot/chart-data', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
signal,
});
if (!response.ok) {
throw new Error(`Chart data failed: ${response.status}`);
}
return response.json();
},
},
};
```
The provider is used for root projection, explicit scoped data, linked
refreshes, and drill frames. Local and server Pivots produce the same
`PivotChartDataset` contract after projection and validation.
## Request contract
`PivotChartDataRequest` includes:
| Field | Purpose |
| --- | --- |
| `requestId` | Unique request identity that the response must echo. |
| `viewId` and `fieldsVersion` | Identify the remote analytical view and field registry. |
| `loadOptions` | Committed Pivot filters, sorting, grouping, and engine options. |
| `uiState` | Optional Pivot UI hints. |
| `axisState` | Expanded row and column paths. |
| `chart.seriesIds` | Requested series selection. |
| `chart.categoryMeasure` | Values-on-rows measure selection. |
| `chart.totals` | Requested totals mode. |
| `chart.limits` | Effective category, series, and point limits. |
| `chart.bindings` | Stable semantic field-to-channel bindings. |
| `chart.scope` | Exact committed revision, row/column paths, and immediate-child depths. |
| `chart.query` | Optional normalized chart-local slice, member filters, sort, and ranking. |
The semantic scope is absolute:
```ts
interface PivotChartSemanticScope {
revision: number;
rowPath: PivotPath;
columnPath: PivotPath;
rowDepth: number;
columnDepth: number;
}
```
The response must contain every immediate child for that scope, not a
viewport-sized subset.
## Response contract
```ts
interface PivotChartDataResponse {
requestId: string;
scope: PivotChartSemanticScope;
categories: PivotChartCategory[];
series: PivotChartSeries[];
values: Array>;
displayValues?: Array>;
hierarchies: {
row?: PivotChartHierarchyDefinition;
column?: PivotChartHierarchyDefinition;
};
totalCategoryCount: number;
totalSeriesCount: number;
}
```
`categories.length` and `series.length` describe the returned complete frame.
The total counts let the client prove that the response is complete and within
limits. Hierarchy definitions and member hierarchy metadata are required when
the response contains drillable members.
## Strict validation
The client rejects:
- a response whose `requestId` differs from the request;
- any change to revision, row/column paths, or requested depths;
- partial frames whose returned and total counts disagree;
- value/display matrices with wrong dimensions;
- duplicate or unstable category/series IDs;
- malformed hierarchy levels or member paths;
- incompatible bindings, selected series, or values-on-rows measures;
- categories, series, or point counts above the effective limits;
- data that violates the selected chart's semantic constraints.
There is no visible-window fallback. A partial response would create a
misleading chart and is treated as an error.
## Chart-local query providers
A provider that executes chart-local analytical queries must opt in with
`capabilities: { query: true }`. Existing providers continue serving charts
without an authored query.
```ts
dataProvider: {
capabilities: { query: true },
async load(request, { signal }) {
// Execute the complete analytical query on the backend.
return loadCompleteChartFrame(request, signal);
},
}
```
For shaped requests, the response must include `query` matching the normalized
`request.chart.query`, alongside the existing request and semantic scope
identities. Missing or different query echoes are rejected. Nonempty frames
must include each requested measure for every semantic member, including null
values, unless an explicit series or category-measure selection narrows them.
The inherited
`loadOptions` remain part of the request; chart-local filters intersect them.
The provider executes ranking globally within the requested semantic scope
before returning complete counts. It builds Other from source transactions
and reaggregates the configured measures. Ranking a remote page or adding its
displayed averages and distinct counts cannot satisfy this contract.
Statistical distributions must describe the same shaped source.
`charts.getDataShapingCapabilities()` reports current support and reasons.
Provider opt-in establishes an integration contract; the frontend cannot
independently prove backend aggregation correctness.
## Cancellation and stale results
Every provider call receives an `AbortSignal`. Abort underlying HTTP, query,
and decoding work when possible. Root loads, linked refreshes, and drill
transitions carry monotonically increasing revisions. The plugin ignores a
completion that is no longer current even if the provider did not stop after
abort.
During drill, the last successful frame remains rendered. During a linked
refresh, the UI exposes a loading state and commits the replacement only after
data validation and renderer update succeed.
## Caching
The plugin caches immutable datasets by committed Pivot revision, effective
data options, bindings, and semantic scope. Concurrent equivalent requests
share the same promise. Successful drill scopes are cached per chart so
breadcrumb navigation does not repeatedly load unchanged data.
Replacing `grid.pivotCharts`, changing the Pivot model/source/filter/sort/group
state, or advancing the committed revision invalidates the relevant cache.
Application/server caches should use the same request identity inputs and must
still return the exact requested scope.
## Dense-data behavior
Defaults are:
```ts
const DEFAULT_PIVOT_CHART_LIMITS = {
maxCategories: 1_000,
maxSeries: 250,
maxDataPoints: 50_000,
};
```
Chart requests use a 10,000-category acquisition default instead. Categorical charts without scrolling automatically choose a Top N from chart type and available area, plus a synthetic Others group, recalculated from source records. The chart explains the reduction. Ordered, relationship and statistical charts keep their own rendering policies.
The built-in renderer automatically shows a native scrollbar when categories
no longer fit: horizontal for columns, vertical for bars. It draws only the
visible category window and keeps the value axis based on all categories.
Scrolling preserves datum identities; CSV/data exports retain the full dataset,
and images capture the visible plot. Scroll position is local display state,
not persisted chart-model state. The raw projector retains the defaults above when no chart type is supplied.
Effective limits apply in this order: built-in defaults, plugin `limits`, then
chart-authored `limits`. The chart model stores only authored overrides, so
omitted keys inherit the current plugin configuration on later operations.
```ts
const chart = await charts.createChart({
container,
limits: { maxCategories: 2_000, maxDataPoints: 100_000 },
});
await chart.update({ limits: { maxCategories: 3_000 } }); // replace overrides
await chart.update({ limits: {} }); // inherit plugin limits again
```
A limits update replaces the authored object; `undefined` leaves it unchanged.
Values must be finite and at least 1, and are rounded down to safe integers.
Unknown keys and non-plain objects are rejected. Limits are budgets, not data
transformations: exceeding one reports `LIMIT_EXCEEDED` and retains the prior
chart. A limits-only update validates the current frame before requesting data;
a combined selection update validates the newly selected frame. Unlinked charts
validate their captured data without requiring an active Pivot.
Matrix points count every category/series cell, including null cells. Statistical
payloads have a separate point budget: raw sample counts, or one summary plus its
outlier and density entries. Both budgets must fit `maxDataPoints`; they are not
added together. Providers receive the effective limits for root, refresh, and
drill requests. The plugin validates returned payloads before caching/rendering.
Within those limits, the renderer degrades deliberately:
- every legend position reserves a valid plot rectangle;
- long legends show a deterministic subset and localized hidden count;
- category ticks and data labels are thinned without removing data;
- final text layout measures localized and customized fonts, fits long names
with ellipses and full hover captions, and omits labels that cannot fit without
overlapping another label. Numeric data labels are omitted rather than shortened;
- Cartesian marks retain exact values;
- density scatter, histograms, strip plots, and large heatmaps can aggregate
observations into rendered bins; those bins expose inspection descriptions
without claiming an individual observation's selection or drill identity;
- unstacked line/area families, combination line/area layers, annotated time
series, and small multiples sample dense paths while retaining real source
points, extrema, and separate finite runs;
- charts with more than 240 rendered inspection targets use one Tab stop with
arrow-key navigation. Dense canonical marks remain reachable; source matrix
dimensions alone do not determine the keyboard policy.
Sampling affects rendered geometry, not the persisted normalized dataset.
Exceeding configured projection limits fails rather than silently truncating
the model.
KPI, gauge and progress-gauge views limit visible tiles to the container's
readable capacity. A localized sampling footer and accessible description report
the source and rendered row counts. Resizing recalculates the capacity; View data
and exports retain the complete dataset. The card itself remains an inspection
target even when its optional text cannot fit.
## Bounded paths and data tables
Sampled SVG paths share a chart-wide budget of at most 4,800 canonical point
marks. Each series also has a pixel-based budget and its family's existing cap.
Sampling retains actual first, minimum, maximum, and last points in each retained
bucket in source order when the allocated budget can represent them. Null values split paths before sampling. When there are more
finite runs than the budget can represent, the renderer keeps a bounded set of
separate runs and reports omitted observations; it never draws a line across an
omitted gap.
Sampling changes drawing work. Canonical values, scale domains, saved data,
supported semantic annotations, and shared crosshair lookup remain complete. Localized
chart descriptions report the source and rendered counts. Column rectangles and
stacked/normalized geometry retain their complete visible cells, so their DOM
cost still grows with the dataset. Projection limits remain the safety boundary
for those families.
View data uses RevoGrid's native row and column virtualization, keeping every
canonical cell reachable by scrolling. Sorting applies to the complete frame,
with stable ties and null values last in both directions. The grid is read-only;
selection and column resizing remain available.
## Optional ECharts Canvas renderer
Applications can inject a modular ECharts runtime through the existing renderer
configuration. Pivot does not import or fetch ECharts at runtime:
```ts
import * as echarts from 'echarts/core';
import { BarChart, LineChart } from 'echarts/charts';
import { GridComponent, LegendComponent, TitleComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { createPivotChartsEChartsRenderer } from '@revolist/pivot';
echarts.use([
BarChart, LineChart, GridComponent, LegendComponent,
TitleComponent, TooltipComponent, CanvasRenderer,
]);
grid.pivotCharts = {
renderer: createPivotChartsEChartsRenderer(echarts),
};
```
Choose the renderer before creating a chart. The adapter supports
`groupedColumn`, `groupedBar`, and `line`, basic presentation, grid lines, and
PNG export. Pointer activation uses the original canonical cell. A bounded HTML
inspector provides keyboard access to finite values without creating one DOM
node per value. Null line values remain gaps.
The adapter rejects authored axes, colors, series overrides, navigation,
annotations, selection, cross-highlighting, and unsupported chart options.
It preserves the complete dataset and existing projection limits. It does not
provide automatic aggregation, SVG/vector export, or an automatic backend switch.
Use the default SVG renderer when those features are required.
## Renderer preview performance
The gallery uses the same chart definitions and renderer functions as full
charts, but with `renderMode: 'preview'`. Preview mode is decorative and omits
tooltips and interactive focus targets. Intersection-based mounting defers a
preview until its card approaches the viewport. SVG snapshots are cached by
type, renderer, theme, and locale. Canvas previews are recreated on remount,
because cloning DOM nodes does not copy a Canvas bitmap. Their compact layout
hides axes, titles, legends, and labels.
## Geographic capability
Geographic views are opt-in because map geometry, projections, tiles, and
licenses belong to the provider:
```ts
const pack = createPivotChartGeographicCapabilityPack(provider);
const host = await pack.render({
container,
chartType: 'choropleth',
dataset,
bindings: { geographic: ['dimension:country'], value: ['measure:sales'] },
});
```
The pack exposes `choropleth`, `proportionalSymbolMap`, and
`geographicHeatmap`. These views are not built-in gallery types and do not
change `PivotChartModel`. Importing Pivot Charts alone loads no geographic
assets or map dependency.
## Production checklist
- Return complete immediate-child frames from the analytical engine.
- Preserve stable field, category, series, hierarchy, and path identities.
- Echo the request ID and semantic scope exactly.
- Forward abort signals to network and query work.
- Set limits that match renderer and server budgets.
- Include formatted display values when chart and Pivot formatting must match.
- Log provider errors with request/view IDs without exposing raw sensitive
source rows.
- Exercise root, linked refresh, drill, retry, breadcrumb, and stale-response
paths before release.
[Previous: Headless API and persistence](../headless-api-and-persistence/) ·
[Pivot Charts overview](../) ·
[Remote Pivot guide](/guides/pivot/server-side/) ·
[API reference](/api/pivot-charts/)
### Statistical provider data
Return source distributions in `response.analyticalFrame.derived.distributions`
when serving box plots or violin charts. Each entry is keyed by returned
`categoryId` and `seriesId` and contains either finite `kind: 'samples'`
observations or a `kind: 'summary'` with ordered quartiles and whiskers. Violin
summaries additionally require ordered nonnegative density points. Raw sample
arrays, summary points, outliers, and density allocations are checked against
the chart's `maxDataPoints` budget. The provider must supply the complete requested
frame; it must not substitute aggregated Pivot cells for raw observations.
See [Source distributions](../data-and-bindings/#source-distributions) for the
public data shape and statistical conventions.
---
# Compatibility, migration and deployment
URL: https://pro.rv-grid.com/guides/pivot/compatibility/
Source: src/content/docs/guides/pivot/compatibility.mdx
Description: Review verified Pivot frameworks, package formats, TypeScript settings, performance evidence and unsupported scope.
## Product and package policy
Pivot provides local grid integration, headless analytical controllers and remote adapter contracts. Use this page to choose the verified package format, compiler settings and deployment scope for your application.
Synchronous CommonJS export removal changes the package contract and requires deliberate migration. Keep the published release line and lockfile aligned with your application’s support policy.
Recorded installed-package evidence applies to the tested product artifacts; requalify replacement bytes. The verified Pivot tarball SHA-256 is `a41ad879e01cb03e0bf3004d557d5b95d72a4236dcf6adf4dc35190aa44a2a00`.
## Verified integration matrix
| Consumer | Recorded evidence | Limits of that evidence |
| --- | --- | --- |
| Vanilla | Installed packages, public Core 4.29.0, Chromium 147.0.7727.15 | Update/refusal/recovery/remount workflow, not all browser versions |
| React 19.2.5 | Wrapper 4.29.0, strict app types and production build, same Chromium | No framework SSR/hydration certification |
| Vue 3.5.33 | Vue3 wrapper 4.29.0, strict app types and production build, same Chromium | Vue2 unqualified |
| Angular 22.1.0 | Wrapper 4.29.0, real ngc AOT/strict templates/production linker, same Chromium | Other Angular versions unqualified |
| Node ESM | 22.22.3, 24.15.0, 26.7.0; root/headless/charts and public controls | DOM-free execution, not grid SSR |
| Browser UMD | Chrome 155, public Core ESM namespace-to-global bootstrap plus classic Pro/Pivot scripts | Not a pure three-classic-script or Node require claim |
Firefox/WebKit, Svelte, trial packages and unlisted framework/runtime versions need separate qualification. Recorded keyboard, controls, theme/zoom/forced-color/RTL and lifecycle exercises do not establish full accessibility compliance. Actual VoiceOver/screen-reader output remains unverified; VoiceOver settings were left unchanged.
## ESM, UMD and CommonJS migration
Root, `/headless` and `/charts` exports support ESM `import`. Synchronous `require('@revolist/pivot')` deliberately fails with `ERR_PACKAGE_PATH_NOT_EXPORTED` before dependency bootstrap. A CommonJS host can use asynchronous ESM import:
```js
async function loadPivot() {
return import('@revolist/pivot/headless');
}
```
Qualify that host/runtime as an ESM integration. The browser artifact `dist/pivot.umd.cjs` is a classic script/global contract despite its filename; it does not establish Node CommonJS support. Keep matching Core/Pro globals and the same aggregation compiler owner. Cross-format/copy registry identity is unsupported. The focused facades share the root module graph and are not dependency-isolated bundles. CSS exports `/styles.css` and `/dist/pivot.css` remain available.
## TypeScript application settings
The installed fixtures checked strict application code with TypeScript 6.0.3:
```json
{
"compilerOptions": {
"strict": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"skipLibCheck": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
```
`skipLibCheck: false` remains unqualified: published Core 4.29.0 produces event-map inheritance, lodash-reference and internal `@type` declaration errors. NodeNext declaration checking is excluded because current extensionless declarations do not satisfy that resolver. JavaScript Node ESM execution is a separately verified contract. Do not mask these limitations with workspace aliases or ambient consumer shims when accepting installed packages.
## Migrate analytical reports deliberately
1. Retain the accepted report and known KPI results before changing policies.
2. Keep V1/default aggregate semantics unless deliberately adopting explicit V2 operations.
3. Add a matching `aggregationSemantics` execution pin; this rejects location mismatch, not arithmetic differences.
4. Restore runtime sources/engines/callbacks independently of saved JSON.
5. Execute updated reports, compare null/count/distinct/total/formula results, and persist only after success.
6. Replace native configs as complete configurations, or use the owning authoring controller for individual edits.
See [semantics](/guides/pivot/concepts/analytical-semantics/) and [persistence](/guides/pivot/concepts/report-persistence/).
## Supported limits and measured workload status
SDK resource metrics are optional and omitted metrics are unbounded. They do not imply a universal supported row count. Set limits using actual hierarchy width/depth, distinct cardinality, callback costs, device and export shape. Reference/backend limits are separate from SDK limits. See [exact resource units](/guides/pivot/concepts/execution-and-limits/).
The recorded workload qualification **failed** its unchanged 100000-fact warm-refresh 2-second target. On Apple M1 Pro (8 logical CPUs, 16 GiB), macOS kernel 25.4.0 arm64, Node 24.15.0:
| 100000-fact profile | Cold P95 | Warm P95 | Whole-process peak RSS |
| --- | --- | --- | --- |
| Synchronous | 2706 ms | 2400 ms | 988 MiB |
| Cooperative, 8 ms target | 6559 ms | 5016 ms | 829 MiB |
Each profile contains 20 independent cold processes with one paired warm refresh each. Completed numerical/ordering/refusal/recovery checks passed. RSS includes setup/oracle/controller and diagnostics; it is not engine heap or an enforced memory bound. Warm `refresh()` still recomputes as a source barrier; these are not cache-hit latencies. These host-specific observations are not customer SLOs.
The first million-fact worker hit a 180-second aggregate-process watchdog covering setup, cold/warm and diagnostics, without a per-phase result. This does not prove an individual refresh exceeded 180 seconds. Million-fact correctness completion/latency/RSS remain unqualified; 39 planned Node processes were unrun and no browser workload samples ran. Functional browser checks do not fill that performance evidence gap. No threshold was relaxed.
## Deployment responsibilities and deferred scope
The application owns identity/tenant authorization, field allowlists, driver snapshots and cancellation/deadlines, query/concurrency admission, cache revocation, durable report storage and representative workload/accessibility acceptance. The SQLite reference is executable repository tooling, not shipped managed infrastructure.
Explicitly deferred or unqualified: selected opposite-axis tuple summary sorting; native-local/canonical clicked-header unification; additional connectors/date/backend/strict profiles; general custom SQL aggregates; background export jobs; governed shared-view catalogs; exact-decimal arithmetic; untested browsers/frameworks and actual assistive-technology output. Advertise only supported capabilities and fail visibly when a report needs more.
Continue with [remote deployment](/guides/pivot/server-side/adapters-and-security/), [bounded exports](/guides/pivot/concepts/exporting-reports/) and [Pivot migration guidance](/guides/migrations/#pivot-release-migration).
---
# Aggregations and Calculation Policies
URL: https://pro.rv-grid.com/guides/pivot/concepts/aggregations/
Source: src/content/docs/guides/pivot/concepts/aggregations.mdx
Description: Configure explicit numeric and scalar aggregation, counts, percentages, own-grain totals, execution pins and precision limits.
A measure summarizes the source facts at one row/column coordinate. Choose its input, null and invalid-value policies explicitly. A measure's label, `dataType` and number format do not change those policies.
## Recommended local configuration
Use a V2 report with `createPivotStrictAggregators({ input: 'number', invalid: 'error' })` for numeric fields and `input: 'scalar'` for text/Boolean counts and typed distinct values. Both presets skip null and missing values. Persist `aggregationSemantics: 'local'` alongside the operations. This configuration requires matching current Pivot and Pro packages.
Validate the fixture after installing your package artifacts: the expected grand average is **5** and distinct customers **3**. Compilation/import success alone does not prove policy execution. A package build that preprocesses compiled callbacks as default numeric IDs can instead return 3.3333333333333335 and 1; that build does not preserve the declared input policies. Keep the accepted report and use artifacts that pass these controls. An execution pin does not detect a duplicated aggregate-runtime identity.
The helper returns **portable V2 declarations**, not a native callback map. Execute the report with a V2 controller or compile it with `compilePivotReport(report, runtime, { version: 2, source })` before assigning the issued configuration to `grid.pivot`.
```ts
// docs-check: complete
import {
createPivotController,
createPivotStrictAggregators,
type PivotReportV2,
} from '@revolist/pivot/headless';
const report: PivotReportV2 = {
kind: 'revogrid:pivot.report',
version: 2,
aggregationSemantics: 'local',
fields: [
{ id: 'region', source: { kind: 'physical', prop: 'region' } },
{ id: 'amount', source: { kind: 'physical', prop: 'amount' },
aggregators: createPivotStrictAggregators({ input: 'number', invalid: 'error' }) },
{ id: 'customer', source: { kind: 'physical', prop: 'customer' },
aggregators: createPivotStrictAggregators({ input: 'scalar', invalid: 'error' }) },
{ id: 'row', dataType: 'number',
source: { kind: 'expression', expression: { type: 'literal', value: 1 } },
aggregators: createPivotStrictAggregators({ input: 'number', invalid: 'error' }) },
],
rows: ['region'], columns: [], filters: [],
measures: [
{ id: 'total', fieldId: 'amount', aggregator: 'sum' },
{ id: 'average', fieldId: 'amount', aggregator: 'avg' },
{ id: 'numericValues', fieldId: 'amount', aggregator: 'count' },
{ id: 'customerValues', fieldId: 'customer', aggregator: 'count' },
{ id: 'customers', fieldId: 'customer', aggregator: 'distinct' },
{ id: 'rows', fieldId: 'row', aggregator: 'count' },
],
totals: { grandTotal: true, subtotals: true },
};
const source = [
{ region: 'North', amount: 10, customer: 'Alice' },
{ region: 'North', amount: null, customer: 'Bob' },
{ region: 'South', amount: 0, customer: '' },
];
const controller = createPivotController({ reportVersion: 2, report, source });
try {
const outcome = await controller.refresh();
if (outcome.status !== 'committed') throw new Error('Report did not commit');
console.table(controller.getSnapshot().view?.model.pinnedBottomSource);
// Grand total: sum 10; average 5; numeric values 2;
// customer values 3; distinct customers 3; rows 3.
} finally {
controller.dispose();
}
```
The preview demonstrates the configured measure layout. Its source/configuration is a separate example; use the explicit policy above when defining a report's input contract.
## Null, empty, invalid and zero
| Input | `input: 'number'` preset | `input: 'numeric-string'` preset | `input: 'scalar'` preset |
| --- | --- | --- | --- |
| `null`, `undefined`, missing property | Skip | Skip | Skip |
| `0` | Eligible number | Eligible number | Eligible number |
| `''`, whitespace-only text | Invalid | Invalid | Eligible text; preserved exactly |
| `'2'`, `' -2e3 '` | Invalid | Eligible numeric value | Eligible text; preserved exactly |
| `false`, `true` | Invalid | Invalid | Eligible Boolean |
| NaN, Infinity, objects, Date, BigInt, Symbol | Invalid | Invalid | Invalid |
Invalid values fail execution by default. Select `invalid: 'skip'` only when excluding those observations is intended. Numeric-string input accepts trimmed decimal/scientific syntax, including `.5` and `1.`; hexadecimal and nonfinite text are invalid. A strict numeric aggregate never calls application object coercion.
For individual operation declarations, `operation.nulls: 'include'` is supported with scalar input for `count`, `distinct`, `first`, `last`, `mode` and runtime callbacks. Null and missing then represent null occurrences; count includes each occurrence and distinct counts one null identity. Numeric aggregates do not support including null as a numeric observation. An absent row/column intersection can remain absent/blank rather than becoming an explicit zero-count bucket; an existing all-null bucket is a different case.
## Count rows, values and distinct values
| Question | Configuration | Meaning |
| --- | --- | --- |
| How many facts? | Count the literal-one expression field in the complete example | Every contributing source occurrence, regardless of another field's value |
| How many non-null scalar values? | Scalar `count`, nulls skipped | Includes empty strings, whitespace and Booleans |
| How many numeric values? | Numeric `count` | Counts only eligible numeric inputs under the selected invalid policy |
| How many distinct typed values? | Scalar `distinct` | `1`, `'1'` and `true` differ; `0` and `-0` agree; text comparison is case-sensitive |
| How many SQL rows? | `count` on an authorized guaranteed-nonnull fact ID | The standard protocol uses a selector; `count(amount)` is a non-null amount count, not `COUNT(*)` |
A constant row expression does not validate an unused amount field. Local typed distinct uses SameValueZero identity. SQL distinct uses the backend's type/collation rules and must be tested separately.
## Current defaults without an operation declaration
Omitted/empty `dimensions[].aggregators` uses `commonAggregators`. A nonempty map replaces that field's registry; every requested operation must be present. V1 and V2 builtin declarations without `operation` use these callbacks. Physical `dataType` is descriptive and does not enable strict validation.
For the default **numeric IDs** `sum`, `avg`, `min`, `max`, `median`, `range`, `variance`, `stdDev` and `distinct`, native Pivot applies `Number(value)` and keeps finite inputs. Consequently null, empty/whitespace text and false become zero, true becomes one, and numeric strings become numbers. Undefined and other nonfinite conversions are discarded; conversion itself can throw or invoke an object's coercion callback. Default `count` counts input occurrences, including null, missing and invalid values. Default numeric `distinct` does not count distinct customer names.
On `[10, null]`, default AVG is **5**; numeric preset AVG is **10**. On the 24-fact fixture with sum 396 and six null amounts, these policies yield **16.5** and **22**, respectively. Both are supported policies; choose and label the intended meaning.
ID preprocessing is based on the authored ID. An alias without an `operation` does not automatically receive its builtin's preprocessing. A compiled operation selects by builtin identity, preserves its explicit policy and receives raw observations. Prefer the presets instead of relying on alias/coercion interactions.
For a newly added stock measure, a declared string, Boolean or date field defaults to `count`; numeric/unknown fields default to `sum`. A configured nonempty catalog instead selects its first operation. Numeric presets start with `sum`; scalar presets start with `count`. Specify each report measure's `aggregator` explicitly.
## Aggregate results at each grain
| Operation with an explicit numeric/scalar contract | Empty eligible input | Result |
| --- | --- | --- |
| `sum`, `count`, `distinct` | `0` | Eligible sum, occurrence count or typed distinct count |
| `avg`, `min`, `max`, `median`, `range` | `null` | Numeric statistic |
| `variance`, `stdDev` | `null` | Population statistic, ddof 0 |
| `varianceSample`, `stdDevSample` | `null` | Sample statistic, ddof 1; at least two eligible observations |
| `first`, `last`, `mode` | `null` | Original eligible type/order; mode chooses the first source occurrence in a frequency tie |
Leaves, subtotals and grand totals each aggregate their contributing original facts. They do not average displayed averages or sum distinct counts. For groups containing `[10]` and `[20, 20, 20]`, the grand average is **17.5**, not 15. Configure independent group/total operations only when those grains intentionally use different measures; those overrides are local-only.
Use `createPivotWeightedAverage({ id, valueFieldId, weightFieldId })` for a paired weighted average. Add its returned fields and measures to a V2 report. Values `[10, 20]` with weights `[1, 3]` yield **70/4 = 17.5** at the total grain. Only jointly eligible pairs contribute to both numerator and denominator; missing pairs are excluded, zero/net-zero weights produce null when the denominator is zero, and nonfinite products are excluded. This recipe requires finite-number/null dependencies and is local-only. A general formula dividing two independent sums is not the same eligibility rule.
## Percentages and running percentages
`values[].calculation` transforms an aggregate; it does not change fact eligibility. Portable field-based calculations use `fieldId`, while native configurations use `field`. Formula operands use stable measure IDs.
| Calculation | Result |
| --- | --- |
| `percentOfGrandTotal` | Current aggregate / grand aggregate |
| `percentOfRowTotal`, `percentOfColumnTotal` | Current aggregate / its corresponding total |
| `percentOfParentRowTotal`, `percentOfParentColumnTotal` | Current aggregate / its immediate parent aggregate |
| `percentDifferenceFrom` | (Current − comparison) / comparison |
| `percentRunningTotal` | Prefix of ordered peer aggregates / sum of that peer sequence |
| `index` | Current × grand / (row total × column total) |
Ratios are numeric values: `0.5` means 50%; the standard percentage formatter displays it as `50%`. Missing/nonfinite inputs and zero denominators produce null. A missing comparison or a coordinate outside the selected field's peer sequence also produces null. Running calculations follow the analytical peer order and hierarchy scope, not viewport pages; sparse descendant positions stay aligned. They do not sum duplicated subtotal rows into the peer sequence. For average measures, running percentage sums peer averages; it is not a cumulative average over raw facts.
Hidden total presentation does not remove the aggregate denominators required by calculations. Hiding/expanding a branch changes presentation, not fact scope. Source filters select contributing facts; result filters select aggregate groups and test the total independently. Do not assume the total is the sum of visible retained groups. See [Totals](/guides/pivot/concepts/totals/).
A finite percentage difference between −1e308 and 1e308 is −2. Equal 1e308 peers have running percentages 0.5 and 1 even when the intermediate raw sum overflows. A plain unrepresentable difference or running total is null. No arbitrary-precision or exact cancellation guarantee is provided.
## Execution pins and server support
`aggregationSemantics: 'local' | 'server'` is a top-level native configuration or V1/V2 report property. Plugin/controller preparation and report compilation/migration reject a pin that disagrees with the selected engine **before adapter loading**. Server mode, any adapter, or any remote store selects the server path; otherwise preparation selects local execution. Reports without a pin remain supported and unpinned.
A pin prevents accidental engine switching. **It does not guarantee identical local/server results**, change null/count policies, validate backend collation or certify an adapter. Persist both the pin and explicit operation declarations. Direct local model, cooperative-work and `ClientPivotEngineAdapter` entry points also enforce the pin before reading facts. These APIs validate their actual local execution path even when the configuration contains server-looking engine metadata.
The standard remote vocabulary is `sum`, `min`, `max`, `avg`, `count`, `distinct`, subject to the trusted field registry and advertised per-field capability profile. Median, population/sample statistics, versioned local operations, custom local callback semantics, independent group/total operations and paired weighted recipes are not standard server summaries. A standard ID does not promise that every backend implements it.
Post-aggregate server calculations require both the corresponding engine capability and an adapter/backend that implements the operation at complete analytical scope. `calculations: true` or an explicit calculation list advertises support; it does not run local calculation code over remote pages. The flat SQLite baseline rejects post-aggregate calculations. Its trusted hierarchy and analytical opt-ins now support their declared prefix/totals and calculation/ranking/sorting profiles; date grouping and independent local operations remain excluded. See [current reference profiles](/guides/pivot/server-side/adapters-and-security/#executable-sqlite-reference). See [Remote API](/guides/pivot/server-side/remote-api-reference/) and [Backend validation](/guides/pivot/server-side/backend-validation/).
Ordinary HTTP load, full-export forwarding and SQL planning reject unknown summary properties, including local `operation` policies and arbitrary metadata. The standard server adapter checks full-export summaries before calling an application store; the HTTP store checks them before transport. Backends must independently validate authorization, policies and supported calculations. See [Complete export](/guides/pivot/server-side/complete-export/).
## Precision, persistence and existing reports
Calculations use JavaScript IEEE-754 numbers, not decimal accounting or arbitrary precision. Strict aggregate nonfinite output fails execution; genuinely nonfinite derived results are null. Default callbacks can retain nonfinite output. CSV/XLSX and chart projection do not improve numerical precision. Custom callbacks retain their own arithmetic obligations.
Preserve existing policies when adding a pin to a saved report. Converting a configuration into a report does not convert its callbacks into strict operations. Review the report's KPIs before deliberately replacing aggregate declarations. Persist a candidate only after successful execution; keep the accepted report when validation or calculation fails. V2 operation documents use explicit V2 parsing/import, while V1 saved-report helpers accept V1 only. Readers must support the pin property; readers with a closed schema that lacks it reject pinned documents. See [Report specifications](/guides/pivot/concepts/report-specifications/) and [Migrations](/guides/migrations/#pivot-calculation-policies-and-saved-reports).
## Native callback maps
For a native `PivotConfig`, compile operation callbacks instead of assigning portable declarations to `dimensions[].aggregators`:
```ts
// docs-check: complete
import { compileAggregateOperation, type AggregateOperationV1 } from '@revolist/revogrid-pro/aggregations';
import type { PivotConfig } from '@revolist/pivot';
const operation: AggregateOperationV1 = {
version: 1, input: { type: 'finite-number' }, nulls: 'skip',
invalid: 'error', output: { type: 'finite-number' },
};
const pivot: PivotConfig = {
aggregationSemantics: 'local', rows: ['region'], columns: [],
dimensions: [
{ prop: 'region' },
{ prop: 'sales', aggregators: {
sum: compileAggregateOperation({ builtin: 'sum', operation }),
avg: compileAggregateOperation({ builtin: 'avg', operation }),
} },
],
values: [{ id: 'averageSales', prop: 'sales', aggregator: 'avg' }],
};
// Assign pivot to grid.pivot after installing PivotPlugin and the source records.
```
A nonempty callback map replaces built-ins for that field. Register every operation that its measures and authoring controls need. Use stable measure IDs when a source field appears more than once. Custom functions belong in the local map or a trusted runtime binding; a function name alone does not configure a server implementation.
## Calculation FAQ
### Why do two AVG configurations return 16.5 and 22?
The source sum is 396. Default numeric AVG treats six null facts as zero and
divides by 24; the numeric preset skips those facts and divides by 18. Neither
formatting nor an execution pin changes that policy. Choose it in the operation.
### Does count mean rows?
Default count counts input occurrences. A numeric preset counts eligible numeric
values; a scalar preset counts non-null scalar values, including empty text.
Use the constant-one expression example for an explicit all-fact row count.
### Can I move a saved local report to a server by changing its engine?
A matching execution pin rejects accidental rebinding. An intentional switch
requires reviewing supported operations and the backend's null/count/totals
contract. It does not guarantee identical results. Keep the accepted report until
the new candidate executes and its known KPIs are verified.
### Can I compute totals or complete exports from displayed cells?
No. Recompute nonadditive totals from contributing facts at each grain and obtain
complete server scope through the backend. A viewport is a window. Averages,
distinct counts and paired weighted measures cannot be summed from displayed cells.
---
# Hierarchy and calculation semantics
URL: https://pro.rv-grid.com/guides/pivot/concepts/analytical-semantics/
Source: src/content/docs/guides/pivot/concepts/analytical-semantics.mdx
Description: Build hierarchies and choose explicit aggregation, null, distinct and formula policies.
## Hierarchies, expansion and totals
A row axis `['region', 'city']` groups region first and city second. A column axis `['year', 'quarter']` forms ordered header levels. Enable `groupAggregations` to show group header aggregates and `totals` for explicit subtotal/grand rows and columns.
```ts
import type { PivotConfig } from '@revolist/pivot';
const config: PivotConfig = {
rows: ['region', 'city'], columns: ['year', 'quarter'],
values: [{ id: 'sales', prop: 'sales', aggregator: 'sum' }],
collapsed: false,
groupAggregations: true,
columnCollapse: { collapsed: false },
totals: { grandTotal: true, subtotals: true },
};
```
With North/A `[10]` and North/B `[20,20,20]`, the North SUM is **70** and strict AVG is **17.5**, whether North is expanded or collapsed. Its AVG is not the average of 10 and 20. For DISTINCT, a customer shared by two cities contributes once to the North distinct total.
Existing native-grid example with expanded column totals. Expansion changes the visible hierarchy while totals retain their original-fact grain.
The same native-grid example with collapsed column groups. Aggregate placeholders retain the branch’s fact scope instead of summing visible child summaries.
Run [the downloadable hierarchy example](/examples/pivot/customer-example.mjs) to compare expanded and collapsed totals and round-trip the V2 report. The screenshot uses the existing financial demo dataset; the script uses six deliberately small facts with independently asserted results.
Use typed path keys through `createPivotExpandedPathKey` and report `presentation.expandedKeyEncoding: 'semantic-v1'` when persisting semantic expansion; avoid hand-built delimiter keys. For a multilevel row hierarchy, enable drill-down with Boolean `collapsed`, persisted `expanded`, or compact row layout. `collapsed: true` starts collapsed and `collapsed: false` starts expanded; explicit `expanded` entries override individual branches. Column collapse has its own policy. See [drill-down](/guides/pivot/concepts/drilldown/) and [totals](/guides/pivot/concepts/totals/) for per-level configuration and suppression. Single-child subtotal suppression follows source occurrences; duplicate facts at one leaf are not a single source occurrence.
The SQLite hierarchy opt-in accepts fixed authorized prefix axes up to eight fields per axis, with separate semantic kinds for group/subtotal/grand coordinates. Literal strings resembling total markers remain real members. The legacy flat reference is a separate profile. Depth capability metadata alone does not implement expansion, subtotals or authorization.
## Explicit numeric and scalar policies
| Profile | `[10, null]` AVG | DISTINCT meaning | Where selected |
| --- | --- | --- | --- |
| Legacy local numeric IDs | 5 | Finite `Number(value)` identities; strings such as customer names are not a text-distinct contract | Existing native configs and V1/default builtins |
| Strict local number | 10 | Eligible finite numbers; null/missing skipped | V2 field aggregators with `createPivotStrictAggregators({ input: 'number' })` |
| Strict local scalar | Not a numeric AVG catalog | Typed strings/numbers/Booleans, case-sensitive; `1`, `'1'`, `true` differ | V2 scalar preset |
| SQL | Backend AVG skips SQL NULL | Backend SQL type/collation rules | Authorized adapter/field operation profile |
`invalid` defaults to `'error'`; choose `'skip'` deliberately. Numeric-string input accepts trimmed decimal/scientific syntax; plain number input rejects numeric text. Scalar input retains empty text and Booleans. Both presets skip null/missing. Default local count counts occurrences; strict count counts eligible values. For SQL row counts choose an authorized nonnull fact key. For local all-fact counts use a literal-one derived field.
```ts
import { createPivotStrictAggregators } from '@revolist/pivot/headless';
const amountField = {
id: 'amount', source: { kind: 'physical' as const, prop: 'amount' },
aggregators: createPivotStrictAggregators({ input: 'number', invalid: 'error' }),
};
```
These are portable V2 operation declarations, not native callback maps. Use `reportVersion: 2` in the controller or explicit V2 compilation; use `aggregationSemantics: 'local'` to reject accidental server binding. A nonempty catalog replaces the field’s available operations. The [complete strict example](/guides/pivot/concepts/aggregations/#recommended-local-configuration) and [downloadable executable](/examples/pivot/customer-example.mjs) show all required fields and visible totals.
Missing intersections can stay absent rather than becoming zero. Existing all-null buckets differ from absent buckets. Strict sum/count/distinct return zero for empty eligible input; strict AVG/min/max return null. Do not equate these with legacy callback empty results or SQL sparse-cell rules.
## Calculations and formula references
Calculations transform an aggregate at its coordinate. Percentage denominators come from the complete applicable fact context; hiding totals, collapsing groups, windowing or applying HAVING does not redefine those denominators. Source WHERE/member filters do change the fact scope. Running/rank calculations use ordered analytical peers; presentation sorting does not define a business calendar. See [aggregation policies](/guides/pivot/concepts/aggregations/).
```ts
const percent = { type: 'percentOfGrandTotal' as const };
const percentagePoints = {
type: 'formula' as const,
referenceValues: 'calculated' as const,
expression: {
type: 'binary' as const, operator: '*' as const,
left: { type: 'measure' as const, id: 'share' },
right: { type: 'number' as const, value: 100 },
},
};
```
Give one measure stable ID `share` with calculation `percent`, and another measure calculation `percentagePoints`. For A=20/B=80, `share` is **0.2/0.8** and the formula is **20/80**. Omit `referenceValues` (default `'raw'`) and the reference to that nonformula measure reads its underlying sum, giving **2000/8000**. References to another formula evaluate that formula using its own reference policy. Definition order does not replace dependency resolution. Unknown IDs, cycles and unknown reference modes reject rather than producing a guessed result.
Zero denominators, missing comparisons and nonfinite derived results yield null. Arithmetic is IEEE-754 binary64; display formats and XLSX do not add decimal accuracy. Exact accounting arithmetic is deferred. The SQLite analytical opt-in supports its declared percentage/parent/index/difference/running/rank/formula DAG profile; the flat baseline rejects calculations. Local V2 operation descriptors, derived fields and independent group/total overrides are not standard server aggregate declarations.
If an execution fails, keep the committed report visible, inspect the error, repair the field/operation or formula dependency, and retry. Never silently convert a customer’s existing legacy policy to strict semantics during migration.
---
# Configuration Reference
URL: https://pro.rv-grid.com/guides/pivot/concepts/configuration-reference/
Source: src/content/docs/guides/pivot/concepts/configuration-reference.mdx
Description: Complete reference for every public PivotConfig option in the current implementation.
`PivotConfig` is the public configuration model for both client-side and server-side Pivot. This page explains every option in the current implementation and how the options interact.
For configuration signatures, see [Pivot API](/api/pivot/); stock constructor signatures are in [Pivot Config API](/api/pivot-config/). Use this page for defaults, behavior, and practical guidance.
`PivotConfig` requires `rows` and `values` in TypeScript. The plugin's direct
`grid.pivot` property and `updatePivotConfig(nextConfig)` accept
`Partial` and default missing axis/value collections to empty arrays.
`updatePivotConfig` submits a complete candidate: merge an intended patch into
current authored state before calling it. An accepted update returns `true`; a
veto or failed/superseded update does not establish a committed configuration.
Use the [authoring facade](/guides/pivot/concepts/custom-integrations/) to observe status and history.
For numeric reports, use the [recommended explicit policies](/guides/pivot/concepts/aggregations/#recommended-local-configuration). The grid setup below uses a compiled finite-number sum callback with nulls skipped and invalid values rejected. Default ID callbacks are separately documented; they are not an input-validation policy.
## Minimal Example
```ts
// docs-check: complete
import { compileAggregateOperation } from '@revolist/revogrid-pro/aggregations';
import type { PivotConfig } from '@revolist/pivot';
const numeric = {
sum: compileAggregateOperation({ builtin: 'sum', operation: {
version: 1, input: { type: 'finite-number' }, nulls: 'skip',
invalid: 'error', output: { type: 'finite-number' },
} }),
};
const pivot: PivotConfig = {
aggregationSemantics: 'local',
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'quarter', name: 'Quarter' },
{ prop: 'sales', name: 'Sales', aggregators: numeric },
],
rows: ['region'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
};
```
## Core Data Model
### `dimensions`
Reusable field definitions. This is where you configure field labels, column behavior, and available aggregators.
- Purpose: Define the fields Pivot can reuse.
- Accepted value: `PivotConfigDimension[]`
- Default: when omitted, plugin/report migration synthesizes definitions for referenced fields; an explicit array is the complete catalog and must contain every referenced field.
- Important interactions: `values[].aggregator` resolves against the matching dimension's registry, falling back to built-ins when its registry is absent or empty.
Provide a catalog to expose unused fields in the configurator, assign useful
captions, or register custom operations. Omitting it is convenient for a fixed
layout; supplying `dimensions: []` does not request inference.
| Dimension option | Meaning and default |
| --- | --- |
| `dimensions[].prop` | Required physical source property; string or number. Properties must be unique and must not collide after string conversion. `$values` is reserved. |
| `dimensions[].dataType` | Optional descriptive `string`, `number`, `date`, or `boolean` metadata; no coercion or inferred formatting. |
| `dimensions[].allowedRoles` | Optional authoring destinations; omitted permits all, `[]` permits none. See policy below. |
| `dimensions[].selectionTarget` | Preferred checkbox destination: `rows`, `columns`, or `values`; a disallowed hint falls back to an allowed role. |
| `dimensions[].fieldGroup` | Display folder label or nested string path for the field list. Does not create analytical hierarchy levels. |
| `dimensions[].description` | Optional field-list description and search text. |
| `dimensions[].hidden` | Defaults false; hides the field from ordinary field lists while retaining configured use. |
| `dimensions[].aggregators` | Map of operation IDs to local functions; a nonempty map replaces built-ins for that field. Omitted/empty uses `commonAggregators`. New stock measures use its first supported operation; otherwise string/Boolean/date fields prefer count and numeric/unknown fields prefer sum. |
| `dimensions[].filterOptions` | Explicit finite scalar choices for the Filters quick selector/member editor. An explicit `[]` takes priority over async enumeration. |
Dimensions also inherit `Partial`: use Core options such as
`name`, `size`, `minSize`, `maxSize`, `pin`, `sortable`, `filter`, `blankSemantics`,
`columnType`, `cellTemplate`, and `cellProperties` for captions and generated grid
presentation. Their complete typed contract is `ColumnRegular` from `@revolist/revogrid`.
Their meaning is owned by Core; arbitrary extension data does not add Pivot
behavior. Portable report columns deliberately accept a narrower JSON-safe
subset; runtime templates and other callbacks require named bindings. See
[Report Specifications](/guides/pivot/concepts/report-specifications/).
`dimensions[].aggregator` and `dimensions[].dimension` are read-only generated
metadata, not authoring settings; configure operations on `values` instead.
#### Field metadata and authoring roles
`PivotConfigDimension` and portable `PivotReportField` accept optional `dataType`
(`string`, `number`, `date`, or `boolean`) and `allowedRoles`
(`rows`, `columns`, `values`, or `filters`). The datatype is descriptive: it does
not coerce facts, choose an aggregator, or apply formatting. `column.name` remains
the portable field label; native dimensions use `name`.
Omitting `allowedRoles` permits every placement; `[]` permits none. Role order is
preserved, and duplicate or unknown roles are invalid. Report validation,
compilation, controller commands and native plugin ingress reject forbidden
placements with `FIELD_ROLE_UNAVAILABLE`, the stable field ID and report path.
Controllers validate the final transaction, so a transaction can change a policy
and move or remove its previous placement together. Failed changes retain the
last committed report and result; successful policy changes participate in history.
The `filters` role means placement in the dedicated Filters area. It does not
prohibit member selections on row or column fields, remove dormant selections,
or authorize source predicates or measure HAVING. Backend registry permissions
remain separate. Runtime column bindings cannot inject either metadata property;
declare them on the field.
The root and headless entries export `PivotFieldRole`,
`isPivotFieldRoleAllowed(field, role)` and `getPreferredPivotFieldRole(field)`.
The preferred checkbox destination is an allowed `selectionTarget`, otherwise
the existing default (Values for a nonempty aggregator map, Rows otherwise),
otherwise the first permitted Rows, Columns or Values role. Filter-only and
empty restrictions return `undefined`; the authored hint remains unchanged.
`createPivotData` and `createPivotDataModel` are trusted execution helpers for raw
configuration, outside this authoring boundary. They do not enforce role policy
or provide backend authorization; use report validation/compilation or a controller
for authored layouts.
The built-in configurator uses the same placement policy for checkboxes, drag and
drop, axis swaps, and context-menu moves. A field restricted to Filters has an
**Add to Filters** button that can be activated from the keyboard; it is disabled
once the field is placed there. Fields with no allowed placement show a reason
and cannot be dragged. A group drop is rejected as a whole if any field cannot
use the destination. Fields selected outside the current search remain selected.
`dataType` is included in field search without changing source values.
For example, `allowedRoles: ['columns']` with `selectionTarget: 'rows'` selects
Columns when checked; it does not change the authored hint. `allowedRoles:
['filters']` exposes the Filters action, while `allowedRoles: []` prevents adding
the field to a layout. Member filtering on an existing Rows field remains
available even when that field cannot be placed in Filters.
Translate placement messages through `i18n` (configurator) or `fieldPanel.texts`
(compact panel): `fieldRoleUnavailable`, `fieldNoPlacement`, `fieldFilterOnly`,
`addToFilters`, and `axisSwapUnavailable`. Context-menu reasons use
`contextMenu.localeText.fieldRoleUnavailable`. Partial overrides inherit the
remaining built-in messages.
### `rows`
Ordered row hierarchy.
- Purpose: Define the vertical analytical hierarchy.
- Accepted value: `ColumnProp[]`
- Default: required
- Important interactions: row drill-down only becomes meaningful when the effective row hierarchy has more than one level.
### `columns`
Ordered generated column hierarchy.
- Purpose: Define the horizontal analytical hierarchy.
- Accepted value: `ColumnProp[]`
- Default: `[]`
- Important interactions: `flatHeaders`, `columnCollapse`, and column totals only matter when column fields exist.
### `values`
Measures to aggregate.
- Purpose: Define which fields are summarized inside each Pivot cell.
- Accepted value: `PivotConfigValue[]`
- Default: required
- Important interactions: values can render as columns or as synthetic rows through `valuesOnRows`.
| Measure option | Meaning and default |
| --- | --- |
| `values[].prop` | Required source field present in the explicit catalog, if supplied. |
| `values[].aggregator` | Required registered operation ID, such as `sum` or `avg`. |
| `values[].id` | Optional stable, unique measure identity. Supply it when repeating a source field; the plugin assigns missing measure IDs. Keep it stable when changing labels or operations. |
| `values[].label` | Preferred display caption; overrides `name` and the field caption. |
| `values[].name` | Fallback measure caption when `label` is absent. |
| `values[].summaries` | Optional `{ group?: string; total?: string }` independent operations. Each inherits the leaf aggregator when absent; total does not inherit group. |
| `values[].calculation` | Optional post-aggregation calculation, including ratios, sequences, rank and formulas. Changes result numbers. |
| `values[].format` | Optional Intl `number`, `currency`, `percent`, `date`, or `datetime` preset, locale/options and blank/invalid display strings. Changes presentation; currency requires a code. |
| `values[].conditionalFormatting` | Optional `{ match?: 'first' \| 'all'; rules: PivotConditionalFormatRule[] }` for measure-wide presentation rules. |
Independent summary operations aggregate eligible original facts at group,
subtotal/collapsed, and grand-total coordinates; grand totals win at
intersections. They require local execution without a supplied adapter/store.
Formula carriers inherit dependency summaries rather than defining their own.
The stock measure editor preserves unsupported current settings during unrelated
edits and admits new calculations according to engine capabilities.
See [Aggregations](/guides/pivot/concepts/aggregations/), [Formatting Presets](/guides/pivot/concepts/formatting-presets/)
and [Configurator API](/guides/pivot/configurator/configurator-api/) for detailed settings.
### `filters`
Ordered fields in the dedicated Filters area. Accepts `ColumnProp[]`; defaults
to `[]`. A field can independently remain on an axis or in Values. The `filters`
allowed role controls this placement, not whether row/column members can be filtered.
### `filterSelections`
Member selections keyed by stringified source property. Inclusive scalar arrays
keep listed members; `{ mode: 'exclude', values: [...] }` excludes listed
members. Missing/empty entries mean All. Scalars are finite numbers, strings,
booleans, or null; typed values such as `1` and `'1'` remain distinct.
Selections execute before aggregation. Stock layout edits prune selections when
a field leaves its final Rows, Columns or Filters placement; changing Values
alone does not clear them. Authored dormant selections are a separate concern.
See [Sorting And Filtering](/guides/pivot/concepts/sorting-filtering/).
### `resultFilters`
Optional `PivotAggregateFilterExpression` applied after aggregation (HAVING).
Leaves identify a configured measure, column path/kind, operation and value;
combine them with `[left, 'and' | 'or', right]` or `['!', expression]`.
This is separate from member selection. Use stable measure IDs for repeated
source measures and an engine that supports the requested filter semantics.
### `topBottom`
Optional local ranking on the first row field:
`{ direction: 'top' | 'bottom', count: number, valueIndex: number, otherLabel?: string }`.
`count` is a positive integer; `valueIndex` is the zero-based configured measure
index, so duplicate source measures are unambiguous. Ranking uses all column
members after member filtering. Omitted members are excluded unless `otherLabel`
groups them into one aggregate. This is client-side ranking, not an automatic
remote ranking endpoint.
## Layout Options
### `flatHeaders`
Flattens grouped column headers into single labels.
- Purpose: Reduce visible nesting in generated columns.
- Accepted value: `boolean`
- Default: `false`
- Common pitfall: flat headers still preserve analytical grouping, they only change header presentation. With `flatHeaders: false`, generated Pivot columns use RevoGrid grouped headers so repeated parent labels are visually grouped.
### `mergeValueHeaders`
Merges generated value headers into the terminal column header labels.
- Purpose: Remove repeated measure headers such as `Revenue` under every generated column member.
- Accepted value: `boolean`
- Default: `false`
- Behavior: with one value, terminal columns use the column member label, for example `Q1`; with multiple values, the value label is appended, for example `Q1 Revenue`.
- Important interactions: can be combined with `flatHeaders`; `valuesOnRows` already moves measures out of generated value columns.
### `valuesOnRows`
Moves values into the row hierarchy.
- Purpose: Render measures as synthetic row members.
- Accepted value: `boolean`
- Default: `false`
- Important interactions: affects row drill-down behavior because values become part of the effective row structure.
### `rowTree`
Controls the effective row hierarchy, including where measures appear.
- Purpose: Place the `$values` pseudo-field at the beginning, middle, or end of the row tree.
- Accepted value: `(ColumnProp | '$values')[]`
- Default: derived from `rows`, with `$values` appended only when `valuesOnRows: true`
- Important interactions: when provided, `rowTree` overrides the value placement implied by `valuesOnRows`.
### `rowAxisLayout`
Controls the visible layout of an active multi-level row hierarchy.
- Purpose: Choose between one compact hierarchy column and one column per row field.
- Accepted value: `'compact' | 'tabular'`
- Default: `'compact'` while row drill-down is active; otherwise `'tabular'`
- Behavior:
- `'compact'` renders group labels and terminal row values in one localized Rows column.
- `'tabular'` preserves separate visible columns for every configured row field.
- Important note: this changes only the grid presentation. Configured row fields remain separate in aggregation, grouping, configurator state, and remote requests.
### `showRowGroupHeaders`
In `rowAxisLayout: 'tabular'`, set `showRowGroupHeaders: false` to hide generated
row group headers and display all detail rows with repeated labels in each row
field column, like an Excel tabular report. Saved `collapsed` and `expanded`
state is ignored until headers are shown again. Defaults to `true` and is ignored
in compact mode. Explicit subtotal and grand-total settings still apply; turn
those off too for a detail-only report.
```ts
rowAxisLayout: 'tabular',
showRowGroupHeaders: false,
totals: { subtotals: false, grandTotal: false },
```
## Configurator Options
### `hasConfigurator`
Enables the built-in Pivot configurator panel.
- Purpose: Let users rearrange fields visually.
- Accepted value: `boolean`
- Default: `false`
### `mountTo`
Custom configurator mount target.
- Purpose: Render the configurator outside the default grid wrapper.
- Accepted value: `HTMLElement`
- Default: internal grid wrapper mount
### `showColumns`
Hide or show the configurator columns zone.
- Purpose: Control configurator UI visibility.
- Accepted value: `boolean`
- Default: `true`
### `showRows`
Hide or show the configurator rows zone.
- Purpose: Control configurator UI visibility.
- Accepted value: `boolean`
- Default: `true`
### `showValues`
Hide or show the configurator values zone.
- Purpose: Control configurator UI visibility.
- Accepted value: `boolean`
- Default: `true`
### `showFilters`
Shows the configurator Filters area. Accepts `boolean`; defaults `true`.
Like the other `show*` options, it changes UI visibility only. An explicit
matching `fieldPanel.show*Fields` setting takes precedence in both stock constructors.
### `fieldPanel`
Settings for compact field areas and shared stock configuration controls.
The compact panel is opt-in; `hasConfigurator` independently enables the full panel.
| Field-panel option | Meaning and default |
| --- | --- |
| `fieldPanel.visible` | Show the compact in-grid panel; defaults false. |
| `fieldPanel.showResetLayout` | Show Reset layout where a reset action is available; defaults false. |
| `fieldPanel.allowFieldDragging` | Allow field dragging; defaults true. |
| `fieldPanel.allowFieldActions` | Show Add field, Actions and Clear controls; defaults true, independent of dragging. |
| `fieldPanel.allowFieldRemoving` | Allow Remove/Clear controls; defaults true in the full configurator and false in compact. |
| `fieldPanel.showRowFields` | Show Rows; falls back to `showRows`, then true. |
| `fieldPanel.showColumnFields` | Show Columns; falls back to `showColumns`, then true. |
| `fieldPanel.showDataFields` | Show Values; falls back to `showValues`, then true. |
| `fieldPanel.showFilterFields` | Show Filters; falls back to `showFilters`, then true. |
| `fieldPanel.texts` | Partial `PivotConfigLocaleInput`; overrides root `i18n` in both stock mount helpers. |
These controls are UI policy, not authorization. See [Field Panel](/guides/pivot/concepts/field-panel/).
### `contextMenu`
Advanced semantic Pivot menu, enabled by default; `false` disables it.
`PivotContextMenuConfig` accepts `areas` and `items` boolean maps to hide
particular surfaces/actions, `allowFieldRemoving`, `drillThrough: false | {
pageSize?, columns?, visibleColumns?, columnChooser? }`, `filter: false | { pageSize? }`, `clipboard: false | {
formatted? }`, `export: false | { fileName? }`, and partial `localeText`.
Omitted switches retain built-in behavior; a visible action can still be disabled
by engine support, missing editor integration, or current report status.
Menu translations use `localeText` independently of configurator `i18n`.
See [PivotContextMenuConfig](/api/pivot/#pivotcontextmenuconfig).
See [Drill Through](/guides/pivot/concepts/drill-through/) for the compact detail
grid, searchable column chooser, full runtime grid configuration, custom column
and cell templates, event hooks, and APIs for updating an open dialog.
### `i18n`
Configurator strings.
- Purpose: Override built-in English labels.
- Accepted value: `PivotConfigLocaleInput` (partial strings, callbacks and nested label maps)
- Default: built-in English copy
- Precedence: English defaults → root `i18n` → `fieldPanel.texts` in stock mount helpers; nested label maps merge by key.
Application field captions, group/member data and saved-view names remain
application data. Callback entries translate text containing those values;
`diagnosticMessage` translates host/engine messages. Partial native dictionaries
are supported, while explicitly authored portable report nested maps currently
require all known keys. Runtime locale callbacks need named bindings for report
export. Lightweight state JSON accepts JSON-only strings and partial nested
label maps in both locale locations; callback functions remain outside that
strict JSON boundary. See [Export And State](/guides/pivot/concepts/export-state/) before choosing persistence.
## Totals
### `totals`
Controls grand totals and subtotals.
- Purpose: Add analytical summary rows and columns.
- Accepted value: `PivotConfigTotals`
- Default: all disabled
| Totals option | Meaning and default |
| --- | --- |
| `totals.grandTotal` | Show a grand-total row and generated value columns; defaults false. |
| `totals.subtotals` | Show intermediate hierarchy totals; defaults false. |
| `totals.disabledSubtotals` | `{ rows?, columns? }` with `fields?: ColumnProp[]` and zero-based `levels?: number[]`; omit to retain all enabled subtotals. |
| `totals.grandTotalLabel` | Optional grand-total display caption. |
| `totals.subtotalLabel` | Optional subtotal display caption. |
| `totals.suppressSingleChildSubtotals` | Hide a subtotal duplicating its only source-backed leaf; defaults false. |
| `totals.suppressGrandTotalWhenSingleLeaf` | Hide a grand-total row duplicating the only source-backed leaf; defaults false. |
See [Totals](/guides/pivot/concepts/totals/) for behavior examples.
## Drill-Down State
### `collapsed`
Initial row drill-down state.
- Purpose: Enable grouped row expansion behavior.
- Accepted value: `boolean`
- Default: disabled unless `expanded` is provided or `rowAxisLayout: 'compact'` is set
- Important note: only matters when the effective row hierarchy has more than one level
### `expanded`
Persisted grouped row expansion state.
- Purpose: Restore row drill-down state.
- Accepted value: `Record`
- Default: `undefined`
### `groupAggregations`
Render aggregate values inside grouped Pivot drill-down rows.
- Purpose: Show grouped branch aggregates under generated Pivot metric columns, not only the group label.
- Accepted value: `boolean`
- Default: `false`
- Important note: currently applies to the client-side Pivot path. Server-side Pivot still needs backend-precomputed grouped aggregates for exact support.
### `expandedKeyEncoding`
Optional `'semantic-v1'` opts `expanded` into typed path keys. Create keys with
`createPivotExpandedPathKey` and preserve the encoding alongside the map.
Omitted means comma-joined keys; typed keys avoid collisions between
commas, nulls and differently typed members. See [Drilldown](/guides/pivot/concepts/drilldown/).
### `groupLabels`
Optional `{ empty?: string; null?: string }` display labels for empty strings
and null/undefined axis members. Defaults to empty labels. This affects captions,
not member identity or filtering; see [Field Metadata](/guides/pivot/concepts/field-metadata/).
### `groupLabelColumn`
Controls which visible row cell renders grouped row labels.
- Purpose: Keep grouped labels visible when grouped fields are hidden with column-hide.
- Accepted value: `'grouped' | 'firstVisible'`
- Default: `'grouped'`
- Behavior:
- `'grouped'` keeps labels in the grouped field column.
- `'firstVisible'` renders labels in the first currently visible row cell.
### `columnCollapse`
Generated column collapse config.
- Purpose: Enable collapsible Pivot column groups.
- Accepted value: `boolean | PivotColumnCollapseConfig`
- Default: enabled and collapsed when `columns` is non-empty
- Disable with: `false` or `{ enabled: false }`
| Column-collapse option | Meaning and default |
| --- | --- |
| `columnCollapse.enabled` | Enable generated column drill-down; defaults true when column dimensions exist. |
| `columnCollapse.collapsed` | Default collapsed state; defaults true. Persisted group state takes precedence. |
| `columnCollapse.filterable` | Show aggregate filters on collapsed/group headers; defaults true. Expanded leaves retain their ordinary filters. |
| `columnCollapse.aggregator` | Override collapsed bucket operation for every measure (string) or by value source property (map). Defaults to the normal measure/summary policy. |
| `columnCollapse.placeholder` | Override placeholder label/template with a string, ColumnTemplateFunc or ColumnCollapsePlaceholder. |
### `columnLevels`
Per-level generated column behavior.
- Purpose: Override collapse, subtotal, filtering, and sorting behavior for a
specific zero-based level in `columns`.
- Accepted value: `Record`
- Default: omitted; global Pivot and value-dimension settings apply
| Per-level option | Meaning and default |
| --- | --- |
| `columnLevels[].collapsible` | Falls back to `columnCollapse.enabled`. |
| `columnLevels[].collapsed` | Falls back to `columnCollapse.collapsed`. |
| `columnLevels[].subtotal` | Show this level's subtotal while global subtotals are enabled. Overrides disabledSubtotals for the level. |
| `columnLevels[].subtotalLabel` | Optional per-level subtotal caption. |
| `columnLevels[].subtotalPosition` | `'before'` (default) or `'after'` descendants. |
| `columnLevels[].subtotalValues` | Ordered value-property subset; defaults to all configured measures. |
| `columnLevels[].filterable` | Falls back to the value dimension and columnCollapse policy. |
| `columnLevels[].sortable` | Falls back to the value dimension's sortable flag. |
```ts
columns: ['year', 'quarter', 'month'],
totals: { subtotals: true },
columnLevels: {
0: {
subtotal: true,
subtotalLabel: 'Year Total',
subtotalPosition: 'before',
subtotalValues: ['sales'],
filterable: true,
sortable: true,
},
1: {
subtotal: false,
filterable: false,
sortable: false,
},
}
```
An explicit `subtotal` value in `columnLevels` takes precedence over
`totals.disabledSubtotals.columns` for that level. `totals.subtotals` remains
the global on/off switch.
See [Column Totals With Drill-Down](/guides/pivot/concepts/totals/#column-totals-with-drill-down)
for the complete configuration matrix, resolution order, interaction
guarantees, and screenshots.
### `collapsedColumns`
Persisted generated column collapse state.
- Purpose: Restore collapsed column groups.
- Accepted value: `boolean | Record`
- Default: `undefined`
## Engine Options
### `aggregationSemantics`
Pins the report to local or server execution before loading data.
- Accepted value: `'local' | 'server'`
- Default: omitted, preserving existing unpinned report behavior
- Persistence: retained in native configuration and V1/V2 portable reports
`'local'` rejects an engine with server mode, an adapter, or a remote store;
`'server'` rejects local execution. Pin older saved reports before permitting
engine rebinding. A pin does not change aggregate policies or certify local/SQL
equivalence: default local AVG includes null as zero, whereas SQL AVG skips null.
Review KPI results and explicitly migrate the report before changing its pin.
Versioned local aggregate operations require local execution. Direct local model,
cooperative-work and `ClientPivotEngineAdapter` APIs enforce the pin before reading
facts, using their actual local execution path even with server-looking metadata.
Readers must support this property to import pinned reports.
See [Aggregation policies](/guides/pivot/concepts/aggregations/) for defaults,
recommended V2 presets and complete examples.
### `engine`
Switches between local and remote analytical execution.
- Purpose: Select the execution owner; changing engines can change calculation semantics and requires explicit validation.
- Accepted value: object
- Default: omitted, which keeps current client-side behavior
Nested options:
### `engine.mode`
- Accepted value: `'client' | 'server'`
- Default: omitted
- Behavior: `'server'` makes `PivotPlugin` load through an adapter or remote store
### `engine.adapter`
- Accepted value: `PivotEngineAdapter`
- Behavior: custom execution boundary used directly by the plugin
### `engine.remoteStore`
- Accepted value: `PivotRemoteStore`
- Behavior: framework-agnostic HTTP or transport-backed store used by the server adapter
### `engine.capabilities`
Optional `PivotEngineCapabilities` for analytical UI admission. `aggregation`,
`filter`, `filterValues`, `sort`, and `drilldown` are optional booleans;
`calculations` is a boolean or supported `PivotValueCalculationType[]`.
Adapter/store capabilities are read first; explicitly supplied config capability
properties win. Omitted or opaque support does not establish availability for
commands that require an advertised capability.
Capabilities describe actual execution support; they do not implement it. For
example, the HTTP remote store has no built-in member-choice method, so
`filterValues: true` alone does not create an enumeration endpoint. Supply a
working adapter/store integration and advertise only what it can execute.
See [Remote API Reference](/guides/pivot/server-side/remote-api-reference/).
### `engine.viewId`
- Accepted value: `string`
- Default: `'default'`
- Behavior: backend semantic view or dataset identifier
### `engine.fieldsVersion`
- Accepted value: `string`
- Default: `'local'`
- Behavior: field registry version or checksum
### `engine.rowAxis`
- Accepted value: `Partial`
- Default: `{ offset: 0, limit: 100 }`
- Behavior: initial row-axis analytical window hint
### `engine.columnAxis`
- Accepted value: `Partial`
- Default: `{ offset: 0, limit: 24 }`
- Behavior: initial column-axis analytical window and hierarchy frontier.
`expandedPaths` and `collapsedPaths` use typed analytical paths and are
updated automatically by Pivot column expand/collapse controls in server
mode.
## Realistic Example
This layout reuses the `numeric` callback map from the minimal example.
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region', sortable: true },
{ prop: 'rep', name: 'Rep' },
{ prop: 'year', name: 'Year' },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'sales',
name: 'Sales',
aggregators: numeric,
},
],
rows: ['region', 'rep'],
columns: ['year', 'quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
collapsed: true,
groupAggregations: true,
hasConfigurator: true,
};
```
## Common Mistakes
- Supplying an incomplete explicit `dimensions` catalog. Omit it for inference, or include every referenced field and any additional fields users should discover.
- Expecting `columns` to be required. A row-only Pivot is valid.
- Using `columnCollapse` without generated column groups.
- Assuming `engine` changes the public config shape. It selects the execution path; it does not preserve aggregate semantics automatically, and a mismatching pin rejects the switch.
## Related Guides
- [Plugin Lifecycle](/guides/pivot/concepts/plugin-lifecycle/)
- [Configurator API](/guides/pivot/configurator/configurator-api/)
- [Field Panel](/guides/pivot/concepts/field-panel/)
- [Server-Side Pivot](/guides/pivot/server-side/)
- [OLAP Best Practices](/guides/pivot/server-side/olap-best-practices/)
---
# Custom Integrations
URL: https://pro.rv-grid.com/guides/pivot/concepts/custom-integrations/
Source: src/content/docs/guides/pivot/concepts/custom-integrations.mdx
Description: Build custom Pivot constructors, toolbars, field editors and async integrations using checked public package APIs.
Choose one analytical owner. An attached grid already has a `PivotPlugin` controller; its `getAuthoringController()` facade lets your application author that same report. A standalone renderer uses `createPivotController` from `@revolist/pivot/headless` instead.
| Integration | Application owns | Public surface |
| --- | --- | --- |
| Viewer | Rendering policy and permitted interactions | Committed runtime, exports and optional drill |
| Custom constructor | Four-zone controls and application admission | Attached authoring commands, snapshots, history |
| Stock panel outside grid | Host element, update wiring and cleanup | `definePivotConfigurator` |
| Toolbar or field editor | Controls, download lifetime and presentation | Visible export, stable field/measure commands |
| Async application API | Auth, tenant scope, response validation and execution | Client/server adapter contract |
| Standalone engine | Rendering, source replacement and disposal | Headless controller |
## Complete application mount
Install and connect the RevoGrid element with `PivotPlugin` once before calling the recipe. The host owns grid destruction. This mount requires an inactive Pivot, supplies the source/config once, and returns cleanup for only the application shell.
```ts
import { PivotPlugin } from '@revolist/pivot';
import { mountPivotIntegration } from './public-integration/index';
// In your framework's mounted/effect callback, after the grid is connected:
grid.plugins = [PivotPlugin];
const integration = await mountPivotIntegration({
grid, constructorHost, toolbarHost, editorHost,
onCommitted(snapshot) {
// Ready revisions only. Session descriptors are not automatically portable.
saveAcceptedLayout(snapshot.report?.specification);
},
});
// On framework cleanup:
integration.dispose();
```
Copy the complete checked modules below into `public-integration/`, retaining their relative imports. Every library import uses a public package entry. The displayed source is imported directly from the executable recipe files, so the documentation and browser fixture exercise the same code. Run `pnpm --filter @revolist/pivot check:recipes` in the monorepo after building Pivot to compile against actual installed `dist` declarations and execute the DOM-free headless example.
## Four-zone constructor and field editor
The constructor starts with populated Rows, Columns, Values and Filters. Use Add, Remove, Up/Down and cross-zone controls to edit the report. The same Revenue field has distinct SUM and AVG measure IDs. The atomic action swaps axes and relabels one measure in one history entry; Undo, Redo and Reset use the same owner as the grid.
Member filters accept one typed JSON scalar, for example `2025` or `"Australia"`; Clear removes that selection. Field label/allowed-role controls change metadata, while the measure editor independently changes aggregation, label and numeric formatting. Field IDs and source properties are never edited as captions.
Controls render from `pendingReport` while loading and committed `report` otherwise. A pending shelf is intent; the grid retains the last committed values until success. Invalid commands can throw synchronously, and accepted operations can return prevented, superseded, cancelled, noop or failed. The recipe catches both forms and does not call every resolved promise a commit. Inputs stay mounted while typing; grouped controls restore keyboard focus after a shelf change.
## Viewer policy
Pass `mode: 'viewer'` to omit constructor/editor controls and report import. This recipe synchronously prevents **all** subsequent configuration proposals, captures the public row expansion event before Core mutates its grouping view, and disables column collapse controls (including per-level overrides). Column collapse events are post-mutation notifications and are not veto points. Export and supported drill remain available. A viewer that permits expansion needs a narrower explicit policy instead.
There is no Pivot `readOnly` authoring switch. Core editing restrictions do not freeze analytical layout, and a UI veto is not backend authorization. Apply tenant and field permissions on the application API too.
## Toolbar, persistence and drill
CSV and XLSX use `plugin.exportCsv()` and `plugin.exportXlsx()`; TSV uses `plugin.getVisibleExportData()`: readable grouped captions, raw numeric aggregates, nested headers and pinned totals. The remote export covers the current logical report/window, not every fact in the backend. Exporting raw `grid.source` loses renderer-generated group aggregates.
CSV, XLSX and TSV require a settled result. The checked toolbar disables data exports while an analytical request is loading and explains when they become available again. Retained grid pixels during loading do not promise that visible-export capture is available. Report JSON is a separate configuration operation.
Report JSON describes configuration, not data. Export can reject session-only callbacks or transient linked filters. Supply explicit `reportOptions` with named runtime Maps when importing bound reports, and `reportVersion: 2` only when your application admits V2. The import path retains strict version/runtime admission; the authoring facade does not accept `report.replace`.
Drill inputs are semantic row/column paths, with a maximum 100-row page in this recipe. `loadPivotDrilldown` also supports positional `dataIndex` when selecting a measure. Requests carry an AbortSignal and are fenced to the captured committed revision. Cancelling, changing revisions or disposing the shell prevents stale results from painting the UI. Capability checks require an active runtime first; unsupported drill is disabled and explained.
Optional `chart` options create an unlinked standalone chart using an explicit application-supplied dataset/model via the public `/charts` entry. The recipe destroys only the chart it creates, including when initialization finishes after unmount. It does not recompute Pivot aggregates or claim that this unlinked chart follows report edits automatically. Use the existing [Pivot Charts integration](/guides/pivot-charts/) for linked chart behavior.
## Server adapters and honest async state
`createHttpAdapter(baseUrl)` posts load, drilldown and filter-values requests to application endpoints, forwards AbortSignal and rejects non-2xx responses. Its advertised capabilities are a contract your endpoints must implement; response validation and authentication remain application responsibilities. Set `config.engine` to `{ mode: 'server', adapter }`. Only `client` and `server` are public engine modes.
`createSimulatedAdapter()` is explicitly an **in-memory simulation** over the supplied sample configuration/facts. `setDelay(ms)`, `failNext()` and `requests` support testing retained results, failure and latest-wins behavior. It advertises drill/member lookup unavailable and deliberately ignores abort to test stale-result fencing. It is not a production backend test.
| Operation | Client | Server |
| --- | --- | --- |
| Commands/history | Attached facade | Same facade; async outcomes |
| Member lookup/drill | Public methods, committed scope | Adapter support required |
| Export | Current visible logical result | Current returned window |
| Isolated analytical query | Exact-source capability check | Unsupported |
| Source/runtime replacement | Host/report import | Host/report import |
For synchronous application admission, prevent `pivot-config-update` before execution. Persist a newly committed authoring snapshot afterwards. Do not save proposals as successful results or build a second headless controller to mirror the grid.
## Exact executable sources
## Lifetime
Dispose the returned shell before removing its hosts. This unsubscribes constructors/editors/persistence, aborts drill, removes viewer veto and releases its chart. It does not destroy the grid or erase its report. Clear the existing Pivot explicitly before mounting a new integration that initializes source/config. Captured authoring facades survive clear/re-enable; after plugin destroy they remain terminal. Pending adapter work belongs to the plugin and can be cancelled explicitly when the grid owner tears down.
The compile-only `authority.type-test.ts` additionally rejects private controller access, attached disposal/source replacement, `report.replace`, invented hybrid mode and cross-entry authority imports. These negative assertions are never executed in the application.
---
# Dimensions
URL: https://pro.rv-grid.com/guides/pivot/concepts/dimensions/
Source: src/content/docs/guides/pivot/concepts/dimensions.mdx
Description: Understand Pivot dimensions, how they map to fields, and which column options carry over into Pivot.
A Pivot dimension is the reusable definition of a field. It tells Pivot what the field is called, how it should behave in generated headers, and which aggregators are valid when that field is used as a measure source.
For new local reports, prefer the [V2 numeric/scalar presets](/guides/pivot/concepts/aggregations/#recommended-local-configuration). The native maps below demonstrate ID registration with default callbacks: they do not declare strict input policies. A nonempty map replaces that field's built-ins. Datatype metadata controls authoring defaults, not aggregate coercion or validation.
## What A Dimension Is
Each dimension is a `PivotConfigDimension`. In practice it is a partial RevoGrid column definition plus Pivot-specific metadata.
```ts
const dimensions = [
{ prop: 'region', name: 'Region', sortable: true },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'sales',
name: 'Sales',
aggregators: {
sum: commonAggregators.sum,
avg: commonAggregators.avg,
},
},
];
```
`prop` is the source-of-truth field id. `rows`, `columns`, and `values` all refer back to these same `prop` values.
## How Dimensions Are Reused
- In `rows`, a dimension becomes part of the leading row hierarchy.
- In `columns`, a dimension becomes part of the generated header hierarchy.
- In `values`, the same field acts as the carrier for the aggregated measure.
This shared model is what keeps Pivot configuration consistent. You define a field once, then reuse it on whichever analytical axis makes sense.
## Column Options That Matter In Pivot
`PivotConfigDimension` extends `Partial`, so the most useful inherited options are:
- `prop`: The field id Pivot uses everywhere.
- `name`: Display label for generated headers and configurator entries.
- `sortable`: Enables sorting of generated members for that dimension.
- `order`: Initial sort direction for generated members when sorting is enabled.
- `filter`: Filter metadata carried into generated columns.
- `columnType`: Lets generated value columns reuse an existing column type.
- `template`: Custom rendering for generated cells or headers.
- `cellProperties`: Per-cell visual or behavioral overrides.
- `size`, `minSize`, `maxSize`: Width behavior for generated columns.
The most important rule is that Pivot does not invent a second metadata model. Generated columns are still RevoGrid columns, and the dimension is where their base behavior comes from.
## Aggregators Live On The Dimension
Dimensions also declare which aggregators are available when the field is used in `values`.
```ts
{
prop: 'margin',
name: 'Margin',
aggregators: {
sum: commonAggregators.sum,
avg: commonAggregators.avg,
max: commonAggregators.max,
},
}
```
This matters in two places:
- `values[].aggregator` resolves against the dimension.
- The configurator uses the dimension's registered aggregators to populate its value selector.
## Good Dimension Design
- Keep `prop` stable. Changing it breaks saved Pivot layouts.
- Use business-friendly `name` values because generated headers reuse them.
- Define aggregators only where they make semantic sense.
- Avoid using display labels as identifiers. `prop` should be the identifier, `name` should be presentation.
## Common Mistakes
- Omitting a dimension for a field that later appears in `values`.
- Registering an aggregator on a text field just because the function exists.
- Expecting dimension order to control row or column placement. Placement is controlled by `rows` and `columns`, not by the `dimensions` array order.
## Next
Continue with [Aggregations](/guides/pivot/concepts/aggregations/) to see how measures are resolved from these dimensions.
---
# Drill Through
URL: https://pro.rv-grid.com/guides/pivot/concepts/drill-through/
Source: src/content/docs/guides/pivot/concepts/drill-through.mdx
Description: Inspect source records with a customizable detail grid, searchable column selection, and runtime grid and cell APIs.
Choose **Drill Through** from a Pivot value cell's context menu to inspect the
source records that contributed to that cell. The dialog grid uses the
`compact` theme by default and starts read-only, with sorting, resizing, column
movement, and filtering enabled. It has its own grid instance; configuring it
does not change the main Pivot grid.
## Choose available and visible fields
The **Columns** dropdown supports multiple selections, field-name search,
selecting or clearing all search results, and sorting its list in source order,
ascending, or descending label order. Sorting this list does not sort source
records. Use the detail grid's column headers to sort records.
```ts
const nextConfig = {
...pivot.getRuntimeSnapshot()!.config,
contextMenu: {
drillThrough: {
pageSize: 200,
columns: ['region', 'representative', 'sales'],
visibleColumns: ['representative', 'sales'],
columnChooser: true,
},
},
};
await pivot.updatePivotConfig(nextConfig);
```
- `columns` controls the requested/available field scope. When omitted, the
chooser discovers fields in the returned records, excluding internal `__`
properties. An empty `columns` array retains automatic discovery.
- `visibleColumns` controls the initial visible subset. Omit it to show all
available columns; use `[]` to hide all of them. Hidden definitions remain
available in the chooser.
- `columnChooser: false` hides the dropdown; programmatic visibility updates
still work.
- `pageSize` limits the requested/displayed records and defaults to `100`.
These settings are serializable in saved reports. Chooser changes apply to the
current dialog; use `onColumnsChange` below to keep application state and pass
it as `visibleColumns` when opening the next dialog. Showing a field through the
chooser does not request additional records or fields from the server. Make
sure the drill-through provider returns every field users may select.
## Configure the full detail grid
Obtain the installed `PivotPlugin` instance and call
`setDrillThroughOptions()` before opening the dialog. Its `grid` accepts
`Partial` from `@revolist/revogrid`: theme, column definitions,
column types, custom editors, plugins, additional data, row sizing, editing,
selection, and other grid properties are supported. Properties are applied after
the dialog defaults. Complete `grid.columns` definitions override generated
columns and define the chooser's available fields, including grouped columns.
```ts
import { PivotPlugin } from '@revolist/pivot';
const plugins = await grid.getPlugins();
const pivot = plugins.find(plugin => plugin instanceof PivotPlugin)!;
let visibleColumns = ['representative', 'sales'];
pivot.setDrillThroughOptions({
grid: {
theme: 'darkCompact', // Override the compact default.
rowSize: 32,
readonly: true,
additionalData: { currency: 'EUR' },
columns: [
{ prop: 'representative', name: 'Representative', filter: true, sortable: true },
{
prop: 'sales',
name: 'Sales',
size: 160,
filter: 'number',
sortable: true,
columnTemplate: () => 'Sales · EUR',
cellTemplate: (_h, { model, prop }) =>
new Intl.NumberFormat('en', {
style: 'currency', currency: 'EUR',
}).format(Number(model[prop])),
cellProperties: ({ model, prop }) => ({
class: { 'negative-sales': Number(model[prop]) < 0 },
}),
},
],
},
onColumnsChange(columns) {
visibleColumns = [...columns].map(String);
},
onGridReady(session) {
// Use native grid events for cell interaction and other extensions.
const handleCellClick = (event: Event) => {
console.log('Detail grid interaction', event, session.context);
};
session.grid.addEventListener('click', handleCellClick);
return () => session.grid.removeEventListener('click', handleCellClick);
},
});
```
Generated columns also inherit matching Pivot dimension metadata, including
`cellTemplate`, `columnTemplate`, `cellProperties`, column types, and formatters.
Explicit `grid.columns` give you complete control over individual columns and
cells. To enable editing, configure `readonly: false` and any column-level
`readonly` settings. Editing affects the dialog's records; persisting edits or
refreshing Pivot aggregates remains application-owned.
Runtime options apply to future dialogs. `setDrillThroughOptions()` replaces the
previous options; call it with `{}` to restore defaults. Functions, plugin
constructors, and grid objects stay outside report JSON. Reapply these options
when creating a new Pivot plugin instance.
## Update an open dialog
```ts
const session = pivot.getDrillThroughDialog();
if (session) {
session.setVisibleColumns(['sales']);
console.log(session.getVisibleColumns());
console.log(session.getAvailableColumns());
session.updateGrid({ theme: 'compact', rowSize: 28 });
// New columns replace the chooser inventory and initially show all new fields.
session.updateGrid({
columns: [{ prop: 'sales', name: 'Amount', filter: 'number', sortable: true }],
});
// session.close();
}
```
`getDrillThroughDialog()` returns `undefined` when no detail grid is open or
while records are still loading. `setVisibleColumns()` ignores unknown fields
and invokes `onColumnsChange`. `getAvailableColumns()` returns leaf definitions;
column group structure and pinning are retained when hiding/showing fields.
Use `updateGrid({ columns })` when replacing columns so the chooser stays in
sync. Other grid properties can also be updated directly on `session.grid`.
`onGridReady` runs after the detail grid is added to the dialog. It may return a
cleanup function, which runs once when the dialog closes, is replaced, or its
owner is destroyed, including the header close button and Escape paths. The
session's `context` contains the clicked cell's Pivot paths and report snapshot.
## Client and server records
Client Pivot resolves source records locally. Server Pivot requires a provider
with drill-through support and uses the clicked cell paths, requested columns,
and record limit. The footer displays the returned record count and the total
reported by the provider. Grid customization changes presentation and interaction;
it does not change the analytical scope of the drill-through request.
---
# Drill-Down
URL: https://pro.rv-grid.com/guides/pivot/concepts/drilldown/
Source: src/content/docs/guides/pivot/concepts/drilldown.mdx
Description: Understand row expansion, column collapse, and the state that controls Pivot exploration.
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.
Open the full demo and framework source.
## Row Drill-Down
Row drill-down is enabled when:
- the effective row hierarchy has more than one level
- `collapsed` is a boolean, `expanded` state exists, or `rowAxisLayout` is `'compact'`
```ts
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
- active row drill-down uses one compact Rows column by default
- `rowAxisLayout: 'tabular'` keeps one visible column per configured row field
Compact mode changes only the generated grid columns. The Pivot model, field
panel, persisted configuration, and remote requests continue to use separate
`region` and `rep` dimensions.
```ts
const pivot: PivotConfig = {
rows: ['region', 'rep'],
values: [{ prop: 'sales', aggregator: 'sum' }],
collapsed: true,
rowAxisLayout: 'tabular', // opt back into separate Region and Rep columns
};
```
`PivotPlugin` translates the Pivot row structure into RevoGrid grouping options, then emits `pivot-config-update` when users expand or collapse groups.
## Grouped Row Aggregates
Set `groupAggregations: true` to render aggregate values inside grouped Pivot rows.
```ts
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:
```ts
const pivot: PivotConfig = {
rows: ['region', 'product'],
columns: ['quarter'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
collapsed: true,
rowAxisLayout: 'tabular',
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
Column drill-down is built into Pivot whenever `columns` is non-empty. Generated
groups start collapsed without additional configuration.
```ts
columns: ['year', 'quarter', 'month']
```
Behavior:
- generated Pivot column groups become collapsible
- generated groups start collapsed at the highest level and expand one level at a time
- set `columnCollapse: false` or `{ enabled: false }` to disable column drill-down
- set `columnCollapse: { collapsed: false }` to start with all generated groups expanded
- use `columnLevels` to override collapse, subtotal, filter, and sort behavior
independently at each zero-based column level
- collapsed groups render a placeholder header/cell
- placeholder cells use a collapsed-bucket aggregator, not a naive sum of visible child cells
- expanded subtotal columns appear before descendant columns by default, keeping
the parent aggregate visible and actionable
- `columnLevels[level].subtotalValues` can keep that aggregate to one value
field—or any ordered subset—when the Pivot has multiple measures
- expanding reveals one level only; descendants and sibling branches keep
their current state
- aggregate filters and sorting remain attached to the same analytical group
when its compact and expanded presentations switch
Persisted state for generated groups lives in `collapsedColumns`.
See [Column Totals With Drill-Down](/guides/pivot/concepts/totals/#column-totals-with-drill-down)
for the complete subtotal/collapse behavior matrix, precedence rules, and
screenshots.
## Related Options
- `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
- `columnLevels`: configure each generated column hierarchy level
- `collapsedColumns`: persisted generated column collapse state
## Placeholder Behavior
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.
## State Persistence
`pivot-config-update` announces a synchronous, cancelable proposal. Persisting its payload can save a change that later fails or is superseded. Subscribe to `pivotPlugin.getAuthoringController()` and save each newly committed snapshot whose `status` is `ready`; `snapshot.revision` prevents duplicate writes. The accepted report includes drill state. See [the complete persistence subscription](../plugin-lifecycle/#pivot-config-update) and [portable report export](../report-specifications/) for runtime bindings and restore behavior.
## Common Mistakes
- 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.
## Next
Continue with [Sorting And Filtering](/guides/pivot/concepts/sorting-filtering/) or [Plugin Lifecycle](/guides/pivot/concepts/plugin-lifecycle/).
## Query Original Records
`PivotPlugin.loadPivotDrilldown` queries the original client source without requiring
`engine.mode`. A custom adapter or remote store receives the same request instead.
Use semantic axis values, rather than rendered labels or generated property keys:
```ts
const page = await pivotPlugin.loadPivotDrilldown({
cell: { rowPath: ['North'], columnPath: [2025], dataIndex: 0 },
customColumns: ['orderId', 'sales'],
offset: 0,
limit: 50,
signal: abortController.signal,
});
// page.data contains this page; page.totalCount counts all matching records.
```
For the built-in client engine:
- Paths are typed prefixes in the configured axis order. `[]` means the total for
that axis; a shorter path addresses a subtotal. Missing source values match
`null`. Values-on-rows uses the analytical row fields without a value-label
pseudo-field; `dataIndex` identifies the configured measure instance.
- Active member selections, independent chart filter contributions and Core
source predicates constrain the facts. Aggregate result filters do not become
source-record predicates. Opaque custom Core evaluators are rejected when an
exact source predicate cannot be evaluated.
- Top/Bottom N uses the report's ranking. An Other cell returns its original
transactions and labels, in original source order. Hidden ranked-out members
contribute no facts when the report excludes Other.
- `customColumns` projects known source/configured fields. Omit it to return all
source properties; an empty array returns empty record objects with the count.
Applications decide which fields to expose. This local projection is not an
authorization boundary.
- Offset must be a nonnegative safe integer and limit a positive safe integer.
Invalid selectors, measure indices and paths reject instead of widening the
query. Already-aborted requests reject.
For calculated measures, records describe the selected row/column coordinate.
They do not enumerate every operand of a running total, ratio or other
post-aggregation formula, and summing them need not reproduce the displayed
calculation.
`loadPivotFilterValues` also works in default local mode. Its domain excludes the
requested field's own Pivot member selection while retaining Core source filters,
other fields and independent filter owners. Search runs before distinct-value
paging; missing values are exposed as `null`.
Standalone `ClientPivotEngineAdapter` queries use the constructor's source and
configuration. Its `filterValues` request additionally accepts a portable filter
tree. Comparisons preserve primitive types, text predicates are case-sensitive,
and unsupported or malformed predicates reject. Grid Core predicates are
available through `PivotPlugin`, which owns that state.
---
# Execution, limits and recovery
URL: https://pro.rv-grid.com/guides/pivot/concepts/execution-and-limits/
Source: src/content/docs/guides/pivot/concepts/execution-and-limits.mdx
Description: Choose local scheduling and understand exact resource accounting units, defaults and recovery behavior.
## Local execution
Use `localExecution` on `createPivotController`, or `grid.pivot.engine.localExecution` for the attached plugin. Keep a single analytical owner per grid.
```ts
let sourceRevision = 0;
const localExecution = {
mode: 'cooperative' as const, budgetMs: 8,
sourceRevision: () => sourceRevision,
};
// Supply localExecution to createPivotController({ report, source, localExecution }).
// Before changing accepted facts: sourceRevision++; then mutate; then refresh().
```
| API | Type/default/units | Behavior |
| --- | --- | --- |
| `localExecution.mode` | `'cooperative'`; runtime opt-in | Default omission retains synchronous timing |
| `budgetMs` | Number, default 8; milliseconds, greater than 0 and at most 50 | Target between yields, not a deadline |
| `sourceRevision` | Required callback returning a stable value | Compared with `Object.is`; change before source mutation |
| `autoLoad` | Boolean, false | Start execution on controller creation |
| `historyLimit` | Nonnegative integer, 100 | Zero disables undo history |
| Query axis offsets/limits | Nonnegative safe integers; omitted means complete local window | Zero limit means empty window |
| `cancel()` | No arguments | Settles pending work; prevents stale publication |
| `dispose()` | No arguments | Cancels work and releases subscriptions |
Keep source membership, order and values stable until an operation settles. Source revision is a lease, not a deep snapshot; unannounced nested mutations cannot be reliably detected. Metadata admission is still synchronous. Opaque callbacks/getters/Proxy traps and atomic JavaScript operations can exceed the cooperative target. Remote scheduling, deadlines and database cancellation belong to the adapter/host.
## Runtime resource API
`PivotResourceLimits` is runtime-only: pass `resourceLimits` to the standalone controller or `engine.resourceLimits` in native config. Each omitted metric is **unbounded**. Supplied values must be nonnegative safe integers; zero is valid. Unknown metrics, accessors or invalid policy shapes throw `TypeError`. Application/controller and selected engine policies intersect using the lower per-metric ceiling; reports cannot raise application ceilings.
| Metric | Exact accounting unit | Example ceiling | What it does not measure |
| --- | --- | --- | --- |
| `maxSourceSlots` | Original selected array length, including holes | 10000 | Eligible facts, fields or heap |
| `maxModelStateEntries` | Cumulative entries created in instrumented model Map/Set/dictionary owners: group/aggregate buckets, hierarchies, collision/subtotal/calculation/semantic indexes, ranked provenance and remote materialization, including replicated indexes | 200000 | Unique members alone, all JS objects or bytes |
| `maxRetainedAggregateValues` | Cumulative raw aggregate input slots retained per destination occurrence | 500000 | Source length alone; one fact can contribute at several grains |
| `maxModelRecords` | Cumulative generated/owned record creations, including raw dictionaries and publication copies | 50000 | Nested objects or current live-record count |
| `maxModelProperties` | Cumulative first own top-level property creations on those records, including string/symbol metadata references | 500000 | Recursive payload size or subsequent overwrites |
| `maxExportDataCells` | Body plus pinned indexed row spans × exported leaf columns, labels/blanks included | 100000 | Only nonempty numeric cells |
| `maxExportHeaderNodes` | Header object occurrences, shared subtrees counted per occurrence | 2000 | Unique header identities |
| `maxExportUtf8Bytes` | Final escaped CSV/print HTML UTF-8 bytes, BOM/delimiters/markup included | 2000000 | Workbook ZIP size or heap |
| `maxWorkbookXmlBytes` | Sum of uncompressed XLSX XML part UTF-8 bytes | 4000000 | ZIP/container overhead or memory |
Model counters are deterministic accounting in named SDK owners, not a global allocation interceptor. Nested application payloads and custom callback allocations stay outside them. Cumulative work is not refunded merely because temporary state is released. These example ceilings are application choices, not tested capacity promises.
## Intent and contribution limits
`PivotIntentLimits` belongs to controller `intentLimits` or runtime `engine.intentLimits`. All metrics default to unbounded, use nonnegative safe integers and accept zero. Invalid policies throw; excess admitted intent raises `WINDOW_LIMIT_EXCEEDED`.
| Metric | Unit |
| --- | --- |
| `maxMetadataNodes` | JSON container/scalar occurrences including root, excluding property keys |
| `maxMetadataBytes` | Canonical admitted projection JSON UTF-8 bytes, not heap |
| `maxFilterNodes` | Explicit WHERE/HAVING conditions, connectives, negations and HAVING list containers |
| `maxMembershipValues` | Membership operand occurrences, including report/native selections |
`engine.memberFilterLimits` independently bounds transient contribution `maxOwners`, `maxTuplesPerOwner`, `maxPredicates` and `maxBytes`. Bytes count serialized intent: a complete request envelope or ordered active-owner `[[ownerId, tuples], ...]`; repeated references count per occurrence. Omitted metrics are unbounded and zero is valid. These limits do not establish tenant authorization.
## SQL reference work limits
The optional SQLite analytical profile requires trusted `workLimits`:
| Metric | Unit/default | Exclusions |
| --- | --- | --- |
| `maxDenseCoordinates` | Each cube’s row-prefix × column-prefix product, including roots; required nonnegative safe integer | Scans, SQL workspace, heap |
| `maxMaterializedRows` | Cumulative rows written to operation-owned SQL temporary tables; copies count again; required nonnegative safe integer | Internal indexes, temp bytes, CPU |
| `maxRankingMembers` | Positive ranking-member ceiling, default 1000 | All-purpose memory bound |
These are reference/backend policies, not fields of `PivotResourceLimits`. An authorized cache hit performs zero new temporary work; tightening a work ceiling to zero does not invalidate an otherwise authorized cached response. Bound cache entries/bytes/TTL and reauthorize separately. Synchronous SQLite cannot interrupt an executing statement with AbortSignal.
## Errors and recovery
Execution operations return `committed`, `noop`, `prevented`, `superseded`, `cancelled` or `failed`; only a commit publishes a new revision. Validate exceptions separately from asynchronous outcomes.
```ts
try {
const outcome = await controller.refresh();
if (outcome.status === 'failed') {
console.error(outcome.error.message, outcome.error.code, outcome.error.details);
// Keep controller.getSnapshot().view visible. Offer fewer facts/measures,
// a smaller export, or a host-approved revised policy, then retry.
}
} catch (error) {
console.error('Invalid input or policy', error);
}
```
For resource refusals, bounded controller details contain `stage`, `metric`, `limit`, `observed`. `observed: null` means unavailable/not representable, not zero. Arbitrary provider payloads are excluded. Last committed view/revision remain usable after execution failure; source intent and retained view can have different `sourceRevision`/`viewSourceRevision`.
The [runnable example](/examples/pivot/customer-example.mjs) commits six facts under a six-slot ceiling, refuses seven facts with `WINDOW_LIMIT_EXCEEDED`, checks unchanged revision, and successfully recovers with six facts. It also refuses an oversized CSV before retrying a smaller output.
| Error/situation | Customer action |
| --- | --- |
| `WINDOW_LIMIT_EXCEEDED` | Reduce source/cardinality/export or obtain a host-approved limit change; retry |
| `REVISION_MISMATCH` | Refresh current registry/report/snapshot; never download stale output |
| `INVALID_SELECTOR` / `INVALID_SUMMARY_TYPE` | Select authorized fields/operations advertised by this engine |
| `HIERARCHY_UNAVAILABLE` / `INVALID_HIERARCHY_PATH` | Use a supported hierarchy/profile and valid typed paths |
| `AUTH_REQUIRED` / `FORBIDDEN` | Reauthenticate or request access; do not retry with client-supplied tenant metadata |
| Missing runtime binding / invalid report | Restore the named host binding or repair the document; preserve the accepted report |
See [exports](/guides/pivot/concepts/exporting-reports/) and [deployment acceptance](/guides/pivot/compatibility/). No logical counter promises hard heap, CPU, SQL preemption, latency or concurrency guarantees.
---
# Export And State
URL: https://pro.rv-grid.com/guides/pivot/concepts/export-state/
Source: src/content/docs/guides/pivot/concepts/export-state.mdx
Description: Export semantic visible Pivot data and distinguish report/state persistence from source facts.
Use the installed `PivotPlugin` instance to capture the current visible logical result. `getVisibleExportData()` supplies readable grouped captions and semantic raw aggregates, nested column headers and pinned totals. Reading `grid.source` directly misses values in synthetic group rows. Exporting the original facts misses the entire Pivot layout.
```ts
import { PivotPlugin, buildPivotCopyTsv } from '@revolist/pivot';
const pivotPlugin = (await grid.getPlugins()).find(
(plugin): plugin is PivotPlugin => plugin instanceof PivotPlugin,
);
if (!pivotPlugin) throw new Error('Pivot is not installed');
const csv = pivotPlugin.exportCsv({ bom: true }); // string
const xlsx = pivotPlugin.exportXlsx(); // Uint8Array
const table = pivotPlugin.buildPrintTable(); // escaped HTML table
const tsv = buildPivotCopyTsv(pivotPlugin.getVisibleExportData());
await navigator.clipboard.writeText(tsv);
```
These methods capture the current visible logical result automatically, including group captions and aggregates, subtotals, grouped headers, and pinned totals. CSV options support delimiters, a UTF-8 BOM, and a grouped-header separator. XLSX retains numeric cells and nested headers. Exported numbers retain raw precision.
XLSX also carries standard measure number, currency and percent formats, including inferred percentage calculations, duplicate measures and values on rows. Formatting changes the workbook's display without rounding or converting numeric cells to text. Excel uses the viewer's decimal and grouping separators; currency labels and their position come from the configured locale. Required and optional decimal places, standard three-digit grouping, signs and accounting parentheses are retained.
Advanced Intl formats that cannot be represented faithfully use Excel `General` while keeping the raw numeric value: compact/scientific/engineering notation, significant-digit or nondefault rounding rules, padded integer digits, `signDisplay: 'exceptZero'`, unit formats, currency names, non-Latin digits, and nonstandard grouping. Date/datetime measure formats, custom cell renderers, conditional colors/classes and custom blank/invalid display text are not translated into XLSX styles. Use the printable table for configured display strings. Unformatted standalone-helper inputs retain their existing default numeric style.
`buildPrintTable()` applies the configured measure display formats (including inferred percentage calculations), while CSV/XLSX retain raw numeric precision. Declare a measure `format` such as `{ preset: 'currency', currency: 'USD', locale: 'en-US' }` to share its grid and print presentation. It returns a table with nested header spans and pinned-total rows marked with `class="total"`. The application supplies surrounding HTML, print styles, titles, and the browser print dialog; users can choose Save as PDF there. Pivot does not create PDF bytes or open windows automatically. Treat the returned markup as generated output; cell and header values are HTML-escaped.
For a detached or headless result, use the standalone `exportVisiblePivotToCsv`, `exportVisiblePivotToXlsx`, and `buildPivotPrintTable` helpers from `@revolist/pivot` or `@revolist/pivot/headless`. Pass resolved rows and nested columns, such as the output of `getVisibleExportData()`. The helpers do not resolve Core grouping carriers themselves.
The XLSX helper accepts `PivotXlsxExportInput`. Its optional `cellNumberFormats` matrix holds native Excel format codes in flattened leaf-column order, with body rows followed by pinned-bottom rows. `getVisibleExportData()` captures this matrix automatically from semantic measure identities. Keep it aligned if you reorder detached rows or columns. CSV ignores this presentation metadata.
The complete download, copy, report import/export and drill implementation is the checked `toolbar.ts` in [Custom Integrations](/guides/pivot/concepts/custom-integrations/). Applications own filenames, downloads, clipboard access, print dialogs, and branding.
Use V2 portable reports for the [recommended explicit aggregation policies](/guides/pivot/concepts/aggregations/). An execution pin survives portable V1/V2 report roundtrip; it does not guarantee matching local/server results. Configuration JSON is lossy for callbacks, not a substitute for report/runtime bindings. Preserve calculation meaning when [configuring saved reports](/guides/migrations/#pivot-calculation-policies-and-saved-reports).
## Visible window versus full dataset
Visible export captures the logical current result, including virtualized visible report rows and applicable totals; it is not just DOM pixels. In server mode it exports the returned report/window. It does not fetch every backend record. Full-dataset exports require an explicit server export operation with the same scope and permissions.
CSV, XLSX and TSV require a settled result. The checked toolbar disables data exports while an analytical request is loading and explains when they become available again. Retained grid pixels during loading do not promise that visible-export capture is available. Report JSON is a separate configuration operation.
## Portable report JSON
`pivotPlugin.exportPivotReportJson()` saves authored report configuration. `importPivotReport(json, options)` validates and executes a candidate through normal preventable admission. Supply named runtime bindings separately for callbacks, sources and adapters; explicitly opt in with `reportVersion: 2` for V2 documents. Live transient filters or unbound session callbacks can prevent portable export. JSON report documents do not contain the result facts.
## Lightweight state JSON remains supported
`serializePivotStateJson(config)` and `parsePivotStateJson(json)` transport JSON-safe `PivotConfig` state, including axes, values, filters, expansion and formatting. This remains distinct from portable semantic report documents and runtime registries. JSON-only `fieldPanel.texts` entries, including nested label maps such as `aggregationNames`, are preserved. Function-valued locale messages remain runtime callbacks and require explicit report bindings rather than state JSON.
```ts
import { serializePivotStateJson, parsePivotStateJson } from '@revolist/pivot';
const state = serializePivotStateJson(currentAcceptedConfig);
localStorage.setItem('sales:pivot-state', state);
const saved = localStorage.getItem('sales:pivot-state');
if (saved) {
try {
const accepted = await pivotPlugin.updatePivotConfig(parsePivotStateJson(saved));
if (!accepted) showMessage('Saved layout was not committed');
} catch (error) {
showMessage(String(error));
}
}
```
Persist committed state after successful execution, not every `pivot-config-update` proposal. Neither report JSON nor state JSON serializes source records or callback functions. Keep application bindings outside JSON and handle parse/import errors.
The Vue, React, Angular, and Vanilla TypeScript export/state examples use these resolved export APIs.
### Persistent named views
The attached **Save views** menu supports scoped durable storage through `PivotPlugin.setSavedViewStorage`. See [Persistent saved views](/guides/pivot/concepts/saved-views/) for browser storage, application adapters, conflict handling and recovery.
## Related product guides
[CSV, XLSX, print and complete exports](/guides/pivot/concepts/exporting-reports/).
---
# CSV, XLSX, print and complete exports
URL: https://pro.rv-grid.com/guides/pivot/concepts/exporting-reports/
Source: src/content/docs/guides/pivot/concepts/exporting-reports.mdx
Description: Export captured local reports or request complete authorized server snapshots with explicit output limits.
## Choose an export scope
| API | Result | Scope |
| --- | --- | --- |
| `pivot.exportCsv(options)` | String | Captured currently loaded logical report |
| `pivot.exportXlsx(options)` | `Uint8Array` | Same loaded scope, grouped headers and supported native number formats |
| `pivot.getVisibleExportData(options)` | Detached export data | Capture for local writers; not a complete remote fetch |
| `buildPivotPrintTable(input)` | Escaped HTML string | Supplied resolved result; does not open a print dialog |
| `pivot.exportFullReport('csv' | 'xlsx', options)` | Result containing `content` | Complete filtered remote report, requires `fullExport: true` |
A server viewport may contain only a window. Neither local writers nor exporting the loaded model fetch missing server groups. Use complete export for that requirement.
```ts
import { buildPivotPrintTable } from '@revolist/pivot/headless';
const captured = pivot.getVisibleExportData({
resourceLimits: { maxExportDataCells: 100000 },
});
const printable = buildPivotPrintTable({
...captured, resourceLimits: { maxExportUtf8Bytes: 2000000 },
});
// Place printable in the application's print surface and invoke its print flow.
```
Local captures detach owned headers/records from later grid changes. Captured policy ceilings cannot be raised by writer options; the stricter ceiling wins. Application nested payloads/formatters retain their documented atomic boundaries. Headless helpers `exportVisiblePivotToCsv` and `exportVisiblePivotToXlsx` accept supplied columns/body/pinned records; see the [executable writer example](/examples/pivot/customer-example.mjs).
| CSV option | Default | Meaning |
| --- | --- | --- |
| `columnDelimiter` | `,` | Cell separator |
| `rowDelimiter` | CRLF | Row separator |
| `bom` | false | Prefix UTF-8 BOM |
| `headerPathDelimiter` | ` / ` | Flatten grouped captions |
| `resourceLimits` | All omitted/unbounded | [Physical output counters](/guides/pivot/concepts/execution-and-limits/#runtime-resource-api) |
CSV preserves raw numeric output and escapes delimiters/quotes. XLSX keeps numeric values and supported native Excel measure formats; arbitrary appearance/conditional formatting is not workbook styling. Print HTML contains escaped resolved cells. None of these writers improves binary64 arithmetic precision.
## Complete remote export
```ts
const abort = new AbortController();
try {
const result = await pivot.exportFullReport('xlsx', {
signal: abort.signal,
limits: { maxRows: 5000, maxCells: 100000 },
onProgress: progress => console.log(progress.phase, progress.receivedBytes),
});
// Save result.content through the host download flow after success.
} catch (error) {
console.error(error); // Offer smaller scope or retry; retain the current report.
}
// Wire a Cancel control to abort.abort().
```
| Complete-export limit | Default/library ceiling | Units |
| --- | --- | --- |
| `maxRows` | 10000 | Body plus total rows |
| `maxColumns` | 256 | Flattened columns including labels and expanded measures |
| `maxCells` | 500000 | Physical rows × columns |
| `maxBytes` | 16000000 | Response JSON and formatted output byte ceiling |
Complete-export limits are positive integers; applications can lower them, not raise the library ceilings. This differs from optional zero-valid local resource counters. Refuse excess output; never silently truncate a complete report.
The backend echoes `requestId` and `queryKey`, declares `complete: true`, `scope: 'complete-filtered-report'`, and an `atomic-at-export` snapshot ID. It must read domains, counts, facts and totals from one authorized operation-owned snapshot. This is an export-time guarantee, not necessarily the data version of an earlier screen. Dataset-version consistency across requests needs an explicit host contract.
The request retains filters, ranking, sorting, collapse/totals/value orientation and labels while excluding viewport offsets/limits. Other must be reaggregated from original facts. SDK validation rejects incomplete/stale/mismatched output. Authentication, authorization, actual driver snapshots/deadlines/cancellation and server resource release remain backend obligations.
Cancellation settles public export promises and blocks stale downloads even if a custom adapter ignores its signal. It cannot guarantee that such an adapter stopped its database query. Background job infrastructure is deferred. See the [complete protocol](/guides/pivot/server-side/complete-export/) and [remote deployment guide](/guides/pivot/server-side/adapters-and-security/).
---
# Field Metadata
URL: https://pro.rv-grid.com/guides/pivot/concepts/field-metadata/
Source: src/content/docs/guides/pivot/concepts/field-metadata.mdx
Description: Configure Pivot field labels, hidden dimensions, empty group labels, measure aliases, and selective subtotals.
Field metadata is the information Pivot uses to describe fields before they become row groups, column groups, filters, or measures. Use it to give users clearer labels, keep internal fields out of normal field lists, and make generated totals easier to read.
Open the full demo and framework source.
## Hidden Dimensions
Mark a dimension as `hidden` when it should still be available to the Pivot config but should not appear in normal field lists.
```ts
const dimensions = [
{ prop: 'region', name: 'Region' },
{ prop: 'quarter', name: 'Quarter' },
{
prop: 'internalSegment',
name: 'Internal Segment',
description: 'Internal reporting segment.',
hidden: true,
},
];
const pivot = {
dimensions,
rows: ['region'],
columns: ['quarter', 'internalSegment'],
filters: ['internalSegment'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
};
```
Hidden does not remove the field from the Pivot engine. In this example, `internalSegment` can still be used in `columns` and `filters`; it is only hidden from helper-driven field lists unless you explicitly show hidden fields.
## Show Hidden Fields In Search
Use `filterPivotDimensions` for searchable field lists. Hidden dimensions are omitted by default. Pass `{ showHidden: true }` when you want an admin or advanced-user mode that reveals them.
```ts
import { filterPivotDimensions } from '@revolist/pivot';
const visibleFields = filterPivotDimensions(dimensions, searchText);
const allFields = filterPivotDimensions(dimensions, searchText, {
showHidden: true,
});
```
The helper searches `prop`, `name`, `description`, `dataType`, and `fieldGroup`, so descriptions are useful for discovery even when the visible label is short.
## Empty And Null Group Labels
Real data often contains empty strings, `null`, or `undefined`. Pivot can replace those values with user-facing labels in row and column groups.
```ts
const pivot = {
rows: ['region', 'channel'],
columns: ['quarter'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
groupLabels: {
empty: '(Empty)',
null: '(No value)',
},
};
```
`groupLabels.empty` is used for empty strings (`''`). `groupLabels.null` is used for `null` and `undefined`. Without these options, the current implementation displays empty labels for those group members.
## Measure Aliases
Each value can have its own display label. Use `label` for the preferred generated measure name. Use `name` as a fallback when `label` is not set.
```ts
const pivot = {
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue $' },
{ prop: 'units', aggregator: 'sum', name: 'Units Sold' },
],
};
```
Aliases are useful when the source field name is technical, abbreviated, or shared by multiple reports.
## Disabled Subtotals
Global subtotals can stay enabled while specific subtotal rows or subtotal columns are skipped. Target subtotals by axis field or by zero-based axis level.
```ts
const pivot = {
rows: ['region', 'channel', 'product'],
columns: ['quarter', 'internalSegment'],
values: [{ prop: 'revenue', aggregator: 'sum' }],
totals: {
grandTotal: true,
subtotals: true,
disabledSubtotals: {
rows: {
fields: ['channel'],
},
columns: {
levels: [0],
},
},
},
};
```
In this example, Pivot keeps subtotal generation enabled overall, skips row subtotals for the `channel` field, and skips column subtotals at level `0`.
## Complete Config
```ts
const pivot = {
dimensions: [
{ prop: 'region', name: 'Region', description: 'Sales territory.' },
{ prop: 'channel', name: 'Channel', description: 'Go-to-market channel.' },
{ prop: 'product', name: 'Product' },
{ prop: 'quarter', name: 'Quarter' },
{ prop: 'internalSegment', name: 'Internal Segment', hidden: true },
{ prop: 'revenue', name: 'Revenue' },
{ prop: 'units', name: 'Units' },
],
rows: ['region', 'channel', 'product'],
columns: ['quarter', 'internalSegment'],
filters: ['internalSegment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue $' },
{ prop: 'units', aggregator: 'sum', name: 'Units Sold' },
],
groupLabels: {
empty: '(Empty)',
null: '(No value)',
},
totals: {
grandTotal: true,
subtotals: true,
disabledSubtotals: {
rows: { fields: ['channel'] },
columns: { levels: [0] },
},
},
};
```
## Common Mistakes
- Expecting `hidden: true` to prevent a field from being used in `rows`, `columns`, `filters`, or `values`. It only affects field-list helpers.
- Passing `{ showHidden: true }` to the Pivot config. It belongs to `filterPivotDimensions`, not `pivot`.
- Using only `groupLabels.empty` when the source data contains `null` or `undefined`.
- Disabling `totals.subtotals` and then expecting `disabledSubtotals` to do anything. Selective disabling only matters when `subtotals: true`.
---
# Field Panel
URL: https://pro.rv-grid.com/guides/pivot/concepts/field-panel/
Source: src/content/docs/guides/pivot/concepts/field-panel.mdx
Description: Enable and use the compact Pivot field panel above the grid, including field actions, measures, filters and sorting.
The field panel is the compact Pivot editor above the grid. Its four areas show the active **Rows**, **Columns**, **Values** and **Filters**. Users can add and move fields, edit measures, and filter records while keeping the result in view.
Open the full demo and framework source.
## Enable It
```ts
import { PivotPlugin, type PivotConfig } from '@revolist/pivot';
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region', dataType: 'string' },
{ prop: 'category', name: 'Category', dataType: 'string' },
{ prop: 'quarter', name: 'Quarter', dataType: 'string' },
{ prop: 'sales', name: 'Sales', dataType: 'number', allowedRoles: ['values'] },
{ prop: 'active', name: 'Active', dataType: 'boolean', filterOptions: [true, false] },
],
rows: ['region'],
columns: ['quarter'],
values: [{ id: 'sales-sum', prop: 'sales', aggregator: 'sum' }],
filters: ['active'],
hasConfigurator: false,
fieldPanel: { visible: true },
};
const grid = document.querySelector('revo-grid')!;
grid.plugins = [PivotPlugin];
grid.pivot = pivot;
grid.source = [
{ region: 'North', category: 'Audio', quarter: 'Q1', sales: 120, active: true },
{ region: 'North', category: 'Video', quarter: 'Q2', sales: 80, active: false },
{ region: 'South', category: 'Audio', quarter: 'Q1', sales: 90, active: true },
];
```
`fieldPanel.visible` defaults to `false`. Setting it to `true` lets `PivotPlugin` mount and manage the panel above its grid. `hasConfigurator` controls the [full configurator](/guides/pivot/configurator/configurator/) independently. Keep it `false` when only the compact panel is needed.
## Use the Panel
| Area | Purpose | Try it |
| --- | --- | --- |
| Rows | Group records vertically; field order defines the hierarchy. | Add Category, then drag it before or after Region. |
| Columns | Group records into column headers. | Move Quarter here or swap the row and column axes. |
| Values | Aggregate source values into each result cell. | Open the Sales cog to choose an aggregation or **Edit measure**. |
| Filters | Filter source records before aggregation. | Open the Active cog and choose **Edit filter**. |
Use the **+** beside an area to search the field catalog and add an eligible field. The cog beside a placed field opens its actions: settings such as aggregation or **Edit filter** come first, followed by layout actions such as assigning another area or moving earlier/later, then removal when allowed. Dividers separate these groups. Available actions follow the field's `allowedRoles` and visible areas.
Drag chips to reorder or move them between compatible areas. The cog's layout actions provide a keyboard alternative. **Swap Rows and Columns** exchanges the axes together; the sort button beside a row field changes its sort order.
In a wide host, Rows sits beside Values and Columns, with Filters above. In a narrow host, the areas stack and chips wrap to fit the available space. Long captions truncate within a chip; hover the caption to read its full label while the action controls remain available.
In **Edit filter**, search for members, choose Include or Exclude, select values, and apply. Selections retain their original types: Boolean `true`, number `1`, string `'1'` and `null` are distinct members. Active selections appear above the shelves and can be reopened or cleared. `filters` stores field placements; `filterSelections` stores the selected members.
The preview's **Panel actions** case disables dragging so you can try **+**, cog actions, removal, Clear and Reset directly. The **Filter cell template** case shows custom member presentation.
## Visibility and Permissions
These options belong inside `fieldPanel`:
| Option | Default | Effect |
| --- | --- | --- |
| `visible` | `false` | Mount the compact panel through `PivotPlugin`. |
| `allowFieldDragging` | `true` | Allow drag-and-drop field movement. |
| `allowFieldActions` | `true` | Show the + picker and cog action menus. |
| `allowFieldRemoving` | `false` in the compact panel | Show field Remove controls and allow Clear actions. |
| `showResetLayout` | `false` | Show Reset layout when the owner provides the reset action. |
| `showRowFields`, `showColumnFields`, `showDataFields`, `showFilterFields` | Inherit `showRows`, `showColumns`, `showValues`, `showFilters`; otherwise `true` | Show individual areas. |
For example, allow keyboard actions and removal while disabling dragging:
```ts
fieldPanel: {
visible: true,
allowFieldDragging: false,
allowFieldActions: true,
allowFieldRemoving: true,
showResetLayout: true,
}
```
Explicit area options override the `show*` flags in both stock constructors. Hiding an area preserves its analytical configuration. These UI permissions are independent: disabling dragging retains + and cog actions. Setting `allowFieldActions: false` hides those menus and restores the inline aggregation and filter selectors; sorting, axis swapping and member selection can still be available. Use [application admission policy](/guides/pivot/concepts/custom-integrations/) when layout edits must be rejected.
## Labels and Custom Hosts
Root `i18n` supplies shared labels; `fieldPanel.texts` overrides those labels in both stock mount helpers. Overrides are partial and missing entries retain English defaults:
```ts
fieldPanel: {
visible: true,
texts: {
values: 'Measures',
editFilter: 'Choose members',
addFieldToArea: area => `Add to ${area}`,
},
}
```
Field captions come from `dimensions[].name`. See [field metadata](/guides/pivot/concepts/field-metadata/) for types, folders and allowed placements.
For an application-owned host, use `definePivotFieldPanel(host, config, actions)` and dispose it with the returned cleanup function or `disposePivotFieldPanel(host)`. The host owns applying accepted changes and rerendering the controlled configuration. The helpers do not create a separate analytical owner; follow the [Configurator API](/guides/pivot/configurator/configurator-api/) for update callbacks, member loading and cleanup.
## Reuse Dropdown Cell Templates
Filter fields with `filterOptions` can reuse the owning dimension's dropdown
presentation. Enable `dropdown.syncCellTemplate` and provide the same dropdown
source metadata used by the cell template:
```ts
const statusBadge = (h, { model, value }) =>
h('span', { class: `status-badge ${model.tone}` }, value);
const pivot = {
dimensions: [{
prop: 'status',
filterOptions: ['Open', 'Closed'],
cellTemplate: statusBadge,
dropdown: {
syncCellTemplate: true,
source: [
{ value: 'Open', label: 'Open', tone: 'green' },
{ value: 'Closed', label: 'Closed', tone: 'gray' },
],
},
}],
filters: ['status'],
};
```
With default actions, open the filter field's cog and choose **Edit filter** to
see the synchronized content in the member editor. The inline selector
uses it when `allowFieldActions: false` and a quick selection is available.
Explicit dropdown renderers remain
authoritative. When the field panel is mounted by `PivotPlugin`, grid
`columnTypes` and `additionalData` are forwarded to the template resolver.
---
# Formatting Presets
URL: https://pro.rv-grid.com/guides/pivot/concepts/formatting-presets/
Source: src/content/docs/guides/pivot/concepts/formatting-presets.mdx
Description: Format Pivot values with locale-aware number, currency, percent, and date presets, then highlight analytical cells with conditional formatting.
Formatting presets help Pivot reports show numbers, money, percentages, and dates in a readable way. Conditional formatting presets then help users spot high revenue, low margin, or other important analytical values.
Open the full demo and framework source.
Formatting changes display, not input eligibility or arithmetic. Percentage calculations store ratios (`0.5` means 50%); a percentage format does not divide the value again. Configure null/invalid/count semantics through [Aggregation policies](/guides/pivot/concepts/aggregations/) and preserve those operations when saving reports.
## Basic Setup
Import the helpers from `@revolist/pivot` and create reusable formatters for the value types in your report:
```ts
import {
createPivotValueFormatter,
formatPivotValue,
pivotConditionalFormattingPresets,
createPivotConditionalCellProperties,
PivotPlugin,
type PivotConfig,
} from '@revolist/pivot';
const revenueFormatter = createPivotValueFormatter({
preset: 'currency',
currency: 'USD',
locale: 'en-US',
options: { maximumFractionDigits: 0 },
});
const marginFormatter = createPivotValueFormatter({
preset: 'percent',
locale: 'en-US',
options: { minimumFractionDigits: 1, maximumFractionDigits: 1 },
});
```
`createPivotValueFormatter()` returns a function. Use it when the same formatting rule is reused by many cells. Use `formatPivotValue()` when you only need to format one value:
```ts
const formattedLabel = formatPivotValue(null, {
preset: 'date',
nullDisplay: 'No close date',
});
```
## Value Formatters
Pivot formatters are display helpers. They do not change the original source data or the aggregated number that Pivot calculates. Keep your source values as real numbers and dates, then format them at render time.
```ts
const columnTypes = {
pivotCurrency: {
cellTemplate: (_h, props) => revenueFormatter(props.value),
cellProperties: () => ({ class: { 'align-right': true } }),
},
pivotPercent: {
cellTemplate: (_h, props) => marginFormatter(props.value),
cellProperties: () => ({ class: { 'align-right': true } }),
},
};
```
Attach those column types to the Pivot dimensions that produce analytical value cells:
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' },
{ prop: 'segment', name: 'Segment' },
{ prop: 'revenue', name: 'Revenue', columnType: 'pivotCurrency' },
{ prop: 'marginRate', name: 'Margin', columnType: 'pivotPercent' },
],
rows: ['region', 'closeDate'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' },
],
};
```
## Locale And Presets
The formatter presets use the browser `Intl` formatters. Choose the preset that matches the value:
- `number`: plain numeric values such as counts, units, or scores.
- `currency`: money values. Always provide `currency`, for example `"USD"` or `"EUR"`.
- `percent`: ratio values such as `0.315`, displayed as `31.5%`.
- `date`: date-only values.
- `datetime`: date and time values.
```ts
const integerFormatter = createPivotValueFormatter({
preset: 'number',
locale: 'en-US',
options: { maximumFractionDigits: 0 },
});
const eurFormatter = createPivotValueFormatter({
preset: 'currency',
currency: 'EUR',
locale: 'de-DE',
});
const dateFormatter = createPivotValueFormatter({
preset: 'date',
locale: 'en-US',
options: { month: 'short', day: '2-digit', year: 'numeric' },
});
```
## Conditional Formatting Presets
Use `pivotConditionalFormattingPresets` to describe matching rules, and `createPivotConditionalCellProperties()` to turn those rules into a RevoGrid `cellProperties` function.
```ts
const conditionalCellProperties = createPivotConditionalCellProperties(
[
pivotConditionalFormattingPresets.gt(300000, {
field: 'revenue',
style: {
backgroundColor: 'rgba(22, 163, 74, 0.16)',
color: '#166534',
fontWeight: '600',
},
}),
pivotConditionalFormattingPresets.lt(0.28, {
field: 'marginRate',
style: {
backgroundColor: 'rgba(220, 38, 38, 0.14)',
color: '#991b1b',
fontWeight: '600',
},
}),
pivotConditionalFormattingPresets.between(0.28, 0.34, {
field: 'marginRate',
style: {
backgroundColor: 'rgba(234, 179, 8, 0.14)',
color: '#854d0e',
},
}),
],
{ match: 'all' },
);
```
Available presets:
- `gt(value, props)`: greater than a number.
- `lt(value, props)`: less than a number.
- `between(min, max, props)`: between two numbers, including both bounds.
- `equal(value, props)`: equal to a value.
- `textContains(value, props)`: contains text.
The optional `field` narrows a rule to a generated value field. For example, `field: 'revenue'` applies to generated revenue cells even when Pivot creates column paths such as `Software|revenue`.
## Analytical Cells
By default, `createPivotConditionalCellProperties()` only formats Pivot analytical cells. These are generated cells from the `values` area, not row header fields such as `region` or `closeDate`.
This default prevents a rule like `gt(300000)` from accidentally styling row dimensions that happen to contain comparable values. If you intentionally want rules to apply outside analytical value cells, enable `includeNonAnalytical`:
```ts
const allCellProperties = createPivotConditionalCellProperties(rules, {
includeNonAnalytical: true,
});
```
Most Pivot reports should keep the default and attach conditional formatting to value dimensions:
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{
prop: 'revenue',
name: 'Revenue',
columnType: 'pivotCurrency',
cellProperties: conditionalCellProperties,
},
{
prop: 'marginRate',
name: 'Margin',
columnType: 'pivotPercent',
cellProperties: conditionalCellProperties,
},
],
rows: ['region'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum' },
{ prop: 'marginRate', aggregator: 'avg' },
],
};
```
## Practical Example
This report groups opportunities by region and close date, splits generated value columns by segment, formats revenue as currency, formats margin as a percent, and highlights high revenue or weak margin.
```ts
const rows = [
{ region: 'North America', closeDate: '2026-01-16', segment: 'Software', revenue: 184000, marginRate: 0.37 },
{ region: 'North America', closeDate: '2026-01-22', segment: 'Services', revenue: 126000, marginRate: 0.31 },
{ region: 'EMEA', closeDate: '2026-02-06', segment: 'Services', revenue: 118000, marginRate: 0.28 },
];
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'closeDate', name: 'Close date', columnType: 'pivotDate' },
{
prop: 'revenue',
name: 'Revenue',
columnType: 'pivotCurrency',
cellProperties: conditionalCellProperties,
},
{
prop: 'marginRate',
name: 'Margin',
columnType: 'pivotPercent',
cellProperties: conditionalCellProperties,
},
],
rows: ['region', 'closeDate'],
columns: ['segment'],
values: [
{ prop: 'revenue', aggregator: 'sum', label: 'Revenue' },
{ prop: 'marginRate', aggregator: 'avg', label: 'Avg margin' },
],
totals: {
subtotals: true,
grandTotal: true,
},
groupLabels: {
null: formatPivotValue(null, { preset: 'date', nullDisplay: 'No close date' }),
},
};
const grid = document.querySelector('revo-grid');
grid.columnTypes = columnTypes;
grid.plugins = [PivotPlugin];
grid.pivot = pivot;
grid.source = rows;
```
## Common Mistakes
- Formatting the source data before Pivot aggregates it. Keep values numeric so `sum`, `avg`, and comparisons work correctly.
- Using `preset: 'percent'` with whole percentages. `31.5%` should usually be stored as `0.315`, not `31.5`.
- Forgetting `currency` when using the `currency` preset.
- Expecting conditional formatting to affect row fields by default. It targets analytical value cells unless `includeNonAnalytical` is enabled.
- Omitting `field` on a conditional rule when the report has several value fields. Without `field`, the rule can match every analytical value cell.
- Expecting `cellTemplate` formatting to change conditional comparisons. Conditional rules evaluate the raw cell value, not the formatted display string.
## Next
Continue with [Sorting And Filtering](../sorting-filtering/) or [Configuration Reference](/guides/pivot/concepts/configuration-reference/).
---
# Headless Controller
URL: https://pro.rv-grid.com/guides/pivot/concepts/headless-controller/
Source: src/content/docs/guides/pivot/concepts/headless-controller.mdx
Description: Build your own Pivot authoring interface with report commands, subscriptions, and the analytical engine.
Use `createPivotController` to execute a portable report and build an authoring
interface without creating a RevoGrid element. The controller owns accepted
report state, pending changes, execution, and successful authoring history.
The `@revolist/pivot/headless` subpath supports ESM imports. It shares the package
root's runtime modules to preserve callback and report identity; it does not
promise a smaller dependency footprint. The existing Pivot plugin remains the
grid integration API. For a custom constructor on an existing grid, use its
`getAuthoringController()` facade; do not mirror it with another headless owner.
See [Custom Integrations](/guides/pivot/concepts/custom-integrations/) for complete checked recipes.
For new calculations, use the [complete V2 numeric/scalar example](/guides/pivot/concepts/aggregations/#recommended-local-configuration), with `reportVersion: 2` and a persisted execution pin. The V1 layout example below uses default callbacks. Controller scheduling changes responsiveness, not aggregate eligibility or own-grain totals; synchronous and cooperative execution use the same calculation policies.
## Create and execute
```ts
import { createPivotController, type PivotReport } from '@revolist/pivot/headless';
const report: PivotReport = {
kind: 'revogrid:pivot.report',
version: 1,
fields: [
{ id: 'region', sourceProp: 'region' },
{ id: 'revenue', sourceProp: 'sales' },
],
rows: ['region'],
columns: [],
filters: [],
measures: [{ id: 'revenue-total', fieldId: 'revenue', aggregator: 'sum' }],
};
const controller = createPivotController({
report,
source: [{ region: 'North', sales: 10 }, { region: 'North', sales: 5 }],
});
await controller.refresh();
const snapshot = controller.getSnapshot();
console.log(snapshot.view?.model.source);
```
By default, creation validates the report and runtime bindings synchronously. It starts idle
unless `autoLoad: true` is supplied. Use the runtime registries described in
[Portable Reports](/guides/pivot/concepts/report-specifications/) for custom callbacks, bound sources,
and server adapters. There is no public executor override.
## Cooperative local execution
Large local reports can opt into cooperative preparation so the browser can
process input, cancellation timers and repaint between calculation batches.
The controller publishes one completed view; cancellation preserves the last
committed view. Default execution retains its existing timing behavior.
```ts
let dataRevision = 0;
const controller = createPivotController({
report,
source: records,
localExecution: {
mode: 'cooperative',
budgetMs: 8,
sourceRevision: () => dataRevision,
},
});
const pending = controller.refresh();
// For example, call this from an application's Cancel control:
controller.cancel();
await pending;
// Advance the revision BEFORE modifying accepted source data.
dataRevision++;
records[0].sales = 25;
await controller.refresh();
```
Keep source membership, order and record values stable until an operation
settles. `sourceRevision()` is checked with `Object.is`; return a changed scalar
or object identity before changing source data. A detected revision change
fails the operation without publishing mixed revisions. The engine does not
freeze application records or deep-clone arbitrary objects, accessors or runtime
callbacks. Unannounced nested changes cannot be reliably detected.
`budgetMs` is a target between task yields, defaults to 8, and must be greater
than zero and at most 50. It is not a deadline or a hard limit on an application
callback. Custom aggregators, accessors, parsers and stateful comparators remain
opaque synchronous calls; one such call can block longer than the target. Stock
calculation kernels, source admission, filtering and result ownership share
resumable implementations with the synchronous path.
Report and query metadata are still owned and structurally validated synchronously
when submitted. The task budget applies to the admitted operation; it does not
make arbitrarily large intent documents or native JavaScript intrinsics preemptible.
With cooperative execution, source-dependent validation is asynchronous and
completes before proposal callbacks or publication. Report and runtime metadata
are still validated when creating the controller. Observe the operation outcome
for source-validation errors. Changing the source revision also prevents reuse
of cached calculations from the previous revision.
Starting a cooperative admission retires older pending work immediately. If the
new candidate later fails validation or a proposal is prevented, the committed
view and successful history remain available; the older operation does not
resume. Controllers using synchronous execution retain their existing preflight
and publication timing.
For an existing grid, configure the same runtime policy through
`grid.pivot.engine.localExecution` and use
`pivotPlugin.getAuthoringController().cancel()` on the owning plugin instance.
Do not create a second controller for the same grid. Scheduling callbacks are
runtime authority and are not serialized into portable reports. Remote adapters
continue to own remote query scheduling and cancellation.
## Subscribe to committed and pending state
`getSnapshot()` returns the same reference until state changes. `subscribe()`
does not call its listener immediately; read the initial snapshot yourself.
During loading, `report` and `view` retain the last usable pair. `pendingReport`
exposes the accepted candidate. An execution failure sets `status: 'error'` and
an error diagnostic while preserving that usable pair.
```ts
const unsubscribe = controller.subscribe(() => {
const state = controller.getSnapshot();
console.log(state.status, state.pendingReport, state.error);
});
const outcome = await controller.dispatch({
type: 'measure.configure',
measureId: 'revenue-total',
patch: { label: 'Revenue' },
});
if (outcome.status === 'failed') console.error(outcome.error.message);
unsubscribe();
```
Report data and owned view containers are readonly. Application records, runtime
handles, and custom aggregate payloads remain application-owned. Nested opaque
payloads are not recursively frozen; do not use them to change report state.
## Commands and transactions
Commands address stable field and measure IDs. Repeated uses of one source field
can therefore be configured independently. Moves use the destination index after
removing the source item. Filters are independent of row/column assignment.
```ts
await controller.transaction([
{ type: 'measure.add', fieldId: 'revenue', measureId: 'average-revenue', aggregator: 'avg' },
{ type: 'measure.configure', measureId: 'average-revenue', patch: { label: 'Average revenue' } },
{ type: 'totals.configure', patch: { grandTotal: true } },
]);
// Delete an optional setting explicitly.
await controller.dispatch({
type: 'measure.configure',
measureId: 'average-revenue',
unset: ['label'],
});
await controller.undo();
await controller.redo();
```
A transaction validates its final references and commits as one history entry.
Accepted edits queued within one microtask compose on pending intent and can
share one execution. Superseded operations settle as `superseded`; invalid
commands throw before changing state. A failed operation does not advance the
history cursor. `historyLimit` defaults to 100; zero disables undo history.
`reset()` restores the initial report as an authoring operation.
## Source changes and lifetime
Call `replaceSource(records)` when replacing application records, or `refresh()`
after mutating records in place. Source, runtime replacement, and refresh are
execution barriers and preserve accepted pending authoring choices.
If new source execution fails, `sourceRevision` describes the current input
intent while `viewSourceRevision` identifies the input behind the retained view.
`replaceRuntime(runtime)` explicitly replaces execution bindings and executes;
this differs from the plugin's nonexecuting `setPivotReportRuntime` defaults.
`cancel()` promptly settles pending work even when a remote adapter ignores its
abort signal. Late results cannot publish. `dispose()` cancels work and removes
subscriptions. Call it when the owning component is destroyed.
For persistence, use `getReport()` or `exportReportJson()`. Runtime callbacks and
source records remain outside the JSON report and must be reattached by the app.
## Public queries and operation results
The optional constructor `query` and `replaceQuery(query)` describe execution
scope separately from the authored report. `sourceFilter` uses tuple predicates
before aggregation; `having` addresses measure IDs after aggregation. `sort`
uses active axis fields and optional `bySummary` measure IDs. Bare string/number
selectors address physical properties; `{ kind: 'field', fieldId }` addresses a
report field. The [Portable Reports query example](/guides/pivot/concepts/report-specifications/#execute-version-2-without-a-grid)
shows the complete typed shape and version admission rules.
`rowOffset`, `rowLimit`, `columnOffset`, and `columnLimit` are nonnegative safe
integers. Zero means an empty analytical window; omitted local windows include
the complete result. `replaceQuery({})` clears public query intent, preserving
report member selections. Query changes are outside authoring undo/redo history,
and `snapshot.query` describes the last committed view.
| Operation | Purpose |
| --- | --- |
| `replaceReport(report)` | Validate and execute another report in the existing owner. |
| `dispatch(command)` / `transaction(commands)` | Apply one authoring edit or one atomic batch. |
| `replaceQuery(query)` | Replace the public execution scope. |
| `replaceSource(records)` / `replaceRuntime(runtime)` | Replace application inputs and execute. |
| `refresh()` | Execute current intent, including in-place record changes. |
| `undo()` / `redo()` / `reset()` | Navigate successful authored changes or restore the initial report. |
| `getReport()` / `exportReportJson()` | Read portable authored state for application-owned persistence. |
| `cancel()` / `dispose()` | Cancel pending execution or end the owner's lifetime. |
Async execution operations resolve to a `PivotControllerOutcome`: `committed`,
`noop`, `prevented`, `superseded`, `cancelled`, or `failed`. Only `committed`
publishes a new revision. For `failed`, inspect `outcome.error` and the retained
snapshot. Invalid command/query/report inputs can throw during validation before
execution; handle both exceptions and failed outcomes in application controls.
Subscriptions take no snapshot argument: read `controller.getSnapshot()` inside
the listener and unsubscribe on cleanup.
## Related product guides
[Runtime limits, accounting units and recovery](/guides/pivot/concepts/execution-and-limits/).
---
# Plugin Lifecycle
URL: https://pro.rv-grid.com/guides/pivot/concepts/plugin-lifecycle/
Source: src/content/docs/guides/pivot/concepts/plugin-lifecycle.mdx
Description: Understand how PivotPlugin applies, updates, and clears Pivot state on a RevoGrid instance.
`PivotPlugin` is the runtime layer that turns the direct `grid.pivot` config into rendered rows, generated columns, and grouped state. Understanding its lifecycle helps you persist user choices, avoid conflicting updates, and reason about when Pivot owns the grid payload.
## Activation
Pivot activates when:
- `PivotPlugin` is present in `grid.plugins`
- `grid.pivot` contains a config
The plugin observes the direct `pivot` property and reapplies Pivot whenever the config changes. `additionalData.pivot` updates are still supported for compatibility.
## What Happens On Apply
When `applyPivot` runs:
1. the plugin captures the original source, columns, grouping, and pinned-bottom rows on first activation
2. it decides whether to use the client or server execution path
3. it generates or loads Pivot rows and columns
4. it writes those generated structures back to the grid
This means Pivot is temporarily the owner of the rendered grid payload.
## Client Path
In client mode:
- raw grid source is read from the captured original data
- `createPivotData` materializes Pivot rows
- `pivotColumns` generates the column tree
- grand total rows are moved to `pinnedBottomSource`
## Server Path
In server mode:
- the plugin resolves a `PivotEngineAdapter`
- it builds a `PivotLoadRequest`
- it aborts any stale in-flight remote request
- only the latest response is allowed to update the grid
This is the last-write-wins behavior that protects the UI when filters or layouts change quickly.
## Configurator Integration
If `hasConfigurator` is enabled, the plugin renders the configurator and wires its updates back into the Pivot state. Each configurator update:
- emits `pivot-config-update`
- applies the updated config unless the event was prevented
## `pivot-config-update`
This is a synchronous, cancelable **proposal** before execution. Call `event.preventDefault()` to veto it. A proposal can later fail or be superseded; persisting its payload as an accepted report saves uncommitted intent.
Subscribe to the attached authoring facade for status/history and persist newly ready revisions:
```ts
const authoring = pivotPlugin.getAuthoringController();
let savedRevision = -1;
const veto = (event: Event) => {
if (!applicationAllowsChanges()) event.preventDefault();
};
grid.addEventListener('pivot-config-update', veto);
const unsubscribe = authoring.subscribe(() => {
const snapshot = authoring.getSnapshot();
if (snapshot.status === 'ready' && snapshot.revision !== savedRevision) {
savedRevision = snapshot.revision;
persistAccepted(snapshot.report); // Session descriptors may require runtime bindings for portable export.
}
});
// On shell cleanup:
unsubscribe();
grid.removeEventListener('pivot-config-update', veto);
```
The existing `pivot-model-changed` event and `getRuntimeSnapshot()` describe committed analytical output. The authoring facade also describes pending/error state, retained reports and Undo/Redo. Loading/error never claims pending pixels are committed. Rapid remote commands compose on pending intent; superseded requests cannot overwrite the latest result. Synchronous framework echoes of accepted configs need not create a second owner or a persistence feedback loop.
## Reapplication Triggers
Pivot recalculates when:
- `grid.pivot` changes
- `additionalData.pivot` changes
- the underlying non-Pivot source changes
- the underlying non-Pivot columns change
- row drill-down or column collapse state changes through plugin-managed interactions
The plugin intentionally avoids treating already-generated Pivot rows or columns as new base inputs.
## Clearing Pivot
`clearPivot()`:
- aborts any remote load
- removes managed Pivot state
- restores the original source, columns, grouping, and pinned-bottom rows
Removing `grid.pivot` has the same practical effect. Removing `additionalData.pivot` also clears Pivot when that path is the active source.
The stable attached authoring facade reports `inactive` before activation and after clear. Re-enable reuses it with a newer global revision. Destroy reports terminal `destroyed`, cancels pending work and clears subscriptions. Its restricted commands cannot replace source/runtime or dispose the plugin; those remain host/report-import authority.
See [Custom Integrations](/guides/pivot/concepts/custom-integrations/) for the exact checked constructor, toolbar and cleanup modules.
## Best Practices
- Treat direct `grid.pivot` / framework `pivot` binding as the durable state you own.
- Use `pivot-config-update` for synchronous admission and ready authoring snapshots for committed persistence.
- Do not manually overwrite `grid.source` or `grid.columns` with Pivot active unless you mean to replace the base data and trigger a reapply.
## Related Guides
- [Configuration Reference](/guides/pivot/concepts/configuration-reference/)
- [Pivot Charts](/guides/pivot-charts/)
- [Configurator API](/guides/pivot/configurator/configurator-api/)
- [Field Panel](/guides/pivot/concepts/field-panel/)
- [Server-Side Pivot](/guides/pivot/server-side/)
---
# Top/Bottom ranking and summary sorting
URL: https://pro.rv-grid.com/guides/pivot/concepts/ranking-and-summary-sorting/
Source: src/content/docs/guides/pivot/concepts/ranking-and-summary-sorting.mdx
Description: Rank complete analytical peers, reaggregate Other from facts and sort hierarchy prefixes within the supported scope.
## Rank the first row field
Native `topBottom` and portable report `ranking` select members of the **first row field at root scope**. Native configuration addresses the zero-based `valueIndex`; portable reports use a stable `measureId`, so repeated source fields remain unambiguous.
```ts
const ranking = {
direction: 'top' as const, count: 1,
measureId: 'total', otherLabel: 'Other',
};
// report.ranking = ranking;
// Native equivalent:
const topBottom = { direction: 'top' as const, count: 1, valueIndex: 0, otherLabel: 'Other' };
```
For A `[10]`, B `[20,20,20]`, C `[5]`, D `[15]`, Top 1 by SUM keeps **B=60**, and **Other=30**. Strict Other AVG is **10**, because its three omitted facts are reaggregated. It is not an average or sum of the displayed groups. Grand strict AVG is **15** over all six facts. Without `otherLabel`, omitted members are omitted from the display. The [executable example](/examples/pivot/customer-example.mjs) asserts the Other sum and average.
Calculated scores use the complete filtered peers before selection/Other. Display calculations then recompute on the selected members plus source-reaggregated Other. Thus a ranking score and the final displayed contextual value can differ. Support includes calculations/formulas requiring complete context; it is not bucket-local sorting of partially loaded aggregates.
An optional typed `column: { kind, path }` chooses a ranking score coordinate: `leaf`, `subtotal`, `collapsed` or `grandTotal`. Omission scores across columns. Paths must match active column depth/kind; this ranking coordinate is distinct from the summary-sort profile below. Use a noncolliding Other label, especially for the SQL reference, which rejects collisions with genuine authorized members. Original physical values and immutable fact keys retain drill lineage even when the ranked field is measured or appears on the opposite axis.
## Summary sort scope
```ts
await controller.replaceQuery({
sort: [{ selector: 'region', bySummary: 'average', desc: true }],
});
```
Here `average` is a configured stable measure ID, not a caption or an aggregate callback name. Each sibling prefix scores its **own complete aggregate against the opposite grand total**, before axis windows. An AVG prefix uses all its facts; DISTINCT never adds child distinct counts. Calculated/formula measures use their calculated result. WHERE and ranking establish the cube; HAVING selects output without redefining score denominators. Subtrees, subtotals and grand placement remain intact in ordered export.
| Option | Type/default | Meaning and limitation |
| --- | --- | --- |
| `selector` | Physical string/number or `{ kind: 'field', fieldId }`; required | Must resolve to one active row/column axis field |
| `bySummary` | Optional stable measure result ID | Omission sorts members; unknown/ambiguous measures reject |
| `desc` | Boolean; false | Descending when true |
| Query `sort` omitted | Existing defaults | Retains grouped direction defaults |
| Query `sort: []` | Explicit empty list | Clears grouped direction defaults on both axes and uses source-order fallback |
| Remote `summarySort` | `{ version: 1, oppositeAxis: 'grandTotal' }` plus `sort: true` | Explicit opt-in for complete-prefix sorting |
Score ties/unspecified levels follow local authorized source occurrence order or SQL authorized fact-key order. Align those orders when comparing engines. The SQL reference orders null scores first ascending/last descending and compares member strings by UTF-16 code units.
**Selected opposite-axis tuple/path sorting is deferred.** The native server action **Sort rows by grand total** targets the first physical row field and requires a row axis. Clicking a particular column header does not make that column tuple the scoring scope. Local native clicked-header sorting still follows Core and is separate from canonical local/headless query ordering.
## Recovery and server admission
A server must advertise and implement this profile; a Boolean sort capability on an older engine retains its older contract. Unknown measures, invalid axis selectors and malformed opted profiles refuse before transport. Keep the existing committed view, choose an available measure/axis or remove `bySummary`, then retry. See [runtime errors](/guides/pivot/concepts/execution-and-limits/#errors-and-recovery).
The SQLite analytical profile supports root row ranking and complete-prefix summary sort, including both axes, source-reaggregated Other and full export. Its flat baseline does not. Customer adapters must qualify these independently; a flag is not authorization or SQL execution. See [adapters and security](/guides/pivot/server-side/adapters-and-security/).
---
# Saved reports and runtime bindings
URL: https://pro.rv-grid.com/guides/pivot/concepts/report-persistence/
Source: src/content/docs/guides/pivot/concepts/report-persistence.mdx
Description: Persist analytical intent, restore named runtime dependencies and recover from storage conflicts safely.
## What to save
A portable `PivotReport` contains field/measure IDs, axes, operation declarations, filters, ranking, totals and presentation. V2 explicitly admits derived fields and operation policies; opt into V2 parsing/execution. A native `PivotConfig` is a grid configuration, not the same persistence format.
Do not serialize source records, functions, DOM mounts, adapter instances, credentials or source revision callbacks as report authority. Restore named host bindings (sources, engines, aggregate callbacks, text callbacks) before executing a report that needs them. A missing binding is a recoverable integration error, not permission to fall back to a different engine.
```ts
const json = controller.exportReportJson();
// Send json to the application's authorized persistence endpoint.
// After restoring the matching runtime, parse/import with explicit V2 opt-in
// when the document uses version 2. Execute before updating the saved record.
```
The [downloadable executable](/examples/pivot/customer-example.mjs) round-trips an executed V2 hierarchy through `exportReportJson`, explicit V2 parsing and serialization.
Use [portable report APIs](/guides/pivot/concepts/report-specifications/) for exact V1/V2 parse/compile/serialize signatures. `aggregationSemantics` pins local/server execution; keep it with the existing operations when migrating. V1-only `savePivotReport`/`loadPivotReport` helpers are distinct from V1/V2 durable saved-view collections.
## Attached saved views
The stock attached menu is session-only unless configured. The public browser adapter supplies durable origin storage:
```ts
import { createBrowserPivotViewStorage } from '@revolist/pivot';
pivot.setSavedViewStorage({
scope: `${userId}/${workspaceId}/${reportId}`,
storage: createBrowserPivotViewStorage(),
});
```
Browser scopes separate application data but are not an authorization boundary. Browser writes require a secure context with Web Locks for atomic comparisons; storage-disabled/quota/unsupported errors remain visible. On user change replace/clear the storage integration. `setSavedViewStorage()` returns to a new empty session list without deleting durable records. Replacing storage cancels pending restores and retires the previous manager.
For a server store implement public `PivotViewStorage.read` and `compareAndSwap` with an **authorized atomic compare-and-swap** on the entire stored string. A separate read then write is not atomic. See [Persistent saved views](/guides/pivot/concepts/saved-views/) for the interface and failure behavior.
| Setting/operation | Default/limit | Recovery |
| --- | --- | --- |
| Attached storage | Session snapshots | Install a durable adapter explicitly |
| Collection size | 200 views, 2000000 UTF-16 code units | Delete/archive views; actual browser quota may be lower |
| Concurrent write | Compare-and-swap conflict | Reload views, inspect current state, retry explicitly |
| Failed save | Draft name retained | Repair storage/auth/quota and retry |
| Failed restore | Current report retained | Restore bindings/fields or repair/delete saved view |
| Malformed collection | Not overwritten | Back up and repair through host storage, then Reload views |
Standalone configurator `actions.viewManager.save/rename/remove` may return a Promise; resolve only after durable host acceptance and reject on failure. `list()` and `subscribe()` stay synchronous: maintain a cached list and notify after changes. A write committed before cancellation cannot be undone by retiring the UI callback.
## Host-owned persistence policy
Authorize every server read/write against the authenticated user and workspace. Handle retention, sharing, encryption, conflict policy and schema migration in the application. Governed shared-view catalogs are deferred. Validate a restored report, reattach current runtime registries, execute it, inspect known KPIs, and only then announce success or save the migrated document.
For custom UIs attached to a grid use `pivot.getAuthoringController()` so history and committed state have one owner. It deliberately excludes source/runtime replacement and `report.replace`; use the plugin’s supported import path for report replacement. Standalone controllers expose their own full lifecycle. See [custom integrations](/guides/pivot/concepts/custom-integrations/).
---
# Portable Reports
URL: https://pro.rv-grid.com/guides/pivot/concepts/report-specifications/
Source: src/content/docs/guides/pivot/concepts/report-specifications.mdx
Description: Save declarative Pivot reports and resolve application-owned runtime behavior by stable IDs.
A portable report describes fields, measures, filters, calculations, totals, and
presentation preferences. Its field and measure IDs remain independent of display
labels and physical source properties. Save the report as JSON; supply callbacks,
source records, adapters, and mounts from application-owned runtime registries.
## Define and compile a report
```ts
import {
compilePivotReport,
parsePivotReport,
serializePivotReport,
type PivotReport,
} from '@revolist/pivot';
const report: PivotReport = {
kind: 'revogrid:pivot.report',
version: 1,
fields: [
{ id: 'region', sourceProp: 'region', column: { name: 'Region' } },
{ id: 'revenue', sourceProp: 'sales' },
],
rows: ['region'],
columns: [],
filters: [],
measures: [{
id: 'revenue-total',
fieldId: 'revenue',
aggregator: 'sum',
label: 'Revenue',
format: { preset: 'currency', currency: 'USD' },
}],
totals: { grandTotal: true },
};
const json = serializePivotReport(report);
const restored = parsePivotReport(json);
const { config } = compilePivotReport(restored);
// Set original records before enabling Pivot on this grid.
grid.source = records;
grid.pivot = config;
```
Validation returns detached report data and identifies invalid references before
execution. The compiler produces the existing `PivotConfig`; existing local and
remote owners still perform the aggregation and rendering.
## Explicit version 2 source documents
`PivotReport`, `PivotReportField`, and `PIVOT_REPORT_VERSION` retain their version 1
contracts. Default `validatePivotReport` and `parsePivotReport` calls accept
version 1. To inspect or store a version 2 source document, explicitly select its
schema with `{ version: 2 }`.
```ts
import {
upgradePivotReport,
validatePivotReport,
type PivotReportV2,
} from '@revolist/pivot';
// Pure conversion: field IDs and other authored settings remain unchanged.
const upgraded = upgradePivotReport(report);
const sourceDocument: PivotReportV2 = {
...upgraded,
fields: [
...upgraded.fields,
{
id: 'adjusted-revenue',
dataType: 'number',
source: {
kind: 'expression',
expression: {
type: 'binary',
operator: '*',
left: { type: 'field', fieldId: 'revenue' },
right: { type: 'literal', value: 1.2 },
},
},
},
],
};
const validated = validatePivotReport(sourceDocument, { version: 2 });
const sourceJson = serializePivotReport(validated);
const sourceRestored = parsePivotReport(sourceJson, { version: 2 });
```
Parsing, validation and serialization never evaluate fields or upgrade a document
implicitly. To execute version 2, pass the actual accepted source to the same
compiler: `compilePivotReport(sourceDocument, runtime, { version: 2, source })`.
Assign its issued `config` to `grid.pivot`. Immutable configuration spreads retain
that authority; JSON copies do not. Default compilation and raw configuration
migration remain strict version 1. Saved-report envelopes currently retain their
version 1 contract; persist a version 2 document with its explicit parser instead.
Version 2 fields replace `sourceProp` with exactly one `source` declaration:
| Source | Declaration |
| --- | --- |
| Physical | `{ kind: 'physical', prop }`; `prop` is a string or finite number. |
| Expression | `{ kind: 'expression', expression }`; references use stable field IDs. |
| Runtime | `{ kind: 'runtime', bindingId, dependencies }`; dependencies are ordered, unique field IDs. No callback is serialized. |
Expression and runtime fields require a declared scalar `dataType`: `string`,
`number`, or `boolean`. Physical fields retain optional descriptive datatype
metadata, including `date`. Document validation does not evaluate facts or infer
an expression's output type.
The closed expression grammar supports scalar/null `literal` nodes, `field`
references, numeric `binary` nodes (`+`, `-`, `*`, `/`), `concat` with at least two
`args`, and `coalesce` with at least one argument. Executable JavaScript, formula
source strings, arbitrary functions, measure references, and date operations are
outside this grammar.
Validation checks references across the entire field catalog, including unused
fields and every coalesce branch. Forward references are allowed; self references,
cycles, and duplicate runtime dependencies are rejected. Catalog order is retained.
Limits are 32 AST levels, 1,024 nodes per expression, 8,192 AST nodes per report,
and 16,384 dependency references per report; repeated references count separately.
Root nodes count as level one and as a node. Unknown keys, accessors, sparse arrays,
and non-JSON values are rejected rather than silently removed.
## Reattach custom aggregates
Each field can declare an explicit aggregate list. Runtime entries reference a
named application callback. Built-in entries can expose a built-in aggregate
under an application-selected name.
```ts
import type { PivotReportRuntime } from '@revolist/pivot';
const positiveSum = (values: unknown[]) => values.reduce(
(sum, value) => {
const amount = Number(value);
return sum + (Number.isFinite(amount) ? Math.max(0, amount) : 0);
},
0,
);
const customReport: PivotReport = {
...report,
fields: report.fields.map(field => field.id === 'revenue'
? {
...field,
aggregators: [{
id: 'sum',
kind: 'runtime' as const,
bindingId: 'positive-revenue',
}],
}
: field),
};
const runtime: PivotReportRuntime = {
aggregators: new Map([['positive-revenue', positiveSum]]),
};
const saved = serializePivotReport(customReport);
const compiled = compilePivotReport(parsePivotReport(saved), runtime);
```
A nonempty aggregate list replaces the default aggregate map for that field,
matching existing dimension behavior. Include every aggregate that the field's
measures need. A runtime aggregate named `sum` overrides built-in `sum`.
An omitted or empty list uses the existing built-ins.
## Calculation policy and execution pin
For new local reports, use V2 fields with `createPivotStrictAggregators({ input: 'number', invalid: 'error' })` for numbers and `input: 'scalar'` for non-null value counts/typed distinct. These presets skip null/missing values; empty text remains a scalar value. See [Aggregation policies](/guides/pivot/concepts/aggregations/) for complete runnable numeric, scalar and row-count examples.
Place `aggregationSemantics: 'local'` or `'server'` at the report top level. It survives serialization, parsing, native-to-report conversion and plugin export/import. Compilation and plugin/controller preparation reject a mismatching engine before loading. The pin prevents accidental engine switching; it does not guarantee identical local/server results or convert an aggregate's policy. Direct local model, cooperative-work and adapter entry points also enforce it before reading facts, based on their actual local execution path.
To configure an existing report safely, retain its current aggregate declarations, input meaning and runtime bindings while adding the pin for its actual engine. Execute and compare known KPI values before saving Pivot. Replacing its operations with a numeric preset is a separate calculation-policy decision: `[10, null]` is 5 with default AVG and 10 with the numeric preset. Do not combine an unreviewed policy replacement with engine rebinding.
V1 parsing uses `parsePivotReport(json)`; V2 parsing requires `parsePivotReport(json, { version: 2 })`. Persist V2 JSON through application-owned storage; `savePivotReport`, `loadPivotReport` and the saved-report envelope helpers accept V1 only. Readers must support the optional pin property: a closed reader schema without it rejects a pinned report. Keep matching reader/writer package versions rather than deleting the pin silently. See [Saved-report configuration](/guides/migrations/#pivot-calculation-policies-and-saved-reports).
## Bindings and field mapping
| Runtime registry | Purpose |
| --- | --- |
| `fields` | Map a stable field ID to a physical source property |
| `columns` | Reattach column callbacks and application column extensions |
| `aggregators` | Reattach aggregate callbacks |
| `placeholders` | Reattach complete column-collapse placeholder behavior |
| `texts` | Reattach localization callbacks |
| `sources` | Resolve application-owned original record arrays |
| `engines` | Resolve trusted adapter/store options and capabilities |
| `mounts` | Resolve application-owned configurator mount elements |
Reference those registries from the corresponding report properties:
`fields[].runtimeColumnId`, `fields[].aggregators[].bindingId` for runtime
aggregates, `presentation.columnCollapse.placeholder.bindingId`, and
`bindings.sourceId`, `bindings.engineId`, `bindings.mountId`, `bindings.i18nId`,
or `bindings.fieldPanelTextsId`. Physical field overrides use the field ID
directly as the registry key.
Text entries normally contain strings. Existing localization callback keys can
also contain `{ kind: 'builtin-default' }` to select the corresponding built-in
callback explicitly. Compilation restores that callback's original identity;
custom callbacks still come from the `texts` registry. An omitted field-panel
text inherits the global localization, while an explicit built-in callback
overrides it. Migration preserves this distinction.
When a report binds a source, compilation returns it as `compiled.source`;
the compiler does not assign records to a grid or execute a query. Install the
original records through the application's existing grid/source owner before
enabling the compiled Pivot configuration.
For example, `fields: new Map([['revenue', 'net_sales']])` changes the physical
mapping while the report still references `revenue`. The compiler rewrites axes,
measure fields, field-based calculations, member filters, subtotal rules, and
other source-field references consistently. Mappings that collide in the current
engine's string-keyed properties are rejected.
Formula operands reference stable measure IDs. Field-based calculations use
`fieldId`. Ranking uses `measureId`, so reordering measures does not change the
measure used for ranking. Conditional-format `generatedFields` selectors retain
literal generated-grid properties; they are compatibility selectors and do not
automatically follow arbitrary layout changes.
V1 result filters use standard summary identities, including `distinct`. V2 result filters can reference configured local operation aliases and custom aggregates. Their stable measure ID, field ID, aggregate ID and calculation must match the configured measure. Extra local request summaries also require that exact identity. Server execution accepts only its standard vocabulary and authorized capabilities; local operation declarations do not become server semantics.
Runtime bindings use `Map` entries in distinct categories. Report JSON cannot
grant backend field access, provide SQL, or override engine capabilities. Supply
that authority through the application's engine and backend configuration.
## Handle diagnostics
```ts
import { PivotReportError } from '@revolist/pivot';
try {
compilePivotReport(restored, runtime);
} catch (error) {
if (!(error instanceof PivotReportError)) throw error;
for (const diagnostic of error.diagnostics) {
console.error(diagnostic.code, diagnostic.path, diagnostic.message);
}
}
```
Missing bindings, unknown fields/measures, duplicate IDs, unsupported versions,
and ambiguous physical mappings fail explicitly. Functions, unsupported objects,
cycles, non-finite numbers, and other values that cannot roundtrip through the
report's JSON contract require a runtime binding or a corrected report.
Use JSON strings or string arrays for formatting locales. Place functional column
metadata, custom placeholders, and localization callbacks in their corresponding
runtime registries. The `createSerializablePivotConfig` helper remains a
lossy projection and cannot preserve these behaviors by itself.
## Migrate an existing configuration
Use `createPivotReportFromConfig` to convert an existing `PivotConfig` into a
portable report. Supply explicit IDs when your application already has names for
its fields and measures.
```ts
import {
createPivotReportFromConfig,
migratePivotReport,
type PivotConfig,
} from '@revolist/pivot';
const existingConfig: Partial = {
rows: ['region'],
values: [{ prop: 'sales', aggregator: 'sum' }],
};
const migrationOptions = {
fieldIds: [{ sourceProp: 'sales', fieldId: 'revenue' }],
measureIds: [{ valueIndex: 0, measureId: 'revenue-total' }],
};
const migrated = createPivotReportFromConfig(existingConfig, migrationOptions);
const oldState = JSON.stringify({
kind: 'revogrid:pivot.state',
version: 1,
config: existingConfig,
});
const migratedState = migratePivotReport(oldState, migrationOptions);
```
`migratePivotReport` accepts portable reports, saved reports, state JSON version 1,
saved-view version 2, and raw native configurations. Unknown envelopes and
unsupported versions fail explicitly. Existing measure IDs are preserved;
conflicting explicit measure IDs are rejected.
Mapping selectors use the configuration's actual physical properties, preserving
the distinction between numeric and string properties. Compiled configurations
retain immutable identity metadata through ordinary object spreads, so subsequent
exports preserve their original portable field IDs and source properties. Export
derives axes, values, filters, and presentation from the current configuration.
Custom callbacks and handles need matching runtime registries during migration.
If multiple binding names resolve to the same callback, select a name explicitly
with the migration options. Migration rejects values or properties that would
otherwise disappear; it does not use the lossy projection.
## Import and export through the plugin
Use the `PivotPlugin` instance to export the current configuration, including
changes made through the built-in configurator. The plugin keeps field and measure
identity while deriving the report from the current configuration.
```ts
import { PivotPlugin } from '@revolist/pivot';
async function transferCurrentReport(targetGrid: HTMLRevoGridElement) {
const plugins = await grid.getPlugins();
const pivot = plugins.find(plugin => plugin instanceof PivotPlugin);
if (!pivot) throw new Error('Install PivotPlugin on the source grid');
pivot.setPivotReportRuntime({ runtime });
const current = pivot.getPivotReport();
const portableJson = pivot.exportPivotReportJson();
const targetPlugins = await targetGrid.getPlugins();
const targetPivot = targetPlugins.find(plugin => plugin instanceof PivotPlugin);
if (!targetPivot) throw new Error('Install PivotPlugin on the target grid');
const accepted = await targetPivot.importPivotReport(portableJson, { runtime });
return { current, accepted };
}
```
`setPivotReportRuntime` replaces the plugin's default report options without
executing Pivot. Per-call options apply to that call only; a supplied `runtime`
replaces the whole registry object rather than merging its categories. Runtime
callbacks, adapters, mounts, and sources retain their application-owned identity.
Import validates, migrates, and compiles before emitting the preventable Pivot
configuration event. Invalid reports and missing bindings throw `PivotReportError`
before execution. The returned boolean is `true` only when the operation commits
or is an unchanged successful report (`noop`). Prevention, execution failure,
cancellation, and supersession return `false`.
A pending or failed remote import retains the last successfully committed report
and analytical model. Read `getAuthoringController().getSnapshot()` for loading,
error, and pending report state; a proposal is not a successful import. Runtime
callbacks and backend requests can have application-owned side effects, so this
retention does not roll back external systems.
The plugin obtains the physical source from the grid's source owner; its report
options do not accept a source override. A report with a source binding resolves
that source from the runtime registry on import. If the application later replaces
`grid.source`, update the corresponding source registry entry before exporting;
an entry that still points to the old array produces a binding diagnostic.
Reports without a source binding remain reusable with the target grid's data.
The context-menu **Pivot Report JSON** export uses this same live export path.
Export failures display a diagnostic dialog and do not start a download.
Export currently rejects active linked-view member-filter contributions with an
actionable diagnostic, because saving the base report would omit that additional
scope. Remove those contributions before exporting; an explicit choice between
saving the base report and effective linked-view scope is not yet available.
## Save and load a portable report
`savePivotReport`, `loadPivotReport`, and `createPivotSavedReportState` support
**version 1 report documents only**, using your existing `PivotRemoteStore`.
Their saved-report envelope version is separate from backend record versions.
Runtime registries remain application-owned and are not stored in the report.
For an explicitly opted-in V2 report, persist `serializePivotReport(document)`
through application-owned storage and restore it with
`parsePivotReport(json, { version: 2 })`. Then execute it with the existing V2
controller or plugin import options. Passing V2 to the V1 saved-report helpers is
rejected; those helpers have no V2 admission option.
```ts
import {
savePivotReport,
loadPivotReport,
type PivotRemoteStore,
} from '@revolist/pivot';
async function persistReport(store: PivotRemoteStore) {
await savePivotReport(store, {
userId: 'current-user',
viewId: 'sales-analysis',
name: 'Sales analysis',
report: migrated,
});
const loaded = await loadPivotReport(store, {
userId: 'current-user',
viewId: 'sales-analysis',
});
return compilePivotReport(loaded.report, runtime);
}
```
The stored envelope is `revogrid:pivot.saved-report` version 1. Validation happens
before the store receives a save request. Load options can include `migration`
options for configuration payloads and an abort `signal`; save options also accept an
abort signal. The store owns request execution and cancellation.
The returned `version` describes the portable payload schema. `recordVersion`,
when present, comes from the backend response and is separate from schema
selection. Use `loadPivotReport` for portable reports; `loadPivotView` retains its
existing configuration-based contract.
## Preserve remote viewport state
`presentation.viewport` stores optional `rows` and `columns` axis settings:
`offset`, `limit`, `expandedPaths`, and `collapsedPaths`. It requires an
`engineId` binding. This keeps changed analytical windows and server collapse
paths in the report while adapters, backend identifiers, and capabilities remain
in the application-owned engine binding.
A supplied axis object replaces that axis's bound options; `{}` clears them.
An omitted axis inherits its engine binding's options. Compilation preserves
adapter and store identities while applying the portable viewport.
## Execute version 2 without a grid
The existing headless controller executes both versions in one lifetime when
created with `reportVersion: 2`. Omitting that option keeps the version 1 contract.
Its normal commands, transactions, source replacement, undo and redo retain the
same state owner; version 2 does not create a second engine.
```ts
import { createPivotController, type PivotReportRuntimeV2 } from '@revolist/pivot/headless';
const runtime: PivotReportRuntimeV2 = {};
// This document uses a portable expression and needs no runtime accessor.
const controller = createPivotController({
reportVersion: 2,
report: sourceDocument,
source: records,
runtime,
query: {
sourceFilter: [{ kind: 'field', fieldId: 'adjusted-revenue' }, '>', 100],
sort: [{ selector: { kind: 'field', fieldId: 'region' }, bySummary: 'revenue-total', desc: true }],
},
});
await controller.refresh();
const committed = controller.getSnapshot();
await controller.replaceQuery({});
controller.dispose();
```
Use IDs actually present in your report for selectors and `bySummary`. A tagged
`{ kind: 'field', fieldId }` addresses a semantic field. Bare strings and numbers
always address physical source properties, including uncatalogued properties in
WHERE; an internal generated selector is not a public field reference. Each sort
descriptor must identify exactly one active row or column axis. `bySummary` is a
configured measure ID, so different descriptors can sort by different measures.
`query.sourceFilter` applies before aggregation. `query.having` applies afterward
and identifies a configured `measureId`, `columnPath`, `columnKind`, `operation`
and `value`. Authored result filters, bridge filters and public HAVING remain
conjoined. `replaceQuery({})` clears only the public query layer; it does not clear
native member selections, Core filters or linked-view contributions.
Query inputs are owned JSON. They are separate from portable report history.
`snapshot.query` is null before the first successful result, then describes only
the committed view (the default is `{}`). Pending or failed input does not change
the visible query. Queries retain original tagged intent across source changes;
an undo or report replacement that removes a queried field or measure rejects
without moving the history cursor. Public tagged input is admitted against the
committed catalog and revalidated against the operation's target catalog.
`rowOffset`, `rowLimit`, `columnOffset` and `columnLimit` accept nonnegative safe
integers. Zero is an empty window. Omitted local windows include the complete
result; remote engines retain their existing viewport defaults.
Runtime accessors receive the original `row`, its original numeric `sourceIndex`,
and `get(fieldId)` for declared dependencies. `get` is valid only during that
callback frame. Outputs must be synchronous scalar values of the declared type,
or null. The evaluator permits at most 128 simultaneously active runtime frames.
Callbacks are application code with application side effects; they are not
sandboxed. Physical fields preserve raw values for custom aggregators while
expressions and accessor dependencies use the typed scalar lane.
Initial source selection uses a bound source registry entry, then the supplied
source, then an empty array. Explicit source replacements take precedence;
changing the report's source ID selects its new registry entry, otherwise the
current source remains. Empty arrays are valid. Replacing sources or runtime
accessors rebuilds evaluation authority; presentation-only changes can reuse
aggregates. Field configuration commands cannot change `source` or `sourceProp`;
replace the report to edit source definitions.
Remote engines accept physical-only version 2 catalogs and receive physical
backend selectors. Any expression or runtime field in the catalog, including an
unused field, rejects before remote authority is called.
For plugin imports, explicitly pass `{ reportVersion: 2, runtime }` to
`importPivotReport`. V2 exports use the same explicit options on `getPivotReport`
or `exportPivotReportJson`; provide caller-owned bindings for all required
callbacks. Private session bindings are never implicit export authority.
`loadPivotFilterValues` accepts its existing request object with `field` set to a
tagged selector for semantic fields, and reads the committed result scope.
Native Core conditions on semantic fields use the captured stock Core evaluator
locally, including its casing, coercion, Boolean grouping and blank rules.
Custom or overridden semantic predicates, cell parsers and blank callbacks are
rejected before application field evaluation. Physical-only remote catalogs use
the existing V1 canonical transport mapping; its comparison semantics belong to
the backend and are not claimed to match local Core predicates. A native blank
condition without a transport mapping is rejected rather than omitted. Remote
drillthrough retains the existing backend view-ID and cell scope contract.
Clearing Pivot restores physical filters to the raw grid and retires derived
conditions. A physical alias can be restored only when its raw destination is
unique and its captured parser/blank settings are equivalent. Conflicting aliases
or different raw settings reject clear atomically; remove the conflicting native
filter before clearing. Destroy still disposes the plugin after such a rejection.
## Related product guides
[Saved reports, runtime bindings and persistence](/guides/pivot/concepts/report-persistence/).
---
# Persistent saved views
URL: https://pro.rv-grid.com/guides/pivot/concepts/saved-views/
Source: src/content/docs/guides/pivot/concepts/saved-views.mdx
Description: Configure scoped durable storage for the attached Pivot Save views menu.
The attached configurator's **Save views** menu uses session snapshots by default. To keep named views after a page reload, configure the plugin instance with an explicit storage scope:
```ts
import { PivotPlugin, createBrowserPivotViewStorage } from '@revolist/pivot';
const pivot = (await grid.getPlugins()).find(plugin => plugin instanceof PivotPlugin);
pivot.setSavedViewStorage({
scope: `${authenticatedUserId}/${workspaceId}/${reportId}`,
storage: createBrowserPivotViewStorage(),
});
```
The menu can create a view, update it with the current report, restore it, rename it, delete it, or reload the saved list. Updates keep the name and identity. Persistent views contain validated portable report documents (V1 or V2) and row height. They do not contain source rows or serialized callbacks. Register runtime bindings with `setPivotReportRuntime` before restoring reports that require them. Portable reports use the existing validation, migration and preventable import lifecycle.
Use a separate scope for each authenticated user, workspace and report. Browser storage belongs to the origin and is not an authorization boundary. Clear or replace the integration when the authenticated user changes. `pivot.setSavedViewStorage()` returns the menu to session snapshots; it never deletes durable data. Every storage integration switch cancels pending restores and clears the detached session snapshots. Returning to session mode starts with an empty list, so previous user names and runtime bindings cannot reappear.
The browser adapter uses `localStorage` and origin-wide **Web Locks** for atomic comparisons between participating tabs. It requires a secure context with Web Locks for writes. It surfaces unsupported-browser, storage-disabled and quota errors. Applications needing a server store or a browser without Web Locks can implement `PivotViewStorage`:
```ts
import type { PivotViewStorage } from '@revolist/pivot';
const storage: PivotViewStorage = {
async read(scope, signal) { /* return the exact stored string, or null */ },
async compareAndSwap(scope, expected, next, signal) {
// Authorize scope on the server. Atomically replace only if the entire
// current value equals expected. Return false on conflict.
// Honor abort before committing. Do not implement a separate read/write pair.
},
};
pivot.setSavedViewStorage({ scope, storage });
```
Changes from another session cause a conflict rather than an overwrite. Select **Reload views**, inspect the latest list and retry. The library does not silently merge or automatically retry conflicting writes. All writers to the same browser key must use the same locking contract. A write already committed before cancellation cannot be undone.
A failed restore keeps the current report. Missing fields or runtime bindings are diagnosed; restore the host bindings, update the saved view with the current valid report, or delete that view. The field check conservatively requires the saved field catalog to exist in the current report. Malformed collection data is kept intact and never overwritten: back it up and repair it through the host storage, then select **Reload views**. A failed save retains the name draft so the user can retry.
The collection has a limit of 200 views and 2 million UTF-16 code units of serialized data. Actual browser storage quotas can be lower. `clearPivot()` cancels in-flight saved-view operations while keeping durable records; destroying the plugin or replacing storage retires the old manager and aborts pending restoration.
## Calculation policy in saved views
Persistent view collections accept V1/V2 report documents and preserve their operations and top-level `aggregationSemantics`. This differs from the separate V1-only `savePivotReport`/`loadPivotReport` helpers. Add a pin to an existing report without replacing its aggregate declarations, execute the candidate and review known KPIs before saving. Restore runtime bindings before loading reports that use them. See [Safe saved-report configuration](/guides/migrations/#pivot-calculation-policies-and-saved-reports).
---
# Sorting And Filtering
URL: https://pro.rv-grid.com/guides/pivot/concepts/sorting-filtering/
Source: src/content/docs/guides/pivot/concepts/sorting-filtering.mdx
Description: Learn what sorting and filtering affect in Pivot output and how client-side and remote/server-side behavior differ.
Sorting and filtering in Pivot operate on analytical members and generated measures, not on the final rendered cells alone. The same ideas exist in both client-side and server-side Pivot, but the execution layer is different.
Source filters select contributing facts; result filters select aggregate groups and test total coordinates independently. Totals are recomputed at their own grain, not by summing retained displayed rows. For numeric/scalar eligibility, configured local aliases and count meaning, use [Aggregation policies](/guides/pivot/concepts/aggregations/). Server filters require supported summary identities and complete backend scope.
## Client-Side Sorting
Client-side Pivot uses dimension metadata when generating row and column members.
Relevant dimension options:
- `sortable`
- `order`
If a Pivot column dimension is marked sortable, generated members for that dimension are ordered using the configured comparer and sort direction.
## Client-Side Filtering
Generated value columns inherit configured filter metadata from their dimensions, so the resulting grid can expose filter-ready columns. This is still based on the generated Pivot output, not on the raw fact table semantics of a remote analytical engine.
## What Actually Gets Sorted
In client mode:
- row hierarchy ordering comes from the grouped source data
- column hierarchy ordering comes from the configured dimension metadata
- measure cells are results of aggregation, not independently sorted records
## Server-Side Sorting And Filtering
In server mode:
- filter expressions are part of `PivotLoadRequest.loadOptions.filter`
- sort directives are part of `PivotLoadRequest.loadOptions.sort`
- the analytical engine is responsible for applying them before returning the visible window
That distinction matters because server-side Pivot should sort and filter the cube window, not just the already-rendered grid cells.
## Sort By Summary
The remote request contract also supports sort-by-summary semantics through `bySummary` on sort descriptors. Use that when your backend can rank groups by a measure rather than only by a dimension label.
## Best Practices
- Keep client-side sorting expectations simple: it orders generated members, not facts.
- Use remote/server-side filtering for large datasets or security-sensitive selectors.
- Do not rely on client-generated columns as the trust boundary for secure filtering. The field registry belongs on the server side.
## Next
Continue with [Plugin Lifecycle](/guides/pivot/concepts/plugin-lifecycle/) to see how Pivot owns and restores grid state.
## Related product guides
[Contextual ranking and supported summary-sort scope](/guides/pivot/concepts/ranking-and-summary-sorting/).
---
# Totals
URL: https://pro.rv-grid.com/guides/pivot/concepts/totals/
Source: src/content/docs/guides/pivot/concepts/totals.mdx
Description: Learn how Pivot grand totals and subtotals work across rows and generated columns.
Totals help users move from detailed branch-level summaries to whole-table summaries. RevoGrid Pivot supports grand totals and subtotals through `pivot.totals`.
Open the full demo and framework source.
## Calculation meaning
Use the [explicit numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/) when defining measure input. At each leaf, subtotal and grand-total grain, Pivot recomputes the configured operation from contributing facts. For groups `[10]` and `[20, 20, 20]`, numeric AVG is **17.5** at the grand total, not 15. Distinct and paired weighted totals are likewise nonadditive. A weighted total uses eligible numerator/weight sums at that grain; a zero denominator is null.
Percentages retain numeric ratios: 0.5 means 50%. Hidden totals remain available as denominators; collapse/expansion changes presentation. Running percentage divides a prefix of ordered peer aggregates by that peer sum, not by displayed subtotal rows or only the viewport. Source filters change fact scope; result filters select groups and independently test totals, so totals need not equal the sum of retained displayed groups. Independent local `values[].summaries` can deliberately select different group/total operations; standard remote execution rejects these overrides.
Server totals must come from complete authorized source scope under the backend's declared policy. `aggregationSemantics` pins execution location; it does not establish equal local/server null, count or precision semantics.
## The Totals Config
```ts
totals: {
grandTotal: true,
subtotals: true,
grandTotalLabel: 'All Sales',
subtotalLabel: 'Subtotal',
}
```
## What Each Option Does
- `grandTotal`: Adds a grand-total row. When column dimensions exist, it also adds grand-total value columns.
- `subtotals`: Adds subtotal rows for intermediate row branches and subtotal value columns for intermediate column branches.
- `grandTotalLabel`: Overrides the default `"Grand Total"` label.
- `subtotalLabel`: Overrides the default `"Subtotal"` label.
- `suppressSingleChildSubtotals`: Hides subtotal rows for single-source branches where the subtotal duplicates the only leaf.
- `suppressGrandTotalWhenSingleLeaf`: Hides the grand-total row when the whole result is one source-backed leaf.
## Smart Suppression
Use suppression when totals are enabled for broad reports, but small filtered results should avoid duplicate summary rows:
```ts
totals: {
subtotals: true,
grandTotal: true,
suppressSingleChildSubtotals: true,
suppressGrandTotalWhenSingleLeaf: true,
}
```
Suppression is conservative. Multiple source rows, multiple meaningful leaves, or disabled `subtotals` / `grandTotal` keep the existing total behavior.
## Row Totals
When row fields exist:
- subtotal rows are inserted after the descendants of each branch
- the grand-total row is rendered separately and pinned at the bottom
This keeps the detailed body rows readable while still making the full-table total visible.
## Column Totals
When column fields exist:
- intermediate column paths can expose subtotal value columns
- the full column hierarchy can expose grand-total value columns
Pivot does not create a separate total table. Totals become part of the generated analytical structure.
## Column Totals With Drill-Down
Column totals and column collapse are designed to work together. A collapsed
group shows one aggregate carrier. Expanding that group keeps the parent
aggregate visible as a subtotal column and reveals only the next configured
level.
Collapsed years use one aggregate carrier per year. Grand-total columns
remain independent from the branch collapse state.
Expanding 2023 reveals its configured Year Total first, followed by the
next-level month groups. The subtotal spans the otherwise unused lower
header depth.
Drill-down is progressive: expanding January reveals only January's
measures. Sibling months stay collapsed and Year Total remains one Sales
subtotal column.
### Behavior Matrix
| Configuration | Collapsed group | Expanded group |
| --- | --- | --- |
| `totals.subtotals: false` | Uses the regular collapsed aggregate bucket. | Shows descendants without a parent subtotal column. |
| `totals.subtotals: true` and the level subtotal is enabled | Uses the level subtotal as the aggregate carrier. | Shows the same subtotal plus the next visible descendant level. |
| `columnLevels[level].subtotal: false` | Uses the regular collapsed aggregate bucket for that level. | Omits the subtotal for that level, even when other levels show subtotals. |
| `columnLevels[level].collapsible: false` | The level cannot enter a collapsed state. | The level stays expanded; its subtotal can still be shown. |
| `subtotalPosition: 'before'` | Position is not visible while the branch is compact. | Places the parent subtotal before descendant columns. This is the default. |
| `subtotalPosition: 'after'` | Position is not visible while the branch is compact. | Places the parent subtotal after descendant columns. |
| `subtotalValues: ['sales']` | The compact carrier exposes only Sales for that subtotal. | The subtotal contains only Sales, while descendant groups can still expose every configured measure. |
| `totals.grandTotal: true` | Grand-total columns and the pinned grand-total row remain visible. | Grand totals remain independent from individual branch expansion. |
### Recommended Per-Level Configuration
This configuration matches the screenshots:
```ts
const pivot: PivotConfig = {
rows: ['country', 'segment'],
columns: ['year', 'month'],
values: [
{ prop: 'sales', aggregator: 'sum' },
{ prop: 'profit', aggregator: 'sum' },
{ prop: 'unitsSold', aggregator: 'sum' },
],
columnCollapse: {
enabled: true,
collapsed: true,
},
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: false,
sortable: false,
},
},
totals: {
subtotals: true,
grandTotal: true,
},
};
```
`columnLevels` uses the zero-based order of `columns`: level `0` configures
`year`, level `1` configures `month`, and so on.
### Resolution And Precedence
The following rules make mixed configurations predictable:
1. `totals.subtotals` is the global subtotal gate. When it is `false`, no
`columnLevels[level].subtotal` setting can enable a subtotal.
2. With global subtotals enabled, an explicit
`columnLevels[level].subtotal` overrides
`totals.disabledSubtotals.columns` for that level.
3. An explicit `columnLevels[level].collapsible` overrides the global
`columnCollapse` setting for that level. When omitted, the level inherits
`columnCollapse.enabled`.
4. `columnLevels[level].collapsed` controls the initial state for that level
and falls back to `columnCollapse.collapsed`.
5. Persisted `collapsedColumns` state wins over both initial-state defaults for
the matching generated group.
6. `subtotalLabel`, `subtotalPosition`, `subtotalValues`, `filterable`, and
`sortable` apply only to their configured level.
7. Grand-total columns do not belong to a generated hierarchy level, so
`columnLevels` does not change them.
### Filtering And Sorting Through Collapse
The subtotal or collapsed aggregate represents one analytical group across
both visual states. A filter or sort applied to that aggregate therefore stays
active when the group expands or collapses:
- expanding does not broaden the filter to every newly visible child column
- collapsing does not discard the active filter or sort direction
- the active control moves between the compact carrier, parent group proxy,
and visible subtotal without changing its analytical property
- `filterable: false` removes aggregate filter controls for that level
- `sortable: false` removes aggregate sorting for that level
The expand/collapse control appears before the group title. Expanding a group
reveals one hierarchy level only; it does not expand its descendants or sibling
branches.
### Header Spanning
A direct subtotal such as `Year Total` fills the unused descendant header rows
vertically. It remains one value column; it does not span horizontally across
the descendant columns. This keeps the subtotal aligned with deeper headers
without covering the parent expand/collapse control.
## Example
For:
```ts
rows: ['region', 'rep'],
columns: ['year', 'quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
```
You get:
- leaf rows for each `region -> rep`
- subtotal rows for each `region`
- grand total at the bottom
- leaf value columns for each `year -> quarter`
- subtotal value columns for each `year`
- grand-total value columns across all years and quarters
## Important Notes
- Subtotals only appear when a hierarchy has more than one level to summarize.
- Grand totals use the configured label only for display. The internal total keys stay synthetic.
- A column subtotal remains the same analytical aggregate through collapse and
expansion; only its header presentation changes.
- Per-level subtotal, collapse, filter, and sort settings are independent.
- In remote/server mode, totals stay part of the analytical contract and should be computed by the engine, not recomputed from visible UI cells.
## Common Mistakes
- Expecting `subtotals: true` to add subtotal rows when there is only one row field.
- Expecting grand totals to stay inside the body rows. In the current implementation, grand total rows are pinned bottom rows.
- Assuming `subtotal: true` can bypass `totals.subtotals: false`.
- Assuming expanding a year should expand every month below it.
- Confusing a regular collapsed bucket with a subtotal carrier. They look
similarly compact, but only a level with an enabled subtotal exposes that
same subtotal as a visible column after expansion.
## Next
Continue with [Values On Rows](/guides/pivot/concepts/values-on-rows/) or [Drill-Down](/guides/pivot/concepts/drilldown/).
---
# Values On Rows
URL: https://pro.rv-grid.com/guides/pivot/concepts/values-on-rows/
Source: src/content/docs/guides/pivot/concepts/values-on-rows.mdx
Description: Move Pivot measures into the row hierarchy and understand when that layout works best.
By default, Pivot renders measures as generated value columns. `valuesOnRows` flips that layout so measures become synthetic row members instead.
Open the full demo and framework source.
## Default Layout vs Values On Rows
Default layout:
```txt
rows: Region
columns: Quarter
values: Sales, Margin
```
This usually produces:
- one row branch per region
- one generated value column per quarter and measure
With `valuesOnRows: true`, the same measures become row members:
- Region
- Sales
- Margin
That is useful when:
- you have many measures and few row dimensions
- you want a more compact column area
- users compare measures vertically rather than horizontally
## Configuration
```ts
const pivot: PivotConfig = {
rows: ['region'],
columns: ['quarter'],
values: [
{ prop: 'sales', aggregator: 'sum' },
{ prop: 'margin', aggregator: 'avg' },
],
valuesOnRows: true,
};
```
## Explicit Values Position
Use `rowTree` with the `$values` pseudo-field when measures need to appear at a specific hierarchy level:
```ts
rowTree: ['carModel', 'cellId', '$values']
rowTree: ['carModel', '$values', 'cellId']
rowTree: ['$values', 'carModel', 'cellId']
```
`rowTree` is the explicit advanced API. If it is provided, it controls value placement even when `valuesOnRows` is also set.
## What Changes Internally
- Pivot adds a synthetic row field called `Values`.
- Generated value columns become value labels in the row hierarchy.
- Grouping and row drill-down now treat the value label as part of the row structure.
This is why row drill-down helpers account for `valuesOnRows` when deriving row props.
## Tradeoffs
Benefits:
- fewer generated columns
- easier comparison of multiple measures
- clearer layouts on narrow screens
Costs:
- more synthetic rows
- row drill-down can feel deeper because the value label becomes part of the hierarchy
- users expecting a spreadsheet-style “measures across columns” layout may find it less familiar
## When Not To Use It
Avoid `valuesOnRows` when:
- you only have one measure
- your users think in terms of wide analytical columns
- you already have a deep row hierarchy and want to keep rows compact
## Next
Continue with [Drill-Down](/guides/pivot/concepts/drilldown/) to see how row and column expansion behave in both layouts.
---
# Configurator
URL: https://pro.rv-grid.com/guides/pivot/configurator/
Source: src/content/docs/guides/pivot/configurator/index.mdx
Use the [Pivot configurator](./configurator/) to explore the built-in authoring interface, or use the [controlled authoring API](./configurator-api/) in your application.
---
# Configurator
URL: https://pro.rv-grid.com/guides/pivot/configurator/configurator/
Source: src/content/docs/guides/pivot/configurator/configurator.mdx
Description: Learn what the Pivot configurator does and how users interact with rows, columns, and values.
The configurator gives users a visual way to arrange dimensions in Rows, Columns, Values and Filters without writing a new `PivotConfig` by hand. Users can drag fields or use the Add field and Actions controls with a keyboard.
Define the field's calculation policy before exposing it in the constructor. Use [V2 numeric/scalar presets](/guides/pivot/concepts/aggregations/) for explicit input meaning; compiled native maps contain callbacks, not portable declarations. A configured catalog selects its first supported operation. Without one, string/Boolean/date fields prefer count; numeric/unknown fields prefer sum. Specify row count versus eligible-value count deliberately.
## What It Does
The configurator renders:
- a dimension list
- a rows drop zone
- a columns drop zone
- a values drop zone
- a filters area and member selections
- shared measure settings and, when wired, Display, Undo/Redo and Save Views controls
When users move a field:
- the configurator proposes a combined layout patch, including any member selections pruned by the move
- the native `PivotPlugin` emits the synchronous, cancelable `pivot-config-update` proposal
- the owner validates and executes the candidate; only accepted results update committed state
## Enable It
This example shows the full constructor. For the compact editor above the grid, see [Field Panel](/guides/pivot/concepts/field-panel/).
```ts
const pivot: PivotConfig = {
dimensions: [
{ prop: 'region', name: 'Region' },
{ prop: 'quarter', name: 'Quarter' },
{ prop: 'sales', name: 'Sales' },
],
rows: ['region'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
hasConfigurator: true,
};
```
## Visibility Controls
Use these options to hide zones:
- `showRows`
- `showColumns`
- `showValues`
- `showFilters`
Explicit `fieldPanel.showRowFields`, `showColumnFields`, `showDataFields`, and `showFilterFields` override the corresponding `show*` setting in both constructors. Defaults show every area. These only affect the configurator UI. They do not remove support for the corresponding Pivot feature.
The Reset layout action is hidden by default. Set `fieldPanel.showResetLayout: true` to expose it in either the full configurator or compact field panel.
## Mounting
By default, `PivotPlugin` creates the configurator inside the grid wrapper. If you set `mountTo`, it mounts into the supplied element instead.
## Aggregator Selection
The configurator’s value zone reads available aggregators from the matching dimension. A nonempty custom map is exact; omitted or empty registries use built-ins. A newly added measure uses the first registered operation. Measure labels can be translated without changing those operation IDs.
## Measure Settings
Open **Actions → Edit measure** for one placed measure. Stage its label, aggregation, numeric format, calculation and summary operations, then Apply them together. Cancel discards the draft. Measures using the same source field keep separate identities and settings.
**Show values as** offers ratios, running totals, rank and previous/next comparisons. **Summary operations** can use separate registered operations for local group and grand-total scopes. Availability follows engine support; existing advanced settings remain preserved when editing other fields. See [Configurator API](/guides/pivot/configurator/configurator-api/) for draft ownership, execution restrictions and supported settings.
## i18n
Configurator labels come from `i18n`. If omitted, Pivot uses the built-in English defaults from `PIVOT_CONFIG_EN`.
Overrides can be partial, including `fieldTypeNames`, `aggregationNames`, and
`measureCalculationNames`. Missing entries retain English defaults. Aggregation
labels change the UI text only; registered operation IDs remain unchanged.
```ts
i18n: {
rows: 'Lignes',
fieldTypeNames: { number: 'Nombre' },
aggregationNames: { sum: 'Somme', avg: 'Moyenne' },
aggregationForField: field => `Agrégation de ${field}`,
}
```
`fieldPanel.texts` overrides root `i18n` in both stock mount helpers. Field names, group/member data and saved-view names come from your application. Function entries in
`PIVOT_CONFIG_EN` let you translate labels containing those names or counts.
## Common Mistakes
- Expecting omitted `dimensions` to expose unused source fields. Inference covers referenced fields; supply a useful complete catalog for field discovery.
- Expecting hidden zones to disable Pivot features globally.
- Treating the configurator as the state owner. It is a UI for editing Pivot state, not the persistence layer.
## Next
Continue with [Field Panel](/guides/pivot/concepts/field-panel/) for the compact in-grid layout, [Configurator API](/guides/pivot/configurator/configurator-api/) for the standalone embedding surface, or [Server-Side Pivot](/guides/pivot/server-side/) for remote analytical usage.
---
# Configurator API
URL: https://pro.rv-grid.com/guides/pivot/configurator/configurator-api/
Source: src/content/docs/guides/pivot/configurator/configurator-api.mdx
Description: Mount the stock Pivot panel outside the grid with live updates, typed combined patches and cleanup.
`definePivotConfigurator(element, config, actions)` mounts the stock panel in your own host and returns cleanup. It renders configuration controls; it does not execute Pivot or own the grid. A custom visual constructor can use the [attached authoring facade](../../concepts/custom-integrations/) instead.
The checked stock-panel recipe forwards `onUpdateConfiguration(patch: PivotConfigurationPatch)` to an awaited `plugin.updatePivotConfig` call. This combined patch contains changed rows, columns, values and filters, plus member selections pruned by that layout change; it is not an unrestricted arbitrary config patch. Removing a field from its final filterable role sends its placement and cleared criteria together in one update. Cross-zone/group moves use the same combined route. Its local draft composes pending edits, then reconciles from accepted runtime state after success, veto or failure. It handles filter selections, Reset and disposal too.
For calculation settings, use the [explicit aggregation contract](/guides/pivot/concepts/aggregations/). The UI selects configured operation IDs; it does not infer null, invalid or count policies from labels. Server calculation choices depend on advertised capabilities and actual backend support. Persist accepted operations and the execution pin together.
## Layout callbacks
- `onUpdateConfiguration(PivotConfigurationPatch)`: preferred combined layout update, including optional `filterSelections` when layout changes prune member criteria.
- `onUpdateColumns`, `onUpdateRows`, `onUpdateValues`, `onUpdateFilters`: ordered zone callbacks retained for compatibility; combined wiring avoids multiple regenerations for one action.
- `onUpdateFilterSelections`: individual member-filter edits, using inclusive arrays or explicit exclusion selections. Individual zone and selection callbacks remain supported, but separate application callbacks cannot guarantee one atomic external update.
- `sorting` and `onToggleSort(prop)`: application-supplied public sorting integration. The restricted authoring facade does not invent a sorting command.
- `onResetLayout`: application reset action.
Sorting callbacks are consumed by the compact `definePivotFieldPanel`; the full `definePivotConfigurator` currently has no sort control. Forwarding them to the full helper does not enable sorting. Apply sorting through the plugin or a separate application control. Mount only one stock panel per host. Its cleanup disposes Preact rendering and mount classes and removes the subscription. Application teardown owns the grid/plugin separately.
Embedded `hasConfigurator` uses the same underlying component and plugin admission. Visibility and translation settings remain `showRows`, `showColumns`, `showValues`, `showFilters`, `i18n` and field-panel texts. Role metadata and stable dimensions keep valid placements discoverable; backend permissions remain separate.
## Constructor arguments and controlled updates
`definePivotConfigurator(el, config?, actions?, diagnostics?, templateContext?)`
returns a cleanup function. `definePivotFieldPanel(el, config?, actions?, layout?,
diagnostics?, templateContext?)` mounts the compact variant. Both accept
`Partial` and `Partial`; call the same
helper again on its host with accepted config to update a controlled mount.
Do not mutate a previously supplied config object and expect observation.
`diagnostics` accepts `PivotUiDiagnostics` (`warnings?: string[]`, `error?: string`).
`templateContext` supplies Core `columnTypes` and `additionalData` for existing
field templates. The compact helper's `layout` supplies generated `columns`,
`colSize`, and optional `resizedColumns` for alignment with the table. These
arguments describe UI context; they do not replace the analytical source.
Use a dedicated host. Cleanup disposes rendering, registrations and helper-added
presentation classes/styles; dispose the helper before abandoning the host.
Application code owns analytical subscriptions, backend work and grid disposal.
All layout and display callbacks return void; await your own application update
and re-render from accepted state, as the checked recipe does. A callback invocation
or optimistic control state does not establish that a candidate was committed.
`plugin.updatePivotConfig(nextConfig)` accepts a complete candidate, so merge a
patch into the current authored draft rather than passing a partial patch directly.
## Additional existing actions
| `PivotConfigurationActions` member | Integration contract |
| --- | --- |
| `tableDropTarget` | Associated table HTMLElement for direct field drops; scoped to this full configurator mount. |
| `authoring` | Existing `PivotAuthoringController` used for full-panel Undo/Redo/status. The host still owns layout callback wiring. |
| `onSaveView` | Full-panel Save action; may return void or a Promise. Host owns persistence/export. |
| `viewManager` | Full-panel named-view manager; takes precedence over `onSaveView`. |
| `onUpdateDisplay` | Full-panel `PivotDisplayPatch` callback for totals, values orientation, row-axis layout, flat headers and rowTree. Separate from `PivotConfigurationPatch`. |
| `rowSize` | Current Core grid row height for the full-panel density control. |
| `onUpdateRowSize` | Full-panel density callback; host updates the grid presentation. Density is not part of PivotConfig. |
| `registerEditor` | Registers a live `PivotEditorBridge` in either mount and returns its unregister cleanup. |
| `loadFilterValues` | Async distinct member-choice loader in either mount. Explicit dimension `filterOptions`, including `[]`, takes priority. |
| `filterValuesContext` | Scope lifecycle for async choices: `getSnapshot()` supplies revision/status and `subscribe()` supplies cleanup. |
`PivotDisplayPatch` contains `totals`, `valuesOnRows`, `rowAxisLayout`,
`flatHeaders`, and `rowTree`. Merge it into the current authored candidate through
normal admission. Omission of `onUpdateDisplay` leaves a message explaining how
to connect display controls. Density controls require `onUpdateRowSize`.
These full-panel actions are not forwarded by the compact helper.
A `PivotViewManager` owns `list(): readonly PivotNamedView[]`,
`subscribe(listener): () => void`, `save(name)`, `rename(id, name)`, `remove(id)`,
and `restore(id): Promise`. Named entries have immutable `id` and `name`.
False from restore means the existing preventable update was not accepted. Payload
storage and persistence live in the host; the native plugin's built-in views last
for the active Pivot session and disappear on close/reload.
`PivotEditorBridge.canOpen(request)` and `open(request)` handle an exact measure
ID/prop or member field plus an origin element; a true result means UI scheduling
was accepted. `close()` dismisses without applying or restoring focus. Keep only
currently registered bridges, and revoke them on cleanup. Opening an editor does
not change the report or persist a measure.
## Async member choices
`PivotFilterValuesLoader` receives `{ field, search?, offset, limit, signal? }`
and resolves `PivotFilterValuesResponse` with `values` and `totalCount`.
Return distinct finite scalar values for the requested fixed-offset window and
a nonnegative safe-integer count for the matching scope. The page length must be
`min(limit, max(totalCount - offset, 0))`; an approximate count with short pages
is not this contract. Typed values such as `1` and `'1'` are distinct. Respond to
the abort signal and keep paging/search ordering consistent.
Choices represent the current source/query scope excluding the edited field's
own member selection. Preserve selected members outside the current page/search.
Standalone hosts must supply `filterValuesContext` or replace the loader whenever
an otherwise invisible source/query scope changes. Notify for pending transitions
even at the same revision; loading/inactive/destroyed scopes pause new edits.
The host owns execution capabilities and authorization. Omission of a loader does
not create a backend enumeration endpoint; existing selected members can still be
removed or cleared when choices are unavailable.
## Field actions without dragging
Both stock constructors offer Add field for each visible area and an Actions button for each placed field. Actions can assign a field, move it earlier or later, duplicate an exact measure, or remove it. Clear affects one area. Search and destination choices respect field visibility and allowed roles; unavailable actions explain why they cannot run.
Rows and Columns are exclusive placements. Moving between them preserves independent Values and Filters. Adding a field to Values does not replace existing measures: use Duplicate measure to copy one exact measure with its settings and a fresh ID. Clearing Values preserves member criteria; removing a field from its final Rows, Columns or Filters placement prunes that field's criteria in the same combined patch.
Use `fieldPanel.allowFieldActions: false` to hide the new Add, Actions and Clear controls. It defaults to true and is independent of `allowFieldDragging`; disabling dragging still permits keyboard actions. `allowFieldRemoving` controls Remove and Clear, defaulting to true in the full configurator and false in the compact panel. An explicit false now also suppresses the existing full-configurator Remove buttons. These options control UI availability, not application authorization.
Individual callback integrations must supply every callback needed by an action. For example, moving between axes requires both axis callbacks; removing the final filterable placement can also require `onUpdateFilterSelections`. An incomplete integration disables that action. Prefer the combined callback for atomic owner updates. Stock callbacks return void, so an optimistic control change does not establish that an application accepted or persisted the report. The checked recipe above handles acceptance and reconciliation explicitly.
## Edit one measure
In either stock constructor, open a measure's **Actions → Edit measure** to stage its display label, aggregation, numeric formatting, calculation and summary operations. Typing and selecting leave the report unchanged until **Apply**; **Cancel** or Escape discards the draft. The editor is an inline region, so it also works in standalone mounts without a grid-owned dialog. The existing aggregation selector remains available for quick changes.
A blank display label clears only the label override and reveals the existing measure name or field caption shown in the preview. The source field and stable measure ID stay unchanged. Measures using the same source field are edited independently; supply distinct IDs when configuring them. An existing aggregation absent from the current registry is preserved during label-only edits. New choices come from the matching dimension's aggregator registry, with the same defaults as the quick selector; the analytical owner still validates execution support.
Apply merges changed settings into the latest exact measure and preserves its other metadata. A draft follows a unique stable ID through reorder. If a setting being edited changes externally, reload current settings or cancel before applying. Returning an input to its opening value relinquishes that draft change. Removed or ambiguous targets cannot be applied; measures without IDs are conservatively invalidated by external replacement or reorder.
Editing requires `onUpdateConfiguration` or `onUpdateValues`, visible Values and enabled field actions. Dragging and removal flags do not disable settings editing. Apply submits through the existing void callback contract; it does not certify persistence. Standalone applications own callback exceptions, admission and controlled reconciliation in both individual immediate and scheduled combined integrations. The native plugin owns proposal-veto and asynchronous-failure reconciliation.
Expand **Format** to select Number, Currency or Percent, set optional minimum and maximum decimal places, and keep, remove or override the current locale. Blank decimal bounds use Intl defaults; entered bounds must be integers from 0 to 20, with minimum no greater than maximum. Currency requires an explicit code. The preview uses an illustrative sample, not the current aggregate. Invalid drafts stay local and cannot be applied. **Automatic** removes the explicit format override and uses report defaults, including calculation formatting.
Existing date and advanced formats are preserved until you deliberately replace them. Changing the preset replaces preset-specific options; the editor explains which common settings are retained or replaced. Basic label and aggregation edits preserve untouched formatting. A dirty format conflicts with any external replacement of that format object; reload to establish the new baseline. Reverting format controls restores the original format reference. Formatting affects displayed values; raw analytical values and raw data exports retain their numbers.
Expand **Show values as** for percentages of grand, row, column or parent totals, index, running totals, percent running totals, rank, and differences or percent differences from the previous or next member. Sequence calculations require a field on the selected current axis. They follow the current cube order within each parent; selecting a date field does not create chronological sorting. Missing comparisons and zero denominators produce null results, blank by default; explicit null-display formatting still applies. Running totals treat missing values as zero. **No calculation** clears the override.
Calculations change analytical result numbers; number formatting only changes their display. Original source facts remain unchanged. A percentage calculation produces a fraction, and an explicit format can override its default percentage presentation. Existing formula ASTs and comparisons with explicit member bases are preserved as advanced settings. This editor does not create formulas or arbitrary member-base comparisons; their existing typed APIs remain available.
Expand **Summary operations** to choose separate registered operations for group/subtotal/collapsed coordinates and grand totals. Each scope can inherit the leaf aggregation; grand totals do not inherit the group override. These operations aggregate the original eligible facts, so averaging displayed averages is not equivalent. Grand-total scope takes precedence at intersections. The controls do not turn on visible totals, and existing collapsed-column operation overrides retain their own policy.
Independent summary overrides require local execution without a supplied adapter or remote store. Formula summaries belong to their dependency measures. Remote calculation choices require explicit engine support; omitted or opaque capability information does not establish support. Native mounts receive their engine configuration automatically. Standalone constructors can pass the existing optional `engine` configuration; omitting it assumes local configuration authoring, with execution admission still owned by the host. Current unsupported settings remain preservable during unrelated edits.
Calculation and summary drafts own separate references. An external replacement of a setting being edited requires Reload or Cancel, while untouched settings follow the latest measure. Returning controls to their opening settings restores the original reference and relinquishes the change. Apply submits all changed measure settings together. Analytical changes may recompute or request data; the formatting-only request-reuse guarantee does not apply to them.
---
# Grid configurator
URL: https://pro.rv-grid.com/guides/pivot/configurator/grid-configurator/
Source: src/content/docs/guides/pivot/configurator/grid-configurator.mdx
Description: Configure ordinary grid columns, row groups, group summaries, and column filters using the Pivot package's shared configurator UI.
`GridConfiguratorPlugin` configures an ordinary RevoGrid. It is exported from
`@revolist/pivot` to share the existing configurator UI. It does not install
`PivotPlugin`, execute a Pivot report, or replace the grid's source records.
Pro remains independent of the Pivot package.
For supported signatures, see [Grid Configurator API](/api/grid-configurator/). Its ordinary-grid summary callbacks are separate from [Pivot report input policies](/guides/pivot/concepts/aggregations/); installing this configurator does not select a Pivot aggregation or execution pin.
## Attach to a grid
```ts
import { GridConfiguratorPlugin } from '@revolist/pivot';
import '@revolist/revogrid-pro/styles.css';
import '@revolist/pivot/styles.css';
const grid = document.createElement('revo-grid');
grid.columns = [
{ prop: 'country', name: 'Country', filter: 'string' },
{ prop: 'sales', name: 'Sales', filter: 'number' },
];
grid.source = [
{ country: 'Portugal', sales: 10 },
{ country: 'Portugal', sales: 20 },
];
grid.filter = true;
grid.grouping = { props: [], expandedAll: true };
grid.gridConfigurator = {
fields: [{ prop: 'sales', dataType: 'number' }],
values: [{ prop: 'sales', aggregator: 'sum' }],
};
grid.plugins = [GridConfiguratorPlugin];
document.querySelector('#grid-host')!.append(grid);
```
The default mount inserts a panel immediately before the grid. The application
owns the surrounding layout; for a sidebar, place the grid in a flex or grid
container. The plugin installs or reuses `ColumnHidePlugin` and
`AdvanceFilterPlugin`.
The **Fields** tree controls source-column visibility and order. Existing nested
header groups can be expanded, searched, hidden, and reordered using the same
parent and pin-section restrictions as the column dialog. It does not create or
edit header groups. There is no Pivot column-axis destination.
**Row Groups**, **Values**, and **Filters** are independent roles. Adding a field
to one role does not hide its source column or remove another role. Checkboxes
only control visibility. Hidden fields retain their configuration.
## Mount externally
Wait until the connected grid has initialized, then pass a connected host in the
same document:
```ts
import { defineGridConfigurator } from '@revolist/pivot';
grid.gridConfigurator = { mountTo: false };
await grid.componentOnReady();
const panel = await defineGridConfigurator(
document.querySelector('#sidebar')!,
grid,
{ fields: [{ prop: 'sales', dataType: 'number' }] },
);
// Unmount this surface. The adapter and grid configuration remain installed.
panel.destroy();
```
`defineGridConfigurator` reuses the installed `GridConfiguratorPlugin`, or
installs one with no attached panel. Mounting again moves its single surface;
older cleanup handles cannot remove a newer mount. The external host itself is
never removed.
To release subscriptions and restore the owned group-cell rendering hook, remove
the plugin through the grid's plugin lifecycle. Destroying the adapter preserves
visibility, grouping, filters, and any rendering callback installed afterward.
## Aggregations and filters
Each source column has at most one selected aggregation. Explicit numeric
metadata defaults to `sum`; other or unknown fields default to `count`. Supply
`fields[].aggregators` to restrict a field's available functions,
`fields[].defaultAggregator` to override its default, or configure shared
`aggregators` and `defaultAggregator` options. Functions receive the field values
of matching leaf records, using the existing Pro aggregator semantics.
Values appear beneath their source columns in synthetic group rows. With no row
groups, the selections remain configured but no summary or grand total appears.
Filtered-out records do not contribute. Collapsed descendants still contribute,
and scrolling does not change the results. Calculations are cached outside cell
rendering and refreshed after relevant data, edit, filter, or grouping changes.
Filters open the normal column filter editor. Panel and header filters share the
same canonical state. Removing a filter clears that column's conditions while
preserving unrelated predicates and quick filtering. Columns explicitly marked
`filter: false` cannot be added as filters.
This integration summarizes **locally loaded records**. It does not coordinate
server-side grouping, remote aggregates, or complete-dataset totals for partial
data.
## Options and updates
`GridConfiguratorConfig` exposes `fields`, `values`, `filters`, `aggregators`,
`defaultAggregator`, `areas`, `mountTo`, `theme`, and `width`. `areas` defaults to
`['rows', 'values', 'filters']`; hiding a destination does not clear its settings.
`theme` can override the panel's automatic grid-theme synchronization.
Existing grid columns, hidden-column state, grouping, and filters are
authoritative. Listen to `gridconfiguratorchange` for snapshots containing
`columns`, `rows`, `values`, `filters`, and `activeFilters`.
Use the installed plugin's `getState()` to inspect accepted settings and
`updateConfig(config)` to replace configurator options. Other public methods
include `mount`, `setVisible`, `reorderColumn`, `addField`, `removeField`,
`reorderField`, `setAggregator`, and `openFilter`. Keep runtime aggregator
functions separate when persisting application settings.
Display settings, undo/redo, saved views, and runtime switching between ordinary
Grid and Pivot execution are outside this integration.
---
# Pivot Scenarios
URL: https://pro.rv-grid.com/guides/pivot/features/
Source: src/content/docs/guides/pivot/features.mdx
Description: Find focused examples for supported Pivot workflows and distinguish application-owned extensions from future work.
Use this directory to find the public configuration, a focused live example, and the contract behind each workflow. Examples use deterministic data; remote examples explicitly simulate a server.
For supported calculation behavior and runnable numeric/scalar configuration, see [Aggregation policies](/guides/pivot/concepts/aggregations/). Local operation support, standard server identities and a backend's implemented capabilities are different contracts. Execution pins prevent accidental switching; they do not guarantee equivalent results.
## Supported scenarios and application-owned extensions
| Workflow | Guide and previews |
| --- | --- |
| First report, axes, and facts | [Getting started](/guides/pivot/) · [Data modeling](/guides/pivot/introduction/data-modeling/) |
| Catalog, measures, summaries, and layout | [Configuration reference](/guides/pivot/concepts/configuration-reference/) · [Aggregations](/guides/pivot/concepts/aggregations/) |
| Fields, roles, filters, and authoring | [Configurator](/guides/pivot/configurator/configurator/) · [Controlled authoring API](/guides/pivot/configurator/configurator-api/) |
| Totals, expansion, and original records | [Totals](/guides/pivot/concepts/totals/) · [Drilldown](/guides/pivot/concepts/drilldown/) |
| Formatting and exports | [Formatting](/guides/pivot/concepts/formatting-presets/) · [Export and state](/guides/pivot/concepts/export-state/) |
| Application UI and execution without a grid | [Custom integrations](/guides/pivot/concepts/custom-integrations/) · [Headless controller](/guides/pivot/concepts/headless-controller/) |
| Portable reports and accepted state | [Report specifications](/guides/pivot/concepts/report-specifications/) · [Lifecycle](/guides/pivot/concepts/plugin-lifecycle/) |
| Remote windows, requests, caching, and diagnostics | [Server execution](/guides/pivot/server-side/server-side/) · [Remote API](/guides/pivot/server-side/remote-api-reference/) · [Remote tools](/guides/pivot/server-side/remote-tools/) |
| All native chart families | [Chart type gallery](/guides/pivot-charts/chart-types/) |
| Chart channels, queries, selection, and navigation | [Data and bindings](/guides/pivot-charts/data-and-bindings/) · [Interactions](/guides/pivot-charts/drilldown-and-interactions/) |
| Chart appearance, exports, persistence, and providers | [Customization](/guides/pivot-charts/customization-and-export/) · [Headless chart API](/guides/pivot-charts/headless-api-and-persistence/) · [Server charts](/guides/pivot-charts/server-data-and-performance/) |
## Application-owned work
A public adapter contract does not supply a deployed SQL, OLAP, GraphQL, or streaming service. Applications own their backend, authorization, semantic field registry, and persistence storage. Geographic charts need an optional capability provider and legitimate geometry; the documentation labels schematic provider output explicitly. Custom renderers and optional Canvas/PDF integrations have their own capability and installation boundaries.
## Future work
Streaming ingestion, incremental execution, offline synchronization, and specialized business workflows require separate product design and verification. They are not promised by these examples. The former exhaustive capabilities checklist mixed implemented APIs, foundations, and wishlist items; this directory links the current source-backed guides instead. New APIs and functionality remain follow-up work.
## Related product guides
[Pivot migration guidance and current product boundaries](/guides/migrations/#pivot-release-migration).
---
# Get started with Pivot
URL: https://pro.rv-grid.com/guides/pivot/getting-started/
Source: src/content/docs/guides/pivot/getting-started.mdx
Description: Install Pivot, render a native grid and execute a portable report with checked visible results.
Configure access using [Installation](/guides/installation/), then install compatible packages from your authorized registry. Keep one Core version and one Pro aggregate compiler owner in the lockfile. Select the published versions supported by your application and retain the verified lockfile.
```sh
pnpm add @revolist/revogrid @revolist/revogrid-pro @revolist/pivot
```
## Render a local grid
```ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { PivotPlugin, type PivotConfig } from '@revolist/pivot';
import '@revolist/revogrid-pro/dist/revogrid-pro.css';
import '@revolist/pivot/styles.css';
defineCustomElements();
await customElements.whenDefined('revo-grid');
const grid = document.createElement('revo-grid');
grid.style.height = '360px';
grid.source = [
{ region: 'North', sales: 10 },
{ region: 'North', sales: 5 },
{ region: 'South', sales: 20 },
];
grid.plugins = [PivotPlugin];
grid.pivot = {
rows: ['region'], columns: [],
values: [{ id: 'sales', prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true },
} satisfies PivotConfig;
document.body.append(grid);
```
The result has **North 15**, **South 20** and **Grand Total 35**. Pivot owns the rendered source/columns while active; keep your original facts in the application. Retrieve the installed plugin through `await grid.getPlugins()` for updates and exports. `updatePivotConfig(next)` replaces the configuration rather than applying a deep patch. See [lifecycle](/guides/pivot/concepts/plugin-lifecycle/) and [custom integrations](/guides/pivot/concepts/custom-integrations/).
For wrappers, pass source, plugins and Pivot configuration through the wrapper’s properties and retrieve the underlying grid after mount. Reuse the existing [framework examples](/guides/pivot/showcase/). Dispose standalone controllers/subscriptions when the component unmounts. DOM-free headless execution does not certify grid SSR or hydration.
## Execute without a grid
Download [the executable example](/examples/pivot/customer-example.mjs) into an application with Pivot installed and run:
```sh
node customer-example.mjs
```
It imports only `@revolist/pivot/headless`, prints real tables and asserts strict AVG, nonadditive totals, formulas, ranking, sorting, exports and refusal/recovery. No database or browser is needed. These results use explicit V2 numeric policies, rather than silently changing existing local reports.
```ts
import { createPivotController, type PivotReport } from '@revolist/pivot/headless';
const report: PivotReport = {
kind: 'revogrid:pivot.report', version: 1,
fields: [
{ id: 'region', sourceProp: 'region' },
{ id: 'sales', sourceProp: 'sales' },
],
rows: ['region'], columns: [], filters: [],
measures: [{ id: 'total', fieldId: 'sales', aggregator: 'sum' }],
};
const controller = createPivotController({
report, source: [{ region: 'North', sales: 10 }, { region: 'North', sales: 5 }],
});
try {
const outcome = await controller.refresh();
if (outcome.status === 'failed') throw new Error(outcome.error.message);
console.table(controller.getSnapshot().view?.model.source); // North: 15
} finally {
controller.dispose();
}
```
Creation validates metadata synchronously and starts idle (`autoLoad` defaults to false). Catch validation exceptions as well as failed operation outcomes. During loading/failure, display the retained committed view and the pending/error state separately. Do not save a failed pending report as if it committed.
## Next steps
- [Choose aggregation and hierarchy semantics](/guides/pivot/concepts/analytical-semantics/).
- [Add ranking and summary sorting](/guides/pivot/concepts/ranking-and-summary-sorting/).
- [Bound execution and handle recovery](/guides/pivot/concepts/execution-and-limits/).
- [Connect a secure backend](/guides/pivot/server-side/adapters-and-security/).
- [Save reports and restore bindings](/guides/pivot/concepts/report-persistence/).
- [Review package/compiler migration](/guides/pivot/compatibility/).
---
# Introduction
URL: https://pro.rv-grid.com/guides/pivot/introduction/
Source: src/content/docs/guides/pivot/introduction/index.mdx
Start with [What is Pivot?](./what-is-pivot/), then explore [data modeling](./data-modeling/) and [OLAP concepts](./olap-cube-concepts/).
---
# Data Modeling for Pivot
URL: https://pro.rv-grid.com/guides/pivot/introduction/data-modeling/
Source: src/content/docs/guides/pivot/introduction/data-modeling.mdx
Description: Learn how to structure your data for optimal performance and analytical flexibility in RevoGrid Pivot.
The quality of your Pivot analysis depends entirely on how your data is structured. To get the most out of RevoGrid Pivot, you should understand the difference between **Facts** and **Dimensions**.
## Facts vs. Dimensions
In the world of data modeling (specifically the **Star Schema**), data is divided into two categories:
### 1. Fact Tables (The "Events")
A Fact Table contains the quantitative data you want to measure. Each row represents a specific occurrence or transaction.
- **Characteristics**: Usually has many rows, contains numeric "measures," and "foreign keys" to dimension tables.
- **Example Fields**: `Price`, `Quantity`, `Duration`, `DiscountAmount`.
### 2. Dimension Tables (The "Context")
A Dimension Table contains descriptive attributes that provide context to your facts.
- **Characteristics**: Usually has fewer rows, contains categorical text or dates.
- **Example Fields**: `Product Name`, `Color`, `Region`, `Customer Segment`, `Date (Year/Month/Day)`.
## The "Perfect" Pivot Source
For RevoGrid's client-side Pivot, the "perfect" input is a **Denormalized Flat Table**. This means you have "joined" your facts and dimensions into a single list of objects before passing them to the grid.
### ❌ Bad Data Shape (Normalized)
The grid has to look up values in multiple places. Hard to pivot.
```json
// Transactions
[{ "id": 1, "product_id": 101, "amount": 50 }]
// Products
[{ "id": 101, "name": "Apple" }]
```
### ✅ Good Data Shape (Denormalized)
Everything the pivot needs is in one object.
```json
[
{
"id": 1,
"product": "Apple",
"category": "Fruit",
"region": "North",
"amount": 50,
"date": "2024-05-01"
}
]
```
## Value eligibility
Use raw finite numbers for numeric measures. Null/missing, empty text, zero and invalid values have distinct meanings: the recommended V2 numeric preset skips null/missing and errors on invalid input; the scalar preset preserves empty strings and Booleans. Define row counts with a constant-one field and value/distinct counts with explicit scalar or numeric policies. `dataType` alone does not select these semantics. See [Aggregation policies](/guides/pivot/concepts/aggregations/).
## Modeling Best Practices
### 1. Numeric vs. Categorical
- **Numbers** should be raw, unformatted values (e.g., `1200.50`, not `"$1,200.50"`). This allows aggregators like `sum` and `avg` to work correctly.
- **Categories** should be stable strings. Avoid using ID numbers (like `1`, `2`, `3`) as labels; use human-readable names (like `"Active"`, `"Pending"`, `"Closed"`) so the generated headers make sense.
### 2. Date Hierarchies
Dates are the most common pivot dimension. Instead of just a raw ISO string, it is often helpful to provide pre-calculated date parts if you are doing client-side pivoting:
```json
{
"orderDate": "2024-05-21",
"year": 2024,
"quarter": "Q2",
"month": "May"
}
```
In your `PivotConfig`, you can then set `rows: ['year', 'quarter', 'month']` to create an instant time-drilldown experience.
### 3. Stability of Fields
The `prop` in your [Dimensions](/guides/pivot/concepts/dimensions/) should be stable. If you change a field name in your data, you must update the `prop` in your configuration, or the Pivot will fail to find the data.
## Large Scale Modeling (Server-Side)
If your "Fact Table" has millions of rows, you shouldn't denormalize it into a single JSON array for the browser. Instead, you should keep it in an **OLAP-optimized database** (like ClickHouse, BigQuery, or Snowflake) and use the [Server-Side Pivot](/guides/pivot/server-side/) mode.
In this mode, RevoGrid sends the "Analytical Request" (the rows, columns, and filters) to your backend, which performs the SQL `JOIN` and `GROUP BY` efficiently and returns only the visible summary.
## Next Steps
Now that your data is modeled correctly, learn how to define your [Dimensions](/guides/pivot/concepts/dimensions/) to tell RevoGrid how to use these fields.
---
# What is an OLAP Cube?
URL: https://pro.rv-grid.com/guides/pivot/introduction/olap-cube-concepts/
Source: src/content/docs/guides/pivot/introduction/olap-cube-concepts.mdx
Description: Understand the multidimensional "Cube" metaphor and how Online Analytical Processing (OLAP) powers advanced data discovery.
When working with RevoGrid Pivot, you will often hear the term **OLAP** (Online Analytical Processing) or **OLAP Cube**. While the name sounds complex, the concept is fundamental to modern business intelligence.
## The "Cube" Metaphor
In a traditional spreadsheet, data is 2-dimensional (Rows and Columns). In an **OLAP Cube**, data is **Multidimensional**.
Think of a "Cube" where each side represents a different category (a **Dimension**):
1. **Product** (what we sold)
2. **Time** (when we sold it)
3. **Geography** (where we sold it)
Inside the cube, at the intersection of these three dimensions, lies the **Measure** (e.g., Sales Amount).
By "rotating" this cube, you can view the data from any angle. In RevoGrid Pivot, this "rotation" happens when you move fields between the Rows and Columns axes.
## Core OLAP Operations
OLAP cubes are designed to support four primary analytical operations, all of which are supported by RevoGrid:
### 1. Slicing
Slicing is like taking a single "slice" of the cube.
- **Concept**: Picking one value for a dimension.
- **Example**: "Show me sales only for the **Year 2024**."
- **In RevoGrid**: This is achieved through [Filtering](/guides/pivot/concepts/sorting-filtering/).
### 2. Dicing
Dicing is like cutting a smaller "sub-cube" out of the big one.
- **Concept**: Picking specific values across multiple dimensions.
- **Example**: "Show me sales for **Electronics** in the **North Region** during **Q1**."
- **In RevoGrid**: This is achieved by combining filters and row/column grouping.
### 3. Drill-Down / Drill-Up
Moving through the levels of a hierarchy.
- **Concept**: Going from summarized data to detailed data (Drill-Down) or back (Drill-Up).
- **Example**: Looking at sales by **Year**, then clicking to see **Quarters**, then **Months**.
- **In RevoGrid**: Enabled via the `collapsed` property in [Drill-Down](/guides/pivot/concepts/drilldown/).
### 4. Pivoting (Rotating)
Changing the orientation of the axes.
- **Concept**: Swapping Rows and Columns to see the data differently.
- **Example**: Moving "Region" from the Row axis to the Column axis to compare regions side-by-side.
- **In RevoGrid**: Performed via the [Configurator](/guides/pivot/configurator/) or by updating the `rows` and `columns` arrays in your code.
## Why "Cube" vs. "Flat Table"?
| Feature | Flat Table (SQL) | OLAP Cube |
| :--- | :--- | :--- |
| **Speed** | Slow for large aggregations (calculates on the fly) | Fast (aggregations are often pre-calculated) |
| **Complexity** | Requires complex `JOIN` and `GROUP BY` logic | Logical, business-friendly dimensions |
| **Scale** | Hard to scale to billions of rows for real-time UI | Designed for massive analytical scale |
## How RevoGrid Fits In
RevoGrid acts as the **Viewer** for your OLAP data.
- **Client-Side**: RevoGrid builds a "virtual cube" in memory from your flat JSON data.
- **Server-Side**: RevoGrid communicates with a real OLAP engine (like Snowflake, ClickHouse, or Cube.js) using the [Remote API](/guides/pivot/server-side/remote-api-reference/).
## Next Steps
Now that you understand the "Cube" metaphor, learn how to prepare your data for these operations in [Data Modeling for Pivot](/guides/pivot/introduction/data-modeling/).
---
# What Is Pivot?
URL: https://pro.rv-grid.com/guides/pivot/introduction/what-is-pivot/
Source: src/content/docs/guides/pivot/introduction/what-is-pivot.mdx
Description: A conceptual introduction to Pivot tables, the mental model, and how they transform flat data into analytical insights.
A **Pivot Table** is a data summarization tool that automatically sorts, counts, totals, or averages data stored in one table and displays the results in a second, organized table.
In RevoGrid, the Pivot plugin takes a "flat" list of records and transforms it into a multidimensional view where you can see relationships between different data points at a glance.
Measures can summarize numeric or scalar values. Define count meaning and null/invalid policies with the [recommended aggregation configuration](/guides/pivot/concepts/aggregations/); a numeric format or datatype label does not validate inputs.
## The Transformation: From Facts to Insights
Imagine you have a list of sales transactions. Each row represents a single event:
Date
Region
Category
Sales
2024-01-01 North Electronics $500
2024-01-02 South Electronics $300
2024-01-03 North Furniture $1,200
2024-01-04 North Electronics $450
This "flat" view is great for data entry, but hard to read for analysis. A **Pivot** transforms it by choosing axes:
1. **Row Axis**: Grouping data vertically (e.g., by **Region**).
2. **Column Axis**: Grouping data horizontally (e.g., by **Category**).
3. **Values**: The numbers you want to calculate (e.g., **Sum of Sales**).
### The Resulting Pivot View:
Region
Electronics
Furniture
Grand Total
North $950 $1,200 $2,150
South $300 $0 $300
Grand Total $1,250 $1,200 $2,450
## Core Axes of a Pivot
To work with Pivot, you need to think in terms of three primary axes:
### 1. Rows (The Hierarchy)
Rows define the vertical structure. If you add multiple fields to rows (e.g., `['Region', 'Manager']`), Pivot creates a nested hierarchy. Users can "drill down" from Region to see the individual Managers within that region.
### 2. Columns (The Breakdown)
Columns define the horizontal structure. Unlike normal grid columns, Pivot columns are often **generated** based on the data. If you put `Quarter` in columns, Pivot will automatically create a column for every unique quarter found in your dataset.
### 3. Values (The Measures)
Values are the "meat" of the table. They are the numeric fields that get crunched using **Aggregators** (like `sum`, `avg`, `min`, `max`, or `count`). Every cell in a Pivot table is the result of an aggregation of all raw records that match that specific row and column intersection.
## Why Use RevoGrid Pivot?
- **Massive Data**: RevoGrid's virtualization allows you to pivot millions of rows without crashing the browser.
- **Dynamic Discovery**: Users can drag and drop fields to explore data from different angles.
- **Advanced analysis**: Subtotals, Grand Totals, and Drill-down are built in and optimized.
- **Server-Side Scaling**: For datasets too large for the browser, RevoGrid can connect to an [OLAP Backend](/guides/pivot/server-side/connect-olap-backend/) to perform the heavy lifting on the server while maintaining a smooth UI.
## Next Steps
To understand how these concepts map to actual code, read about [Dimensions](/guides/pivot/concepts/dimensions/) or explore the [Mental Model](/guides/pivot/#mental-model) in the Overview.
---
# Server-Side
URL: https://pro.rv-grid.com/guides/pivot/server-side/
Source: src/content/docs/guides/pivot/server-side/index.mdx
Start with [server-side Pivot](./server-side/), then inspect the [request contract](./remote-api-reference/), [OLAP integration](./connect-olap-backend/), [caching](./caching/), and [remote tools](./remote-tools/).
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## Related product guides
[Current adapters, reference profiles and tenant security](/guides/pivot/server-side/adapters-and-security/).
---
# Remote adapters, capabilities and security
URL: https://pro.rv-grid.com/guides/pivot/server-side/adapters-and-security/
Source: src/content/docs/guides/pivot/server-side/adapters-and-security.mdx
Description: Integrate an authorized Pivot backend and understand SDK, SQLite reference and deployment responsibilities.
## Integration contract
Use a public `PivotEngineAdapter` or `PivotRemoteStore`; `HttpPivotRemoteStore` supplies HTTP transport. [Remote API reference](/guides/pivot/server-side/remote-api-reference/) documents typed request/response envelopes and constructor options. Bind the engine through runtime configuration, not serialized executable code. Set `aggregationSemantics: 'server'` to reject accidental local rebinding.
| Surface | SDK responsibility | Host/backend responsibility |
| --- | --- | --- |
| Load | Map/validate query and response, correlate revisions, publish accepted view | Execute complete authorized scope, return windows and truthful counts |
| Members | `filterValues(request, signal)` posts typed queries | WHERE/search before distinct paging; typed values and matching total count |
| Drillthrough | Typed prefix/ranking lineage and cancellation | Reauthorize fields and original facts; retain duplicates and stable fact order |
| Complete export | Validate complete snapshot/identity/output bounds | Atomic operation-time snapshot, original-fact totals, no truncation |
| Credentials | Runtime `authProvider`, optional fetch `credentials` | Session/token validation, CORS/CSRF, tenant and operation policy |
| Capabilities | Preflight unavailable operations | Truthful implementation and independent admission at every endpoint |
`HttpPivotRemoteStore` routes include `/api/pivot/filter-values` and `/api/pivot/export` under `baseUrl`; advertise them only after implementing the routes. `credentials: 'include'` explicitly enables required cross-origin cookies; omission keeps fetch’s default policy. Never persist tokens, cookies, headers or transport handles in report JSON. Follow the existing [remote store integration](/guides/pivot/server-side/server-side/) for a runnable transport mount.
## Advertise only implemented profiles
| Capability/profile | Practical boundary |
| --- | --- |
| `query` | Registered operation subsets, axis membership and depth; depth alone is not hierarchy support |
| `filterValues` | Backend member choices; loaded aggregate rows are not a complete domain |
| `calculations` | Corresponding complete-context backend calculations, not local math over pages |
| `ranking` | Declared contextual ranking/Other/drill contract; root first-row profile in reference |
| `sort` plus `summarySort` | Version 1 opposite-grand-total prefix ordering; selected tuple sort deferred |
| `fullExport` | Implemented complete atomic bounded export endpoint |
Standard remote summaries are `sum`, `avg`, `min`, `max`, `count`, `distinct`, subject to registry authorization. Local V2 operation descriptors, arbitrary callbacks, derived fields and independent leaf/group/total override semantics are not portable SQL promises. Reject unknown summary properties rather than stripping them and pretending equivalence.
## Executable SQLite reference
The repository’s `packages/pivot/server-reference/` is **not shipped** in the npm package. It demonstrates a caller-owned Node `DatabaseSync`, parameterized values, allowlisted simple identifiers, trusted field registry and tenant-scoped SQL. It is neither a hosted service nor production-driver certification.
| Profile | Opt-in and behavior |
| --- | --- |
| Flat baseline | Scalar authorized row/column selectors, at most one of each; SQL aggregates, WHERE/HAVING, members, drill and bounded flat complete export |
| Distinct acceptance | Separate duplicate/null/empty/scalar fixture; SQL collation/type semantics |
| Hierarchy | Trusted ordered `rowSelectors`/`columnSelectors`, up to eight levels per axis; prefix expansion, subtotals and grand totals; both measure orientations in complete export |
| Analytical | Trusted `analytical` work limits; contextual calculations/formulas, first-row root ranking/Other and complete-prefix summary sorting |
Baseline load windows are at most 40 rows/16 columns; member and fact pages are at most 200. These are reference limits, not universal SDK capacity. Analytical dense-coordinate and temporary-row counters are [separate work units](/guides/pivot/concepts/execution-and-limits/#sql-reference-work-limits). Date buckets, arbitrary SQL expressions and general custom aggregates require separate implementation/acceptance.
For repository developers, run `pnpm --filter @revolist/pivot test:backend` on a runtime with `node:sqlite`, and `test:backend:http` for loopback HTTP integration. The recorded SQL baseline used Node 26.7.0/SQLite 3.53.4. The HTTP suite is separate from browser rendering. Use [backend validation](/guides/pivot/server-side/backend-validation/) to qualify your own adapters; baseline acceptance does not automatically qualify optional analytical profiles.
## Authentication and tenant isolation
The authenticated reference handler calls host `authenticate` and operation/view `authorize` before consuming its deferred body stream. Its default body bound is 1 MiB; trusted configuration may set `maxBodyBytes`. Derive tenant, database/table/view, allowed registry fields, exact axes and policy ceilings from trusted host state. Client report metadata cannot choose them. Parameterize values and reject unauthorized selectors before SQL/cache access.
Reference errors retain 401/403/409/400 for known failures and redact unexpected failures to 500. Authentication is supplied by the application; synthetic reference credentials are not a deployable identity system.
An optional bounded load cache requires trusted dataset watermark, authorization scope and policy revision; absent/empty identity inputs bypass caching. Keys separate tenant, view/version, subject and policy/data identity. Every hit still reauthenticates, reauthorizes and validates the current registry. Do not cache denials, drill facts, members or exports using this load-cache contract. Configure entry/byte/TTL limits independently of SQL work budgets.
## Deployment acceptance
Choose a trusted unique nonnull immutable fact key and explicit ordering. SQL peer ties follow authorized fact-key order; local peers follow source occurrence order. Qualify the real driver’s transaction/snapshot behavior, deadlines, cancellation, query plans, temp storage, concurrency, authorization revocation and data consistency. A per-operation snapshot does not pin data across separate load/export requests.
Synchronous SQLite checks cancellation between statements and cannot preempt an active statement. SDK cancellation can prevent publication while backend work continues. Secure deployment, identity, backups, rate limiting and representative workload/security acceptance remain host work. See [compatibility and current evidence](/guides/pivot/compatibility/).
---
# Validate a SQL Backend
URL: https://pro.rv-grid.com/guides/pivot/server-side/backend-validation/
Source: src/content/docs/guides/pivot/server-side/backend-validation.mdx
Description: Run an independent SQL-backed acceptance baseline and define the remaining production validation scope.
The repository includes a Node-only SQLite reference and a reusable backend acceptance kit under `packages/pivot/server-reference/`. These development tools execute actual SQL against independent numerical fixtures. They are not part of the published browser package or a hosted production backend.
From the repository root, run:
```sh
pnpm --filter @revolist/pivot test:backend
```
Use a Node runtime with `node:sqlite`; the baseline was verified with Node 26.7.0. The command typechecks the reference and acceptance kit and runs the dedicated Node suite with one worker. It needs no external database service.
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## Backend contract
The reference implements `PivotEngineAdapter` and reuses the field registry, request normalization, and logical SQL planner. Its application-owned configuration fixes the authorized view, field-registry version, tenant, row/column fields, and fact ordering key. Authenticate before creating that configuration and reduce the registry to authorized fields. Request JSON must never choose database tables, tenant scope, or raw SQL expressions.
The supported baseline includes flat row/column axes; SQL sum, average, count, minimum and maximum; source filters; row aggregate filters; independent axis windows; typed member search/pages; duplicate measures; authorized drillthrough pages; and complete bounded flat export from one atomic export-time transaction. The export suite includes independent CSV/XLSX fixtures and more groups/columns than normal load windows. Unknown selectors, stale registry versions, forbidden views, oversized requests, and unsupported operations fail explicitly.
Source filters select facts before aggregation. Aggregate filters select row groups and independently test the global summary. Global totals use the complete source-filtered dataset, even when row windows or aggregate filters hide individual groups. They are not sums of visible rows. Column members use the source-filtered scope.
SQL null semantics are explicit: `COUNT(column)` excludes null but counts empty text, and averages use non-null observations. All-null sums, averages, minima and maxima return null. These semantics differ from default local aggregate coercion; a passing SQL baseline does not establish local/remote parity.
## Validate your adapter
`acceptance-kit.ts` exports `runBackendAcceptance` and `REQUIRED_BASELINE_PROFILE`. Populate the independent fixture from `fixtures.ts`, provide application adapters authenticated as each fixture tenant, and run the same assertions against your HTTP or database implementation. See `packages/pivot/server-reference/README.md` for the factory contract and capability profile.
The kit fails when a required capability is missing. Advertising a capability with no corresponding acceptance assertions also fails. A passing result establishes only the required baseline profile and any additional capabilities whose dedicated assertions ran (currently `fullExport` and optional `distinct`).
## Distinct counts and nonadditive totals
For unique customers or other scalar counts, allow `distinct` for the authorized
field and implement `COUNT(DISTINCT column)`. Null is excluded and empty text is
included; use an explicit backend collation policy. Recompute each aggregation
grain from facts rather than adding lower-grain distinct counts.
The optional `distinct` capability has its own `DISTINCT_FACTS` dataset and
`distinctCases`/`distinctExportCases`. Its oracle includes duplicate facts,
customers spanning groups, null-only and sparse coordinates, empty text,
source/HAVING filters, tenant isolation, duplicate-preserving drillthrough and
complete exports. Supply `distinctFixture` adapters when advertising this
profile. Existing baseline targets that omit it keep their original obligations.
The SQLite reference also advertises a requested grand-total column in its
logical column window. That member counts toward the declared axis count and
window limit. The grid will not invent a total column omitted from an
explicitly authoritative server window.
## Complete the production checks
The flat baseline rejects hierarchy expansion/collapse, subtotals and calculations. Separate trusted hierarchy and analytical opt-ins support their declared prefix, subtotal, calculation, ranking and grand-opposite summary-sort profiles. Date/bucket grouping and independent local summaries remain excluded. See [current reference profiles](/guides/pivot/server-side/adapters-and-security/#executable-sqlite-reference). Strict local aggregate parity still requires its own acceptance profile. Complete flat export is covered by the dedicated `fullExportCases`; see [complete server export](/guides/pivot/server-side/complete-export/). Add implementation and independent fixtures before advertising those capabilities.
Run the kit against your actual server, then validate that deployment's authentication, concurrency, timeouts, database transactions and cancellation. The synchronous SQLite reference checks `AbortSignal` between statements; it cannot interrupt a running statement. This baseline does not certify browser rendering, customer infrastructure or production load.
Each SQLite load, complete export, member lookup and drillthrough uses one
operation-owned read snapshot. Real two-connection WAL tests commit new facts
between statements and verify counts, member paths, facts and totals remain
consistent within the response. Operation-owned savepoints preserve an existing
caller transaction on success, failure and observed cancellation. Separate
requests do not share a snapshot; a later export can reflect newer facts than
an earlier viewport.
---
# Caching And Cache Keys
URL: https://pro.rv-grid.com/guides/pivot/server-side/caching/
Source: src/content/docs/guides/pivot/server-side/caching.mdx
Description: Understand how RevoGrid Pivot generates deterministic cache keys for safe and efficient remote analytical execution.
In server-side Pivot, caching is critical for performance and cost management. Analytical queries can be expensive and slow; deduplicating identical requests ensures that the backend only performs the work once.
RevoGrid Pivot uses **deterministic cache keys** to identify identical analytical windows.
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## What Is A Cache Key?
A cache key is a unique string that represents the exact state of an analytical request. If any part of the request changes (filters, grouping, viewport, or data version), a new key is generated.
`HttpPivotRemoteStore` shares identical **in-flight, signal-free reads**. Once a
request settles, its entry is removed; a later call fetches again. Calls with an
`AbortSignal` use independent transports so cancelling one caller cannot cancel
another caller's request. The store does not cache completed results.
Your backend or application cache can use these keys to memoize completed
results. It owns expiration and invalidation when data, permissions, or field
semantics change. The `cacheStatus` returned by the backend reports that cache's
outcome; it does not prove a browser cache hit.
## Key Components
A stable Pivot cache key is composed of several semantic layers:
| Component | Description | Why It Matters |
| :--- | :--- | :--- |
| `tenantId` | Separates keys for the client or organization scope. | Backend authentication and authorization must independently enforce tenant access. |
| `viewId` | Identifies the specific analytical view or dashboard. | Keeps caches separate for different reports using the same fields. |
| `fieldsVersion` | A checksum or version of the field registry. | If you change a field's calculation or data type, old summaries must be invalidated. |
| `datasetWatermark` | A caller-supplied timestamp or batch ID representing data freshness. | Changing the value separates keys; your cache owns expiration and invalidation. |
| `request` | A stable stringification of filters, rows, columns, and windows. | Ensures that changing a single filter or scrolling the viewport yields a specific key. |
The HTTP store's `tenantId` and `datasetWatermark` options affect its client key.
They are not automatically added to request bodies or headers. Use the existing
`authProvider` or an application-owned transport for authenticated routing, and
resolve tenant access and data freshness on the server. Recreate the store when
its constructor-level key scope changes.
## How Keys Are Generated
RevoGrid provides a standard `createPivotCacheKey` utility that builds these keys. It uses a **stable stringification** algorithm, meaning that the order of keys in a JSON object does not change the resulting string.
```ts
// Internal logic simplified
const cacheKey = `pivot:${tenantId}:${viewId}:${fieldsVersion}:${datasetWatermark}:${stableStringify(request)}`;
```
### Stable Stringification Example
Object property order does not change the key. Array order remains significant,
including boolean filter trees; this helper does not normalize semantically
equivalent predicates. These objects illustrate stable serialization, rather
than the tuple-based `PivotFilterExpression` transport grammar:
```ts
// Request A
{ filter: { region: 'US', category: 'Tech' } }
// Request B
{ filter: { category: 'Tech', region: 'US' } }
// Both result in the same stable key:
// "...:{"category":"Tech","region":"US"}"
```
## Observing Cache Status
When using `HttpPivotRemoteStore` or the recommended backend response format, the grid receives cache metadata in the `meta` object:
```ts
{
"data": [...],
"meta": {
"cacheKey": "pivot:tenant-1:sales:v1:20240424:...",
"cacheStatus": "hit", // or 'miss', 'warm', 'bypass'
"elapsedMs": 12
}
}
```
This is useful for UI diagnostics and understanding whether your analytical engine is performing efficiently.
## Best Practices
- **Keep `viewId` Stable**: Do not use random IDs for views if you want users to benefit from shared caching across sessions.
- **Update `datasetWatermark`**: Your backend should provide a watermark (like a `max(updated_at)` or a batch ID) to the client so the cache key updates automatically when data changes.
- **Normalize Requests**: If you are implementing a custom `PivotRemoteStore`, ensure you normalize filters and descriptors before generating keys to maximize cache hits.
## Next
- [Server-Side Pivot](/guides/pivot/server-side/)
- [Remote API Reference](/guides/pivot/server-side/remote-api-reference/)
- [OLAP Best Practices](/guides/pivot/server-side/olap-best-practices/)
---
# Export a Complete Server Report
URL: https://pro.rv-grid.com/guides/pivot/server-side/complete-export/
Source: src/content/docs/guides/pivot/server-side/complete-export.mdx
Description: Export the complete filtered Pivot report with an authorized, bounded atomic backend snapshot.
`exportCsv()`, `exportXlsx()` and `getVisibleExportData()` export the currently loaded logical report. A server viewport can contain only a small window. Use `exportFullReport()` when the user needs every filtered analytical group and column.
```ts
const controller = new AbortController();
const result = await pivotPlugin.exportFullReport('xlsx', {
signal: controller.signal,
limits: { maxRows: 5000, maxCells: 100000 },
onProgress: progress => console.log(progress.phase, progress.receivedBytes),
});
// result.content is Uint8Array for XLSX, string for CSV.
// Let your application save it using its normal download flow.
```
The stock Pivot context menu exposes **Complete Filtered CSV** and **Complete Filtered XLSX** for server reports. These commands remain disabled unless the engine explicitly advertises `fullExport: true`. A shared accessible dialog displays busy/received-byte status, supports cancel and dismissal, and reports failures. Retry by reopening Export. A committed report change, clear or destroy cancels the request and prevents a stale download.
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## Backend contract
Implement optional `PivotEngineAdapter.exportReport(request, context)` or `PivotRemoteStore.exportReport(request, context)` and advertise `capabilities.fullExport`. `HttpPivotRemoteStore` posts JSON to `/api/pivot/export`; configure its `capabilities`, `authProvider`, `credentials` and hooks as usual. Injected adapters use the same envelope/response validation contract.
`PivotFullExportRequest.query` includes the committed view, registry version, source filters, aggregate filters, summaries, sorting, UI hints, semantic collapse paths, value-axis placement, totals configuration and field/measure labels. It deliberately excludes axis offset/limit windows. The canonical `queryKey` must be echoed exactly. A correlation ID and query key identify the export; neither grants permission.
The backend must authenticate each request, authorize view and fields, bind tenant scope from the authenticated session, enforce its own limits, and reject unsupported layouts/calculations explicitly. Do not trust client capability flags or tenant/user labels. Do not build a complete export by walking changing viewport pages.
Return `PivotFullExportResponse` with:
- Matching `requestId` and `queryKey`.
- `scope: 'complete-filtered-report'` and `complete: true`.
- A nonempty snapshot ID and `snapshot.consistency: 'atomic-at-export'`.
- Primitive `columns`, `rows` and `totals`; column headers are string paths and optional `summaryId` identifies the configured measure. `rowCount` includes total rows and `columnCount` includes label and expanded measure columns.
An atomic export-time database transaction/snapshot must cover domains, counts, cells and totals. This guarantee captures data **at export time**. It does not promise the same dataset version as an earlier on-screen viewport. Applications requiring that stronger guarantee must pin and authorize a backend dataset version in their semantic view contract.
The library rejects incomplete, stale or mismatched metadata, invalid cells, unknown measure identities and oversized tables. It reuses the existing CSV/XLSX writers, preserves raw numbers and safe native Excel measure formats, and never follows a server-provided file URL. Arbitrary/custom appearance and conditional-format rules are not exported as workbook styling.
## Limits and cancellation
All requests include four positive hard limits, each bounded by library ceilings: 10,000 rows including totals, 256 flattened columns including labels, 500,000 cells and 16,000,000 bytes. Applications may lower these limits. Backends must reject an oversized complete result; they must never silently truncate it. Both the response JSON and formatted CSV/XLSX output must fit the byte limit.
The HTTP store checks declared content length and counts stream bytes before JSON decoding. Each export owns its transport lifetime, authentication, telemetry and cancellation. Public export promises settle on cancellation even when an injected adapter ignores the signal; such adapters still need actual backend query cancellation/timeouts to release server resources. Local formatting is bounded synchronous work.
## Real SQL reference
`packages/pivot/server-reference` includes a SQLite implementation using one read transaction and independent golden acceptance assertions. It covers complete flat reports beyond normal load windows, source `WHERE`, row `HAVING`, independent full-source grand totals, duplicate/no-ID measures, authorized tenants/fields, limits and pre-cancellation. Run:
```sh
pnpm --filter @revolist/pivot test:backend
```
The flat baseline supports at most one row and one column field. Trusted hierarchy and analytical opt-ins additionally support their declared prefix/totals, both measure orientations and analytical exports. Independent local summary policies and date grouping remain excluded. See [current reference profiles](/guides/pivot/server-side/adapters-and-security/#executable-sqlite-reference). SQL COUNT/null semantics remain as documented in backend validation. Synchronous SQLite statements cannot be interrupted while executing. This repository reference and kit do not certify a customer backend, production authentication, concurrency or warehouse performance.
## Calculation consistency
The export backend must use the same authorized fact scope and declared aggregate
policies as its report contract. Recompute averages, distinct and weighted totals
at each grain; do not add displayed averages or distinct counts. Percentage values
remain ratios, and a writer/number format does not add numerical precision.
Ordinary-load and full-export summary admission reject unknown properties such as
`operation`. The server adapter checks before forwarding to an application store,
and the HTTP store checks before transport. The backend independently rejects
unsupported policies and calculations. An execution
pin and canonical query identity do not certify matching local/server arithmetic
or permit unsupported local V2 operations. The flat SQLite baseline rejects post-aggregate calculations; its analytical opt-in supports the declared complete-context calculations and retains SQL null/count policies.
---
# Connect An OLAP Backend
URL: https://pro.rv-grid.com/guides/pivot/server-side/connect-olap-backend/
Source: src/content/docs/guides/pivot/server-side/connect-olap-backend.mdx
Description: Wire RevoGrid Pivot to an OLAP-capable backend through HttpPivotRemoteStore and an application API.
This guide shows the practical wiring pattern for connecting RevoGrid Pivot to an OLAP-capable backend. The important rule is simple: the browser talks to your application API, and your application API talks to the cube, warehouse, or semantic layer.
For the conceptual background, read [Server-Side Pivot](/guides/pivot/server-side/) first. For the transport contract, see [Remote API Reference](/guides/pivot/server-side/remote-api-reference/).
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## Live Simulated Server Example
The focused cases above exercise the public load and fact-drill contracts over deterministic in-memory facts. Inspect the request and response details to see the simulated server boundary. The HTTP route and lifecycle examples appear in [Remote API Reference](../remote-api-reference/) and [Remote Tools](../remote-tools/).
Open the full demo and framework source.
## Architecture
The recommended production boundary looks like this:
```txt
RevoGrid + PivotPlugin
-> HttpPivotRemoteStore
-> your /api/pivot/* endpoints
-> field registry + validation + planner/adapter
-> OLAP engine / warehouse / cube API
```
Do not connect the browser directly to the OLAP database. Your backend layer must own:
- authentication
- tenant isolation
- selector whitelisting
- request normalization
- query translation
- caching and observability
## Minimum Pieces You Need
1. A client `PivotConfig` with `engine.mode = 'server'`
2. An `HttpPivotRemoteStore`
3. Backend `POST /api/pivot/load`
4. Backend `POST /api/pivot/drilldown`
5. A field registry for safe public field ids
6. A planner or adapter that translates Pivot requests into your backend’s OLAP query shape
## Client Setup
The client stays small. It only needs to describe the Pivot layout and point the plugin at your application API.
```ts
import {
HttpPivotRemoteStore,
PivotPlugin,
type PivotConfig,
} from '@revolist/pivot';
import { PaginationPlugin } from '@revolist/revogrid-pro';
const remoteStore = new HttpPivotRemoteStore({
baseUrl: '', // Same-origin /api/pivot/* routes are already included by the store.
tenantId: 'tenant-a',
datasetWatermark: 'sales-cube-v42',
authProvider: async () => ({
Authorization: `Bearer ${await getAccessToken()}`,
}),
});
const pivot: PivotConfig = {
aggregationSemantics: 'server',
dimensions: [
{ prop: 'Region', name: 'Region' },
{ prop: 'Category', name: 'Category' },
{ prop: 'Date', name: 'Date' },
{
prop: 'SalesAmount',
name: 'Sales',
},
],
rows: ['Region', 'Category'],
columns: ['Date'],
values: [{ prop: 'SalesAmount', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
engine: {
mode: 'server',
remoteStore,
viewId: 'sales-by-region',
fieldsVersion: 'sha256:fields-v1',
rowAxis: { offset: 0 }, // limit driven by pagination.itemsPerPage
columnAxis: { offset: 0, limit: 24 },
},
};
// Add PaginationPlugin to enable server-side row paging.
// PivotPlugin will read itemsPerPage as the rowAxis limit and keep
// pagination.total in sync with response.rowAxis.totalCount.
grid.plugins = [PivotPlugin, PaginationPlugin];
grid.pivot = pivot;
grid.additionalData = {
pagination: {
itemsPerPage: 50,
initialPage: 0,
total: 0,
},
};
```
## What The Client Sends
At runtime, `PivotPlugin` converts the visible Pivot state into a `PivotLoadRequest` and sends it through `HttpPivotRemoteStore`.
That request includes:
- `rows`, `columns`, and `values`
- filters and sort directives
- `rowAxis` and `columnAxis` windows
- expansion state represented as analytical paths
The client does not build SQL and it does not know how your cube works internally.
## Backend Load Endpoint
This example uses a lightweight Node/Express style handler for readability. The contract itself is framework-agnostic.
```ts
import type { Request, Response } from 'express';
import {
createFieldRegistry,
normalizePivotLoadRequest,
type PivotLoadRequest,
type PivotLoadResponse,
} from '@revolist/pivot';
const registry = createFieldRegistry([
{
id: 'Region',
label: 'Region',
dataType: 'string',
expression: { kind: 'column', value: 'sales.region' },
allowedOperations: ['=', '<>', 'in'],
},
{
id: 'Category',
label: 'Category',
dataType: 'string',
expression: { kind: 'column', value: 'sales.category' },
allowedOperations: ['=', '<>', 'in'],
},
{
id: 'Date',
label: 'Date',
dataType: 'date',
expression: { kind: 'column', value: 'sales.order_date' },
allowedOperations: ['=', '>=', '<='],
allowedGroupIntervals: ['year', 'quarter', 'month', 'day'],
},
{
id: 'SalesAmount',
label: 'Sales Amount',
dataType: 'number',
expression: { kind: 'column', value: 'sales.amount' },
allowedOperations: ['=', '>', '>=', '<', '<='],
allowedSummaries: ['sum', 'avg', 'min', 'max', 'count'],
drilldownVisible: true,
},
]);
export async function postPivotLoad(req: Request, res: Response) {
const tenantId = await requireTenant(req);
const request = req.body as PivotLoadRequest;
const normalized = normalizePivotLoadRequest(request, registry);
const cubeResult = await runCubeLoad({
tenantId,
viewId: normalized.viewId,
fieldsVersion: normalized.fieldsVersion,
request: normalized,
});
const response: PivotLoadResponse = {
requestId: request.requestId,
version: 1,
data: cubeResult.data,
summary: cubeResult.summary,
rowAxis: cubeResult.rowAxis,
columnAxis: cubeResult.columnAxis,
meta: cubeResult.meta,
};
res.json(response);
}
```
## Translating The Pivot Request Into An OLAP Query
Your adapter layer is where Pivot concepts become backend-native OLAP concepts.
Recommended mapping:
- Pivot `rows` -> cube row dimensions
- Pivot `columns` -> cube column dimensions
- Pivot `totalSummary` and `groupSummary` -> cube measures
- Pivot `filter` -> cube filter tree
- `expandedPaths` / `collapsedPaths` -> hierarchy frontier exceptions
- `uiState.columnGroupsCollapsedByDefault` -> column hierarchy default state
- `rowAxis` / `columnAxis` -> returned analytical windows
Example adapter:
```ts
type RunCubeLoadArgs = {
tenantId: string;
viewId: string;
fieldsVersion: string;
request: ReturnType;
};
async function runCubeLoad(args: RunCubeLoadArgs) {
const { request } = args;
const cubeQuery = {
dimensions: {
rows: (request.loadOptions.rows ?? []).map((item) => ({
id: item.selector,
interval: item.groupInterval,
desc: item.desc,
})),
columns: (request.loadOptions.columns ?? []).map((item) => ({
id: item.selector,
interval: item.groupInterval,
desc: item.desc,
})),
},
measures: (request.loadOptions.totalSummary ?? []).map((summary) => ({
id: summary.selector,
aggregate: summary.summaryType,
})),
filters: request.loadOptions.filter,
sort: request.loadOptions.sort ?? [],
window: request.viewport,
uiState: request.uiState,
};
const result = await cubeClient.loadPivotWindow(cubeQuery);
return {
data: result.cells,
summary: result.summaryRows,
rowAxis: {
totalCount: result.rowAxis.totalCount,
returned: result.rowAxis.returned,
offset: request.viewport.rowAxis.offset,
limit: request.viewport.rowAxis.limit,
expandedPaths: request.viewport.rowAxis.expandedPaths,
paths: result.rowAxis.paths,
nodes: result.rowAxis.nodes,
},
columnAxis: {
totalCount: result.columnAxis.totalCount,
returned: result.columnAxis.returned,
offset: request.viewport.columnAxis.offset,
limit: request.viewport.columnAxis.limit,
expandedPaths: request.viewport.columnAxis.expandedPaths,
collapsedPaths: request.viewport.columnAxis.collapsedPaths,
paths: result.columnAxis.paths,
nodes: result.columnAxis.nodes,
},
meta: {
cacheKey: result.cacheKey,
generatedAt: result.generatedAt,
elapsedMs: result.elapsedMs,
cacheStatus: result.cacheStatus,
warnings: result.warnings ?? [],
},
};
}
```
## Drilldown Endpoint
Drilldown should return the underlying facts for a visible summary cell, not a second aggregated result.
```ts
import type { Request, Response } from 'express';
import {
createFieldRegistry,
normalizePivotDrilldownRequest,
type PivotDrilldownRequest,
} from '@revolist/pivot';
export async function postPivotDrilldown(req: Request, res: Response) {
await requireTenant(req);
const request = req.body as PivotDrilldownRequest;
const normalized = normalizePivotDrilldownRequest(request, registry);
const facts = await cubeClient.drilldown({
rowPath: normalized.cell.rowPath,
columnPath: normalized.cell.columnPath,
dataIndex: normalized.cell.dataIndex,
fields: normalized.customColumns ?? ['Region', 'Category', 'Date', 'SalesAmount'],
offset: normalized.offset,
limit: normalized.limit,
});
res.json({
requestId: request.requestId,
data: facts.rows,
totalCount: facts.totalCount,
meta: {
cacheStatus: facts.cacheStatus ?? 'bypass',
},
});
}
```
## Request Lifecycle
1. The grid shows a Pivot layout with row and column windows.
2. `PivotPlugin` builds a `PivotLoadRequest`.
3. `HttpPivotRemoteStore` sends the request to your application API.
4. Your backend authenticates, validates selectors, and normalizes the request.
5. Your planner or cube adapter translates the normalized request into backend-native OLAP calls.
6. The cube or warehouse returns only the visible analytical block.
7. Your backend maps that result into `PivotLoadResponse`.
8. RevoGrid materializes the returned window into visible columns and rows.
## What Changes For A True OLAP Cube
If your backend already exposes dimensions, measures, and drilldown concepts, the frontend setup stays the same. Only the backend adapter changes.
Typical differences:
- date grouping may map directly to cube hierarchy levels
- drilldown may map to cube “drill through” APIs
- row and column windows may map to axis slicing instead of SQL paging
- `avg` and derived measures may already be modeled by the cube
That is why the recommended contract stays generic HTTP. The transport is stable even when the backend engine changes.
## Do Not Do This
- Do not connect the browser directly to the OLAP database.
- Do not trust selectors from the client.
- Do not return the full cube by default.
- Do not compute totals from rendered UI values.
## Related Guides
- [Server-Side Pivot](/guides/pivot/server-side/)
- [Remote API Reference](/guides/pivot/server-side/remote-api-reference/)
- [OLAP Best Practices](/guides/pivot/server-side/olap-best-practices/)
---
# OLAP Best Practices
URL: https://pro.rv-grid.com/guides/pivot/server-side/olap-best-practices/
Source: src/content/docs/guides/pivot/server-side/olap-best-practices.mdx
Description: Design Pivot-friendly analytical models with stable dimensions, secure field registries, and viewport-aware execution.
RevoGrid Pivot is not itself an OLAP database, but the server-side foundation is designed to work well with OLAP-style engines, semantic layers, and warehouse-backed pivot services. This page explains the practices that keep that integration correct and maintainable.
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## Start With Facts And Dimensions
The best Pivot source model is:
- a fact table with additive events or transactions
- dimensions that describe those facts
Examples:
- facts: orders, sales lines, support incidents, page views
- dimensions: region, product, customer segment, time
This model keeps grouping and drilldown semantics predictable.
## Treat The Field Registry As A Semantic Layer
Your client should use public field ids. Your backend should map them through a field registry.
Good registry properties:
- stable field ids
- business-friendly labels
- safe backend expressions
- explicit allowed operations
- explicit allowed summaries
- explicit allowed grouping intervals
This prevents raw selector leakage and becomes the trust boundary for every analytical request.
## Design Hierarchies Intentionally
Rows and columns should represent real analytical hierarchies, not arbitrary field lists.
Good examples:
- `['Region', 'Rep']`
- `['Year', 'Quarter', 'Month']`
- `['Category', 'Subcategory']`
Poor examples:
- mixing unrelated dimensions only because the UI allows it
- saving unstable intermediate fields that are likely to change name or meaning
## Prefer Additive Measures
`sum` and `count` are easiest to reason about across subtotals and grand totals.
Be careful with:
- `avg`
- ratios
- percentages
- any metric derived from already-aggregated cells
For those, totals should come from base facts or planner-managed derived summaries, not by averaging visible children.
## Separate UI State From Analytical State
Keep these concerns separate:
- analytical definition: fields, filters, summaries, sort
- UI state: expanded paths, collapsed columns, viewport windows
This separation makes saved state more stable and keeps backends from mixing presentation-only state with semantic query meaning.
## Return Windows, Not The Whole Cube
For large datasets, the backend should return:
- only the visible row-axis window
- only the visible column-axis window
- only the summaries needed for the visible analytical block
Returning the full cube defeats the whole purpose of server-side Pivot.
## Drilldown Should Mean Facts
When users drill into a summary cell, the backend should return the underlying facts scoped by:
- the current filters
- the row path
- the column path
- the selected measure if needed
That keeps drilldown trustworthy and consistent with visible summaries.
## Keep Cache Keys Semantic
Stable, semantic cache keys are the difference between a useful analytical cache and an unsafe one. Because Pivot requests are complex, your backend must identify identical windows deterministically.
Good Pivot cache keys should include:
- tenant and view identifiers
- field registry versions
- dataset watermarks (freshness)
- normalized filters, groupings, and summaries
- viewport windows
See [Caching and Cache Keys](/guides/pivot/server-side/caching/) for the full breakdown of how these keys are constructed and why they matter for safety and performance.
## Recommended Implementation Pattern
The most reliable production wiring is:
```txt
browser Pivot UI
-> HttpPivotRemoteStore
-> application API
-> field registry + normalization + planner/adapter
-> OLAP cube / warehouse
```
This keeps the public Pivot model stable while allowing your backend to translate the same request into SQL, a semantic-layer API, or a cube-native query format.
Use [Connect An OLAP Backend](/guides/pivot/server-side/connect-olap-backend/) for a practical client + backend example.
## What To Avoid
- exposing backend column names to the client
- concatenating raw selectors into SQL
- computing totals from already-rendered UI cells
- using row indexes as expansion identifiers
- coupling the grid to a specific backend framework
## Current Product Position
Today, RevoGrid Pivot already provides:
- a shared client/server config model
- a field registry and normalization layer
- a planner abstraction
- a transport-agnostic remote store
- visible-window materialization into RevoGrid
What it does not claim to be:
- a full OLAP engine
- a warehouse execution service
- a finished large-scale remote virtualization system on its own
That distinction is important. RevoGrid should remain the UI and viewport layer, while the analytical engine remains replaceable.
## Related Guides
- [Server-Side Pivot](/guides/pivot/server-side/)
- [Connect An OLAP Backend](/guides/pivot/server-side/connect-olap-backend/)
- [Remote API Reference](/guides/pivot/server-side/remote-api-reference/)
- [Configuration Reference](/guides/pivot/concepts/configuration-reference/)
---
# Remote API Reference
URL: https://pro.rv-grid.com/guides/pivot/server-side/remote-api-reference/
Source: src/content/docs/guides/pivot/server-side/remote-api-reference.mdx
Description: Technical specification for the RevoGrid Pivot remote analytical contract.
The Remote Pivot API is a transport-agnostic JSON contract that allows RevoGrid to communicate with analytical backends, OLAP cubes, or custom SQL planners.
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## Load Request
The `PivotLoadRequest` is sent whenever the grid needs to materialize a visible window of analytical data.
### Root Properties
- `viewId`: Semantic identifier for the dataset (e.g., `sales_cube`).
- `fieldsVersion`: Version of the field registry used to resolve selectors.
- `loadOptions`: The analytical query definition.
- `viewport`: Request windows for row and column axes.
- `uiState`: Optional hints for engine-side formatting.
### Load Options
The `loadOptions` object defines the shape of the analytical query:
- `rows`: Array of group descriptors for the row axis.
- `columns`: Array of group descriptors for the column axis.
- `totalSummary`: Measures to aggregate.
- `filter`: Source predicate expression tree, evaluated before aggregation.
- `having`: Aggregate predicate tree, evaluated after aggregation.
- `sort`: Sorting directives (can include sort-by-summary).
- `groupSummary`: Measures to calculate at every group level.
### Remote summary identities
The standard remote contract accepts `sum`, `min`, `max`, `avg`, `count`, and
`distinct`. A server-side field registry must explicitly authorize each requested
operation through `allowedSummaries`; a valid operation name is not field access
authority. `SqlPivotQueryPlanner` validates total, group, and HAVING summaries
against that registry.
`distinct` means the number of distinct non-null scalar values in the complete
requested source scope. Empty text is a value. SQL backends should use
`COUNT(DISTINCT registered_column)` with their documented collation policy.
Grand totals must be recomputed over facts: a customer appearing in two groups
counts once in the grand total, not twice. Register the operation only after the
backend implements these semantics; older backends may reject it explicitly.
This does not enable arbitrary client aggregate aliases, versioned local
aggregate operations, independent summaries, or derived field compilation on
remote engines. See [backend validation](/guides/pivot/server-side/backend-validation/)
for the optional distinct-count conformance fixture.
### Granular query capabilities
An adapter, remote store, or explicit `engine.capabilities` may supply a versioned
`query` profile. The host obtains this runtime metadata from its trusted backend;
portable V1/V2 reports do not carry this runtime authority. The version identifies
the capability shape; null, precision and collation policies remain separate.
```ts
import type { PivotEngineCapabilities } from '@revolist/pivot';
const capabilities = {
aggregation: true,
query: {
version: 1,
summaryTypes: ['sum', 'count', 'distinct'],
summaryTypesByField: {
revenue: ['sum', 'count'],
customer: ['count', 'distinct'],
privateMeasure: [],
},
maxRowDepth: 3,
maxColumnDepth: 2,
allowedRowFields: ['region', 'country', 'city'],
allowedColumnFields: ['year', 'quarter'],
},
} satisfies PivotEngineCapabilities;
```
Global and per-field lists intersect;
an empty list allows no operation, and an omitted field inherits the global list.
Depth counts physical grouping fields, excluding the measures-on-rows presentation
field. Zero disallows grouping on that axis. Omitted restrictions preserve the
existing transport behavior; this does not enable custom client aggregate aliases.
`allowedRowFields` and `allowedColumnFields` constrain public physical selectors
on their respective axes. An empty list permits no physical field; an omitted
list adds no restriction. These lists intersect across engine and adapter
profiles. They describe membership, not an ordered hierarchy or a required
minimum depth. A backend with a fixed semantic view must still validate exact
axis order and required fields.
Field keys are the backend's public physical selectors. V2 physical-field reports
project those restrictions to their UI selectors for authoring without changing
the transport identifiers. Explicit `engine.capabilities.query` intersects the adapter's inherited profile.
Operation lists intersect and depth limits use the smaller maximum; omitted
limits and explicit `undefined` retain inherited restrictions. Update configuration immutably
when backend metadata changes.
The configurator and context-menu aggregation choices use supported operations.
Layout actions reject unsupported axis fields, swaps and additional hierarchy levels while retaining
removal and presentation edits as recovery actions. Query preparation and HTTP
load/export reject unsupported summaries, including hidden group/HAVING summaries,
or unsupported axis membership/depth before execution. This also covers application
adapters, complete export and reused analytical results. Malformed profiles fail
closed. Errors retain `INVALID_SUMMARY_TYPE` or `HIERARCHY_UNAVAILABLE` with details.
This profile is a preflight and authoring contract, **not authorization**. The
server must still validate every request against its tenant-scoped registry and
actual implementation. A depth limit does not advertise subtotals, expansion,
date grouping or calculated fields. The SQLite reference publishes one physical
field per configured axis through these allowlists, together with its actual
registered SQL summary operations. Existing unsupported fields can be removed
or reordered for recovery; a new unsupported membership cannot be introduced.
### Local adapter requests
`ClientPivotEngineAdapter.load(request, signal)` uses the same aggregation model
as the grid. An empty `loadOptions` keeps the constructor report. Supplied axes
and `totalSummary` replace the corresponding fields; explicit empty arrays clear
them. Stable measure IDs preserve matching labels and presentation metadata.
Constructor member filters remain a source scope, combined with request `filter`
before aggregation. Request HAVING boolean trees run independently at each
returned aggregation grain; totals are not sums of the remaining visible rows.
Sorting runs before paging, preserving hierarchy branches and subtotal placement.
Column windows contain analytical buckets with all requested measures, and the
same window projects body, summary and group-aggregate carriers. Zero-size and
exhausted windows return no value columns with full axis counts. Expansion paths
are typed, validated and copied; row records remain logical model rows for the
grid grouping renderer, while column expansion controls the materialized frontier.
Already-aborted signals reject; synchronous local aggregation cannot be interrupted
mid-computation by an event-loop callback.
Date/numeric group intervals and different group-level summary definitions are
currently rejected by the local adapter rather than ignored. A HAVING target
must resolve to a requested measure and materialized semantic bucket; missing
values for an otherwise valid bucket evaluate as null. Custom constructor
aggregators remain supported for `loadOptions: {}`. Request descriptors for local
`distinct` must match an explicitly configured measure by stable ID, property,
and aggregator. Choose the strict aggregate preset for non-null typed distinct
values; default local coercion is retained for compatibility and is not the SQL
distinct policy above.
### Viewport
The `viewport` defines independent analytical windows:
- `rowAxis`: Contains `offset`, `limit`, and `expandedPaths`.
- `columnAxis`: Contains `offset`, `limit`, `expandedPaths`, and
`collapsedPaths`.
Use typed analytical paths for expansion state, not visible column indexes.
`expandedPaths` and `collapsedPaths` describe exceptions to
`uiState.columnGroupsCollapsedByDefault`. This lets the backend calculate and
return only the visible column frontier in either default mode.
### Normalization Limits
Before planning/execution, requests are typically normalized against backend limits (`PivotLimits`) to keep payloads and query complexity bounded.
Common limits include:
- `maxRowWindowSize`: Maximum allowed `rowAxis.limit`.
- `maxColumnWindowSize`: Maximum allowed `columnAxis.limit`.
- `maxExpandedPaths`: Maximum combined number of expanded and collapsed
frontier paths per axis.
- `maxExpansionDepth`: Maximum hierarchy depth of a single expansion path.
- `maxDrilldownLimit`: Maximum page size for drilldown requests.
For `maxExpansionDepth`, depth is the number of path segments. Example: `['Region', 'Country', 'City']` has depth `3`.
## Load Response
`PivotLoadResponse` returns only the data needed for the current visible window.
### Root Properties
- `data`: Cell records with `__rowPath` and `__columnPath`, or already materialized row records using the configured generated value properties.
- `summary`: Required array of grand-total or pinned rows; use `[]` when there are none.
- `rowAxis`: Window metadata for the row axis.
- `columnAxis`: Window metadata for the column axis.
- `groupAggregates`: Optional grouped-row measures.
- `meta`: Diagnostic metadata.
Important response metadata:
- `cacheStatus`: 'hit', 'miss', 'warm', or 'bypass'.
## Axis Metadata
Both `rowAxis` and `columnAxis` use the same metadata structure.
| Field | Role | Description |
| :--- | :---: | :--- |
| `paths` | Optional in the type | Array of analytical paths used to generate headers; supply it for an authoritative remote column frontier. |
| `kinds` | Optional | Bucket kinds aligned with `paths`: `leaf`, `collapsed`, `subtotal`, or `grandTotal`. Disambiguates literal member strings from synthetic total markers. |
| `totalCount` | Required (Rows) | Total members on server. Required for row-axis scrollbars and pagination. |
| `expandedPaths` | Optional | Expanded analytical paths echoed by the engine. |
| `collapsedPaths` | Optional | Collapsed analytical paths echoed by the engine. |
## Drilldown
Drilldown uses `PivotDrilldownRequest` and `PivotDrilldownResponse`.
The request identifies a visible summary cell by:
- `rowPath`
- `columnPath`
- optional `dataIndex`
The request carries its optional source/member `filter` explicitly. Intersect
that WHERE scope with `cell.rowPath` and `cell.columnPath`, then return matching
fact rows with `offset`, `limit` and `totalCount`. HAVING and analytical axis
windows do not restrict the matching fact page. Validate `customColumns` against
your published registry before exposing source fields.
## Member choices and HTTP routes
`PivotFilterValuesRequest` identifies `viewId`, `fieldsVersion`, the field
`selector`, source `filter`, optional `search`, and zero-based `offset`/`limit`.
It returns `values` and `totalCount`. The source filter excludes that field's own
native member selection so users can change their choice.
The `PivotEngineAdapter.filterValues` and `PivotRemoteStore.filterValues` methods
are optional. `HttpPivotRemoteStore.filterValues` posts the complete request to
`/api/pivot/filter-values`. Implement this route in your application backend
before advertising `filterValues: true`. Apply source filtering and search before
distinct-member paging, and return the matching `totalCount`; do not enumerate
only the currently loaded aggregate window.
The built-in HTTP store uses these fixed paths under its `baseUrl`:
| Operation | Method and path |
| --- | --- |
| Load analytical window | `POST /api/pivot/load` |
| Drill through to facts | `POST /api/pivot/drilldown` |
| Search/page distinct members | `POST /api/pivot/filter-values` |
| Save state/report envelope | `POST /api/pivot/state/save` |
| Load state/report envelope | `GET /api/pivot/state/{userId}/{viewId}` |
User and view path segments are URL-encoded. `baseUrl` prefixes the paths; it
does not remap individual endpoints. Supply a custom store/adapter for another
route layout. State writes for the same user/view are serialized in call order,
including after a failed earlier write. JSON success responses and `204` empty
responses are supported; non-success responses become `PivotError` with the
HTTP status and response payload in `details`.
Pass `credentials: 'include'` to `HttpPivotRemoteStore` when your application
uses cross-origin cookies; omission keeps the browser's fetch default. The
application owns CORS and CSRF policy. `authProvider` supplies fresh request
headers for bearer authentication. Credentials and auth headers stay in runtime
transport options and are not serialized into saved reports. Member-choice
requests use the same auth, cancellation, telemetry and error handling as loads.
## UI State Hints
`uiState` hints allow the backend to optimize its output format:
- `rowHeaderLayout`: 'tree' or 'flat'.
- `showTotalsPrior`: Placement of subtotals.
- `collapsedByDefault`: Initial hierarchy state.
- `columnGroupsCollapsedByDefault`: Default state for generated column groups.
## Calculation and descriptor policies
Ordinary `HttpPivotRemoteStore.load`, server-adapter load and SQL planning accept
only the standard descriptor properties `id`, `selector`, `summaryType`,
`calculation`. Unknown own enumerable properties, including an `operation` policy
or host label/tracing metadata, reject with `INVALID_SUMMARY_TYPE`. Keep host
metadata outside the descriptor. Local V2 operation aliases/result filters do not
extend the remote vocabulary.
Post-aggregate calculations require the corresponding advertised `calculations`
capability and a backend implementation over complete analytical scope. A Boolean
or list capability does not execute the local formula/running/percentage code on
a remote page. The flat SQLite baseline advertises no calculation support and rejects these requests. Its trusted analytical opt-in implements its declared complete-context calculation profile; see [current reference profiles](/guides/pivot/server-side/adapters-and-security/#executable-sqlite-reference). The baseline supports standard flat summaries, including authorized distinct,
with null-skipping SQL AVG/COUNT(column), null empty SUM/AVG/MIN/MAX and zero empty
COUNT/DISTINCT. Empty text is a counted SQL value; collation belongs to the backend.
Versioned local operations, independent group/total overrides and nonphysical V2
fields are rejected by standard server preparation. The execution pin does not
translate these operations. Full-export forwarding checks envelope,
query capabilities, limits and the same summary-property allowlist as ordinary
load before invoking a store or sending HTTP. Independently validate policies in
the application backend before computing/exporting.
---
# Remote Tools
URL: https://pro.rv-grid.com/guides/pivot/server-side/remote-tools/
Source: src/content/docs/guides/pivot/server-side/remote-tools.mdx
Description: Add diagnostics, source-row drilldown, and saved views to a server-side Pivot screen.
Remote tools are small UI and application-layer helpers for server-side Pivot. They help you show request diagnostics, load source rows for a summarized cell, track remote activity, and manage saved Pivot layouts that can be restored into the grid.
These helpers are not a required backend. They sit around a `PivotRemoteStore` or `PivotEngineAdapter` that your app already uses for remote Pivot loading. The focused examples below expose each contract separately. The full demo places its application-owned tools outside the grid wrapper.
Open the full demo and framework source.
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## What This Page Covers
- Diagnostics model: converts remote response metadata into rows and chips your UI can render.
- Drilldown controller: turns a summarized Pivot cell into table-ready source rows.
- Field panel: lets users reorder and move row, column, value, and filter fields before saving a layout. The full demo keeps unused fields in the Filters area so they can be moved into the active layout.
- Saved-view helpers: serialize, load, rename, duplicate, and delete user-owned Pivot configs that can be written back to `grid.pivot`.
- Remote store hooks: expose request activity such as `load`, `drilldown`, `saveState`, and `loadState` for support panels or audit trails.
The grid loads aggregated data through the remote store. Your surrounding app UI can reuse the same store to ask follow-up questions: "How fast was that request?", "Which source rows produced this number?", "Which saved layout should this user load?", or "What remote operation just ran?".
## Import The Helpers
All helpers below are exported from `@revolist/pivot`.
```ts
import {
PivotDrilldownController,
createPivotDiagnosticsModel,
deletePivotSavedView,
duplicatePivotSavedView,
loadPivotView,
renamePivotSavedView,
savePivotView,
type PivotConfig,
type PivotLoadRequest,
type PivotRemoteStoreHooks,
type PivotSavedViewRecord,
} from '@revolist/pivot';
```
## Diagnostics Model
Remote Pivot responses can include `meta` data such as elapsed time, cache status, generated timestamp, and warnings. `createPivotDiagnosticsModel` converts that raw response into a simple display model.
Use it when you want a side panel, footer, debug drawer, or support view that explains what the last remote request did.
```ts
const response = await remoteStore.load({
requestId: `pivot-diagnostics-${Date.now()}`,
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
loadOptions: {
rows: [{ selector: 'region' }],
columns: [{ selector: 'quarter' }],
totalSummary: [{ selector: 'sales', summaryType: 'sum' }],
groupSummary: [{ selector: 'sales', summaryType: 'sum' }],
},
viewport: {
rowAxis: { offset: 0, limit: 50 },
columnAxis: { offset: 0, limit: 12 },
},
uiState: {
collapsedByDefault: true,
rowHeaderLayout: 'tree',
},
} satisfies PivotLoadRequest);
const diagnostics = createPivotDiagnosticsModel(response, {
formatGeneratedAt: (value) => new Date(value).toLocaleString(),
});
for (const row of diagnostics.rows) {
console.log(row.label, row.value);
}
for (const chip of diagnostics.chips) {
console.log(chip.label, chip.tone);
}
```
The returned model has:
- `rows`: stable rows such as elapsed time, generated time, visible rows, and visible columns.
- `chips`: compact status items such as cache status or warnings.
- `warnings`: warning strings from the response metadata.
- `hasWarnings`: a boolean for showing warning states in your UI.
The helper does not make network requests. It only formats a `PivotLoadResponse` that your remote store already returned. In the live demo, the diagnostics panel refreshes against the currently selected layout so the visible rows and columns reflect that saved view.
## Drilldown Controller And Table State
Aggregated Pivot cells often need a "show source rows" action. `PivotDrilldownController` wraps the remote drilldown contract and keeps table state for you.
The controller owns:
- current status: `idle`, `loading`, `success`, or `error`
- table columns inferred from returned rows or `customColumns`
- source rows for the selected summary cell
- total matching row count
- request cancellation when a newer drilldown request starts
```ts
const drilldown = new PivotDrilldownController(remoteStore, {
defaultLimit: 100,
});
const unsubscribe = drilldown.subscribe((state) => {
renderDrilldownTable({
loading: state.loading,
columns: state.columns,
rows: state.rows,
totalCount: state.totalCount,
error: state.error,
});
});
await drilldown.load({
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
cell: {
rowPath: ['North', 'Jane'],
columnPath: [2024, 'Q1'],
},
customColumns: ['region', 'rep', 'year', 'quarter', 'sales'],
offset: 0,
limit: 25,
});
const state = drilldown.getState();
console.log(state.status, state.rows.length, state.totalCount);
unsubscribe();
drilldown.abort();
```
The `cell` value should identify the analytical cell, not the visible DOM cell. Use Pivot paths like `rowPath` and `columnPath` so your backend can apply the same grouping context and return matching fact rows. The controller state is table-ready: render `state.columns`, `state.rows`, `state.totalCount`, and `state.status` directly in your surrounding UI.
## Saved-View Helpers
Saved views are user-owned Pivot configurations. They are useful when users build several layouts, such as "Revenue by Region" and "Quarterly Margin".
The helpers use `store.saveState()` and `store.loadState()` for persistence. Rename, duplicate, and delete operate on your app's list of saved-view records, so you can combine them with your own sync logic, menus, permissions, or undo UI. In an interactive screen, subscribe to `pivotPlugin.getAuthoringController()` and save only newly committed, ready revisions. Read the accepted config from `pivotPlugin.getRuntimeSnapshot()?.config`; keep engine handles and callbacks in application-owned bindings. `pivot-config-update` is a cancelable proposal that can later fail or be superseded, so it is unsuitable as an autosave trigger. See [Plugin Lifecycle](../../concepts/plugin-lifecycle/) for the committed subscription pattern.
```ts
// pivotPlugin is the installed PivotPlugin instance obtained through grid.getPlugins().
const USER_ID = 'user-123';
let views: PivotSavedViewRecord[] = [];
const config: Partial = {
dimensions: [
{ prop: 'region' },
{ prop: 'rep' },
{ prop: 'quarter' },
{ prop: 'sales' },
],
rows: ['region', 'rep'],
columns: ['quarter'],
values: [{ prop: 'sales', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
};
const saved = await savePivotView(remoteStore, {
userId: USER_ID,
viewId: 'north-sales',
name: 'North Sales',
config,
});
views = [...views.filter((view) => view.viewId !== saved.viewId), saved];
const loaded = await loadPivotView(remoteStore, {
userId: USER_ID,
viewId: 'north-sales',
});
const accepted = await pivotPlugin.updatePivotConfig({
...loaded.config,
engine: {
mode: 'server',
remoteStore,
viewId: 'sales',
fieldsVersion: 'sha256:fields-v1',
},
});
if (!accepted) showMessage('Saved view was not committed');
views = renamePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
name: 'North Sales by Quarter',
});
views = duplicatePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
newViewId: 'north-sales-copy',
name: 'North Sales Copy',
});
views = deletePivotSavedView(views, {
userId: USER_ID,
viewId: 'north-sales',
});
```
`savePivotView` stores a detached, JSON-serializable Pivot config using the same
validation as JSON state export. Functions, unsupported class instances,
non-finite numbers, circular references, and sparse or undefined array entries
are rejected before the store is called. Custom aggregator callbacks and runtime
engine handles must stay in application-owned runtime configuration; passing
them directly is an error rather than silently dropping their behavior.
Loading accepts the current saved-view envelope, the supported JSON state
envelope, or an unenveloped native config. Unknown envelope kinds and unsupported
schema versions are rejected. The remote response's `version` is backend record
metadata, separate from an envelope's schema version.
## Remote Store Activity
`HttpPivotRemoteStore` accepts lifecycle hooks for telemetry. They are useful for request logs, support drawers, cache badges, and lightweight audit trails near the grid.
```ts
const hooks: PivotRemoteStoreHooks = {
requestStarted: ({ type }) => {
activity.unshift(`${type}: started`);
},
requestSucceeded: ({ type, cacheStatus }) => {
activity.unshift(`${type}: succeeded${cacheStatus ? ` (${cacheStatus})` : ''}`);
},
requestFailed: ({ type }) => {
activity.unshift(`${type}: failed`);
},
cacheStatusChanged: ({ cacheStatus }) => {
activity.unshift(`cache: ${cacheStatus}`);
},
};
```
The live example uses those hooks with the same mocked `HttpPivotRemoteStore` that powers the grid. Running diagnostics, loading source rows, saving a view, and loading a view all update the activity log.
## Backend Expectations
These helpers assume the remote store supports the operation you call:
- Diagnostics needs `load()` responses with useful `meta`, `rowAxis`, and `columnAxis` data.
- Drilldown needs an adapter or store with a `drilldown()` method.
- Saved views need `saveState()` and `loadState()`.
- Activity logs need store hooks or equivalent instrumentation around your request layer.
You can implement those methods against any backend: REST, GraphQL, local storage for a demo, or an internal state service. RevoGrid does not require a specific saved-view backend.
## Recommended Reading
- [Server-Side Pivot](/guides/pivot/server-side/) - How remote Pivot loading works.
- [Remote API Reference](/guides/pivot/server-side/remote-api-reference/) - Request, response, and drilldown contracts.
- [Caching And Cache Keys](/guides/pivot/server-side/caching/) - Cache metadata used by diagnostics.
---
# Server-Side Pivot
URL: https://pro.rv-grid.com/guides/pivot/server-side/server-side/
Source: src/content/docs/guides/pivot/server-side/server-side.mdx
Description: Connect RevoGrid Pivot to a remote analytical engine without changing the public Pivot configuration model.
Server-side Pivot is for datasets that are too large, too sensitive, or too analytically expensive to fully materialize on the client. RevoGrid still owns the viewport and interaction model, but the analytical engine owns aggregation, windowing, drilldown facts, and cache-aware execution.
For raw type signatures, see [Pivot API](/api/pivot/). For the full request and store contract, continue with [Remote API Reference](/guides/pivot/server-side/remote-api-reference/).
Open the full demo and framework source.
Use `aggregationSemantics: 'server'` to pin plugin/controller execution to the trusted backend. A pin prevents accidental engine switching; it does not guarantee identical local/server results. Choose null, count, distinct, totals and precision policies in the backend contract and validate them against independent facts. For local analysis, use the [V2 numeric/scalar aggregation configuration](/guides/pivot/concepts/aggregations/).
## Architecture
The key boundary is:
```txt
Pivot UI / RevoGrid Plugin
->
Pivot engine adapter
->
remote store or analytical backend
```
In practical terms:
- `PivotPlugin` receives the direct `pivot` config
- `pivot.engine.mode = 'server'` switches the plugin to remote loading
- `PivotEngineAdapter` or `PivotRemoteStore` handles the analytical request
- the grid only receives the visible block to render
## Why The Public Config Stays The Same
The core Pivot model does not change:
- `rows`
- `columns`
- `values`
- `totals`
- drill-down related UI state
That is deliberate. The UI should not need one config model for client Pivot and another for server Pivot.
## Basic Setup
```ts
import {
PivotPlugin,
HttpPivotRemoteStore,
type PivotConfig,
} from '@revolist/pivot';
import { PaginationPlugin } from '@revolist/revogrid-pro';
const remoteStore = new HttpPivotRemoteStore({
baseUrl: 'https://example.internal',
tenantId: 'tenant-a',
datasetWatermark: 'sales-v42',
authProvider: async () => ({
Authorization: `Bearer ${await getToken()}`,
}),
});
const pivot: PivotConfig = {
dimensions: [...],
rows: ['Category', 'Subcategory'],
columns: ['Date'],
values: [{ prop: 'SalesAmount', aggregator: 'sum' }],
totals: { grandTotal: true, subtotals: true },
collapsed: true,
engine: {
mode: 'server',
remoteStore,
viewId: 'sales-by-region',
fieldsVersion: 'sha256:fields-v1',
rowAxis: { },
columnAxis: { },
},
};
// PaginationPlugin drives row-axis paging automatically.
// PivotPlugin reads itemsPerPage as rowAxis.limit and writes
// response.rowAxis.totalCount back to pagination.total after each load.
grid.plugins = [PivotPlugin, PaginationPlugin];
grid.pivot = pivot;
grid.additionalData = {
pagination: {
itemsPerPage: 50,
initialPage: 0,
total: 0, // auto-populated from the first server response
},
};
```
## Pagination With Remote Pivot
`PivotPlugin` integrates directly with `PaginationPlugin`. When both plugins are active:
- `pagination.itemsPerPage` becomes the row-axis page size (`rowAxis.limit`).
- When the user changes page, `PivotPlugin` reloads the server window at the new `rowAxis.offset`.
- After each successful load, `response.rowAxis.totalCount` is written back to `pagination.total` so the pagination panel shows the correct count.
- Changing the pivot structure (rows, columns, values) resets the offset to 0 automatically.
You do not need to handle page-change events yourself. Drop both plugins into `grid.plugins`, bind the Pivot config to `grid.pivot`, and add `pagination` to `additionalData`.
## What RevoGrid Owns vs What The Engine Owns
RevoGrid and `PivotPlugin` own:
- visible grid rendering
- generated RevoGrid columns
- visible row block
- grouped-row metadata used by current drill-down UI
- cancellation and last-response-wins behavior in the plugin lifecycle
The remote engine or backend owns:
- selector validation
- filter execution
- analytical grouping
- summary execution
- row-axis and column-axis windows
- drilldown fact pages
- cache policy and invalidation
## Request Flow
1. `PivotPlugin` sees `engine.mode = 'server'`.
2. It builds a `PivotLoadRequest` from the public Pivot config and current window hints.
3. The adapter or remote store executes the request.
4. The response is materialized into generated columns, visible rows, and pinned totals.
5. RevoGrid renders only the visible analytical window.
## Field Registry And Planner
- Public selectors must be resolved through a field registry.
- Backend expressions must come from the registry, never from client strings.
- Query planning belongs behind a planner abstraction so SQL, OLAP, or warehouse backends can share the same request model.
This repository already includes:
- a field registry model
- request normalization and limit enforcement
- a typed error model
- an SQL-oriented logical planner shape
- a framework-agnostic `HttpPivotRemoteStore`
## Backend Handler Outline
A production Pivot backend translates the analytical request into its native query
format. This application pseudocode shows the contract boundary; `getTenant` and
`myAnalyticalEngine` represent your application code. Authenticate, validate
selectors and normalize requests against your field registry before execution.
```ts
import type { Request } from 'express';
import {
type PivotLoadRequest,
type PivotLoadResponse,
} from '@revolist/pivot';
/**
* Handle POST /api/pivot/load
*/
export async function handlePivotLoad(req: Request): Promise {
const request = req.body as PivotLoadRequest;
const { loadOptions, viewport, viewId, fieldsVersion, uiState } = request;
// 1. Authenticate and resolve tenant
const tenantId = await getTenant(req);
// 2. Extract analytical parameters
const {
rows = [], // Group descriptors for rows
columns = [], // Group descriptors for columns
filter, // Source predicates, before aggregation
having, // Aggregate predicates, after aggregation
sort = [], // Sorting directives
totalSummary = [], // Grand-total measures
groupSummary = [], // Group-level measures (for groupAggregations)
} = loadOptions;
// 3. Extract viewport windows
const { rowAxis, columnAxis } = viewport;
// 4. Execute analytical query (e.g., against SQL or OLAP)
const result = await myAnalyticalEngine.execute({
tenantId,
viewId,
fieldsVersion,
rows,
columns,
filter,
having,
sort,
measures: [...totalSummary, ...groupSummary],
rowWindow: rowAxis,
columnWindow: columnAxis,
// uiState.rowHeaderLayout, uiState.showTotalsPrior, etc.
});
// 5. Build the response
return {
data: result.cells, // REQUIRED: Visible analytical payload
summary: result.totals ?? [], // REQUIRED: Empty array when no summary rows
rowAxis: {
totalCount: result.rowCount, // REQUIRED: Drives scrollbars/pagination
},
columnAxis: {
paths: result.colPaths, // REQUIRED: Defines the generated headers
},
// Optional: Grouped-row aggregate payloads (if groupSummary requested)
groupAggregates: result.groupSummaries,
meta: {
cacheStatus: result.fromCache ? 'hit' : 'miss', // Used for telemetry
},
};
}
```
Source `filter` applies before aggregation; `having` applies to the aggregated
cells. Apply analytical sorting before taking axis windows. Pass the requested
expansion/collapse paths to the backend rather than returning an unbounded cube.
The response's `summary` array is required even when empty. See the
[Remote API Reference](../remote-api-reference/) for the supported response shapes.
## Current Scope
Server-side Pivot is a powerful but complex feature. The standard `PivotPlugin` implements the core windowing and materialization logic, but some advanced scenarios require custom engine adapters:
- **Implemented**: Row-axis windowing, generated columns from `paths`, tree-mode row headers, grouped aggregations, and grand totals.
- **Custom Adapters**: Complex sorting-by-summary or calculated measures typically require a custom `PivotEngineAdapter` to map your specific backend capabilities to the Pivot contract.
- **Column frontier**: Pivot sends typed `expandedPaths` and `collapsedPaths` with the column window. The backend computes that frontier, and Pivot materializes the returned paths and bucket kinds. Expand/collapse can therefore load a new server frontier.
- **Application-owned**: Fetching further column windows beyond the requested window and full-dataset exports require explicit application/backend operations.
## Recommended Reading
- [Remote API Reference](/guides/pivot/server-side/remote-api-reference/) — Detailed JSON schema for requests and responses.
- [Caching And Cache Keys](/guides/pivot/server-side/caching/) — How to ensure deterministic and efficient analytical execution.
- [OLAP Best Practices](/guides/pivot/server-side/olap-best-practices/) — Performance and security tips for backend implementations.
---
# Financial Pivot Showcase
URL: https://pro.rv-grid.com/guides/pivot/showcase/
Source: src/content/docs/guides/pivot/showcase.mdx
Description: Explore sales, profit, and units by country, segment, year, and month using realistic synthetic financial data.
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](https://learn.microsoft.com/en-gb/power-bi/create-reports/sample-financial-download), but the bundled values and product names are generated specifically for this demo.
## What This Example Demonstrates
- 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
## How To Read It
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.
## Related Guides
- [Configuration Reference](/guides/pivot/concepts/configuration-reference/)
- [Plugin Lifecycle](/guides/pivot/concepts/plugin-lifecycle/)
- [Configurator API](/guides/pivot/configurator/configurator-api/)
- [Field Panel](/guides/pivot/concepts/field-panel/)
- [Server-Side Pivot](/guides/pivot/server-side/)
---
# Comprehensive Guide to Using RevoGrid Plugins
URL: https://pro.rv-grid.com/guides/plugin/
Source: src/content/docs/guides/plugin.mdx
RevoGrid is a highly flexible and extensible grid component that supports a powerful plugin system. This guide will walk you through everything you need to know about using RevoGrid plugins, from understanding the basics to creating and integrating your custom plugins.
#### 1. **Understanding RevoGrid Plugins**
RevoGrid plugins are modular pieces of code that extend the functionality of the RevoGrid component. These plugins can:
- Add new features.
- Modify existing behavior.
- Integrate with other libraries or frameworks.
All RevoGrid plugins extend from the `BasePlugin` class, which provides a minimal core and utility methods for interacting with the RevoGrid component.
#### 2. **Setting Up Your Development Environment**
Before you can start working with RevoGrid plugins, ensure you have a development environment set up with the following:
- **Node.js** and **npm** (Node Package Manager) installed.
- A code editor (such as **Visual Studio Code**).
- A basic understanding of TypeScript, as RevoGrid and its plugins are TypeScript-based.
To get started, you need to install RevoGrid in your project:
```bash
npm install @revolist/revogrid
```
#### 3. **Creating a Simple Plugin**
Let's start by creating a simple plugin that add class to odd rows.
1. **Create a New Plugin Class**: Your plugin should extend the `BasePlugin` class provided by RevoGrid.
```typescript
import { BasePlugin, type PluginProviders } from '@revolist/revogrid';
export class RowOddPlugin extends BasePlugin {
constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) {
super(revogrid, providers);
this.addEventListener(
'beforerowrender',
({
detail,
}: CustomEvent) => {
detail.node.$attrs$ = {
...detail.node.$attrs$,
odd: detail.item.itemIndex % 2 !== 0,
};
}
);
}
}
```
2. **Integrate the Plugin**: Once your plugin is created, integrate it with your RevoGrid instance.
```typescript
const gridElement = document.querySelector('revo-grid');
gridElement.plugins.push(new RowOddPlugin(gridElement, providers));
```
#### 4. **Exploring Core Plugin Methods**
When creating more complex plugins, you'll likely use several core methods provided by the `BasePlugin` class:
- **`addEventListener(eventName: string, callback: (e: CustomEvent) => void): void`**: Subscribes to a RevoGrid event.
Example:
```typescript
this.addEventListener('beforecellrender', this.handleCellRender);
```
- **`watch(prop: string, callback: (arg: T) => boolean | void, config?: Partial): void`**: Watches a property on the RevoGrid component for changes.
Example:
```typescript
this.watch('rowSelection', (newValue) => {
console.log('Row selection changed:', newValue);
});
```
- **`emit(eventName: string, detail?: any): CustomEvent`**: Emits a custom event from the RevoGrid component.
Example:
```typescript
this.emit('customEvent', { data: 'someData' });
```
- **`clearSubscriptions(): void`**: Clears all event listeners subscribed by the plugin.
- **`destroy(): void`**: Cleans up the plugin by removing all subscriptions and performing any necessary teardown.
#### 5. **Advanced Plugin Development**
RevoGrid provides a set of providers that you can use to interact with its internal state. These providers include:
- **Data Provider**: Manipulate the grid's data.
- **Dimension Provider**: Manage row and column dimensions.
- **Selection Provider**: Handle cell and row selections.
- **Column Provider**: Manage column data and properties.
- **Viewport Provider**: Handle scrolling and viewport rendering.
Here's how you might use these providers in a more advanced plugin:
```typescript
class HighlightSelectionPlugin extends BasePlugin {
constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) {
super(revogrid, providers);
this.addEventListener('afterselectionchange', this.highlightSelectedCells);
}
private highlightSelectedCells = () => {
const selectedCells = this.providers.selection.getSelectedCells();
selectedCells.forEach(cell => {
const model = this.providers.data.getModel(cell.rowIndex, cell.type);
model.cellProperties = {
style: {
backgroundColor: 'yellow'
}
};
});
};
}
```
#### 6. **Styling with Plugins**
Plugins can also be used to dynamically style the grid. For example, using a plugin to flash cells after they are edited:
```typescript
import './cell-flash.style.css';
class CellFlashPlugin extends BasePlugin {
constructor(revogrid: HTMLRevoGridElement, providers: PluginProviders) {
super(revogrid, providers);
this.addEventListener('onEdit', this.handleEdit);
}
private handleEdit = (e: CustomEvent) => {
const { rowIndex, prop } = e.detail;
const cell = this.revogrid.getCell(rowIndex, prop);
cell.classList.add('flash');
setTimeout(() => cell.classList.remove('flash'), 1000);
};
}
// cell-flash.style.css
.flash {
animation: flash 1s ease;
}
@keyframes flash {
from { background-color: yellow; }
to { background-color: transparent; }
}
```
#### 7. **Best Practices for Plugin Development**
- **Keep Plugins Modular**: Each plugin should have a single responsibility and be as modular as possible.
- **Manage Subscriptions**: Always clear subscriptions when they are no longer needed to avoid memory leaks.
- **Utilize Providers Wisely**: Only use the necessary providers to keep your plugin efficient.
- **Ensure Compatibility**: Test your plugins thoroughly to ensure they work well with other plugins and configurations.
#### 8. **Deploying and Sharing Plugins**
Once you've created a plugin, you can package it as an npm module and share it with the community. Here's a basic setup for packaging your plugin:
1. **Create a `package.json` file**:
```json
{
"name": "revogrid-cell-logger",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"build": "tsc"
},
"dependencies": {
"@revolist/revogrid": "^4.0.0"
}
}
```
2. **Build your plugin**:
```bash
npm run build
```
3. **Publish to npm**:
```bash
npm publish
```
#### 9. **Conclusion**
RevoGrid's plugin system is a powerful way to extend its functionality and adapt it to your needs. Whether you're creating simple event listeners or complex data manipulation tools, the flexibility provided by the `BasePlugin` class and the various providers make it easy to build, deploy, and share custom plugins.
By following this guide, you should now be equipped to start creating your own plugins, enhancing the capabilities of RevoGrid, and contributing to a richer ecosystem for all users.
---
# Drag and Drop (Advanced)
URL: https://pro.rv-grid.com/guides/rows/row-advanced-drag-drop/
Source: src/content/docs/guides/rows/row-advanced-drag-drop.mdx
Description: Custom drag-and-drop behavior and interactions.
Implement sophisticated drag-and-drop functionality within your grid
Reorder plugin in RevoGrid may appear simple at first glance, but it’s quite a complex and powerful feature.
It supports **multi-item reordering**, allowing users to move multiple rows or columns at once while preserving the integrity of the dataset.
Leveraging **`proxyItems`** for efficient handling of the full dataset, the plugin ensures that **filter positioning remains correct** even after reordering, maintaining a seamless user experience.
Beyond its core functionality, plugin plays a **crucial role in supporting other plugins** within RevoGrid.
It facilitates **data transfer** across different operations and plugins, extending the overall capabilities of the grid.
This makes it an essential component for managing dynamic datasets in RevoGrid Pro.
- **Custom Drag-and-Drop Behavior**:
Tailor the drag-and-drop interactions to fit your application’s specific needs. You can configure how rows are moved, allowing for both simple and complex drag-and-drop operations.
- **Enhanced Interactivity**:
Improve the overall usability of your grid. With responsive drag-and-drop functionality, users can quickly reorganize data, making it easier to manage and visualize information.
- **Multi-Row Dragging**:
Users can drag and drop multiple rows at once, simplifying bulk operations. This is particularly useful for scenarios where users need to rearrange large datasets quickly.
- **Flexible Dimensions**:
Allow dragging in any direction—vertically or horizontally—supporting both pinned and classic rows. This flexibility enables users to interact with the grid in a way that feels natural to them.
- **Custom Classes During Dragging**:
Apply custom styles during drag operations to provide visual feedback. This feature helps users understand what they are dragging and where they can drop it, enhancing the overall experience.
- **Drag Handle Customization**:
Implement your own drag handles and apply them to custom cells without limitations. This means you can design specific elements within your rows to serve as drag handles, making it clear to users which parts of the UI are draggable.
- **Extendable Event Source**:
Enhance functionality by extending the drag event source. You can create custom events and interactions that suit your application’s specific requirements, allowing for a more tailored user experience.
### How to use
To enable drag and drop functionality for specific columns, first, import the `RowOrderPlugin` from `@revolist/revogrid-pro` and then simply add the `rowDrag` property to the column configuration:
```ts
import { RowOrderPlugin } from '@revolist/revogrid-pro';
grid.columns = [
{ name: '🆔 ID', prop: 'id', rowDrag: true },
];
// or in row headers
grid.rowHeaders = { rowDrag: true };
// don't forget to add the plugin
grid.plugins = [RowOrderPlugin];
```
### Row Order Preview
Use `grid.rowOrder` to customize what appears in the drag preview. `prop` keeps the existing compact label behavior, and `previewProp` can be a column prop or callback when the preview label needs application-specific text:
```ts
grid.rowOrder = {
prop: 'name',
preview: 'compact',
previewProp: ({ firstItem, itemCount }) => {
const label = firstItem?.name ?? 'Rows';
return itemCount > 1 ? `${label} and ${itemCount - 1} more` : label;
},
};
```
`additionalData.rowOrder` is still supported for compatibility, but direct `grid.rowOrder` configuration is preferred.
### Flash Moved Rows
If `CellFlashPlugin` is installed with `RowOrderPlugin`, successfully moved rows flash automatically after the reorder is committed. The flash applies only to the dragged row or dragged selection block at its new visible position:
```ts
import { CellFlashPlugin, RowOrderPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowOrderPlugin, CellFlashPlugin];
grid.cellFlash = {
rowDuration: 1200,
};
```
Invalid, canceled, or no-op drops do not flash. Existing `cellFlash` options control the row flash duration, CSS class, reduced-motion behavior, and whether flashing is enabled.
To keep `CellFlashPlugin` active for edits or manual flash calls but disable row-order drop flashes, set `rowOrder.flashOnDrop` to `false`:
```ts
grid.rowOrder = {
prop: 'name',
flashOnDrop: false,
};
```
### Live Drop Validation
Use `validateDrop` when some positions should be blocked while the user is dragging. Invalid targets are highlighted immediately and are not committed on mouseup:
```ts
grid.rowOrder = {
prop: 'name',
validateDrop: ({ targetIndex }) => {
if (targetIndex === 0) {
return {
valid: false,
reason: 'The first row is locked.',
};
}
return { valid: true };
},
};
```
You can also validate with events when a plugin or application controller owns the rule:
```ts
grid.addEventListener('beforerowdropvalidate', (event) => {
if (event.detail.targetIndex === 0) {
event.detail.result = {
valid: false,
reason: 'The first row is locked.',
};
}
});
grid.addEventListener('rowdropstatechange', (event) => {
if (!event.detail.result.valid) {
console.info(event.detail.result.reason);
};
});
```
### Grouped Row Drops
When row grouping is active, row order keeps data rows inside their current group by default. This also applies when rows are selected through `RowSelectPlugin`: synthetic group rows are ignored, and only real data rows are validated. For multi-level grouping, the full configured `grouping.props` path must match before the drop is committed.
If your workflow should let users move rows between groups, enable `applyTargetGroupOnDrop`. The row-order plugin will apply the target group values to every moved row and emit normal `beforeedit` and `afteredit` events for each changed group field. Calling `event.preventDefault()` in `beforeedit` cancels the drop.
```ts
grid.grouping = {
props: ['team', 'status'],
expandedAll: true,
};
grid.rowOrder = {
prop: 'name',
preview: 'compact',
applyTargetGroupOnDrop: true,
};
grid.addEventListener('beforeedit', (event) => {
if (event.detail.prop === 'team' && event.detail.val === 'Archived') {
event.preventDefault();
}
});
```
### User-Centric Benefits
By leveraging the advanced drag-and-drop capabilities, users will experience:
- **Increased Efficiency**: Streamline workflows by allowing users to quickly rearrange rows, making data management less cumbersome.
- **Improved Data Visualization**: Users can reorganize data in a way that makes the most sense to them, leading to better insights and understanding.
- **Intuitive Interface**: A well-implemented drag-and-drop feature provides an intuitive interface, reducing the learning curve for new users.
- **Customization Flexibility**: Allowing for custom drag handles and styles gives users the ability to tailor their experience according to their preferences.
Advanced drag-and-drop functionality empowers users to interact with their data in a more dynamic and intuitive way. By customizing the behavior and appearance of drag-and-drop actions, you can significantly enhance the user experience within your grid application. Embrace these capabilities to create a more engaging and efficient interface that meets your users' needs.
---
# Header (Advanced)
URL: https://pro.rv-grid.com/guides/rows/row-advanced-header/
Source: src/content/docs/guides/rows/row-advanced-header.mdx
Description: Custom Row Header
Sophisticated Row Header example based on `RowHeaderPlugin`. The demo uses a task pipeline layout so the row header has a clear job: it anchors each row, opens row-level actions, and keeps row focus visible while the cells provide richer operational context.
- Use `rowHeaders({ showHeaderFocusBtn: true })` for a dedicated row action rail
- Open a row `ContextMenuPlugin` menu directly from the row-header click
- Combine row headers with custom column templates for status, priority, owner, due date, and progress
- Focus the whole row from the row header while preserving readable business data in the grid body
- Style the row-header rail and content cells together so the row action area feels intentional
---
```javascript
// fixing render for multiframework
```
---
# Auto Size Row
URL: https://pro.rv-grid.com/guides/rows/row-autosize/
Source: src/content/docs/guides/rows/row-autosize.mdx
Description: Automatically adjust row heights based on content
The **Row Auto Size Plugin** enhances RevoGrid by automatically calculating and adjusting row heights based on their content. This ensures optimal display of data, particularly useful for cells containing multi-line text, rich content, or varying amounts of information.
When the plugin is enabled, normal grid cells wrap text so the rendered content matches the height calculation. Row heights are recalculated from the wrapped content after source changes, edits, scrolling into new virtual rows, configuration changes, and manual column resizing.
It is a **sophisticated feature** that significantly improves data presentation and user experience:
1. **Dynamic Content Adaptation**: Automatically adjusts row heights to accommodate varying content lengths, ensuring all data is visible without truncation.
2. **Performance Optimization**: Uses efficient caching and virtualization to maintain smooth scrolling even with large datasets and dynamic heights.
3. **Precise Calculations**: Offers both approximate and precise height calculations, allowing you to balance between performance and accuracy.
4. **Event-Driven Updates**: Intelligently recalculates heights only when necessary, such as after edits or viewport changes.
5. **Memory Management**: Implements cleanup strategies to prevent memory leaks and maintain optimal performance during long sessions.
- **Automatic Height Calculation**: Dynamically adjusts row heights based on content.
- **Content-Aware**: Considers line breaks, text wrapping, and column widths.
- **Resize-Aware**: Recalculates visible row heights after manual column resizing.
- **Configurable Limits**: Set minimum and maximum heights to maintain consistency.
- **Custom Calculator Support**: Implement your own height calculation logic.
- **Efficient Caching**: Caches calculated heights to optimize performance.
- **Precise Mode**: Optional precise calculations using DOM measurements.
## Basic Setup
To enable the Row Auto Size Plugin in your RevoGrid setup:
```typescript
import { RowAutoSizePlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.plugins = [RowAutoSizePlugin];
grid.rowAutoSize = {
minHeight: 24,
maxHeight: 200,
preciseSize: true, // Use DOM-based precise calculations
precalculate: true, // Warm approximate row sizes ahead of scroll
precalculateBatchSize: 500, // Rows processed per frame during warmup
};
grid.resize = true; // Optional: row heights adapt after user column resizing
```
## Configuration Options
The plugin supports several configuration options to customize its behavior:
```typescript
type RowAutoSizeConfig = {
// Minimum row height in pixels (default: 24)
minHeight?: number;
// Maximum row height in pixels (default: 200)
maxHeight?: number;
// Use precise DOM-based calculations (slower but more accurate)
preciseSize?: boolean;
// Warm all approximate row heights in chunks before they are scrolled into view (default: true)
precalculate?: boolean;
// Rows processed per animation frame during non-precise warmup (default: 500)
precalculateBatchSize?: number;
// Custom height calculator function
calculateHeight?: (rowData: DataType, columns: ColumnRegular[]) => number | Promise;
};
```
### Custom Height Calculator
You can provide your own height calculation logic through the `calculateHeight` function:
```typescript
grid.rowAutoSize = {
calculateHeight: (rowData, columns) => {
// Custom logic to determine row height
const maxLines = columns.reduce((max, col) => {
const content = rowData[col.prop]?.toString() || '';
const lines = content.split('\n').length;
return Math.max(max, lines);
}, 1);
return maxLines * 24; // 24px per line
}
};
```
## Events and Updates
The plugin automatically responds to various grid events to maintain accurate row heights:
- **Data Changes**: Recalculates heights when the source data is updated
- **Cell Edits**: Updates heights for edited rows
- **Viewport Changes**: Calculates heights for newly visible rows
- **Configuration Changes**: Recomputes all heights when plugin settings change
- **Column Resizing**: Recomputes visible row heights after resized widths are rendered
## Performance Considerations
The plugin is designed with performance in mind, but there are some considerations:
1. **Precise Mode**: Using `preciseSize: true` provides more accurate calculations but is slower as it requires DOM operations.
2. **Precalculation**: In non-precise mode, `precalculate` is enabled by default. The plugin warms row height cache in chunks so rows already have a height when they are scrolled into view.
3. **Batch Size**: `precalculateBatchSize` controls how many rows are processed per animation frame. Larger values warm the cache faster but make each frame heavier.
4. **Large Datasets**: For very large datasets, set `precalculate: false` to calculate only visible rows and preserve demand-driven behavior.
5. **Custom Calculators**: When implementing a custom calculator, ensure it's efficient as it will be called frequently during scrolling.
To disable background warmup:
```typescript
grid.rowAutoSize = {
minHeight: 24,
maxHeight: 200,
precalculate: false,
};
```
## Best Practices
1. **Default Mode for Large Datasets**: Use the default approximate calculation mode for large datasets to maintain performance.
2. **Custom Height Logic**: Use `calculateHeight` when your data needs application-specific sizing.
3. **Reasonable Limits**: Set appropriate `minHeight` and `maxHeight` to prevent extreme row sizes.
4. **Cache Management**: The plugin automatically manages its cache, but consider clearing it when making major data changes.
## Example Use Cases
1. **Multi-line Text**:
```typescript
grid.columns = [{
prop: 'description',
name: 'Description',
size: 200,
}];
grid.source = [{
description: 'This is a long description\nthat spans multiple lines\nand needs appropriate height'
}];
```
2. **Rich Content**:
```typescript
grid.rowAutoSize = {
calculateHeight: (rowData) => {
if (rowData.hasImage) {
return 100; // Taller rows for rows with images
}
return 24; // Default height for other rows
}
};
```
## Conclusion
The Row Auto Size Plugin provides a powerful solution for handling varying content heights in RevoGrid. By automatically adjusting row heights while maintaining performance, it significantly improves the presentation and usability of your grid, especially when dealing with dynamic or rich content.
---
# Rows and Columns Context Menu
URL: https://pro.rv-grid.com/guides/rows/row-context-menu/
Source: src/content/docs/guides/rows/row-context-menu.mdx
Description: Custom context menu for rows and columns
This example demonstrates how to create separate context menus with interactive actions for grid rows and columns. Use the same `ContextMenuPlugin`, but pass row actions through `rowContextMenu` and column-header actions through `columnContextMenu`.
**Key Features**:
- Separate row and column context menu configurations
- Actions with visual indicators
- Styled menu buttons with hover effects
---
---
# Auto-Focus on Next Row WCAG
URL: https://pro.rv-grid.com/guides/rows/row-next-line/
Source: src/content/docs/guides/rows/row-next-line.mdx
Description: Enhances user navigation within the grid by automatically moving the focus to the next row
The plugin enhances user navigation within the grid by automatically moving the focus to the next row when the user reaches the last cell of the current row. This feature streamlines data entry and editing processes by eliminating the need for manual navigation between rows.
**Key Features**:
- **Automatic Navigation**: Focus shifts seamlessly to the next row once the user navigates to the last cell in the current row.
- **Enhanced Efficiency**: Improves data entry and editing efficiency by reducing the need for manual row transitions.
- **Configurable Behavior**: Allows customization of focus behavior to fit specific user needs and grid configurations.
**Benefits:**
- **Improved User Experience**: Simplifies navigation within the grid, making data handling more intuitive.
- **Increased Productivity**: Accelerates data entry tasks by automating row transitions.
**Usage:**
Integrate this plugin into your setup to enable automatic focus shifting, enhancing navigation and overall grid interaction.
```javascript
// fixing render for multiframework
```
---
# Row Odd
URL: https://pro.rv-grid.com/guides/rows/row-odd/
Source: src/content/docs/guides/rows/row-odd.mdx
Description: Configurable row striping
Apply configurable row striping for better readability and data distinction with the help of the `RowOddPlugin`.
```ts
import { RowOddPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowOddPlugin];
grid.rowOdd = {
mode: 'custom',
interval: 3,
offset: 1,
className: 'priority-band',
};
```
`RowOddPlugin` is still a small rendering plugin, but it now covers production theming needs: odd/even striping, custom intervals, runtime updates, and predicate-based row bands.
---
# Row Status
URL: https://pro.rv-grid.com/guides/rows/row-status/
Source: src/content/docs/guides/rows/row-status.mdx
Description: Source-backed todo checkboxes, row attributes, strike-through styling, and hierarchy propagation.
`RowStatusPlugin` turns boolean columns into status checkboxes. The boolean remains in your source model, while the plugin adds semantic attributes to rendered rows and provides a default strike-through treatment.
- Status values are ordinary source booleans, so they can be saved, filtered, exported, and sorted.
- Every status column defines its own row attribute.
- Multiple status columns work independently.
- Tree parents cascade changes to descendants and reflect partial completion with an indeterminate checkbox.
- Completed-row sorting is application policy and stays outside the plugin.
## Basic setup
Register the plugin and add `rowStatus` to a boolean column. Only the strict value `true` activates the status.
```ts
import { RowStatusPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowStatusPlugin];
grid.columns = [
{
prop: 'done',
name: 'Done',
rowStatus: { attribute: 'data-done' },
},
{ prop: 'task', name: 'Task' },
];
grid.source = [
{ task: 'Write release notes', done: false },
{ task: 'Publish the build', done: true },
];
```
An active row receives both `[data-done]` and the common `[row-status]` attribute. The common attribute drives the built-in strike-through style.
Use safe semantic names such as `data-done`, `data-archived`, or an appropriate `aria-*` attribute. Structural and event attributes such as `class`, `style`, and `onclick` are rejected so status configuration cannot overwrite renderer-owned row properties. Invalid names are ignored and reported once in development output.
## Styling
The checkbox cell stays interactive while normal row cells receive the status treatment. Override the CSS variables on the grid when a different presentation is needed:
```css
revo-grid.my-todos {
--revo-row-status-color: #64748b;
--revo-row-status-opacity: 0.7;
--revo-row-status-text-decoration: line-through;
--revo-row-status-checkbox-size: 20px;
--revo-row-status-checkbox-radius: 50%;
}
revo-grid.my-todos .rgRow[data-archived] .rgCell:not(.row-status-cell) {
--revo-row-status-text-decoration: line-through double;
}
```
Row-status controls are circular by default so they remain visually distinct from regular checkbox editors and row-selection checkboxes. Set `--revo-row-status-checkbox-radius` to another value if a different treatment is preferred.
## Multiple status columns
Each column uses its own source prop and attribute. A row keeps `[row-status]` while any configured status is active.
```ts
grid.columns = [
{
prop: 'done',
name: 'Done',
rowStatus: { attribute: 'data-done' },
},
{
prop: 'archived',
name: 'Archived',
rowStatus: { attribute: 'data-archived' },
},
{ prop: 'task', name: 'Task' },
];
```
## Tree todo lists
Add `TreeDataPlugin` and normal tree configuration. Propagation is isolated per status column.
```ts
import { RowStatusPlugin, TreeDataPlugin } from '@revolist/revogrid-pro';
grid.plugins = [TreeDataPlugin, RowStatusPlugin];
grid.tree = {
idField: 'id',
parentIdField: 'parentId',
rootParentId: null,
expandAll: true,
};
grid.columns = [
{ prop: 'done', name: 'Done', rowStatus: { attribute: 'data-done' } },
{ prop: 'task', name: 'Task', tree: true },
];
```
| Action | Result |
| --- | --- |
| Check a parent | The parent and every descendant become complete, including collapsed or filtered descendants. |
| Clear a parent | The parent and every descendant become incomplete. |
| Complete every child | Ancestors become complete from the deepest parent to the root. |
| Complete only some children | The parent value remains false and its checkbox renders indeterminate. |
| Load inconsistent initial values | The source is not rewritten; subtree state is displayed and affected ancestors reconcile on the next edit. |
## Classic row grouping
Classic grouping works automatically when `grid.grouping` is configured. Every synthetic group label renders one checkbox for each row-status column; no extra plugin option is required.
```ts
grid.plugins = [RowStatusPlugin];
grid.grouping = {
props: ['department', 'team'],
expandedAll: true,
};
grid.columns = [
{ prop: 'done', name: 'Done', rowStatus: { attribute: 'data-done' } },
{ prop: 'archived', name: 'Archived', rowStatus: { attribute: 'data-archived' } },
{ prop: 'task', name: 'Task' },
{ prop: 'department', name: 'Department' },
{ prop: 'team', name: 'Team' },
];
```
| Grouping action | Result |
| --- | --- |
| Check a group status | Every real descendant receives that boolean value, including descendants hidden by collapsed groups or filters. |
| Edit one child | Ancestor group checkboxes recompute as checked, unchecked, or indeterminate. |
| Use multiple statuses | Each group checkbox and source prop remains independent. |
| Sort, filter, or regroup | Status remains attached to the real source model through its original physical index. |
| Listen for `rowstatuschange` | Only real models and application-source indexes are included; synthetic group rows are excluded. |
Normal data rows receive the configured attribute directly. Because core grouping headers are synthetic rows rendered through the group-label template, their derived attributes are placed on `.row-status-group-label` instead. The same `[row-status]` styling is applied when every descendant is complete for at least one status.
## Complete batch event
The directly clicked checkbox uses the regular `beforeedit` and `afteredit` events. Listen for `rowstatuschange` when you need the complete hierarchy batch:
```ts
grid.addEventListener('rowstatuschange', event => {
const {
prop,
value,
physicalIndexes,
models,
} = event.detail;
persistStatusChanges({ prop, value, physicalIndexes, models });
});
```
## Move completed items down
Sorting is intentionally configured by the application. Set ascending boolean sorting (`false` before `true`) and reapply it after a completed-status batch:
```ts
grid.columns = [
{
prop: 'done',
name: 'Done',
sortable: true,
order: 'asc',
rowStatus: { attribute: 'data-done' },
},
{ prop: 'task', name: 'Task', tree: true },
];
grid.addEventListener('rowstatuschange', event => {
if (event.detail.prop === 'done') {
void grid.updateColumnSorting({ prop: 'done' }, 'asc', false);
}
});
```
See the generated [`RowStatusPlugin` API reference](/api/row-status/) for exported types, constants, and the full JSDoc example.
---
# Row Transpose
URL: https://pro.rv-grid.com/guides/rows/row-transpose/
Source: src/content/docs/guides/rows/row-transpose.mdx
Description: Learn how to use the Row Transpose Plugin in RevoGrid Pro to switch rows and columns dynamically.
The Row Transpose Plugin modifies the grid by transposing its data, meaning that the original rows are converted into columns and vice versa.
This can be especially useful when dealing with datasets that require pivoting or when users need to explore data from different perspectives.
- **Toggle Transpose**: Switch between the original view and the transposed view.
- **Dynamic Headers**: Customizable transposed headers for better data representation.
- **Custom Transpose Configuration**: Define how the columns and rows should be transformed, including whether to transpose column headers into rows.
## How Transposing Works
The plugin offers two primary functionalities:
- **Original View**: Displays rows and columns as defined in your data model.
- **Transposed View**: When activated, this feature converts rows into columns and columns into rows.
The `RowTransposePlugin` listens for the `row-transpose`.
When triggered, it switches between the original view and the transposed view while preserving the original structure, allowing you to revert to the standard view at any time.
Alternatively, you can access the plugin directly using the following JavaScript code:
```javascript
document.querySelector('revo-grid').getPlugins().then(plugins => {
const plugin = plugins.find(p => p instanceof RowTransposePlugin);
plugin?.transpose();
});
```
When the rows are transposed, RevoGrid enters a virtual transposed mode. This means that events related to row and column edits will return instances of the `TransposedRow` class, which represent the transposed data model. If you wish to retrieve the original model, you can use the `TransposedRow.getOriginalModels` method. This method takes transposed column properties as input and converts the corresponding transposed cell back to the original model.
For example:
```javascript
document.querySelector('revo-grid').addEventListener('beforeedit', async (e) => {
const model = e.detail.model;
console.log('beforeedit', model.getOriginalModels([e.detail.prop])); // returns original row model
});
```
### Customizing the Transpose
The plugin configuration allows you to fine-tune the transposing process. For example, you can choose to transpose the column headers into rows, or leave them unchanged.
```javascript
document.querySelector('revo-grid').additionalData = {
transpose: {
transposedRowHeader: { cellTemplate: () => '', columnTemplate: () => '' }, // defined row/header template
transposedColumnHeader: { columnTemplate: () => '' }, // template for transposed columns
}
};
```
## Conclusion
The **Row Transpose Plugin** is a powerful tool for displaying data in different formats within RevoGrid.
Whether you're working with complex datasets that require pivoting or simply need to view your data from a different perspective, this plugin provides the flexibility to switch between views seamlessly. With customizable options for how rows and columns are transposed, you can easily tailor the grid to your needs.
---
# Selection & Focus
URL: https://pro.rv-grid.com/guides/selection/
Source: src/content/docs/guides/selection/index.mdx
Description: Install the complete RevoGrid Pro selection suite with one plugin.
`SelectionPlugin` is the master entry point for spreadsheet-style selection in
RevoGrid Pro. It composes the compatible cell, row, and column selection
runtimes while keeping their existing events, configuration, and public methods
available.
## Quick setup
```ts
import {
SelectionPlugin,
rowHeaders,
} from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.rowHeaders = rowHeaders({ showHeaderFocusBtn: false });
grid.columns = [
{ prop: 'selected', name: '', size: 52, rowSelect: true },
{ prop: 'name', name: 'Name' },
{ prop: 'team', name: 'Team' },
];
grid.plugins = [SelectionPlugin];
```
One registration enables Ctrl/Cmd multi-range selection, whole-row selection
from row headers, whole-column selection from column headers, checkbox row
selection for `rowSelect` columns, and optional range constraints.
## Included plugins
| Runtime | Responsibility | Activation/configuration |
| --- | --- | --- |
| `MultiRangeSelectionPlugin` | Disjoint cell ranges, shared range rendering, clipboard operations, and range edits | Set `grid.range = true` for range gestures; configure `grid.multiRangeSelection` when needed |
| `RowHeaderPlugin` | Excel-style whole-row selection and full-width row ranges | Configure `grid.rowHeaders` |
| `ColumnSelectionPlugin` | Whole-column selection from headers and keyboard shortcuts | Active when the suite is installed |
| `RowSelectPlugin` | Independent checkbox/programmatic row selection | Add `rowSelect: true` to a column; configure `grid.rowSelect` when needed |
| `RangeSelectionLimitPlugin` | Optional row-only or column-only interactive ranges | Disabled until `grid.rangeSelectionLimit` is set |
The suite installs these runtimes in dependency order and reuses any instance
that is already present. This is safe, although redundant:
```ts
grid.plugins = [SelectionPlugin, RowSelectPlugin];
// There is still only one RowSelectPlugin runtime.
```
Use individual plugins instead of `SelectionPlugin` when the application needs
only a smaller subset.
## Interaction ownership
The master plugin does not introduce another selection store. Each included
plugin remains the sole owner of its existing state:
- A plain cell click drops interactive row-header or column-header selection
and focuses the clicked cell.
- Row-header and column-header selection hand off to each other, so both do not
compete for the active full-width/full-height range.
- Ctrl/Cmd and Shift gestures continue to use the multi-range engine.
- Checkbox row selection stays independent from row-header selection. It keeps
its own selected rows and `rowselected` events.
- Existing plugin methods are retrieved from the child runtime as before:
```ts
const plugins = await grid.getPlugins();
const ranges = plugins
.find(plugin => plugin instanceof MultiRangeSelectionPlugin)
?.getSelectedRanges();
```
`AutoFillPlugin`, `AutoFillPreviewPlugin`, and `RangeCopyPreviewPlugin` are not
installed by `SelectionPlugin`. They own fill/copy editing previews rather than
selection state, and smart AutoFill preview versus exact-copy preview is an
intentional one-of choice. Add the preview behavior that matches the grid.
## Selection guides
Select, edit, clear, and copy disjoint cell ranges.
[Read the guide](./multi-range-selection/)
Select, extend, and toggle complete columns.
[Read the guide](./column-selection/)
Configure checkbox rows and Excel-style row-header gestures.
[Read the guide](./row-select/)
Restrict interactive ranges to one row or one column.
[Read the guide](./range-selection-limit/)
Add the optional exact-copy drag preview.
[Read the guide](./range-copy-preview/)
See the [`SelectionPlugin` API reference](/api/selection/) for the exported
class and dependency metadata.
---
# Column Selection
URL: https://pro.rv-grid.com/guides/selection/column-selection/
Source: src/content/docs/guides/selection/column-selection.mdx
Description: Column selection functionality.
Enable spreadsheet-style selection for complete visible columns. Header labels
select columns, while the dedicated sort and filter affordances keep their
normal behavior.
## User behavior
| Gesture | Result |
| --- | --- |
| Click a header | Replace the selection with that column |
| Shift-click | Select the contiguous span from the anchor column |
| Ctrl/Cmd-click | Toggle a disjoint column |
| Ctrl/Cmd+Shift-click | Add an anchored column span |
| Drag across headers | Select the dragged contiguous span |
| Ctrl/Cmd-drag | Add the dragged span |
| Ctrl+Space | Select the focused cell's entire column |
| Ctrl+Shift+Space | Extend from the column anchor to the focused column |
| Click a sort icon | Sort without changing the column selection |
The plugin reuses RevoGrid's multi-range selection engine, so copying, clearing,
and editing disjoint selected columns follow the same rules as ordinary
multi-range selections. Selections follow column `prop` values through sorting
and reordering and update to the current visible row extent after source or
filter changes.
## Events and programmatic access
`beforecolumnselection` is cancelable. Its detail describes the gesture source,
selection mode, target column, resulting column props, anchor, and original
pointer or keyboard event. Canceling it leaves the normal header action, such as
sorting a sortable column, available.
Use the plugin instance to read selected column props in visual order:
```ts
const plugin = (await grid.getPlugins())
.find(plugin => plugin instanceof ColumnSelectionPlugin);
const selectedColumns = plugin?.getSelectedColumns();
```
```javascript
// fixing render for multiframework
```
---
# Multi-Range Selection
URL: https://pro.rv-grid.com/guides/selection/multi-range-selection/
Source: src/content/docs/guides/selection/multi-range-selection.mdx
Description: Select, clear, and copy multiple disjoint RevoGrid ranges.
`MultiRangeSelectionPlugin` adds spreadsheet-style disjoint range selection while
leaving the native RevoGrid active range in charge of focus, Shift extension, and
editing.
Use it when users need to compare, clear, or copy multiple non-contiguous regions from the
same grid.
Select a range, Ctrl/Cmd+click another cell, Shift+click to extend the new
range, then copy. Plain-text clipboard output includes selected cells only, so
paste does not clear gap cells between disjoint ranges.
## Usage
```ts
import { MultiRangeSelectionPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.plugins = [MultiRangeSelectionPlugin];
```
Cross-viewport autofill is enabled by default when this plugin is installed.
Users can drag the autofill handle between pinned and main row or column
viewports, and the committed result is stored as multiple selected ranges.
Disable only that drag integration with:
```ts
grid.multiRangeSelection = { crossViewportAutofill: false };
```
## User Behavior
1. Select a cell or range normally.
2. Ctrl/Cmd+click another cell to keep the previous range and start a new active range.
3. Shift+click or Shift+Arrow extends only the active range.
4. Tab and Enter move the focused cell inside the active range.
5. Delete and Backspace clear inactive ranges plus the active range.
6. Copy includes inactive ranges plus the active range.
7. Dragging the autofill handle across pinned/main viewport boundaries fills the visible target segments.
8. Plain click clears inactive ranges and starts a normal selection.
## Clipboard Shape
Multi-range copy preserves relative shape for nearby ranges in `text/plain`.
For example, diagonal selected cells are copied with blank cells between them so
external spreadsheets can keep the diagonal layout instead of stacking values
one after another. RevoGrid also writes a custom multi-range clipboard payload;
when the payload is pasted back into RevoGrid, only the selected cells are
edited, so the blank gaps do not clear existing destination values. Very large
sparse selections fall back to selected rectangular blocks to avoid huge
clipboard payloads. Rich `text/html` output mirrors the selected clipboard
shape.
Pinned and regular viewport coordinates are normalized into whole-grid visual
order before copy, so a pinned-left cell followed by the first regular cell is
copied as adjacent clipboard columns.
If only one range is selected, RevoGrid keeps its default copy behavior.
## Programmatic API
Call the public methods on the plugin instance:
```ts
const plugin = (await grid.getPlugins())
.find(plugin => plugin instanceof MultiRangeSelectionPlugin);
plugin?.setSelectedRanges([
{ rowType: 'rgRow', colType: 'rgCol', range: { x: 0, y: 0, x1: 1, y1: 1 } },
{ rowType: 'rgRow', colType: 'rgCol', range: { x: 3, y: 0, x1: 3, y1: 1 } },
]);
const ranges = plugin?.getSelectedRanges();
plugin?.clearSelectedRanges();
```
The grid also dispatches `multirangeselectionchange` whenever the selected ranges
change.
## Rendering
Inactive cells receive:
- attribute: `multi-range-selection`
- class: `multi-range-selection-cell`
- edge classes for the range perimeter
The active range remains the native RevoGrid selection overlay.
---
# Range Copy Preview
URL: https://pro.rv-grid.com/guides/selection/range-copy-preview/
Source: src/content/docs/guides/selection/range-copy-preview.mdx
Description: Shows ghost values for the exact range copy that will be applied on drop.
`RangeCopyPreviewPlugin` shows the copied values that will be applied when a user
drags a selected range with the fill handle. It is a view-only companion plugin:
the data store is not changed until the existing drop-time range copy flow runs.
Use it when users need confidence that a drag will repeat the selected source
range exactly, without AutoFill sequence inference.
Select the first two or three populated rows in the demo, drag the fill handle
downward, and the empty target cells show ghost copied values before mouseup.
## Usage
```ts
import { RangeCopyPreviewPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.plugins = [RangeCopyPreviewPlugin];
```
The plugin uses the same core drag signal as range copy. It does not need
configuration for the default behavior.
## User Behavior
1. Select a source cell or range.
2. Drag the fill handle to a larger target range.
3. Target cells show the exact copied values in a ghost style.
4. Source cells remain visually unchanged.
5. Release the mouse to apply the core range copy.
If the temporary range is cleared, the source changes, or the plugin is destroyed,
the preview is removed immediately.
## Difference From AutoFill Preview
`AutoFillPreviewPlugin` previews smart sequence output from `AutoFillPlugin`, such
as `1, 2` becoming `3, 4` or `Mon, Tue` becoming `Wed`.
`RangeCopyPreviewPlugin` previews exact range copy output:
```txt
Draft, Approved -> Draft, Approved, Draft, Approved
North, West -> North, West, North, West
```
Use one preview plugin at a time for this drag surface:
| Plugin | Preview intent |
| --- | --- |
| `AutoFillPreviewPlugin` | Show predicted smart AutoFill sequence values. |
| `RangeCopyPreviewPlugin` | Show exact copied source values. |
## Rendering
Preview cells receive:
- attribute: `range-copy-preview`
- class: `range-copy-preview-cell`
Default styling uses reduced opacity and italic text so users can distinguish
preview values from committed data.
```css
.range-copy-preview-cell {
opacity: 0.45;
font-style: italic;
}
```
## Notes
The plugin does not mutate `source`, `newData`, or provider stores. It only
overrides the render model during `beforecellrender`, then refreshes affected row
types in a frame batch.
---
# Range Selection Limit
URL: https://pro.rv-grid.com/guides/selection/range-selection-limit/
Source: src/content/docs/guides/selection/range-selection-limit.mdx
Description: Restrict interactive range selection to a single row or column.
`RangeSelectionLimitPlugin` keeps user-created range selections in one dimension.
Use it when a workflow accepts row-only or column-only bulk actions and should not
allow rectangular selections.
The plugin does not replace RevoGrid selection. It intercepts the pending range
at the `beforeapplyrange` lifecycle point, constrains the shape, and then lets the
normal `setrange` flow continue.
## Usage
```ts
import { RangeSelectionLimitPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.range = true;
grid.plugins = [RangeSelectionLimitPlugin];
grid.rangeSelectionLimit = { mode: 'column' };
```
`grid.range = true` is still required because this plugin only constrains range
selection after core range selection is enabled.
## Modes
| Mode | Behavior | Typical use |
| --- | --- | --- |
| `column` | Keeps the focused column fixed and expands only across rows. | Select multiple rows for one field. |
| `row` | Keeps the focused row fixed and expands only across columns. | Select multiple fields for one row. |
### Column Mode
`column` keeps the focused column fixed and allows the selection to expand
vertically:
```ts
grid.rangeSelectionLimit = { mode: 'column' };
```
If the user starts on column `status` and drags diagonally, the selected range
stays inside `status` while the row bounds change.
### Row Mode
`row` keeps the focused row fixed and allows the selection to expand
horizontally:
```ts
grid.rangeSelectionLimit = { mode: 'row' };
```
If the user starts on row `12` and drags diagonally, the selected range stays on
row `12` while the column bounds change.
## Configuration Forms
Boolean shorthand is supported:
```ts
grid.rangeSelectionLimit = true; // same as { mode: 'column' }
grid.rangeSelectionLimit = false; // disabled
```
String shorthand is also supported:
```ts
grid.rangeSelectionLimit = 'column';
grid.rangeSelectionLimit = 'row';
```
The object form is preferred for new code because it leaves room for future
options:
```ts
grid.rangeSelectionLimit = {
mode: 'column',
};
```
For legacy integrations, the plugin also reads `additionalData.rangeSelectionLimit`,
but direct `grid.rangeSelectionLimit` configuration is preferred.
```ts
grid.additionalData = {
rangeSelectionLimit: {
mode: 'row',
},
};
```
## Lifecycle
The plugin uses the existing RevoGrid range lifecycle:
1. RevoGrid computes the pending range from focus and drag/keyboard input.
2. `beforeapplyrange` is emitted by the overlay selection component.
3. `RangeSelectionLimitPlugin` replaces `event.detail.range` with the constrained range.
4. RevoGrid transforms that range to row/column data and emits `beforesetrange`.
5. RevoGrid emits `setrange`, and the selection store receives the constrained range.
The plugin does not add render layers, change data, or override single-cell
focus. If there is no focused cell, it uses the pending range start as the anchor.
## API
```ts
export type RangeSelectionLimitMode = 'column' | 'row';
export type RangeSelectionLimitOptions = {
mode: RangeSelectionLimitMode;
};
export type RangeSelectionLimitConfig =
| boolean
| RangeSelectionLimitMode
| RangeSelectionLimitOptions;
```
## Future Shape Validation
Arbitrary shape validation should be added as a separate mode or validator API so
the existing `row` and `column` modes keep their current behavior.
---
# Row Selection
URL: https://pro.rv-grid.com/guides/selection/row-select/
Source: src/content/docs/guides/selection/row-select.mdx
Description: Row selection in grid
Apply selection to rows in grid.
This is an advanced feature that allows users to select multiple rows in grid and explains how to implement this with the help of the `RowSelectPlugin`.
It is an extremely **powerful and flexible feature**, despite its straightforward functionality.
1. **Handles Complex Edge Cases**: The selection plugin is built to support **extreme edge case scenarios**, ensuring it remains reliable and performant in even the most challenging use cases.
2. **Efficient Redraw Management**: Plugin provides **better control** over the selection updates, boosting **performance** and responsiveness for large datasets. Also it doesn't modify original data set.
3. **Visual Feedback with Highlighted Rows**: The plugin highlights **selected rows** and applies relevant CSS classes, providing **instant visual feedback** to users, making the grid more interactive and intuitive.
4. **Bulk Selection Support**: The plugin simplifies bulk operations by enabling **select all** functionality, allowing users to select and manipulate **multiple rows** at once.
5. **Event-Driven Row Selection**: The plugin emits a `rowselected` event whenever a row is selected, enabling easy collection of **selected rows** and providing flexibility for further actions.
6. **Interaction with Other Features**: The plugin works smoothly with **filtering, sorting, grouping, tree data, and drag-and-drop**, allowing users to **select rows** while still interacting with other grid features without interference.
7. **Advanced Plugin Example**: The `RowSelectPlugin` serves as an **advanced example** of how to create sophisticated, flexible grid plugins, demonstrating how to implement custom solutions for specific needs.
8. **Foundation for Future Plugins**: The plugin sets a strong **foundation** for building more advanced system features, providing a **reusable and extensible pattern** for other interactive grid-based plugins.
- **Row Selection**: Allows users to select multiple rows in the grid.
- **Column Selection**: Enables users to select entire columns.
- **Grouped Row Selection**: Can render optional group-level checkboxes that select all descendant data rows.
- **Checkbox-Based Row Reorder**: Can hand checkbox-selected rows to `RowOrderPlugin` so checked rows drag as one compact block.
- **Automatic Theme Support**: The plugin adapts to light or dark themes based on user settings.
- **Flexible and Extensible**: This plugin demonstrates the potential for creating custom plugins, encouraging developers to expand the grid's functionality further.
To enable the `RowSelectPlugin` in your RevoGrid setup, you have two options:
1. **Using `rowSelect` Prop in Columns**
2. **Using `columnType` with `RowSelectColumnType`**
## Option 1: Using `rowSelect` Prop in Columns
In this approach, the rowSelect property in the column definition enables checkbox-based row selection for the specific column.
```typescript
grid.columns = [
{
prop: '_check',
rowSelect: true,
},
...
];
// Define plugin
grid.plugins = [RowSelectPlugin];
```
## Option 2: Using columnType with RowSelectColumnType
Alternatively, you can use a custom columnType and the RowSelectColumnType to enable row selection.
```typescript
import { RowSelectColumnType, RowSelectPlugin, DEFAULT_SEL_PROP } from '@revolist/revogrid-pro';
grid.source = [];
// Define columns
grid.columns = [{
prop: DEFAULT_SEL_PROP,
columnType: 'select' // Use columnType 'select'
}, ...];
// Define plugin
grid.plugins = [RowSelectPlugin];
// Define the custom column type for selection
grid.columnTypes = { select: new RowSelectColumnType()};
```
## Pointer And Keyboard Gestures
Checkboxes and row headers are independent selection modes. Checkbox state,
anchors, programmatic selection, and `rowselected` events remain owned by
`RowSelectPlugin`. Spreadsheet-style row-header state and anchors remain owned
by `RowHeaderPlugin`, so using one mode never replaces or toggles the other.
Row-header selections are also exposed as full-width multi-ranges, allowing
copy, Delete, and other range operations to act on every selected row. Delete
and Backspace clear editable cells across pinned and regular column sections;
readonly cells and values rejected by strict column validation remain unchanged:
| Gesture | Result |
| --- | --- |
| Click a row checkbox | Toggle that row without clearing other checked rows |
| Click the row-header number, surrounding area, or drag handle | Replace selection and focus the whole row |
| Click an ordinary cell after row-header or row-header drag-handle selection | Clear the row selection and focus the cell |
| Ctrl-click / Cmd-click an ordinary cell after row-header selection | Keep the row selection and add the cell selection |
| Shift-click an ordinary cell after row-header selection | Keep the row selection while extending the active cell range |
| Ctrl-click / Cmd-click a row header | Toggle one row and preserve the other selected rows |
| Shift-click a row header | Select the visible range from the selection anchor |
| Ctrl/Cmd+Shift-click a row header | Add the anchored visible range |
| Space on a focused row-selection checkbox | Toggle that row |
Selections are stored against source rows. Sorting, filtering, and virtual
scrolling do not convert them into rendered viewport positions. A selected row
therefore remains selected while it is outside the viewport or temporarily
hidden by a filter.
## Grouped Row Selection
Grouped row checkboxes are opt-in so existing grouped grids keep their current rendering.
```typescript
grid.columns = [
{ prop: 'selected', rowSelect: true },
{ prop: 'name', name: 'Name' },
{ prop: 'department', name: 'Department' },
];
grid.grouping = { props: ['department'], expandedAll: true };
grid.rowSelect = {
grouping: true,
};
grid.plugins = [RowSelectPlugin];
```
You can also use the future-compatible object form:
```typescript
grid.rowSelect = {
grouping: {
enabled: true,
},
};
```
When enabled, selecting a group checkbox selects all descendant data rows. Selecting only part of a group reflects an indeterminate state on the group checkbox. Synthetic grouping rows are not included in selected row counts.
## Checkbox-Based Row Reorder
`RowSelectPlugin` can opt into `RowOrderPlugin` integration. When enabled, dragging a checked row moves all visible checked data rows in the current row viewport as a compact block.
This feature is disabled by default. Existing row-order behavior is unchanged unless `rowSelect.rowOrder` is enabled.
```typescript
import { RowOrderPlugin, RowSelectPlugin } from '@revolist/revogrid-pro';
grid.columns = [
{ prop: 'drag', name: '', rowDrag: true, size: 42 },
{ prop: 'selected', name: '', rowSelect: true, size: 56 },
{ prop: 'name', name: 'Name' },
{ prop: 'team', name: 'Team' },
];
grid.rowSelect = {
rowOrder: true,
};
grid.rowOrder = {
prop: 'name',
preview: 'compact',
};
grid.plugins = [RowSelectPlugin, RowOrderPlugin];
```
You can also use the object form:
```typescript
grid.rowSelect = {
rowOrder: {
enabled: true,
},
};
```
Checkbox selection takes priority over range selection only when the dragged row is checked. If the dragged row is not checked, `RowOrderPlugin` falls back to its current range-selection or single-row behavior.
Only visible checked data rows move during the drag. Rows hidden by filtering, collapsed grouping, or collapsed tree branches remain selected but are not part of that drag operation. Synthetic grouping rows are never moved as selected data rows.
## Grouping And Row Reorder Together
Grouped row checkboxes and checkbox-based row reorder can be enabled together:
```typescript
grid.grouping = { props: ['team'], expandedAll: true };
grid.rowSelect = {
grouping: true,
rowOrder: true,
};
grid.rowOrder = {
prop: 'name',
preview: 'compact',
};
grid.plugins = [RowSelectPlugin, RowOrderPlugin];
```
With this setup:
- Group checkboxes select or clear all descendant data rows.
- Dragging a checked child row moves visible checked child rows as a compact block.
- Parent and ancestor group checkboxes reflect child selection with checked or indeterminate state.
- Group headers are excluded from select-all counts and row-order moved items.
- Row-order grouping validation still applies, so invalid cross-group drops remain blocked unless your row-order configuration explicitly allows applying target group values.
## Filtering, Sorting, Row Drag, And Tree Data
Row selection is stored against real data row indexes, not the rendered row position. This keeps checkbox state stable when the visible row topology changes.
| Case | Behavior |
| --- | --- |
| Filtering | Selected hidden rows remain selected. The `rowselected` event is re-emitted so `visibleCount` and `visibleRowsCount` reflect the filtered view. |
| Sorting | Checkbox state follows the same data rows after sort order changes. |
| Row drag | Checkbox state follows moved data rows after row order changes. |
| Checkbox row-order | When `rowSelect.rowOrder` is enabled, visible checked rows are moved as a compact block before range selection is considered. |
| Grouping | Group checkboxes are opt-in through `rowSelect.grouping`; synthetic group rows are not counted as selected rows. |
| Tree data | When `TreeDataPlugin` is registered, selecting a tree parent toggles the parent and descendants. Dragging a checkbox-selected tree parent with descendants preserves the subtree relationship. |
## API Quick Reference
| API | Default | Meaning |
| --- | --- | --- |
| `ColumnRegular.rowSelect` | `false` | Renders row-selection checkboxes in normal data cells for that column. A predicate can hide checkboxes for specific cells. |
| `ColumnRegular.rowSelectLabel` | `Select row N` | Accessible checkbox name, as a localized string or `(cell) => string` callback. Use the row's display caption for field lists and other named records. |
| `ColumnRegular.rowSelectAllLabel` | `Select all rows` | Localized accessible name for the column's select-all checkbox. |
| `grid.rowSelect.grouping.label` | `Select group NAME` | Localized `(name) => string` checkbox name when grouping is configured with `{ enabled: true, label }`. Mixed selection state is announced separately. |
| `grid.rowSelect.grouping` | `false` | Enables group-level row-selection checkboxes. Accepts `true` or `{ enabled: true }`. |
| `grid.rowSelect.rowOrder` | `false` | Enables checkbox-selected row drag integration with `RowOrderPlugin`. Accepts `true` or `{ enabled: true }`. |
| `additionalData.rowSelect` | none | Legacy alias for `grid.rowSelect`. Prefer direct `grid.rowSelect`. |
The `rowselected` event exposes totals for both all selectable rows and the currently visible selectable rows:
| Event field | Meaning |
| --- | --- |
| `count` | Selected real data rows across the full dataset. |
| `allRowsCount` | Selectable real data rows across the full dataset. |
| `visibleCount` | Selected real data rows currently visible. |
| `visibleRowsCount` | Selectable real data rows currently visible. |
## Bulk Row Selection Demo
The live demo on this page is the common **Bulk Row Selection** example. It includes flat, grouped, and tree modes, a row checkbox column, row drag handles, group checkbox selection, filtering, sorting, selected/visible totals, and opt-in checkbox-based row reorder.
---
# Server-side Row Grouping
URL: https://pro.rv-grid.com/guides/server-side-grouping/
Source: src/content/docs/guides/server-side-grouping.mdx
Description: Group, expand, and lazy-load millions of rows without loading the full dataset into the browser.
Server-side row grouping for large datasets lets RevoGrid render grouped data from a backend route API. The browser keeps only visible grouped blocks and cached route blocks, while your server owns grouping, sorting, filtering, quick filtering, counts, and aggregates.
Use it for ERP, financial analytics, logs, operations, and reporting tools where a complete client-side dataset is too large.
- Group by one or multiple columns.
- Lazy-load group children on expand.
- Works with server-side Infinity Scroll semantics.
- Supports unknown row counts.
- Keeps browser memory low with block caching.
- Sends sort, filter, quick-filter, and group state to your backend.
## Setup
```ts
import { ServerSideGroupingPlugin } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.serverSideGrouping = {
groupBy: ['country', 'sport'],
loadRows: async (request, signal) => {
const response = await fetch('/api/grid/grouping', {
method: 'POST',
signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
return response.json();
},
};
grid.plugins = [ServerSideGroupingPlugin];
```
`groupBy` and `loadRows` are the only required options. The full configuration can add request tuning, a quick-filter payload, an imperative plugin controller, and an optimized expand-all datasource:
```ts
let groupingPluginControllerApi;
grid.serverSideGrouping = {
groupBy: ['country', 'sport'],
blockSize: 100,
purgeClosedGroups: false,
maxConcurrentRequests: 4,
blockLoadDebounceMs: 25,
quickFilter: { text: '' },
api: api => {
groupingPluginControllerApi = api;
},
loadRows,
loadExpandedGroups: async (request, signal) => {
const response = await fetch('/api/grid/grouping/expand-all', {
method: 'POST',
signal,
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
});
return response.json();
},
};
```
`api` is not a server endpoint. It is a callback that gives the application an imperative controller after the plugin is initialized. Use it from toolbar buttons, route changes, or refresh actions:
```ts
groupingPluginControllerApi?.refreshServerSide();
groupingPluginControllerApi?.expandGroup(['Germany']);
groupingPluginControllerApi?.purgeServerSideCache();
```
`loadRows` is the required server datasource. It loads one route block at a time: root groups, child groups, or final leaf rows. `loadExpandedGroups` is optional and only optimizes `expandAllGroups()` by letting the backend return a flattened group tree in one request.
When `AdvanceFilterPlugin` is active, assigning `grid.quickFilter` refreshes the grouped datasource and forwards normalized `{ text, columns? }` data in every request. An explicitly assigned grid-level quick filter takes precedence over `serverSideGrouping.quickFilter`, including an explicit empty value used to clear search. The config value remains the fallback until the grid-level property is assigned.
The plugin owns the main row source while active. `InfinityScrollPlugin` is complementary in behavior and request shape, but should not also own the same `rgRow` source. If both plugins are registered and `serverSideGrouping` is configured, Infinity Scroll no-ops with a warning.
When combining server-side grouping with selection filters, provide `filter.selection.getItems` from the same backing datasource used by your server loader. The grid source contains generated group rows and only the currently loaded route blocks, so deriving selection options from the visible grid source can produce incomplete values such as only `Empty`.
## Request Contract
`loadRows(request, signal)` receives:
```ts
{
route: ['Germany'],
level: 1,
groupBy: ['country', 'sport'],
start: 0,
limit: 100,
sort: { revenue: 'desc' },
filter: { country: { type: 'contains', value: 'Ger' } },
quickFilter: { text: 'premium' },
parent: { key: 'Germany', count: 12450 },
levelParams: { tenantId: 'acme' }
}
```
Return group rows before the final grouping level:
```ts
{
groups: [
{ key: 'Swimming', count: 2100, aggregates: { Revenue: 'EUR 9.2M' } },
{ key: 'Cycling', count: 1540 }
],
rowCount: 2,
grandTotals: { Revenue: 'EUR 18.7M' }
}
```
Return leaf rows at the final level:
```ts
{
rows: [
{ id: 1, country: 'Germany', sport: 'Swimming', city: 'Berlin', revenue: 1200 }
],
rowCount: 2100,
hasMore: true
}
```
When `rowCount` is omitted, RevoGrid treats the count as unknown and keeps a loading tail while `hasMore` is true.
## Runtime API
The API is exposed through the config callback, not by mutating the grid instance.
```ts
grid.serverSideGrouping = {
groupBy: ['country'],
loadRows,
api: api => {
groupingPluginControllerApi = api;
groupingPluginControllerApi?.expandGroup(['Germany']);
groupingPluginControllerApi?.expandAllGroups();
groupingPluginControllerApi?.refreshRoute(['Germany']);
groupingPluginControllerApi?.purgeServerSideCache();
console.log(groupingPluginControllerApi?.getServerSideCacheState());
},
};
```
Available methods:
| Method | Description |
|---|---|
| `expandGroup(id)` | Expands and lazy-loads a group route. |
| `expandAllGroups()` | Loads group blocks recursively and expands every server group route. Leaf-row blocks still load from the viewport. |
| `collapseGroup(id)` | Collapses a group route. |
| `collapseAllGroups()` | Collapses all group routes currently known by the cache. |
| `refreshServerSide()` | Invalidates all route blocks and reloads root. |
| `refreshRoute(route)` | Invalidates only one route branch when possible. |
| `purgeServerSideCache(route?)` | Clears all cache or one route branch. |
| `getServerSideCacheState()` | Returns debug route, block, loading, and queue state. |
`expandAllGroups()` opens every group route. It does not load every leaf row, so large final groups stay virtualized and fetch visible row blocks through the normal server-side scroll path.
When `loadExpandedGroups` is configured, `expandAllGroups()` uses that single endpoint instead of recursively loading route blocks:
```ts
grid.serverSideGrouping = {
groupBy: ['country', 'sport'],
loadRows,
loadExpandedGroups: async request => ({
groups: [
{ route: ['Germany'], key: 'Germany', count: 12450 },
{ route: ['Germany', 'Swimming'], key: 'Swimming', count: 2100 },
{ route: ['Germany', 'Cycling'], key: 'Cycling', count: 1540 },
],
}),
};
```
Each returned group row must include its full `route`. The plugin rebuilds group route blocks from this flattened tree and keeps leaf-row blocks unloaded until they enter the viewport.
## Configuration
| Option | Default | Description |
|---|---:|---|
| `groupBy` | required | Grouping column props in server route order. |
| `blockSize` | `100` | Direct children requested per block. |
| `purgeClosedGroups` | `false` | Purge child route cache on collapse. |
| `maxConcurrentRequests` | `4` | Limits simultaneous route block requests. |
| `blockLoadDebounceMs` | `0` | Delays block load dispatch after scroll/materialization. |
| `quickFilter` | `undefined` | Global filter payload forwarded to the server. |
| `api` | `undefined` | Receives the client-side plugin controller. This is not a server request callback. |
| `loadRows` | required | Server datasource for root groups, nested group routes, and final leaf rows. |
| `loadExpandedGroups` | `undefined` | Optional one-request expand-all endpoint that returns flattened group rows with routes. |
---
# Cell Validation with Renderers
URL: https://pro.rv-grid.com/guides/validate-basic/
Source: src/content/docs/guides/validate-basic.mdx
Description: Explore different methods for validating cell data in RevoGrid and ensuring data integrity.
One straightforward way to validate cell data is by combining `validate`, `validationTooltip`, and `validationRenderer`.
This marks invalid cells during rendering without changing edit behavior, which is useful for audits, imported data review, and soft warnings.
```ts
import { validationRenderer } from '@revolist/revogrid-pro';
const columns = [
{
prop: 'price',
name: 'Price',
validate: (value, row) => value >= row.floor && value <= row.ceiling,
validationTooltip: (value, row) =>
`Price ${value} must stay between ${row.floor} and ${row.ceiling}`,
...validationRenderer({
severity: 'error',
indicatorPlacement: 'corner',
messagePlacement: 'tooltip',
}),
},
];
```
`validationRenderer` returns `cellProperties` and `cellTemplate`, so it can wrap your existing cell renderer while keeping validation styling in one place.
The helper supports:
- `severity`: `error`, `warning`, or `info`.
- `indicatorPlacement`: `corner`, `start`, `end`, or `none`.
- `messagePlacement`: `tooltip`, `inline`, `both`, or `none`.
- `message`: a custom message resolver when `validationTooltip` is not enough.
- `invalidProperties`: extra cell properties for invalid cells.
`messagePlacement` controls where the validation message is exposed:
- `tooltip` adds `title` and tooltip data attributes.
- `inline` renders the message inside the cell without tooltip attributes.
- `both` renders inline text and tooltip attributes.
- `none` keeps the visual indicator but hides the message text.
:::note
This renderer-only approach shows invalid data already present in the grid. To prevent invalid edits from being committed, use `CellValidatePlugin` from the input validation guide.
:::
---
# Validate user data input
URL: https://pro.rv-grid.com/guides/validate-input/
Source: src/content/docs/guides/validate-input.mdx
Description: Explore strict and soft cell validation for spreadsheet-style data entry.
This demo exposes the rules as controls. Try strict mode to reject invalid edits, soft mode to save invalid data with markers, and the seed/reset actions to simulate cleanup workflows.
The **CellValidatePlugin** validates edits before they are written to the grid source. Use it when the grid should enforce spreadsheet-style rules such as required fields, numeric ranges, enum membership, cross-field limits, and approval gates.
Install it together with **EventManagerPlugin** so edit events are collected through the Pro event pipeline:
```ts
import {
CellValidatePlugin,
EventManagerPlugin,
} from '@revolist/revogrid-pro';
grid.plugins = [
EventManagerPlugin,
CellValidatePlugin,
];
grid.eventManager = {
applyEventsToSource: true,
};
```
:::note
`EventManagerPlugin` should be registered before `CellValidatePlugin`. Validation resolves inside the event manager's pre-commit phase, so normal `gridedit` consumers such as history, audit, flash, and collaboration observe only the accepted patch.
:::
## Clipboard validation resolution
Clipboard paste is atomic by default: one strict validation failure rejects the complete paste. Opt into `valid-cells` when valid destinations should be applied while invalid destinations keep their previous values and validation feedback:
```ts
grid.useClipboard = {
validationResolution: 'valid-cells',
};
```
The policy applies to standard keyboard paste, multi-range/cross-viewport paste, and context-menu paste. It does not change ordinary edits, autofill, readonly filtering, or soft validation. The accepted cells remain one edit transaction, so Undo restores them together without touching rejected destinations.
## Strict and soft validation
By default validation is strict. If `validate(value, model)` returns `false`, the edit is rejected and the source keeps the previous value.
```ts
const columns: ColumnRegular[] = [
{
name: 'Price',
prop: 'price',
validate: (value, model) => {
const price = Number(value);
return Number.isFinite(price) && price >= 50 && price <= 1200;
},
validationTooltip: (value) =>
`Price ${value} must be between 50 and 1200`,
},
];
```
Set `softValidation: true` when invalid values should still be saved and marked visually. This is useful for imported data cleanup flows where users need to see and fix invalid rows in place. Pair it with `validationRenderer` when you want saved invalid values to stay visible after the edit.
```ts
{
prop: 'discount',
softValidation: true,
...validationRenderer(),
validate: (value, model) => Number(value) <= Number(model.price) * 0.3,
validationTooltip: (value, model) =>
`Discount cannot exceed 30% of price (${Math.round(Number(model.price) * 0.3)})`,
}
```
## Candidate row model
Both `validate(value, model)` and `validationTooltip(value, model)` receive the row model. During strict edit validation, the model includes the pending row patch, so cross-field validation works for single edits and pasted batches. If the edit is rejected, the tooltip renderer receives the rejected value and candidate model, which lets the message explain what failed even though the source was not changed.
```ts
{
name: 'Approved',
prop: 'approved',
validate: (value, model) => {
const netPrice = Number(model.price) - Number(model.discount ?? 0);
return netPrice <= 900 || value === true;
},
validationTooltip: () => 'High-value rows require approval',
}
```
`validationTooltip` also receives the column as a third argument: `validationTooltip(value, model, column)`. Use that when one shared message resolver needs column metadata.
## Cross-column example
The validation function can also block a cell based on another column in the same row. This example prevents fees on expired services.
---
# Aggregations
URL: https://pro.rv-grid.com/api/aggregations/
Source: src/content/docs/api/aggregations.md
### `commonAggregators`
Defines a collection of common aggregation functions used for table calculations.
These aggregators transform an array of numerical values into meaningful statistical summaries.
```ts
commonAggregators: {
sum: (values: any[]) => any;
count: (values: any[]) => number;
avg: (values: any[]) => number;
min: (values: any[]) => number;
max: (values: any[]) => number;
median: (values: any[]) => number;
mode: (values: any[]) => string | 0;
range: (values: any[]) => number;
variance: (values: any[]) => number;
stdDev: (values: any[]) => number;
first: (values: any[]) => any;
last: (values: any[]) => any;
distinct: (values: any[]) => number;
};
```
---
### `advancedAggregators`
```ts
advancedAggregators: {
'%oftotal': (values: any[]) => number[];
accSum: (values: any[]) => any;
};
```
---
### `Aggregations`
```ts
export type Aggregations = Record any>;
```
---
# Array Renderer
URL: https://pro.rv-grid.com/api/array-renderer/
Source: src/content/docs/api/array-renderer.md
### `arrayRenderer`
The `arrayRenderer` is a custom cell renderer for RevoGrid that can handle both single values
and arrays of values. It applies different renderers based on the value type.
**Features**:
- Detects if the value is an array or a single value
- For arrays, renders multiple elements using the provided renderer
- For single values, renders a single element using the provided renderer
- Supports custom separator between array items
- Supports custom wrapper for array items
**Usage**:
- Import `arrayRenderer` and use it as a wrapper around your existing renderer
- Configure the renderer with options like separator and wrapper
### Example
```typescript
import { arrayRenderer } from '@revolist/revogrid-pro';
import { linkRenderer } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'links',
name: 'Links',
cellTemplate: arrayRenderer(linkRenderer, {
separator: ', ',
wrapper: 'div',
}),
},
];
```
Example of using multiRenderer to handle different types of values
prop: 'data',
name: 'Data',
cellTemplate: multiRenderer({
default: progressLineRenderer,
renderers: [
{
// Use linkRenderer for URLs
condition: (value) => typeof value === 'string' &&
(value.startsWith('http://') || value.startsWith('https://')),
renderer: linkRenderer,
},
{
// Use progressLineRenderer for numbers
condition: (value) => typeof value === 'number',
renderer: progressLineRenderer,
},
],
separator: ' | ',
wrapper: 'div',
wrapperClass: 'multi-data-container',
}),
};
```ts
export function arrayRenderer(
renderer: ColumnRegular['cellTemplate'],
options: ArrayRendererOptions = {}): Required['cellTemplate'];
```
---
### `multiRenderer`
The `multiRenderer` is a custom cell renderer for RevoGrid that can apply different renderers
based on the value type. It's similar to `arrayRenderer` but allows for more complex rendering logic.
**Features**:
- Detects if the value is an array or a single value
- For arrays, applies a different renderer for each item based on a condition
- For single values, applies a default renderer
- Supports custom separator between array items
- Supports custom wrapper for array items
**Usage**:
- Import `multiRenderer` and use it to define different renderers for different value types
- Configure the renderer with options like separator and wrapper
### Example
```typescript
import { multiRenderer } from '@revolist/revogrid-pro';
import { linkRenderer } from '@revolist/revogrid-pro';
import { progressLineRenderer } from '@revolist/revogrid-pro';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'data',
name: 'Data',
cellTemplate: multiRenderer({
default: progressLineRenderer,
renderers: [
{
condition: (value) => typeof value === 'string' && value.startsWith('http'),
renderer: linkRenderer,
},
],
}),
},
];
```
```ts
export function multiRenderer(config: {
/**
* Default renderer to use if no condition matches
*/
default: ColumnRegular['cellTemplate'];
/**
* Array of renderers with conditions
*/
renderers: Array<{
/**
* Condition function that returns true if the renderer should be used
*/
condition: (value: any) => boolean;
/**
* Renderer to use if the condition is true
*/
renderer: ColumnRegular['cellTemplate'];
}>;
/**
* Separator between array items
* @default ', '
*/
separator?: string;
/**
* Wrapper element for array items
* @default 'div'
*/
wrapper?: string;
/**
* CSS class for the wrapper element
*/
wrapperClass?: string;
/**
* CSS style for the wrapper element
*/
wrapperStyle?: Record;
}): ColumnRegular['cellTemplate'];
```
---
### `ArrayRendererOptions`
```ts
export type ArrayRendererOptions = {
/**
* Separator between array items
* @default ', '
*/
separator?: string;
/**
* Wrapper element for array items
* @default 'div'
*/
wrapper?: string;
/**
* CSS class for the wrapper element
*/
wrapperClass?: string;
/**
* CSS style for the wrapper element
*/
wrapperStyle?: Record;
};
```
---
# Audit History
URL: https://pro.rv-grid.com/api/audit-history/
Source: src/content/docs/api/audit-history.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`)
```ts
interface HTMLRevoGridElement {
auditHistory?: AuditHistoryConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* @deprecated Use the direct `grid.auditHistory` property instead.
*/
auditHistory?: AuditHistoryConfig
}
```
---
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
auditrecord: AuditRecord;
auditrecordsloaded: AuditRecord[];
auditerror: AuditHistoryError;
beforeauditrestore: AuditRestoreEvent;
auditrestore: AuditRestoreEvent
}
```
## Plugin API
### `createLocalStorageAuditHistoryAdapter`
```ts
export function createLocalStorageAuditHistoryAdapter(storageKey = DEFAULT_STORAGE_KEY): AuditHistoryStorageAdapter;
```
---
### `AuditHistoryPlugin`
#### Dependencies
- **Auto-installed** `HistoryPlugin`: Auto-installs or reuses HistoryPlugin for undo/redo shortcuts and replay integration.
```ts
class AuditHistoryPlugin {
getRecords(filter?: AuditHistoryFilter): AuditRecord[];
getCellHistory(rowId: string | number, column: ColumnProp): AuditRecord[];
getRowHistory(rowId: string | number): AuditRecord[];
getRecord(id: string): AuditRecord | undefined;
getTransaction(transactionId: string): AuditRecord[];
getLastChange(rowId: string | number, column?: ColumnProp): AuditChange | undefined;
getStats(filter?: AuditHistoryFilter): AuditHistoryStats;
getRowIdentity(row: DataType | undefined, rowIndex: number, rowType: DimensionRows): string | undefined;
clear(): boolean;
replaceRecords(records: AuditRecord[], options: { merge?: boolean; persist?: boolean } = {}): AuditRecord[];
recordEvent(input: AuditRecordEventInput): AuditRecord | undefined;
createSnapshot(label: string, options: AuditSnapshotOptions = {}): AuditRecord | undefined;
restoreSnapshot(recordOrId: AuditRecord | string): boolean;
async refreshRecords(options: { merge?: boolean } = { merge: true }): Promise;
exportRecords(options: AuditHistoryExportOptions = {}, selection?: AuditRecord[]): string;
canRestoreRecord(context: AuditHistoryRestoreContext): boolean;
restoreCell(change: AuditChange): boolean;
restoreRow(recordOrRowId: AuditRecord | string | number): boolean;
restoreTransaction(transactionId: string): boolean;
}
```
---
### `AuditUser`
```ts
export type AuditUser = {
id: string;
name?: string;
email?: string;
};
```
---
### `BuiltInAuditActionType`
```ts
export type BuiltInAuditActionType =
| 'cell-change'
| 'bulk-paste'
| 'row-added'
| 'row-deleted'
| 'row-restored'
| 'undo'
| 'redo'
| 'restore-cell'
| 'restore-row'
| 'restore-transaction'
| 'api-sync'
| 'formula-update'
| 'snapshot-created'
| 'snapshot-restored';
```
---
### `AuditActionType`
```ts
export type AuditActionType = BuiltInAuditActionType | (string & {});
```
---
### `AuditHistorySource`
```ts
export type AuditHistorySource =
| 'manual'
| 'paste'
| 'api'
| 'formula'
| 'restore'
| 'snapshot'
| 'system';
```
---
### `AuditRecordPresentation`
```ts
export type AuditRecordPresentation = {
source?: AuditHistorySource;
verb?: string;
targetLabel?: string;
detailLabel?: string;
rangeLabel?: string;
avatarLabel?: string;
avatarIndex?: number;
avatarColor?: string;
accent?: boolean;
};
```
---
### `AuditChangePresentation`
```ts
export type AuditChangePresentation = {
cellLabel?: string;
columnLabel?: string;
};
```
---
### `AuditChange`
```ts
export type AuditChange = {
id: string;
rowId?: string;
rowIndex?: number;
rowType: DimensionRows;
column?: ColumnProp;
oldValue?: unknown;
newValue?: unknown;
oldRow?: DataType;
newRow?: DataType;
metadata?: Record;
presentation?: AuditChangePresentation;
};
```
---
### `AuditRecord`
```ts
export type AuditRecord = {
id: string;
transactionId: string;
type: AuditActionType;
changedAt: string;
changedBy: AuditUser;
changes: AuditChange[];
metadata?: Record;
presentation?: AuditRecordPresentation;
};
```
---
### `AuditHistoryFilter`
```ts
export type AuditHistoryFilter = {
rowId?: string | number;
column?: ColumnProp;
userId?: string;
userEmail?: string;
actionType?: AuditActionType;
transactionId?: string;
dateFrom?: string | Date;
dateTo?: string | Date;
};
```
---
### `AuditHistoryStorageAdapter`
```ts
export type AuditHistoryStorageAdapter = {
load?: () => AuditRecord[] | Promise;
save?: (records: AuditRecord[]) => void | Promise;
append?: (record: AuditRecord, records: AuditRecord[]) => void | Promise;
clear?: () => void | Promise;
};
```
---
### `AuditHistoryError`
```ts
export type AuditHistoryError = {
phase:
| 'get-current-user'
| 'metadata'
| 'sanitize'
| 'storage-load'
| 'storage-save'
| 'storage-append'
| 'storage-clear'
| 'on-record'
| 'restore'
| 'export'
| 'replace-records'
| 'record-event'
| 'track-record'
| 'snapshot';
error: unknown;
record?: AuditRecord;
};
```
---
### `AuditHistoryRestoreContext`
```ts
export type AuditHistoryRestoreContext = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `AuditHistoryStats`
```ts
export type AuditHistoryStats = {
totalRecords: number;
totalChanges: number;
firstChangedAt?: string;
lastChangedAt?: string;
byType: Partial>;
byUser: Record;
};
```
---
### `AuditHistoryExportFormat`
```ts
export type AuditHistoryExportFormat = 'json' | 'csv';
```
---
### `AuditHistoryExportOptions`
```ts
export type AuditHistoryExportOptions = {
format?: AuditHistoryExportFormat;
filter?: AuditHistoryFilter;
includeMetadata?: boolean;
};
```
---
### `AuditRecordTrackContext`
```ts
export type AuditRecordTrackContext = {
type: AuditActionType;
changes: AuditChange[];
source?: AuditHistorySource;
metadata?: Record;
record: AuditRecord;
event?: unknown;
};
```
---
### `AuditHistoryConfig`
```ts
export type AuditHistoryConfig = {
getCurrentUser?: () => AuditUser;
getMetadata?: (context: { type: AuditActionType; changes: AuditChange[] }) => Record | undefined;
rowIdProp?: string;
getRowId?: (
row: DataType,
context: { rowIndex: number; rowType: DimensionRows },
) => string | number | undefined;
sanitizeValue?: (
value: unknown,
context: {
rowId?: string;
rowIndex?: number;
rowType: DimensionRows;
column?: ColumnProp;
direction: 'old' | 'new';
},
) => unknown;
shouldTrackChange?: (
change: Omit,
context: { type: AuditActionType },
) => boolean;
shouldTrackRecord?: (context: AuditRecordTrackContext) => boolean;
trackedActionTypes?: AuditActionType[];
ignoredActionTypes?: AuditActionType[];
ignoredColumns?: ColumnProp[] | ((context: { column?: ColumnProp; rowType: DimensionRows; rowId?: string }) => boolean);
captureSourceUpdates?: boolean;
immutable?: boolean;
canRestore?: (context: AuditHistoryRestoreContext) => boolean;
storage?: 'memory' | 'localStorage' | 'custom';
storageKey?: string;
storageAdapter?: AuditHistoryStorageAdapter;
storageProviders?: AuditHistoryStorageAdapter[];
mergeStorageProviders?: boolean;
records?: AuditRecord[];
maxRecords?: number;
disabled?: boolean;
onAuditRecord?: (record: AuditRecord) => void | Promise;
onAuditError?: (error: AuditHistoryError) => void;
onRecordsLoaded?: (records: AuditRecord[]) => void;
};
```
---
### `AuditEventChangeInput` (Extended from `index.ts`)
```ts
export type AuditEventChangeInput = Omit & { id?: string };
```
---
### `AuditRecordEventInput`
```ts
export type AuditRecordEventInput = {
type: AuditActionType;
changes?: AuditEventChangeInput[];
changedAt?: string;
changedBy?: AuditUser;
transactionId?: string;
metadata?: Record;
presentation?: AuditRecordPresentation;
allowEmpty?: boolean;
};
```
---
### `AuditSnapshotOptions`
```ts
export type AuditSnapshotOptions = {
rowType?: DimensionRows;
rows?: DataType[];
metadata?: Record;
presentation?: AuditRecordPresentation;
};
```
---
### `AuditRestoreType`
```ts
export type AuditRestoreType = 'cell' | 'row' | 'transaction' | 'snapshot';
```
---
### `AuditRestoreEvent`
```ts
export type AuditRestoreEvent = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `defineAuditHistoryPanel`
Renders the Audit History panel into `el` for the given RevoGrid element.
The grid must have `AuditHistoryPlugin` installed.
```ts
export function defineAuditHistoryPanel(el: HTMLElement, grid: HTMLRevoGridElement, options: AuditHistoryPanelOptions = {}): AuditHistoryPanelHandle;
```
---
### `AuditHistoryPanelDateFormatContext`
```ts
export type AuditHistoryPanelDateFormatContext = {
mode: 'time' | 'day' | 'filter';
record?: AuditRecord;
};
```
---
### `AuditHistoryPanelEvents`
```ts
export type AuditHistoryPanelEvents = {
beforeRecordSelect?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeCompareOpen?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeJumpToCell?: (context: AuditHistoryPanelJumpContext) => boolean | void;
beforeRestore?: (context: AuditHistoryPanelRestoreContext) => boolean | void;
beforeExport?: (context: AuditHistoryPanelExportContext) => boolean | void;
};
```
---
### `AuditHistoryPanelExportContext`
```ts
export type AuditHistoryPanelExportContext = {
format: AuditHistoryExportFormat;
};
```
---
### `AuditHistoryPanelJumpContext`
```ts
export type AuditHistoryPanelJumpContext = {
record: AuditRecord;
change: AuditChange;
};
```
---
### `AuditHistoryPanelLabels`
```ts
export type AuditHistoryPanelLabels = {
title: string;
liveStatus: string;
recordsSummary: (count: number) => string;
changesSummary: (count: number) => string;
allTab: string;
rowTab: string;
cellTab: string;
tableTab: string;
selectedCell: (rowId: string, column: ColumnProp) => string;
selectedRow: (rowId: string) => string;
export: string;
exportCsv: string;
exportJson: string;
exportCsvTitle: string;
exportJsonTitle: string;
close: string;
openPanel: string;
searchPlaceholder: string;
searchAriaLabel: string;
searchShortcut: string;
userPlaceholder: string;
userAriaLabel: string;
columnAriaLabel: string;
actionAriaLabel: string;
dateFromAriaLabel: string;
dateToAriaLabel: string;
allColumns: string;
allActions: string;
clearFilters: string;
addFilter: string;
removeFilter: (label: string) => string;
noRecords: string;
noComparableRecord: string;
scopedRecordCount: (visibleChanges: number, totalChanges: number) => string;
changedBy: (changedAt: string, user: string) => string;
rowTarget: (rowId: string | undefined, column: ColumnProp | undefined) => string;
cellValues: (oldValue: string, newValue: string) => string;
rowSnapshotAvailable: string;
rowRemoved: string;
restoreCell: string;
restoreRow: string;
restoreTransaction: string;
restoreSnapshot: string;
jumpToCell: string;
revertAll: string;
moreActions: string;
compare: string;
compareTitle: string;
compareBefore: string;
compareAfter: string;
closeCompare: string;
compareEmpty: string;
viewChanges: (count: number) => string;
previousPage: string;
nextPage: string;
pageStatus: (currentPage: number, pageCount: number) => string;
dayToday: string;
dayYesterday: string;
sourceLabels: Record;
actions: Partial>;
actionVerbs: Partial>;
};
```
---
### `AuditHistoryPanelLabelsOptions` (Extended from `index.ts`)
```ts
export type AuditHistoryPanelLabelsOptions = Partial> & {
actions?: Partial>;
actionVerbs?: Partial>;
sourceLabels?: Partial>;
};
```
---
### `AuditHistoryPanelHandle`
```ts
export type AuditHistoryPanelHandle = {
destroy: () => void;
setPlacement: (placement: AuditHistoryPanelPlacement) => void;
refresh: () => Promise;
};
```
---
### `AuditHistoryPanelOptions`
```ts
export type AuditHistoryPanelOptions = {
pageSize?: number;
allowExport?: boolean;
allowCompare?: boolean | ((context: AuditHistoryPanelRecordContext) => boolean);
allowClose?: boolean;
miniOnClose?: boolean;
restoreActions?: false | AuditRestoreType[] | ((context: AuditHistoryPanelRestoreContext) => boolean);
formatDate?: (value: string, context: AuditHistoryPanelDateFormatContext) => string;
events?: AuditHistoryPanelEvents;
placement?: AuditHistoryPanelPlacement;
tooltips?: false | Partial>;
labels?: AuditHistoryPanelLabelsOptions;
};
```
---
### `AuditHistoryPanelRecordContext`
```ts
export type AuditHistoryPanelRecordContext = {
record: AuditRecord;
changes: AuditChange[];
};
```
---
### `AuditHistoryPanelRestoreContext`
```ts
export type AuditHistoryPanelRestoreContext = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `AuditHistoryPanelPlacement`
```ts
export type AuditHistoryPanelPlacement = 'right' | 'left' | 'top' | 'bottom' | 'none';
```
---
### `AuditHistoryPanelTooltipKey`
```ts
export type AuditHistoryPanelTooltipKey =
| 'totalRecords'
| 'totalChanges'
| 'selectedScope'
| 'searchFilter'
| 'userFilter'
| 'columnFilter'
| 'actionFilter'
| 'dateFromFilter'
| 'dateToFilter'
| 'clearFilters';
```
---
### `AUDIT_HISTORY_STORAGE_KEY`
```ts
AUDIT_HISTORY_STORAGE_KEY: string;
```
---
### `cloneValue`
```ts
export function cloneValue(value: T): T;
```
---
### `cloneChange`
```ts
export function cloneChange(change: AuditChange): AuditChange;
```
---
### `cloneRecord`
```ts
export function cloneRecord(record: AuditRecord): AuditRecord;
```
---
### `normalizeRowId`
```ts
export function normalizeRowId(rowId: string | number | undefined): string | undefined;
```
---
### `toTime`
```ts
export function toTime(value: string | Date | undefined, fallback: number);
```
---
### `shouldIncludeRecord`
```ts
export function shouldIncludeRecord(record: AuditRecord, filter?: AuditHistoryFilter);
```
---
### `trimRecords`
```ts
export function trimRecords(records: AuditRecord[], maxRecords: number): AuditRecord[];
```
---
### `mergeRecords`
```ts
export function mergeRecords(current: AuditRecord[], next: AuditRecord[]): AuditRecord[];
```
---
### `createAuditStats`
```ts
export function createAuditStats(records: AuditRecord[], filter?: AuditHistoryFilter): AuditHistoryStats;
```
---
### `stringifyExportValue`
```ts
export function stringifyExportValue(value: unknown): string;
```
---
### `exportAuditRecords`
```ts
export function exportAuditRecords(records: AuditRecord[], options: AuditHistoryExportOptions = {}): string;
```
---
# Audit History Panel
URL: https://pro.rv-grid.com/api/audit-history-panel/
Source: src/content/docs/api/audit-history-panel.md
### `createAuditHistoryPanelDock`
```ts
export function createAuditHistoryPanelDock(
panel: HTMLElement,
grid: HTMLRevoGridElement,
placement: AuditHistoryPanelPlacement,
);
```
---
### `defineAuditHistoryPanel`
Renders the Audit History panel into `el` for the given RevoGrid element.
The grid must have `AuditHistoryPlugin` installed.
```ts
export function defineAuditHistoryPanel(el: HTMLElement, grid: HTMLRevoGridElement, options: AuditHistoryPanelOptions = {}): AuditHistoryPanelHandle;
```
---
### `AuditHistoryPanelDateFormatContext`
```ts
export type AuditHistoryPanelDateFormatContext = {
mode: 'time' | 'day' | 'filter';
record?: AuditRecord;
};
```
---
### `AuditHistoryPanelEvents`
```ts
export type AuditHistoryPanelEvents = {
beforeRecordSelect?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeCompareOpen?: (context: AuditHistoryPanelRecordContext) => boolean | void;
beforeJumpToCell?: (context: AuditHistoryPanelJumpContext) => boolean | void;
beforeRestore?: (context: AuditHistoryPanelRestoreContext) => boolean | void;
beforeExport?: (context: AuditHistoryPanelExportContext) => boolean | void;
};
```
---
### `AuditHistoryPanelExportContext`
```ts
export type AuditHistoryPanelExportContext = {
format: AuditHistoryExportFormat;
};
```
---
### `AuditHistoryPanelJumpContext`
```ts
export type AuditHistoryPanelJumpContext = {
record: AuditRecord;
change: AuditChange;
};
```
---
### `AuditHistoryPanelLabels`
```ts
export type AuditHistoryPanelLabels = {
title: string;
liveStatus: string;
recordsSummary: (count: number) => string;
changesSummary: (count: number) => string;
cellsSummary: (count: number) => string;
rowsSummary: (count: number) => string;
scopedChangesSummary: (visibleChanges: number, totalChanges: number) => string;
additionalChanges: (target: string, additionalCount: number) => string;
countLabel: (count: number) => string;
scopeLabel: (label: string, count: number) => string;
scopeAriaLabel?: string;
/** Optional full sentence formatter for languages with a different word order. */
recordSummary?: (context: AuditHistoryPanelRecordSummaryContext) => string;
allTab: string;
rowTab: string;
cellTab: string;
tableTab: string;
selectedCell: (rowId: string, column: ColumnProp) => string;
selectedRow: (rowId: string) => string;
export: string;
exportCsv: string;
exportJson: string;
exportCsvTitle: string;
exportJsonTitle: string;
close: string;
openPanel: string;
searchPlaceholder: string;
searchAriaLabel: string;
searchShortcut: string;
userPlaceholder: string;
/** Independent visible captions; omitted captions retain the existing placeholder/aria-label defaults. */
userFilterLabel?: string;
columnFilterLabel?: string;
actionFilterLabel?: string;
dateFromFilterLabel?: string;
dateToFilterLabel?: string;
userAriaLabel: string;
columnAriaLabel: string;
actionAriaLabel: string;
dateFromAriaLabel: string;
dateToAriaLabel: string;
dateFromPrefix: string;
dateToPrefix: string;
dateFromChip: (formattedDate: string, prefix: string) => string;
dateToChip: (formattedDate: string, prefix: string) => string;
allColumns: string;
allActions: string;
clearFilters: string;
addFilter: string;
closeFilters: string;
exportResults: (count: number) => string;
removeFilter: (label: string) => string;
noRecords: string;
noComparableRecord: string;
scopedRecordCount: (visibleChanges: number, totalChanges: number) => string;
changedBy: (changedAt: string, user: string) => string;
rowTarget: (rowId: string | undefined, column: ColumnProp | undefined) => string;
cellValues: (oldValue: string, newValue: string) => string;
rowSnapshotAvailable: string;
rowRemoved: string;
restoreCell: string;
restoreRow: string;
restoreTransaction: string;
restoreSnapshot: string;
jumpToCell: string;
revertAll: string;
moreActions: string;
compare: string;
compareTitle: string;
compareBefore: string;
compareAfter: string;
closeCompare: string;
compareEmpty: string;
viewChanges: (count: number) => string;
previousPage: string;
nextPage: string;
pageStatus: (currentPage: number, pageCount: number) => string;
dayToday: string;
dayYesterday: string;
sourceLabels: Record;
actions: Partial>;
actionVerbs: Partial>;
/** Optional translation lookup; undefined falls back to the action map. */
actionLabel?: (actionType: AuditActionType) => string | undefined;
actionVerb?: (actionType: AuditActionType) => string | undefined;
};
```
---
### `AuditHistoryPanelValueFormatContext`
```ts
export type AuditHistoryPanelValueFormatContext = {
side: 'before' | 'after';
change: AuditChange;
};
```
---
### `AuditHistoryPanelRecordSummaryContext`
```ts
export type AuditHistoryPanelRecordSummaryContext = {
user: string;
verb: string;
target: string;
detail?: string;
};
```
---
### `DateTimeDisplayOptions`
```ts
export type DateDisplayFormat = 'locale' | 'eu' | 'us' | 'iso' | 'long';
export type TimeDisplayFormat = 'locale' | '12h' | '24h';
export type DateTimeDisplayOptions = {
locale?: string | string[];
dateFormat?: DateDisplayFormat;
timeFormat?: TimeDisplayFormat;
timeZone?: string;
};
```
The shared formatter is also exported from `@revolist/revogrid-pro`:
```ts
export function formatDateTimeDisplay(
value: unknown,
options: DateTimeDisplayOptions & { mode: 'date' | 'time' | 'dateTime' },
): string | undefined;
```
`dateFormat` controls day headings and date-filter chips. `timeFormat` controls timestamps and chip times. EU and ISO default to 24-hour clocks; US defaults to 12-hour clocks. `locale` controls localized month/day names and the panel language attribute. `timeZone` controls record timestamps and their calendar-day groups; native date-filter inputs remain browser-local. `formatDate` overrides every built-in format. See the [localization guide](/guides/audit-trail/localization/) for configuration examples.
---
### `AuditHistoryPanelLabelsOptions` (Extended from `index.tsx`)
```ts
export type AuditHistoryPanelLabelsOptions = Partial> & {
actions?: Partial>;
actionVerbs?: Partial>;
sourceLabels?: Partial>;
};
```
---
### `AuditHistoryPanelHandle`
```ts
export type AuditHistoryPanelHandle = {
destroy: () => void;
setPlacement: (placement: AuditHistoryPanelPlacement) => void;
refresh: () => Promise;
};
```
---
### `AuditHistoryPanelOptions`
```ts
export type AuditHistoryPanelOptions = DateTimeDisplayOptions & {
pageSize?: number;
allowExport?: boolean;
allowCompare?: boolean | ((context: AuditHistoryPanelRecordContext) => boolean);
allowClose?: boolean;
miniOnClose?: boolean;
restoreActions?: false | AuditRestoreType[] | ((context: AuditHistoryPanelRestoreContext) => boolean);
formatDate?: (value: string, context: AuditHistoryPanelDateFormatContext) => string;
/** Display only; raw record values, filtering and exports remain unchanged. */
formatValue?: (value: unknown, context: AuditHistoryPanelValueFormatContext) => string;
events?: AuditHistoryPanelEvents;
placement?: AuditHistoryPanelPlacement;
tooltips?: false | Partial>;
labels?: AuditHistoryPanelLabelsOptions;
};
```
---
### `AuditHistoryPanelRecordContext`
```ts
export type AuditHistoryPanelRecordContext = {
record: AuditRecord;
changes: AuditChange[];
};
```
---
### `AuditHistoryPanelRestoreContext`
```ts
export type AuditHistoryPanelRestoreContext = {
type: AuditRestoreType;
record?: AuditRecord;
change?: AuditChange;
transactionId?: string;
};
```
---
### `AuditHistoryPanelPlacement`
```ts
export type AuditHistoryPanelPlacement = 'right' | 'left' | 'top' | 'bottom' | 'none';
```
---
### `AuditHistoryPanelTooltipKey`
```ts
export type AuditHistoryPanelTooltipKey =
| 'totalRecords'
| 'totalChanges'
| 'selectedScope'
| 'searchFilter'
| 'userFilter'
| 'columnFilter'
| 'actionFilter'
| 'dateFromFilter'
| 'dateToFilter'
| 'clearFilters';
```
---
### `resolveLabels`
```ts
export function resolveLabels(labels?: AuditHistoryPanelLabelsOptions): AuditHistoryPanelLabels;
```
---
### `getAuditActionLabel`
```ts
export function getAuditActionLabel(labels: AuditHistoryPanelLabels, actionType: AuditActionType): string;
```
---
### `getAuditActionVerb`
```ts
export function getAuditActionVerb(labels: AuditHistoryPanelLabels, actionType: AuditActionType): string;
```
---
### `actionTypes`
```ts
actionTypes: AuditActionType[];
```
---
### `titleizeActionType`
```ts
titleizeActionType: (value: string) => string;
```
---
### `createInitialFilters`
```ts
export function createInitialFilters(): AuditPanelFilters;
```
---
### `stringifyValue`
```ts
export function stringifyValue(value: unknown): string;
```
---
### `formatTime`
```ts
export function formatTime(value: string, mode?: 'compact' | 'day', locale?: string | string[]);
```
---
### `formatPanelDate`
```ts
export function formatPanelDate(
value: string,
options: AuditHistoryPanelOptions,
context: { mode: 'time' | 'day' | 'filter'; record?: AuditRecord },
);
```
---
### `getUserLabel`
```ts
export function getUserLabel(record: AuditRecord);
```
---
### `getAvatarLabel`
```ts
export function getAvatarLabel(record: AuditRecord);
```
---
### `matchesFilters`
```ts
export function matchesFilters(record: AuditRecord, filters: AuditPanelFilters);
```
---
### `getScopedChanges`
```ts
export function getScopedChanges(record: AuditRecord, tab: AuditPanelTab, selected: SelectedAuditTarget): AuditChange[];
```
---
### `getRecordSource`
```ts
export function getRecordSource(type: AuditActionType, record?: AuditRecord): AuditHistorySource;
```
---
### `getTargetLabel`
```ts
export function getTargetLabel(record: AuditRecord, changes: AuditChange[], labels: AuditHistoryPanelLabels);
```
---
### `getDetailLabel`
```ts
export function getDetailLabel(record: AuditRecord, changes: AuditChange[], labels?: AuditHistoryPanelLabels);
```
---
### `isComparable`
```ts
export function isComparable(record: AuditRecord, changes: AuditChange[]);
```
---
### `toTimelineRecord`
```ts
export function toTimelineRecord(
record: AuditRecord,
changes: AuditChange[],
labels: AuditHistoryPanelLabels,
options: AuditHistoryPanelOptions = {},
): TimelineRecord;
```
---
### `groupTimelineRecords`
```ts
export function groupTimelineRecords(records: TimelineRecord[]): TimelineGroup[];
```
---
### `getPlugin`
```ts
export async function getPlugin(grid: HTMLRevoGridElement): Promise;
```
---
### `panelRefreshEvents`
```ts
panelRefreshEvents: readonly ["auditrecord", "auditrecordsloaded", "auditrestore"];
```
---
### `canSelectRecord`
```ts
export function canSelectRecord(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRecordContext);
```
---
### `canOpenCompare`
```ts
export function canOpenCompare(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRecordContext);
```
---
### `canJumpToCell`
```ts
export function canJumpToCell(options: AuditHistoryPanelOptions, context: AuditHistoryPanelJumpContext);
```
---
### `canUseRestoreAction`
```ts
export function canUseRestoreAction(options: AuditHistoryPanelOptions, context: AuditHistoryPanelRestoreContext);
```
---
### `canExportAuditHistory`
```ts
export function canExportAuditHistory(options: AuditHistoryPanelOptions, format: AuditHistoryExportFormat);
```
---
### `AuditHistoryTooltip`
```ts
export function AuditHistoryTooltip({ options, tooltipKey }: { options: AuditHistoryPanelOptions; tooltipKey: AuditHistoryPanelTooltipKey });
```
---
### `AuditPanelTab`
```ts
export type AuditPanelTab = 'cell' | 'row' | 'table';
```
---
### `SelectedAuditTarget`
```ts
export type SelectedAuditTarget = {
rowId?: string;
column?: ColumnProp;
columnLabel?: ColumnProp;
rowIndex?: number;
rowType?: DimensionRows;
};
```
---
### `AuditPanelFilters`
```ts
export type AuditPanelFilters = {
search: string;
user: string;
column: string;
actionType: '' | AuditActionType;
dateFrom: string;
dateTo: string;
};
```
---
### `AuditHistoryPluginRuntime`
```ts
export type AuditHistoryPluginRuntime = {
getRecords: () => AuditRecord[];
getCellHistory: (rowId: string, column: ColumnProp) => AuditRecord[];
getRowHistory: (rowId: string) => AuditRecord[];
getStats: () => AuditHistoryStats;
exportRecords: (options?: AuditHistoryExportOptions) => string;
getRowIdentity: (row: DataType | undefined, rowIndex: number, rowType: DimensionRows) => string | undefined;
canRestoreRecord: (context: { type: AuditRestoreType; record?: AuditRecord; change?: AuditChange; transactionId?: string }) => boolean;
restoreCell: (change: AuditChange) => boolean;
restoreRow: (record: AuditRecord) => boolean;
restoreTransaction: (transactionId: string) => boolean;
restoreSnapshot?: (recordOrId: AuditRecord | string) => boolean;
recordEvent?: (input: AuditRecordEventInput) => AuditRecord | undefined;
createSnapshot?: (label: string, options?: AuditSnapshotOptions) => AuditRecord | undefined;
};
```
---
### `VisibleAuditRecord`
```ts
export type VisibleAuditRecord = {
record: AuditRecord;
changes: AuditChange[];
};
```
---
### `TimelineRecord` (Extended from `types.ts`)
```ts
export type TimelineRecord = VisibleAuditRecord & {
id: string;
source: AuditHistorySource;
verb: string;
userLabel: string;
avatarLabel: string;
avatarIndex?: number;
avatarColor?: string;
targetLabel: string;
detailLabel?: string;
rangeLabel?: string;
changedAtLabel: string;
dayKey: string;
dayLabel: string;
daySubLabel?: string;
accent: boolean;
comparable: boolean;
};
```
---
### `TimelineGroup`
```ts
export type TimelineGroup = {
key: string;
label: string;
subLabel?: string;
records: TimelineRecord[];
};
```
---
### `AuditHistoryPanel`
```ts
export function AuditHistoryPanel({
grid,
options = {},
refreshSignal = 0,
onClose,
}: {
grid: HTMLRevoGridElement;
options?: AuditHistoryPanelOptions;
refreshSignal?: number;
onClose?: () => void;
});
```
---
### `CompareDrawer`
```ts
export function CompareDrawer({ record, labels, options, onClose }: CompareDrawerProps);
```
---
### `Filters`
```ts
export function Filters({ filters, columns, actionTypes, labels, options, searchInputRef, onFiltersChange, onClearFilters }: FiltersProps);
```
---
### `MiniAuditHistoryPanel`
```ts
export function MiniAuditHistoryPanel({
grid,
options,
onOpen,
}: {
grid: HTMLRevoGridElement;
options: AuditHistoryPanelOptions;
onOpen: () => void;
});
```
---
### `Pagination`
```ts
export function Pagination({
visibleRecordCount,
pageSize,
currentPage,
pageCount,
labels,
onPageChange,
}: PaginationProps);
```
---
### `RecordsList`
```ts
export function RecordsList({
groups,
labels,
plugin,
selectedRecordId,
collapsingRecordId,
onSelectRecord,
onRestoreCell,
onRestoreRow,
onRestoreTransaction,
onRestoreSnapshot,
onJumpToChange,
canUseRestoreAction,
canJumpToChange,
}: RecordsListProps);
```
---
### `Toolbar`
```ts
export function Toolbar({
records,
stats,
selected,
tab,
tabCounts,
labels,
options,
onTabChange,
onExport,
onClose,
}: ToolbarProps);
```
---
# Autofill
URL: https://pro.rv-grid.com/api/autofill/
Source: src/content/docs/api/autofill.md
### `createDefaultAutoFillStrategies`
Creates the default AutoFill strategy list in evaluation order.
Strategies are ordered from specific to general, with copy fallback last. The
same list is used by AutoFillPlugin and AutoFillPreviewPlugin when no active
AutoFillPlugin instance can be resolved.
**@returns** Default AutoFill strategies.
```ts
export function createDefaultAutoFillStrategies(): AutoFillStrategy[];
```
---
### `AutoFillPlugin`
The `AutoFillPlugin` enhances RevoGrid with advanced autofill capabilities,
allowing users to automatically fill cell ranges based on various sequence
strategies. This plugin supports numeric, date, and text sequences, making it
versatile for different data types and user needs.
**Features**:
- Utilizes multiple pre-defined strategies for numbers, dates, time values,
text-number labels, weekday/month names, boolean cycles, and copy fallback.
- Listens for the `beforeautofill` to trigger autofill actions based on user
interactions.
- Allows registration of additional custom autofill strategies.
- Applies autofilled data directly to the grid, updating the data store efficiently.
**Usage**:
- Import `AutoFillPlugin` and add it to the RevoGrid's plugin list.
- The grid will automatically handle range selection and autofilling based on the
registered strategies.
### Example
```typescript
import { AutoFillPlugin } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin]; // Add autofill functionality to the grid
```
This plugin is ideal for enhancing user productivity in data entry tasks by
providing intelligent filling of repetitive or sequential data patterns, commonly
used in spreadsheets and data management applications.
```ts
class AutoFillPlugin {
/**
* Registers a custom autofill strategy.
*
* Custom strategies are inserted before the copy fallback strategy so they can
* override generic copy behavior.
*
* @param strategy Strategy function to register.
*/
registerStrategy(strategy: AutoFillStrategy);
/**
* Returns the active strategy list.
*
* The preview plugin reads this list from the active AutoFillPlugin instance so
* preview and final commit use identical strategy order.
*
* @returns Registered strategy list in evaluation order.
*/
getStrategies();
/**
* Register a post-processor shared by autofill preview and commit.
*
* The returned disposer removes only this registration, allowing integration
* plugins to detach without affecting host strategies or other transformers.
*/
registerResultTransformer(transformer: AutoFillResultTransformer): () => void;
/** Return active result transformers in registration order. */
getResultTransformers(): readonly AutoFillResultTransformer[];
}
```
---
### `computeAutoFillData`
```ts
export function computeAutoFillData({
oldRange,
newRange,
type,
colType,
providers,
strategies,
transformers = [],
}: AutoFillComputeOptions): AutoFillComputedData | null;
```
---
### `mapAutoFillMatrixToData`
```ts
export function mapAutoFillMatrixToData(
matrix: any[][],
target: RangeArea,
providers: Pick,
colType: DimensionCols,
): Record;
```
---
### `AutoFillComputedData`
```ts
export type AutoFillComputedData = {
matrix: any[][];
data: Record;
};
```
---
### `AutoFillComputeOptions` (Extended from `index.ts`)
```ts
export type AutoFillComputeOptions = Pick & {
type: DimensionRows;
colType: DimensionCols;
providers: PluginProviders;
strategies: AutoFillStrategy[];
transformers?: readonly AutoFillResultTransformer[];
};
```
---
### `createAutoFillCellLocationMatrix`
Build provider-backed locations for a normalized single-viewport range.
```ts
export function createAutoFillCellLocationMatrix(
range: RangeArea,
rowType: DimensionRows,
colType: DimensionCols,
): AutoFillCellLocation[][];
```
---
### `AutoFillCellLocation`
Provider-backed location of one cell participating in an autofill operation.
```ts
/** Provider-backed location of one cell participating in an autofill operation. */
export type AutoFillCellLocation = {
readonly rowType: DimensionRows;
readonly colType: DimensionCols;
readonly rowIndex: number;
readonly colIndex: number;
};
```
---
### `AutoFillResultTransformerContext` (Extended from `index.ts`)
Context supplied after a strategy has produced its base autofill matrix.
```ts
/** Context supplied after a strategy has produced its base autofill matrix. */
export type AutoFillResultTransformerContext = AutoFillStrategyContext & {
selectedData: any[][];
sourceCells: readonly (readonly AutoFillCellLocation[])[];
targetCells: readonly (readonly (AutoFillCellLocation | undefined)[])[];
};
```
---
### `AutoFillResultTransformer`
Post-processes a strategy result without replacing strategy selection.
```ts
/** Post-processes a strategy result without replacing strategy selection. */
export type AutoFillResultTransformer = (
matrix: any[][],
context: AutoFillResultTransformerContext,
) => any[][];
```
---
### `AutoFillPreviewPlugin`
```ts
class AutoFillPreviewPlugin {
destroy();
}
```
---
### `AutoFillDirection`
Direction in which the autofill drag expands from the selected source range.
`both` is used when the source and target ranges cannot be reduced to a single
horizontal or vertical direction.
```ts
/**
* Direction in which the autofill drag expands from the selected source range.
*
* `both` is used when the source and target ranges cannot be reduced to a single
* horizontal or vertical direction.
*/
export type AutoFillDirection = 'up' | 'down' | 'left' | 'right' | 'both';
```
---
### `AutoFillStrategyContext`
Rich context passed to autofill strategies.
Existing custom strategies can keep using the legacy three-argument signature.
New strategies can read this object when they need selection metadata, drag
direction, dimension types, or plugin providers.
```ts
/**
* Rich context passed to autofill strategies.
*
* Existing custom strategies can keep using the legacy three-argument signature.
* New strategies can read this object when they need selection metadata, drag
* direction, dimension types, or plugin providers.
*/
export type AutoFillStrategyContext = {
/** Original selected/source range used as the autofill seed. */
oldRange: RangeArea;
/** Target range that will be previewed or committed. */
newRange: RangeArea;
/** Direction in which the target range expands from the source range. */
direction: AutoFillDirection;
/** Row dimension type for the active autofill operation. */
rowType: DimensionRows;
/** Column dimension type for the active autofill operation. */
colType: DimensionCols;
/** Plugin providers available to advanced strategy implementations. */
providers: PluginProviders;
/**
* Cross-viewport autofill metadata when a plugin maps one visual gesture to
* several viewport-scoped target ranges.
*/
crossViewport?: {
source: {
rowType: DimensionRows;
colType: DimensionCols;
range: RangeArea;
};
targets: {
rowType: DimensionRows;
colType: DimensionCols;
range: RangeArea;
}[];
};
};
```
---
### `AutoFillStrategy`
Autofill strategy function.
**@param** selectedData Matrix of selected source values, ordered row-major.
**@param** direction Direction in which the autofill drag expands.
**@param** newRange Target range that should be generated.
**@param** context Optional rich strategy context for advanced strategies.
**@returns** Generated target matrix, or `null` when the strategy does not support
the selected values.
**Example:**
```typescript
const copyStrategy: AutoFillStrategy = selectedData => selectedData;
AutoFillPlugin.registerStrategy(copyStrategy);
```
```ts
/**
* Autofill strategy function.
*
* @param selectedData Matrix of selected source values, ordered row-major.
* @param direction Direction in which the autofill drag expands.
* @param newRange Target range that should be generated.
* @param context Optional rich strategy context for advanced strategies.
* @returns Generated target matrix, or `null` when the strategy does not support
* the selected values.
*
* @example
* ```typescript
* const copyStrategy: AutoFillStrategy = selectedData => selectedData;
* AutoFillPlugin.registerStrategy(copyStrategy);
* ```
*/
export type AutoFillStrategy = (
selectedData: any[][],
direction: AutoFillDirection,
newRange: RangeArea,
context?: AutoFillStrategyContext,
) => any[][] | null;
```
---
### `getMatrixSize`
Calculates the row and column count for a RevoGrid range.
**@param** range Range to measure.
**@returns** Matrix size matching the inclusive range coordinates.
```ts
export function getMatrixSize(range: RangeArea): MatrixSize;
```
---
### `flattenMatrix`
Flattens a row-major matrix into a one-dimensional sequence.
**@param** data Matrix to flatten.
**@returns** Row-major sequence of values.
```ts
export function flattenMatrix(data: T[][]): T[];
```
---
### `createMatrix`
Creates a matrix for a target range.
**@param** range Target range that defines output dimensions.
**@param** getValue Callback invoked for each cell position.
**@returns** Generated matrix in row-major order.
```ts
export function createMatrix(
range: RangeArea,
getValue: (position: number, rowIndex: number, colIndex: number) => T,
): T[][];
```
---
### `repeatMatrix`
Tiles source values across the target range while preserving source rows and
columns.
**@param** selectedData Source matrix to repeat.
**@param** targetRange Target range that defines output dimensions.
**@returns** Generated matrix containing repeated source values.
```ts
export function repeatMatrix(selectedData: T[][], targetRange: RangeArea): T[][];
```
---
### `isNil`
Checks whether a value is `null` or `undefined`.
**@param** value Value to check.
**@returns** `true` when the value is missing.
```ts
export function isNil(value: unknown): value is null | undefined;
```
---
### `isNumericValue`
Checks whether a value can be treated as a finite number.
**@param** value Value to check.
**@returns** `true` for finite numbers and numeric strings.
```ts
export function isNumericValue(value: unknown): value is number | string;
```
---
### `parseTextNumber`
Parses the first integer segment inside a text value.
**@param** value Value to parse.
**@returns** Prefix, numeric value, suffix, and zero-padding width, or `null`
when the value does not contain an integer segment.
```ts
export function parseTextNumber(value: unknown);
```
---
### `formatTextNumber`
Formats a text value with a numeric segment and preserved padding.
**@param** prefix Text before the number.
**@param** value Number to format.
**@param** suffix Text after the number.
**@param** width Minimum digit width for zero padding.
**@returns** Formatted text-number value.
```ts
export function formatTextNumber(prefix: string, value: number, suffix: string, width: number);
```
---
### `parseDateValue`
Parses a Date object or supported date string.
**@param** value Value to parse.
**@returns** Parsed date and original format, or `null` for unsupported values.
```ts
export function parseDateValue(value: unknown): ParsedDateValue | null;
```
---
### `formatDateValue`
Formats a date using the preserved input format.
**@param** date Date to format.
**@param** format Output format captured from the selected source value.
**@returns** Date object copy or formatted date string.
```ts
export function formatDateValue(date: Date, format: ParsedDateValue['format']);
```
---
### `addUtcDays`
Adds a number of days using UTC date math.
**@param** date Source date.
**@param** days Number of days to add.
**@returns** New Date instance.
```ts
export function addUtcDays(date: Date, days: number);
```
---
### `addUtcMonths`
Adds a number of months using UTC date math.
**@param** date Source date.
**@param** months Number of months to add.
**@returns** New Date instance.
```ts
export function addUtcMonths(date: Date, months: number);
```
---
### `monthDiff`
Calculates whole-month distance between two UTC dates.
**@param** a Start date.
**@param** b End date.
**@returns** Number of calendar months from `a` to `b`.
```ts
export function monthDiff(a: Date, b: Date);
```
---
### `parseTimeValue`
Parses a supported clock-time string.
**@param** value Value to parse.
**@returns** Parsed seconds and original format, or `null` for unsupported values.
```ts
export function parseTimeValue(value: unknown): ParsedTimeValue | null;
```
---
### `formatTimeValue`
Formats seconds since midnight as a clock-time string.
**@param** totalSeconds Seconds since midnight. Values wrap around 24 hours.
**@param** format Output format captured from the selected source value.
**@returns** Formatted clock-time string.
```ts
export function formatTimeValue(totalSeconds: number, format: ParsedTimeValue['format']);
```
---
### `MatrixSize`
Size of a two-dimensional autofill matrix.
```ts
/**
* Size of a two-dimensional autofill matrix.
*/
export type MatrixSize = {
/** Number of rows in the target matrix. */
rows: number;
/** Number of columns in the target matrix. */
cols: number;
};
```
---
### `ParsedDateValue`
Parsed date value with the visible input format needed for output.
```ts
/**
* Parsed date value with the visible input format needed for output.
*/
export type ParsedDateValue = {
/** Parsed date normalized to a Date object. */
date: Date;
/** Original visible input format. */
format: 'date-object' | 'yyyy-mm-dd' | 'yyyy/mm/dd' | 'mm/dd/yyyy';
};
```
---
### `ParsedTimeValue`
Parsed clock-time value with the visible input format needed for output.
```ts
/**
* Parsed clock-time value with the visible input format needed for output.
*/
export type ParsedTimeValue = {
/** Seconds elapsed since midnight. */
seconds: number;
/** Original visible input format. */
format: 'hh:mm' | 'hh:mm:ss';
};
```
---
### `linearNumericSequenceStrategy`
The `linearNumericSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin designed to generate numeric sequences with a constant interval.
This strategy is ideal for filling grid cells with linear numeric progressions.
**Features**:
- Validates that all selected data elements are numeric, filtering out non-numeric
sequences to prevent errors.
- Computes the common difference between numbers in the sequence to establish a
consistent interval.
- Extends the numeric sequence across the specified range, maintaining the calculated
interval throughout the grid.
- Supports dynamic autofill in both horizontal and vertical directions.
**Usage**:
- Import `linearNumericSequenceStrategy` and register it with the AutoFillPlugin
for use in RevoGrid to enable linear numeric autofill functionality.
### Example
```typescript
import { linearNumericSequenceStrategy } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin];
AutoFillPlugin.registerStrategy(linearNumericSequenceStrategy); // Register strategy
```
This strategy is particularly suited for applications requiring predictable numeric
data entry, such as financial models, inventory lists, or any scenario where
arithmetic progression is needed.
```ts
linearNumericSequenceStrategy: AutoFillStrategy;
```
---
### `dateSequenceStrategy`
The `dateSequenceStrategy` is an autofill strategy designed for RevoGrid's
AutoFillPlugin, which facilitates the automatic generation of consistent date
sequences within grid cells. This strategy is used to extend a selected range
of date values either as Date objects or ISO date strings.
**Features**:
- Validates that all selected data elements are either Date objects or valid
ISO date strings, ensuring consistency in input types.
- Computes the common difference between consecutive dates to determine the
sequence's interval.
- Generates a new date sequence extending the original, maintaining the identified
interval across the target range specified.
- Supports output as either Date objects or 'YYYY-MM-DD' formatted strings,
depending on the input type.
**Usage**:
- Import `dateSequenceStrategy` and register it with the AutoFillPlugin for use
in RevoGrid to enable date autofill functionality.
### Example
```typescript
import { dateSequenceStrategy } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin];
AutoFillPlugin.registerStrategy(dateSequenceStrategy); // Register date sequence strategy
```
This strategy is ideal for applications dealing with chronological data entry,
where users benefit from automatic date progression, such as scheduling tools
or timeline management systems.
```ts
dateSequenceStrategy: AutoFillStrategy;
```
---
### `dateSequenceWithIntervalStrategy`
The `dateSequenceWithIntervalStrategy` is an advanced autofill strategy for
RevoGrid's AutoFillPlugin that facilitates filling grids with date sequences
that maintain a consistent interval. It extends upon basic date sequence
strategies by using a defined interval between consecutive dates.
**Features**:
- Validates that input data consists of either Date objects or ISO date strings.
- Ensures consistency in date intervals across the sequence, filtering out
inconsistent sequences.
- Generates a date sequence extending beyond the initial selection, maintaining
the calculated interval throughout the target range.
- Supports output in both Date objects and 'YYYY-MM-DD' strings, based on input type.
**Usage**:
- Import `dateSequenceWithIntervalStrategy` and register it with the AutoFillPlugin
to enable interval-based date autofill in RevoGrid.
### Example
```typescript
import { dateSequenceWithIntervalStrategy } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [AutoFillPlugin];
AutoFillPlugin.registerStrategy(dateSequenceWithIntervalStrategy); // Register strategy
```
This strategy is particularly useful for applications that require consistent
scheduling or timeline management, where maintaining a regular interval between
dates is essential for data accuracy and usability.
```ts
dateSequenceWithIntervalStrategy: AutoFillStrategy;
```
---
### `textSequenceStrategy`
```ts
textSequenceStrategy: AutoFillStrategy;
```
---
### `copyFallbackStrategy`
The `copyFallbackStrategy` is the final default autofill strategy for
RevoGrid's AutoFillPlugin. It repeats the selected source values when no
sequence-specific strategy can safely infer a smarter progression.
**Features**:
- Repeats the selected matrix in row-major order across the target range.
- Supports plain text, numbers, dates, booleans, objects, and mixed values.
- Avoids surprising inferred sequences for ambiguous input.
- Provides deterministic preview and final apply data for every non-empty
selection.
**Usage**:
- Included by default as the last built-in strategy.
- Custom strategies registered through AutoFillPlugin are inserted before this
fallback so they can override copy behavior.
### Example
```typescript
import { copyFallbackStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(copyFallbackStrategy);
```
```ts
copyFallbackStrategy: AutoFillStrategy;
```
---
### `singleNumericStepStrategy`
The `singleNumericStepStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that expands a single numeric source value into a simple
incrementing numeric sequence.
**Features**:
- Activates only when exactly one selected value is numeric.
- Uses a default step of `+1`.
- Supports numeric strings and number values.
- Falls through for mixed, empty, or multi-value selections so more specific
strategies or the copy fallback can handle them.
**Usage**:
- Included by default before the copy fallback.
- Useful for spreadsheet-like fills such as `1` -> `1, 2, 3`.
### Example
```typescript
import { singleNumericStepStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(singleNumericStepStrategy);
```
```ts
singleNumericStepStrategy: AutoFillStrategy;
```
---
### `textNumberSequenceStrategy`
The `textNumberSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues strings containing an embedded number.
**Features**:
- Supports numeric suffixes, prefixes, and middle-number patterns.
- Preserves leading zero width, for example `INV-099` -> `INV-100`.
- Supports ordinal text such as `1st Floor`, `2nd Floor`, `3rd Floor`.
- Requires a consistent prefix/suffix shape and numeric interval.
- Falls through for ambiguous mixed text patterns.
**Usage**:
- Included by default before the legacy text suffix strategy.
- Use it for identifiers, labels, invoice numbers, and numbered locations.
### Example
```typescript
import { textNumberSequenceStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(textNumberSequenceStrategy);
```
```ts
textNumberSequenceStrategy: AutoFillStrategy;
```
---
### `weekdayMonthNameStrategy`
The `weekdayMonthNameStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues English weekday and month name sequences.
**Features**:
- Supports full names such as `Monday` and `January`.
- Supports three-letter names such as `Mon` and `Jan`.
- Preserves the selected full-name or short-name format.
- Wraps around calendar boundaries, for example `Sat, Sun` -> `Mon`.
- Falls through when weekday and month labels are mixed or ambiguous.
**Usage**:
- Included by default before generic text and copy strategies.
- Use it for spreadsheet-like calendar labels in planning grids.
### Example
```typescript
import { weekdayMonthNameStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(weekdayMonthNameStrategy);
```
```ts
weekdayMonthNameStrategy: AutoFillStrategy;
```
---
### `dateUnitSequenceStrategy`
The `dateUnitSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues date sequences using calendar-aware day and
month units.
**Features**:
- Supports Date objects and common date strings: `YYYY-MM-DD`, `YYYY/MM/DD`,
and `MM/DD/YYYY`.
- Preserves the selected input format in generated values.
- Infers daily, weekly, monthly, quarterly, and yearly progressions through
consistent day or month steps.
- Uses a one-day step for a single selected date.
- Falls through for invalid, mixed-format, or irregular date selections.
**Usage**:
- Included by default before legacy date strategies.
- Use it for scheduling, planning, and timeline grids where visible date
formatting should remain stable.
### Example
```typescript
import { dateUnitSequenceStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(dateUnitSequenceStrategy);
```
```ts
dateUnitSequenceStrategy: AutoFillStrategy;
```
---
### `timeSequenceStrategy`
The `timeSequenceStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that continues clock-time strings.
**Features**:
- Supports `HH:mm` and `HH:mm:ss` formats.
- Preserves the selected time format in generated values.
- Infers a consistent interval from multiple selected times.
- Uses a one-hour step for a single selected time.
- Wraps around 24-hour boundaries.
**Usage**:
- Included by default before generic text and copy strategies.
- Use it for scheduling grids, time slots, and recurring interval data.
### Example
```typescript
import { timeSequenceStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(timeSequenceStrategy);
```
```ts
timeSequenceStrategy: AutoFillStrategy;
```
---
### `booleanCycleStrategy`
The `booleanCycleStrategy` is an autofill strategy for RevoGrid's
AutoFillPlugin that repeats boolean-like toggle values.
**Features**:
- Supports boolean values: `true` and `false`.
- Supports common string pairs: `true/false`, `yes/no`, and `on/off`.
- Preserves the original selected values while repeating the selected cycle.
- Requires both string pair values to be present before string cycling.
- Falls through for single string values so plain copy fallback can handle them.
**Usage**:
- Included by default before the generic copy fallback.
- Use it for toggles, status columns, and simple alternating flags.
### Example
```typescript
import { booleanCycleStrategy } from '@revolist/revogrid-pro';
AutoFillPlugin.registerStrategy(booleanCycleStrategy);
```
```ts
booleanCycleStrategy: AutoFillStrategy;
```
---
# Avatar
URL: https://pro.rv-grid.com/api/avatar/
Source: src/content/docs/api/avatar.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
avatarIndexProp?: ColumnProp;
avatarLabelProp?: ColumnProp;
avatarProp?: ColumnProp;
avatarSize?: number;
rectangular?: boolean
}
```
## Plugin API
### `createAvatarElement`
Creates the shared avatar presentation for DOM-based overlays and plugins.
```ts
export function createAvatarElement(
document: Document,
options: AvatarTemplateOptions = {},
): HTMLSpanElement;
```
---
### `getAvatarCellBackground`
```ts
export function getAvatarCellBackground(index = 0);
```
---
### `getAvatarInitials`
```ts
export function getAvatarInitials(value: unknown);
```
---
### `getAvatarIndex`
```ts
export function getAvatarIndex(value: unknown);
```
---
### `avatarTemplate`
```ts
export function avatarTemplate(h: any, options: AvatarTemplateOptions = {});
```
---
### `AvatarTemplateOptions`
```ts
export type AvatarTemplateOptions = {
ariaLabel?: string;
className?: string;
index?: number;
initials?: string;
key?: string | number;
label?: string;
rectangular?: boolean;
size?: number;
style?: Record;
value?: unknown;
};
```
---
### `AVATAR_CELL_BACKGROUNDS`
```ts
AVATAR_CELL_BACKGROUNDS: readonly ["linear-gradient(135deg, #7dd3fc, #2563eb)", "linear-gradient(135deg, #facc15, #b45309)", "linear-gradient(135deg, #f9a8d4, #be185d)", "linear-gradient(135deg, #a5b4fc, #4f46e5)", "linear-gradient(135deg, #86efac, #15803d)", "linear-gradient(135deg, #fda4af, #be123c)", "linear-gradient(135deg, #c4b5fd, #7c3aed)", "linear-gradient(135deg, #fdba74, #c2410c)"];
```
---
### `avatarRenderer`
```ts
avatarRenderer: CellTemplate> | undefined;
```
---
### `avatarWithTextRenderer`
```ts
avatarWithTextRenderer: CellTemplate> | undefined;
```
---
# Buttons
URL: https://pro.rv-grid.com/api/buttons/
Source: src/content/docs/api/buttons.md
### `DeleteButton`
```ts
DeleteButton: ({ onClick, locale, }: { onClick: (e: MouseEvent) => void; locale?: { title?: string | undefined; ariaLabel?: string | undefined; } | undefined; }) => any;
```
---
# Calendar
URL: https://pro.rv-grid.com/api/calendar/
Source: src/content/docs/api/calendar.md
### `CalendarWeekStart`
```ts
export type CalendarWeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
```
---
### `CalendarView`
```ts
export type CalendarView = 'month' | 'year';
```
---
### `CalendarDateRange`
```ts
interface CalendarDateRange {
readonly from: ISODateString;
readonly to: ISODateString
}
```
---
### `CalendarTranslations`
All non-date text rendered by the shared calendar.
```ts
interface CalendarTranslations {
readonly previousMonth: string;
readonly nextMonth: string;
readonly previousYear: string;
readonly nextYear: string;
readonly chooseMonth: string;
readonly calendar: string;
readonly today: string;
readonly invalidSelection: string
}
```
---
### `CalendarDayContext`
```ts
interface CalendarDayContext {
readonly outside: boolean;
readonly disabled: boolean;
readonly selected: boolean;
readonly previewed: boolean;
readonly today: boolean
}
```
---
### `CalendarSelectionValidationContext`
```ts
interface CalendarSelectionValidationContext {
readonly month: ISODateString;
readonly view: CalendarView;
readonly selection?: CalendarDateRange;
readonly preview?: CalendarDateRange;
readonly referenceDate?: ISODateString
}
```
---
### `CalendarSelectionValidationResult`
```ts
export type CalendarSelectionValidationResult = boolean | string;
```
---
### `CalendarProps`
```ts
interface CalendarProps {
readonly month: ISODateString;
readonly focusedDate: ISODateString;
readonly referenceDate?: ISODateString;
readonly selection?: CalendarDateRange;
readonly preview?: CalendarDateRange;
readonly locale?: string;
readonly weekStartsOn?: CalendarWeekStart;
readonly minDate?: ISODateString;
readonly maxDate?: ISODateString;
readonly isDateDisabled?: (date: ISODateString) => boolean;
readonly showOutsideDays?: boolean;
readonly allowOutsideMonthSelection?: boolean;
readonly fixedWeeks?: boolean;
readonly showTodayButton?: boolean;
/** Hides the complete month/year heading and navigation row. */
readonly showHeader?: boolean;
/** Convenience switch for both previous and next actions. */
readonly showNavigation?: boolean;
readonly showPreviousButton?: boolean;
readonly showNextButton?: boolean;
readonly showMonthLabel?: boolean;
readonly showWeekdays?: boolean;
/** Allows the month/year heading to open a twelve-month overview. Defaults to true. */
readonly enableYearView?: boolean;
/** Available views. Defaults to month and year; supersedes `enableYearView`. */
readonly views?: readonly CalendarView[];
readonly defaultView?: CalendarView;
readonly isMonthDisabled?: (month: ISODateString) => boolean;
/** Overrides any subset of the exported English calendar translations. */
readonly translations?: Partial;
readonly className?: string;
readonly dayClassName?: string | ((date: ISODateString, context: CalendarDayContext) => string | undefined);
readonly renderDay?: (date: ISODateString, context: CalendarDayContext) => ComponentChildren;
readonly formatMonth?: (month: ISODateString, locale: string) => string;
readonly formatWeekday?: (date: ISODateString, locale: string) => string;
readonly formatDayLabel?: (date: ISODateString, locale: string) => string;
readonly onMonthChange: (month: ISODateString) => void;
readonly onFocusedDateChange: (date: ISODateString) => void;
readonly onSelect: (date: ISODateString) => void;
readonly onPreviewDateChange?: (date?: ISODateString) => void;
readonly validateSelection?: (
date: ISODateString,
context: CalendarSelectionValidationContext,
) => CalendarSelectionValidationResult;
readonly onInvalidSelection?: (reason: string, date: ISODateString) => void;
readonly onViewChange?: (view: CalendarView) => void
}
```
---
### `MoveCalendarFocusOptions`
```ts
interface MoveCalendarFocusOptions {
readonly date: ISODateString;
readonly key: string;
readonly shiftKey?: boolean;
readonly weekStartsOn: CalendarWeekStart;
readonly minDate?: ISODateString;
readonly maxDate?: ISODateString;
readonly isDisabled?: (date: ISODateString) => boolean
}
```
---
### `DEFAULT_CALENDAR_TRANSLATIONS`
Complete English fallback used when a consumer does not override independent calendar text.
```ts
DEFAULT_CALENDAR_TRANSLATIONS: Readonly;
```
---
### `createCalendarMonth`
```ts
export function createCalendarMonth(
month: ISODateString,
weekStartsOn: CalendarWeekStart,
fixedWeeks = true,
): readonly ISODateString[];
```
---
### `orderCalendarRange`
```ts
export function orderCalendarRange(from: ISODateString, to: ISODateString): CalendarDateRange;
```
---
### `isDateInCalendarRange`
```ts
export function isDateInCalendarRange(date: ISODateString, range?: CalendarDateRange): boolean;
```
---
### `isCalendarDateDisabled`
```ts
export function isCalendarDateDisabled(
date: ISODateString,
options: Pick,
): boolean;
```
---
### `isCalendarMonthDisabled`
```ts
export function isCalendarMonthDisabled(
month: ISODateString,
options: Pick & {
readonly isMonthDisabled?: (month: ISODateString) => boolean;
},
): boolean;
```
---
### `moveCalendarFocus`
```ts
export function moveCalendarFocus(options: MoveCalendarFocusOptions): ISODateString | undefined;
```
---
### `Calendar`
```ts
export function Calendar({
locale = 'en-US',
weekStartsOn = 1,
showOutsideDays = false,
allowOutsideMonthSelection = false,
fixedWeeks = true,
showTodayButton = false,
showHeader = true,
showNavigation = true,
showPreviousButton = showNavigation,
showNextButton = showNavigation,
showMonthLabel = true,
showWeekdays = true,
enableYearView = true,
translations = {},
formatMonth = defaultMonthFormat,
formatWeekday = defaultWeekdayFormat,
formatDayLabel = defaultDayLabel,
...props
}: CalendarProps);
```
---
### `CalendarHeader`
```ts
export function CalendarHeader({
label,
previousLabel,
nextLabel,
labelAction,
showLabel = true,
showPrevious = true,
showNext = true,
previousDisabled = false,
nextDisabled = false,
onPrevious,
onNext,
}: {
label: string;
previousLabel: string;
nextLabel: string;
labelAction?: { ariaLabel: string; onClick: () => void };
showLabel?: boolean;
showPrevious?: boolean;
showNext?: boolean;
previousDisabled?: boolean;
nextDisabled?: boolean;
onPrevious: () => void;
onNext: () => void;
});
```
---
### `CalendarWeekdays`
```ts
export function CalendarWeekdays({
dates,
locale,
format,
}: {
dates: readonly ISODateString[];
locale: string;
format: (date: ISODateString, locale: string) => string;
});
```
---
### `CalendarDay`
```ts
export function CalendarDay(props: CalendarDayProps);
```
---
### `CalendarDayProps`
```ts
interface CalendarDayProps {
readonly date: ISODateString;
readonly label: string;
readonly className?: string;
readonly outside: boolean;
readonly hidden: boolean;
readonly disabled: boolean;
readonly focused: boolean;
readonly selected: boolean;
readonly previewed: boolean;
readonly rangeStart: boolean;
readonly rangeEnd: boolean;
readonly previewStart: boolean;
readonly previewEnd: boolean;
readonly today: boolean;
readonly content?: ComponentChildren;
readonly setRef: (date: ISODateString, element: HTMLButtonElement | null) => void;
readonly onFocus: (date: ISODateString) => void;
readonly onPreview: (date?: ISODateString) => void;
readonly onKeyDown: (event: h.JSX.TargetedKeyboardEvent, date: ISODateString) => void;
readonly onSelect: (date: ISODateString) => void
}
```
---
### `CalendarMonthGrid`
```ts
export function CalendarMonthGrid({
dates,
month,
focusedDate,
referenceDate,
selection,
preview,
showOutsideDays,
allowOutsideMonthSelection,
dayClassName,
renderDay,
isDisabled,
formatDayLabel,
setDayRef,
onFocus,
onPreview,
onKeyDown,
onSelect,
}: {
dates: readonly ISODateString[];
month: ISODateString;
focusedDate: ISODateString;
referenceDate?: ISODateString;
selection?: CalendarDateRange;
preview?: CalendarDateRange;
showOutsideDays: boolean;
allowOutsideMonthSelection: boolean;
dayClassName?: string | ((date: ISODateString, context: CalendarDayContext) => string | undefined);
renderDay?: (date: ISODateString, context: CalendarDayContext) => ComponentChildren;
isDisabled: (date: ISODateString) => boolean;
formatDayLabel: (date: ISODateString) => string;
setDayRef: (date: ISODateString, element: HTMLButtonElement | null) => void;
onFocus: (date: ISODateString) => void;
onPreview: (date?: ISODateString) => void;
onKeyDown: (event: h.JSX.TargetedKeyboardEvent, date: ISODateString) => void;
onSelect: (date: ISODateString) => void;
});
```
---
### `CalendarYearGrid`
```ts
export function CalendarYearGrid({
month,
referenceDate,
minDate,
maxDate,
isMonthDisabled,
locale,
ariaLabel,
onSelect,
onEscape,
}: {
month: ISODateString;
referenceDate?: ISODateString;
minDate?: ISODateString;
maxDate?: ISODateString;
isMonthDisabled?: (month: ISODateString) => boolean;
locale: string;
ariaLabel: string;
onSelect: (month: ISODateString) => void;
onEscape: () => void;
});
```
---
# Calendars
URL: https://pro.rv-grid.com/api/calendars/
Source: src/content/docs/api/calendars.md
### `calendarKey`
```ts
export function calendarKey(calendarId: CalendarId): string;
```
---
### `normalizeIsoWorkingDays`
```ts
export function normalizeIsoWorkingDays(
days: readonly number[] | undefined,
fallback: readonly number[] = DEFAULT_CALENDAR.workingDays,
): readonly number[];
```
---
### `jsWeekdayFromIsoWeekday`
```ts
export function jsWeekdayFromIsoWeekday(day: number): number;
```
---
### `jsWeekdaysFromIsoWeekdays`
```ts
export function jsWeekdaysFromIsoWeekdays(days: readonly number[]): readonly number[];
```
---
### `normalizeCalendarHolidaySet`
```ts
export function normalizeCalendarHolidaySet(holidays: readonly ISODateString[] | undefined): ReadonlySet;
```
---
### `normalizeCalendarWorkingHours`
```ts
export function normalizeCalendarWorkingHours(
workingHours: CalendarWorkingTimeRange | readonly CalendarWorkingTimeRange[] | undefined,
options: { readonly errorPrefix?: string } = {},
): readonly CalendarResolvedWorkingTimeRange[];
```
---
### `normalizeCalendar`
```ts
export function normalizeCalendar(
calendar: CalendarEntity,
options: { readonly workingHoursErrorPrefix?: string } = {},
): ResolvedCalendarEntity;
```
---
### `createResolvedCalendarMap`
```ts
export function createResolvedCalendarMap(
calendars: readonly CalendarEntity[] | undefined,
options: { readonly workingHoursErrorPrefix?: string } = {},
): Map;
```
---
### `createCalendarMap`
```ts
export function createCalendarMap(calendars: readonly CalendarEntity[] | undefined): Map;
```
---
### `resolveCalendarFromMap`
```ts
export function resolveCalendarFromMap(
target: CalendarLookupTarget,
calendarsById: ReadonlyMap,
primaryCalendar: CalendarEntity | null,
): CalendarEntity | null;
```
---
### `resolveCalendarFromList`
```ts
export function resolveCalendarFromList(
context: CalendarListLookupContext,
target?: CalendarLookupTarget,
): CalendarEntity | null;
```
---
### `resolveCalendarItemFromMap`
```ts
export function resolveCalendarItemFromMap(
target: CalendarLookupTarget,
items: ReadonlyMap,
): T | null;
```
---
### `isCalendarHoliday`
```ts
export function isCalendarHoliday(
date: ISODateString | string,
calendar: Pick | Pick | null | undefined,
): boolean;
```
---
### `isCalendarWorkingWeekday`
```ts
export function isCalendarWorkingWeekday(
weekday: number,
calendar: Pick | Pick | null | undefined,
): boolean;
```
---
### `isWorkingCalendarDate`
```ts
export function isWorkingCalendarDate(
date: ISODateString | string,
weekday: number,
calendar: (
Pick
| Pick
| null
| undefined
),
): boolean;
```
---
### `isCalendarWorkingHour`
```ts
export function isCalendarWorkingHour(
date: ISODateString | string,
calendar: CalendarEntity | ResolvedCalendarEntity | null | undefined,
): boolean;
```
---
### `CalendarId`
Unique identifier for a working calendar.
```ts
/** Unique identifier for a working calendar. */
export type CalendarId = string | number;
```
---
### `CalendarWorkingTimeRange`
Time-of-day working window for calendar-aware schedulers.
```ts
interface CalendarWorkingTimeRange {
/** Start time in `HH:mm` or AM/PM format. */
readonly start: string;
/** End time in `HH:mm` or AM/PM format. */
readonly end: string
}
```
---
### `CalendarResolvedWorkingTimeRange`
Parsed working time window expressed as minutes from midnight.
```ts
interface CalendarResolvedWorkingTimeRange {
readonly startMinutes: number;
readonly endMinutes: number
}
```
---
### `CalendarEntity`
Defines the working schedule for tasks, events, and resources.
Working days use ISO weekday numbers: 1 = Monday ... 7 = Sunday.
```ts
interface CalendarEntity {
/** Unique calendar identifier. */
readonly id: CalendarId;
/** Display name, for example `"Standard"` or `"Night Shift"`. */
readonly name: string;
/** IANA time zone, for example `"America/New_York"`. */
readonly timeZone: string;
/** ISO weekday numbers that are working days. */
readonly workingDays: readonly number[];
/** Specific dates that are non-working regardless of weekday. */
readonly holidays: readonly ISODateString[];
/** Optional working time windows inside a working day. */
readonly workingHours?: CalendarWorkingTimeRange | readonly CalendarWorkingTimeRange[];
/** Number of working hours per day, used for effort/capacity calculations. */
readonly hoursPerDay: number
}
```
---
### `DEFAULT_CALENDAR`
Default Monday-Friday calendar shared by Gantt and Event Scheduler.
```ts
DEFAULT_CALENDAR: {
id: string;
name: string;
timeZone: string;
workingDays: number[];
holidays: never[];
workingHours: { start: string; end: string; };
hoursPerDay: number;
};
```
---
### `ResolvedCalendarEntity`
Calendar with derived lookup-friendly fields used by schedulers.
```ts
interface ResolvedCalendarEntity {
readonly calendar: CalendarEntity;
readonly workingDays: readonly number[];
readonly workingHours: readonly CalendarResolvedWorkingTimeRange[];
readonly holidays: ReadonlySet
}
```
---
### `CalendarListLookupContext`
```ts
interface CalendarListLookupContext {
readonly calendars: readonly CalendarEntity[];
readonly primaryCalendarId?: CalendarId
}
```
---
### `CalendarLookupTarget`
```ts
export type CalendarLookupTarget = CalendarId | { readonly calendarId?: CalendarId } | undefined;
```
---
# Cell Flash
URL: https://pro.rv-grid.com/api/cell-flash/
Source: src/content/docs/api/cell-flash.md
## Module Extensions
### `CellTemplateProp` (Extended from `@revolist/revogrid`)
```ts
interface CellTemplateProp {
/**
* Previous value captured when the flash was requested.
*/
previousValue?: any;
/**
* Direction resolved by the flash plugin.
*/
flashDirection?: CellFlashDirection;
/**
* Active flash state for the rendered cell.
*/
flashState?: any
}
```
---
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
/**
* Enables flash feedback for this column.
*
* Legacy `(value) => boolean` predicates are supported. Context-aware
* predicates can inspect the previous value, row model, row type, and edit
* event metadata, and can return a decision object to customize a flash.
*
* @example
* ```ts
* const columns = [
* { prop: 'name', name: 'Name', flash: value => Boolean(value) },
* {
* prop: 'price',
* name: 'Price',
* flash: (value, context) => ({
* flash: value !== context.previousValue,
* rowFlash: true,
* direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
* }),
* },
* ];
* ```
*/
flash?: CellFlashPredicate
}
```
---
### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`)
```ts
interface HTMLRevoGridElement {
/**
* Cell flash plugin configuration.
*
* @example
* ```ts
* const grid = document.querySelector('revo-grid');
* grid.plugins = [CellFlashPlugin, EventManagerPlugin];
* grid.cellFlash = {
* duration: 1200,
* mode: 'cell-and-row',
* queue: 'merge',
* };
* ```
*/
cellFlash?: CellFlashConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* Additional data property for cell flash configuration.
*
* Useful for framework wrappers where all plugin options are passed through
* one memoized or computed `additionalData` object.
*
* @example
* ```tsx
* const additionalData = useMemo(() => ({
* cellFlash: {
* duration: 900,
* mode: 'cell-and-row',
* },
* }), []);
* ```
*/
cellFlash?: CellFlashConfig
}
```
## Plugin API
### `CellFlashPlugin`
Highlights recently changed cells and rows.
Existing usage with `flash: (value) => true`, `FLASH_CELL_EVENT`, and
`cellFlashArrowTemplate()` remains supported. Configure richer behavior
through `grid.cellFlash` or call the instance methods for manual dashboard
updates.
**Example:**
```typescript
import { CellFlashPlugin, EventManagerPlugin } from '@revolist/revogrid-pro';
const grid = document.querySelector('revo-grid');
grid.plugins = [CellFlashPlugin, EventManagerPlugin];
grid.columns = [
{ prop: 'name', name: 'Name' },
{ prop: 'price', name: 'Price', flash: value => value !== undefined },
];
```
**Example:**
```typescript
grid.cellFlash = {
mode: 'cell-and-row',
queue: 'merge',
duration: 900,
rowDuration: 1400,
aria: { enabled: true },
};
```
**Example:**
```typescript
grid.columns = [
{
prop: 'inventory',
name: 'Inventory',
flash: (value, context) => ({
flash: value !== context.previousValue,
rowFlash: Number(value) === 0,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
ariaLabel: `Inventory changed to ${value}`,
}),
},
];
```
#### Dependencies
- **Event integration** `EventManagerPlugin`: Integrates with EventManagerPlugin edit events for automatic edit flashes.
- **Event integration** `HistoryPlugin`: Integrates with HistoryPlugin undo and redo replay edit events when present.
- **Config integration** `additionalData.cellFlash`: Reads cell flash configuration from additionalData.cellFlash.
```ts
class CellFlashPlugin {
/**
* Flashes one cell manually.
*
* Use this for live-feed updates or external actions that do not come through
* RevoGrid edit events. The method keeps the old event-driven behavior intact
* and only applies an additional manual flash state.
*
* @param target Cell coordinates and optional flash metadata.
*
* @example
* ```ts
* const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin);
* plugin?.flashCell({
* rowIndex: 3,
* prop: 'price',
* previousValue: 42,
* value: 45,
* direction: 'up',
* duration: 800,
* });
* ```
*/
flashCell(target: CellFlashCellTarget);
/**
* Flashes a batch of changed cells using the same detail shape as the
* `flashcell` event.
*
* Column `flash` predicates are evaluated for each changed prop. Queue and
* mode options override the configured defaults for this call only.
*
* @param detail Batch detail containing changed values, row type, and optional previous values.
* @param options Optional queue, mode, and duration overrides for this batch.
*
* @example
* ```ts
* plugin.flashCells(
* {
* type: 'rgRow',
* data: { 0: { price: 45, status: 'Updated' } },
* previousData: { 0: { price: 42, status: 'Draft' } },
* eventTypes: ['live-feed'],
* },
* { queue: 'merge', mode: 'cell-and-row' },
* );
* ```
*
* @example
* ```ts
* grid.dispatchEvent(new CustomEvent('flashcell', {
* detail: {
* type: 'rgRow',
* data: { 1: { price: 50 } },
* previousData: { 1: { price: 48 } },
* },
* }));
* ```
*/
flashCells(detail: FlashBatchDetail, options?: CellFlashApplyOptions);
/**
* Flashes one or more rows manually.
*
* Row flashes are rendered through `beforerowrender`, so virtualized rows keep
* the correct visual state when they re-enter the viewport before expiry.
*
* @param targets One row target or an array of row targets.
* @param options Optional queue, mode, and row duration overrides.
*
* @example
* ```ts
* plugin.flashRows(
* [
* { rowIndex: 0, direction: 'changed' },
* { rowIndex: 4, direction: 'down', rowClassName: 'risk-row-flash' },
* ],
* { queue: 'merge', rowDuration: 1200 },
* );
* ```
*/
flashRows(targets: CellFlashRowTarget | CellFlashRowTarget[], options?: CellFlashApplyOptions);
/**
* Clears active flash state.
*
* Without a target, all active cell and row flashes are cleared. Passing a
* row target clears the row plus all active cell flashes in that row. Passing
* both `rowIndex` and `prop` clears one cell.
*
* @param target Optional cell or row target to clear.
*
* @example
* ```ts
* plugin.clearFlash(); // clear all flashes
* plugin.clearFlash({ rowIndex: 2 }); // clear row 2 and its cell flashes
* plugin.clearFlash({ rowIndex: 2, prop: 'price' }); // clear one cell
* ```
*/
clearFlash(target?: Partial);
destroy();
}
```
---
### `cellFlashArrowTemplate`
```ts
cellFlashArrowTemplate: (templateOrOptions?: CellTemplate> | CellFlashArrowTemplateOptions | undefined, templateOptions?: CellFlashArrowTemplateOptions | undefined) => CellTemplate> | undefined;
```
---
### `resolveCellFlashConfig`
Resolves partial public configuration into the plugin's internal defaults.
**Example:**
```typescript
const config = resolveCellFlashConfig({ duration: 1500, queue: 'merge' });
console.log(config.mode); // "cell"
```
```ts
export function resolveCellFlashConfig(
config?: CellFlashConfig,
): ResolvedCellFlashConfig;
```
---
### `CellFlashDirection`
Direction metadata attached to an active cell or row flash.
The default resolver returns `up` or `down` for numeric changes,
`changed` for non-numeric value changes, and `none` when no previous value
is available.
**Example:**
```typescript
const columns = [
{
prop: 'price',
name: 'Price',
flash: (value, context) => ({
flash: true,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
}),
},
];
```
```ts
/**
* Direction metadata attached to an active cell or row flash.
*
* The default resolver returns `up` or `down` for numeric changes,
* `changed` for non-numeric value changes, and `none` when no previous value
* is available.
*
* @example
* ```ts
* const columns = [
* {
* prop: 'price',
* name: 'Price',
* flash: (value, context) => ({
* flash: true,
* direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
* }),
* },
* ];
* ```
*/
export type CellFlashDirection = 'up' | 'down' | 'changed' | 'none';
```
---
### `CellFlashMode`
Defines whether flash requests affect cells, rows, or both.
**Example:**
```typescript
grid.cellFlash = {
mode: 'cell-and-row',
rowDuration: 1400,
};
```
```ts
/**
* Defines whether flash requests affect cells, rows, or both.
*
* @example
* ```ts
* grid.cellFlash = {
* mode: 'cell-and-row',
* rowDuration: 1400,
* };
* ```
*/
export type CellFlashMode = 'cell' | 'row' | 'cell-and-row';
```
---
### `CellFlashQueueMode`
Controls how new flash requests interact with active flashes.
`replace` preserves the legacy behavior by clearing existing flashes before
applying a new batch. `merge` keeps existing flashes active until their own
timers expire.
**Example:**
```typescript
grid.cellFlash = {
queue: 'merge',
maxActive: 300,
};
```
```ts
/**
* Controls how new flash requests interact with active flashes.
*
* `replace` preserves the legacy behavior by clearing existing flashes before
* applying a new batch. `merge` keeps existing flashes active until their own
* timers expire.
*
* @example
* ```ts
* grid.cellFlash = {
* queue: 'merge',
* maxActive: 300,
* };
* ```
*/
export type CellFlashQueueMode = 'replace' | 'merge';
```
---
### `CellFlashContext`
Runtime information passed to context-aware column flash predicates and
direction resolvers.
**Example:**
```typescript
const columns = [
{
prop: 'status',
name: 'Status',
flash: (value, context) => {
return context.previousValue !== value && context.eventTypes?.includes('edit');
},
},
];
```
```ts
interface CellFlashContext {
/** New value from the edit/manual flash batch. */
value: unknown;
/** Previous value captured from the event detail or current row model. */
previousValue: unknown;
/** Row index in the target row type. */
rowIndex: number;
/** Column property for the changed cell. */
prop: ColumnProp;
/** Column definition for the changed cell. */
column: ColumnRegular;
/** Current row model, when available from the data provider. */
model?: DataType;
/** RevoGrid row bucket, for example `rgRow`, `rowPinStart`, or `rowPinEnd`. */
type: DimensionRows;
/** Edit/history event type metadata when emitted by EventManagerPlugin. */
eventTypes?: string[];
/** Original flash/edit event detail for advanced consumers. */
detail?: Partial
}
```
---
### `CellFlashDecision`
Per-change decision returned from a context-aware column `flash` predicate.
Returning `true` is equivalent to `{ flash: true }`. Return a decision object
to customize duration, direction, classes, row flashing, or accessibility
text for a specific value change.
**Example:**
```typescript
const columns = [
{
prop: 'inventory',
name: 'Inventory',
flash: (value, context) => ({
flash: true,
rowFlash: Number(value) === 0,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
className: Number(value) === 0 ? 'cell-flash-danger' : undefined,
ariaLabel: `Inventory changed to ${value}`,
}),
},
];
```
```ts
interface CellFlashDecision {
/** Whether the changed cell should flash. Defaults to true for decision objects. */
flash?: boolean;
/** Whether the containing row should flash for this change. */
rowFlash?: boolean;
/** Cell flash duration in milliseconds for this change. */
duration?: number;
/** Row flash duration in milliseconds for this change. */
rowDuration?: number;
/** Direction metadata for templates, attributes, and CSS selectors. */
direction?: CellFlashDirection;
/** Additional class applied to the flashing cell. */
className?: string;
/** Additional class applied to the flashing row. */
rowClassName?: string;
/** Message announced through the plugin aria-live region when enabled. */
ariaLabel?: string
}
```
---
### `CellFlashPredicate`
Column flash configuration.
Legacy predicates that only accept `(value) => boolean` still work. New
predicates may accept the second `CellFlashContext` argument and return a
`CellFlashDecision`.
**Example:**
```typescript
const columns = [
{ prop: 'name', name: 'Name', flash: true },
{
prop: 'price',
name: 'Price',
flash: (value, context) => ({
flash: value !== context.previousValue,
direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
}),
},
];
```
```ts
/**
* Column flash configuration.
*
* Legacy predicates that only accept `(value) = > boolean` still work. New
* predicates may accept the second `CellFlashContext` argument and return a
* `CellFlashDecision`.
*
* @example
* ```ts
* const columns = [
* { prop: 'name', name: 'Name', flash: true },
* {
* prop: 'price',
* name: 'Price',
* flash: (value, context) => ({
* flash: value !== context.previousValue,
* direction: Number(value) > Number(context.previousValue) ? 'up' : 'down',
* }),
* },
* ];
* ```
*/
export type CellFlashPredicate =
| boolean
| ((value: unknown, context: CellFlashContext) => boolean | CellFlashDecision);
```
---
### `CellFlashAriaConfig`
Accessibility announcement configuration for cell and row flashes.
**Example:**
```typescript
grid.cellFlash = {
aria: {
enabled: true,
label: state => `Row ${state.rowIndex + 1} changed`,
},
};
```
```ts
interface CellFlashAriaConfig {
/** Enables aria-live announcements for flash requests. */
enabled?: boolean;
/** Live-region politeness. Defaults to `polite`. */
live?: 'off' | 'polite' | 'assertive';
/** Controls the `aria-atomic` attribute on the live region. Defaults to true. */
atomic?: boolean;
/** CSS class applied to the generated live region. */
liveRegionClassName?: string;
/** Static label or callback that returns the announcement text. */
label?: string | ((state: CellFlashState) => string)
}
```
---
### `CellFlashLabels`
User-facing labels used by the plugin.
These labels are used for default accessibility announcements. A decision or
manual target `ariaLabel` still has highest priority, followed by
`aria.label`, then these per-kind labels.
**Example:**
```typescript
grid.cellFlash = {
aria: true,
labels: {
cellUpdated: state => `Updated ${String(state.prop)} on row ${state.rowIndex + 1}`,
rowUpdated: state => `Updated row ${state.rowIndex + 1}`,
},
};
```
```ts
interface CellFlashLabels {
/** Default announcement for cell flashes. */
cellUpdated?: string | ((state: CellFlashState) => string);
/** Default announcement for row flashes. */
rowUpdated?: string | ((state: CellFlashState) => string)
}
```
---
### `CellFlashConfig`
Public plugin configuration accepted by `grid.cellFlash` and
`additionalData.cellFlash`.
Defaults preserve the original plugin behavior: flashing is enabled,
cell-only, lasts 1000ms, replaces active flashes on each event, and clears on
source reset.
**Example:**
```typescript
grid.plugins = [CellFlashPlugin, EventManagerPlugin];
grid.cellFlash = {
duration: 1200,
mode: 'cell-and-row',
queue: 'merge',
rowClassName: 'price-row-flash',
aria: { enabled: true },
labels: {
cellUpdated: state => `Price cell changed on row ${state.rowIndex + 1}`,
rowUpdated: state => `Market row ${state.rowIndex + 1} changed`,
},
directionResolver: ({ value, previousValue }) => {
if (Number(value) > Number(previousValue)) return 'up';
if (Number(value) < Number(previousValue)) return 'down';
return 'changed';
},
};
```
```ts
interface CellFlashConfig {
/** Enables or disables new flash requests. */
enabled?: boolean;
/** Default cell flash duration in milliseconds. */
duration?: number;
/** Default row flash duration in milliseconds. */
rowDuration?: number;
/** Default flash target mode. */
mode?: CellFlashMode;
/** Default active flash queue behavior. */
queue?: CellFlashQueueMode;
/** Maximum number of active cell and row flashes retained at once. */
maxActive?: number;
/** Clears active flash state when the grid source changes. */
clearOnSourceChange?: boolean;
/** Disables animation when the user prefers reduced motion. */
respectReducedMotion?: boolean;
/** Enables or configures aria-live announcements. */
aria?: boolean | CellFlashAriaConfig;
/** User-facing labels used by default plugin announcements. */
labels?: CellFlashLabels;
/** Base class applied to flashing cells. */
className?: string;
/** Base class applied to flashing rows. */
rowClassName?: string;
/** Optional custom direction resolver used before the default resolver. */
directionResolver?: (context: CellFlashContext) => CellFlashDirection
}
```
---
### `ResolvedCellFlashConfig`
```ts
interface ResolvedCellFlashConfig {
enabled: boolean;
duration: number;
rowDuration: number;
mode: CellFlashMode;
queue: CellFlashQueueMode;
maxActive: number;
clearOnSourceChange: boolean;
respectReducedMotion: boolean;
aria: Required>
& Pick;
labels: Required;
className: string;
rowClassName: string;
directionResolver?: (context: CellFlashContext) => CellFlashDirection
}
```
---
### `CellFlashState`
```ts
interface CellFlashState {
rowIndex: number;
prop?: ColumnProp;
type: DimensionRows;
value?: unknown;
previousValue?: unknown;
direction: CellFlashDirection;
className?: string;
rowClassName?: string;
ariaLabel?: string;
startedAt: number;
duration: number
}
```
---
### `CellFlashCellTarget`
Target accepted by `CellFlashPlugin.flashCell()`.
**Example:**
```typescript
const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin);
plugin?.flashCell({
rowIndex: 0,
prop: 'price',
previousValue: 42,
value: 47,
direction: 'up',
});
```
```ts
interface CellFlashCellTarget {
rowIndex: number;
prop: ColumnProp;
type?: DimensionRows;
value?: unknown;
previousValue?: unknown;
duration?: number;
direction?: CellFlashDirection;
className?: string;
ariaLabel?: string
}
```
---
### `CellFlashRowTarget`
Target accepted by `CellFlashPlugin.flashRows()`.
**Example:**
```typescript
const plugin = grid.getPlugins?.().find(p => p instanceof CellFlashPlugin);
plugin?.flashRows([
{ rowIndex: 2, direction: 'changed', rowClassName: 'row-flash-warning' },
{ rowIndex: 5, direction: 'down' },
]);
```
```ts
interface CellFlashRowTarget {
rowIndex: number;
type?: DimensionRows;
duration?: number;
direction?: CellFlashDirection;
rowClassName?: string;
ariaLabel?: string
}
```
---
### `CellFlashApplyOptions`
Per-call options for manual and event-driven flash batches.
**Example:**
```typescript
plugin.flashCells(
{ type: 'rgRow', data: { 0: { price: 12 } }, previousData: { 0: { price: 10 } } },
{ queue: 'merge', mode: 'cell-and-row', duration: 800 },
);
```
```ts
interface CellFlashApplyOptions {
queue?: CellFlashQueueMode;
mode?: CellFlashMode;
duration?: number;
rowDuration?: number
}
```
---
### `CellFlashTemplatePropExtension`
```ts
interface CellFlashTemplatePropExtension {
previousValue?: any;
flashDirection?: CellFlashDirection;
flashState?: any
}
```
---
### `CellFlashArrowTemplateOptions`
Options for `cellFlashArrowTemplate()`.
Defaults preserve the original helper output: wrapper class `cell-flash`,
arrow class `cell-flash-arrow`, `up`/`down`/`changed` direction classes,
and `↑` / `↓` symbols.
**Example:**
```typescript
cellTemplate: cellFlashArrowTemplate({
symbols: { up: '+', down: '-', changed: '~' },
className: 'market-flash',
arrowClassName: 'market-flash-icon',
directionClassNames: { up: 'positive', down: 'negative' },
})
```
```ts
interface CellFlashArrowTemplateOptions {
/** Direction symbols rendered before the cell content. */
symbols?: Partial>;
/** Wrapper class applied around the arrow and cell content. */
className?: string;
/** Class applied to the symbol element. */
arrowClassName?: string;
/** Direction-specific wrapper classes. */
directionClassNames?: Partial>
}
```
---
### `CellFlashTemplateParams` (Extended from `index.ts`)
```ts
export type CellFlashTemplateParams = CellTemplateProp & CellFlashTemplatePropExtension;
```
---
# Cell Focus
URL: https://pro.rv-grid.com/api/cell-focus/
Source: src/content/docs/api/cell-focus.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
/**
* The focus function for the column.
*/
focus?: (detail: BeforeSaveDataDetails) => boolean
}
```
## Plugin API
### `CellColumnFocusVerifyPlugin`
The CellColumnFocusVerifyPlugin is a RevoGrid plugin that enables custom focus
verification logic for cells based on their column configurations. This plugin
intercepts the cell focus initialization event to apply additional validation rules
defined at the column level, ensuring that only cells meeting specific criteria
can receive focus.
**Key Features**:
- Listens to the `beforecellfocusinit` event, allowing for pre-focus validation.
- Utilizes column-level focus functions to determine whether a cell can be focused.
- Prevents default focus behavior if the column's focus condition is not satisfied,
enabling dynamic user interaction control based on custom logic.
**Usage**:
- Integrate this plugin into RevoGrid by adding it to the grid's plugin array.
- Define custom focus validation logic within the column definition using the `focus`
property, which should be a function that returns a boolean based on the event detail.
### Example
```ts
import { CellColumnFocusVerifyPlugin } from './cell-column-focus-verify';
const grid = document.createElement('revo-grid');
grid.plugins = [CellColumnFocusVerifyPlugin];
grid.columns = [
{
prop: 'num',
name: 'Numeric Column',
focus: (detail) => detail.value > 10, // Custom focus validation logic
},
];
```
```ts
class CellColumnFocusVerifyPlugin {}
```
---
# Cell Link
URL: https://pro.rv-grid.com/api/cell-link/
Source: src/content/docs/api/cell-link.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
link?: LinkConfig | any
}
```
## Plugin API
### `linkRenderer`
The `linkRenderer` is a custom cell editor for RevoGrid that provides a link input
to edit URL values directly within the grid cells.
**Features**:
- Renders a link element for cells, allowing users to click and navigate to URLs
- Supports different link configurations (target, styling, etc.)
- Extracts protocols from displayed text while preserving the full URL for navigation
- Automatically dispatches a `celledit` event upon change, updating the grid's data model
**Usage**:
- Import `linkRenderer` and assign it to the `cellTemplate` property of a column in the RevoGrid.
- Configure link behavior using the `linkConfig` property.
- Ideal for columns that contain URLs or links to external resources.
### Example
```typescript
import { linkRenderer } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'website',
name: 'Website',
cellTemplate: linkRenderer,
link: {
target: '_blank',
showProtocol: false,
className: 'custom-link',
},
},
];
```
**Event Handling**:
- The `linkRenderer` dispatches a `celledit` event whenever the link value changes.
- The event detail contains:
- `rgCol`: The column index of the edited cell.
- `rgRow`: The row index of the edited cell.
- `type`: The type of the cell.
- `prop`: The property of the cell being edited.
- `val`: The new URL value.
- `preventFocus`: A flag to control grid focus behavior (default: `false`).
This editor is particularly useful in scenarios where users need to view and interact with
URLs directly from the grid, providing a quick and user-friendly interface for link data.
```ts
linkRenderer: CellTemplate> | undefined;
```
---
# Cell Merge
URL: https://pro.rv-grid.com/api/cell-merge/
Source: src/content/docs/api/cell-merge.md
## Module Extensions
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
/**
* Additional data property for cell merge configuration.
*
* @deprecated Use `grid.cellMerge` instead.
* @example
* grid.additionalData = {
* cellMerge: [
* {
* row: 0,
* column: 0,
* rowSpan: 2,
* colSpan: 2,
* rowType: 'rgRow',
* colType: 'rgCol',
* }
* ]
* }
*/
cellMerge?: MergeData[]
}
```
---
### `HTMLRevoGridElement` (Extended from `global`)
```ts
interface HTMLRevoGridElement {
cellMerge?: MergeData[];
'cell-merge'?: MergeData[]
}
```
## Plugin API
### `CellMergePlugin`
The `CellMergePlugin` is a RevoGrid plugin designed to manage and render merged cells within a grid.
It allows users to visually and functionally combine multiple adjacent cells into a single larger cell,
offering a more cohesive data presentation within the grid interface.
**Key Features**:
- Merging Logic: Extends the size of specified cells and hides cells covered by the merged area.
- Event Management: Overrides grid events and focus behavior to accommodate hidden cells due to merging.
- Dynamic Updates: Listens for data changes and adjusts merged configurations accordingly.
- UI Rendering: Adjusts cell sizes and parameters for merged cells during rendering.
**Usage**:
- This plugin should be included in the grid's plugin array to enable cell merging functionality.
- Configure the merge data through the grid's `cellMerge` property to define which cells should be merged.
### Example
```typescript
import { CellMergePlugin } from './cell-merge-plugin';
const grid = document.createElement('revo-grid');
grid.plugins = [CellMergePlugin]; // Add merge plugin
grid.cellMerge = [
{ row: 1, column: 1, rowSpan: 2, colSpan: 2 },
];
```
Events:
- Listens to a variety of events like `APPLY_RANGE_EVENT`, `FOCUS_APPLY_EVENT`, etc., to handle merge configurations and rendering.
#### Dependencies
- **Event integration** `APPLY_RANGE_EVENT`, `FOCUS_APPLY_EVENT`, `EDIT_RENDER_EVENT`: Integrates with range, focus, and edit overlay events to preserve merged-cell behavior.
- **Config integration** `grid.cellMerge`: Reads merge configuration from the grid cellMerge DOM property.
```ts
class CellMergePlugin {
destroy(): void;
}
```
---
### `MergeData`
Represents a range of cells that should be merged.
The coordinates are relative to the top-left cell of the range.
```ts
/**
* Represents a range of cells that should be merged.
* The coordinates are relative to the top-left cell of the range.
*/
export type MergeData = {
/**
* The row index of the top-left cell of the range.
*/
row: number;
/**
* The column index of the top-left cell of the range.
*/
column: number;
/**
* The number of rows to span. If not specified, the range will span only one row.
*/
rowSpan?: number;
/**
* The type of the row dimension. If not specified, it will default to 'rgRow'.
*/
rowType?: DimensionRows;
/**
* The number of columns to span. If not specified, the range will span only one column.
*/
colSpan?: number;
/**
* The type of the column dimension. If not specified, it will default to 'rgCol'.
*/
colType?: DimensionCols;
};
```
---
### `MergeRowCache`
```ts
export type MergeRowCache = Partial>;
```
---
### `MergeCache`
```ts
export type MergeCache = Partial>;
```
---
### `getCellMergeExcelRanges`
```ts
export function getCellMergeExcelRanges(
context: CellMergeExcelExportContext = {},
): CellMergeExcelRange[];
```
---
### `getCellMergeExcelProviderOptions`
```ts
export function getCellMergeExcelProviderOptions(
context: CellMergeExcelExportContext = {},
): CellMergeExcelProviderOptions;
```
---
### `applyCellMergeWriteExcelFileSpans`
```ts
export function applyCellMergeWriteExcelFileSpans({
cellRows,
inPlace,
...context
}: ApplyCellMergeWriteExcelFileSpansOptions): TCell[][];
```
---
### `CellMergeExcelSegment`
```ts
export type CellMergeExcelSegment = {
/**
* Segment start in the export matrix before the generated header offset is
* applied. Omit when segment lengths are supplied in grid order.
*/
start?: number;
/** Number of rows or columns in the segment. */
length?: number;
};
```
---
### `CellMergeExcelSegments`
```ts
export type CellMergeExcelSegments = Partial>;
```
---
### `CellMergeExcelExportContext`
```ts
export type CellMergeExcelExportContext = {
/** Explicit merge data. Wins over merge data read from `revogrid`. */
cellMerge?: MergeData[];
/** Optional grid element source for `cellMerge` or legacy `cell-merge`. */
revogrid?: Pick & Partial>;
/**
* Generated header rows before data rows. ExportExcel currently generates one
* header row, so this defaults to 1.
*/
headerRows?: number;
/**
* Row segment lengths or starts in export data-row order. Starts are counted
* after generated header rows; the helper adds `headerRows`.
*/
rowSegments?: CellMergeExcelSegments;
/**
* Column segment lengths or starts in export column order.
*/
columnSegments?: CellMergeExcelSegments;
};
```
---
### `CellMergeExcelRange`
```ts
export type CellMergeExcelRange = {
/** Zero-based start row in the provider matrix, including generated headers. */
startRow: number;
/** Zero-based start column in the provider matrix. */
startColumn: number;
/** Zero-based end row in the provider matrix, including generated headers. */
endRow: number;
/** Zero-based end column in the provider matrix. */
endColumn: number;
rowSpan: number;
columnSpan: number;
rowType: DimensionRows;
colType: DimensionCols;
source: MergeData;
};
```
---
### `CellMergeExcelProviderOptions`
```ts
export type CellMergeExcelProviderOptions = {
/** Generic provider merge ranges in zero-based export matrix coordinates. */
mergeRanges: CellMergeExcelRange[];
};
```
---
### `CellMergeWriteExcelFileCell`
```ts
export type CellMergeWriteExcelFileCell = Record | null | undefined;
```
---
### `ApplyCellMergeWriteExcelFileSpansOptions` (Extended from `index.ts`)
```ts
export type ApplyCellMergeWriteExcelFileSpansOptions =
CellMergeExcelExportContext & {
cellRows: TCell[][];
/** Mutate the supplied `cellRows` matrix instead of returning a patched copy. */
inPlace?: boolean;
};
```
---
### `MergeCacheService`
Service responsible for managing the cell merge cache.
This service handles all operations related to storing, retrieving, and manipulating
the cache of merged cells, separating these concerns from the main plugin logic.
```ts
class MergeCacheService {
/**
* Clears the entire merge cache
*/
public clearCache(): void;
/**
* Gets the current merge cache
*/
public getCache(): MergeCache;
/**
* Check cache for merged cells and return spans
*/
public getSpans(types: AllDimensionType, startX: number, startY: number);
/**
* High level mapping start
*/
public setMerge(data: MergeData[] | undefined);
}
```
---
# Cell Validate
URL: https://pro.rv-grid.com/api/cell-validate/
Source: src/content/docs/api/cell-validate.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
Adds validation properties to ColumnRegular.
```ts
interface ColumnRegular {
/**
* Optional property to enable soft validation, when set to true, the cell will be saved if it is invalid.
*/
softValidation?: boolean;
/**
* Optional function to provide validation message text for invalid cells.
* @param cellValue - The value of the cell.
* @param model - The full row model. During CellValidatePlugin edit validation,
* this includes the pending row patch for the edit being validated.
* @param column - The column definition that owns the validator.
* @returns The validation message for the cell.
*/
validationTooltip?(cellValue?: any, model?: any, column?: ColumnRegular): any;
/**
* Optional severity used by validation renderers for invalid cell styling.
* @param cellValue - The value of the cell.
* @param model - The full row model for the cell.
* @param column - The column definition that owns the validator.
* Defaults to `error`.
*/
validationSeverity?(cellValue?: any, model?: any, column?: ColumnRegular): ValidationSeverity;
/**
* Optional properties applied by validationRenderer when a cell is invalid.
*/
invalidProperties?: ColumnRegular['cellProperties'];
/**
* Optional function to validate the cell value.
* @param cellValue - The value of the cell.
* @param model - The full row model containing all column values. During
* CellValidatePlugin edit validation, this includes the pending row patch.
* @returns `true` if the value is valid, otherwise `false`.
*/
validate?(cellValue?: any, model?: any): boolean
}
```
## Plugin API
### `CellValidatePlugin`
The `CellValidatePlugin` is a RevoGrid plugin designed to enforce cell validation within
the grid, ensuring data integrity by preventing invalid data entries from being saved.
It integrates seamlessly with RevoGrid's event system and requires the `EventManagerPlugin`
for full functionality.
**Features**:
- Resolves validation before unified grid edits are published to normal edit consumers.
For row-header-owned batches and clipboard `valid-cells` mode, invalid cells are
removed and highlighted while valid cells continue. Other edit origins retain their
existing validation behavior.
The validator receives a candidate row model that includes the pending row patch, so
cross-column validation works for single-cell edits and pasted row batches.
- Adds visual indicators to invalid cells by tagging them during the `BEFORE_CELL_RENDER_EVENT`,
providing immediate feedback to users about validation errors.
- Integrates custom validation logic through column definitions, supporting both strict
and soft validation modes.
- Utilizes `validationRenderer` for rendering cells with validation feedback, allowing
customization of how invalid cells are displayed using tooltips or other UI elements.
**Usage**:
- Implement this plugin in a RevoGrid instance to enable cell validation logic and improve
data quality by preventing erroneous entries.
### Example
```ts
import { CellValidatePlugin } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.plugins = [CellValidatePlugin];
```
#### Dependencies
- **Required** `EventManagerPlugin`: Requires EventManagerPlugin to receive edit events for validation.
```ts
class CellValidatePlugin {}
```
---
### `isValidationRendererFunction`
```ts
export function isValidationRendererFunction(
fn: T | undefined,
): fn is ValidationRendererFunction;
```
---
### `invalidCellProps`
```ts
export function invalidCellProps(invalid = false);
```
---
### `ValidationSeverity`
Visual severity used by `validationRenderer` for invalid-cell styling and tooltip type.
```ts
/** Visual severity used by `validationRenderer` for invalid-cell styling and tooltip type. */
export type ValidationSeverity = 'error' | 'warning' | 'info';
```
---
### `ValidationIndicatorPlacement`
Position of the invalid-cell indicator rendered by `validationRenderer`.
```ts
/** Position of the invalid-cell indicator rendered by `validationRenderer`. */
export type ValidationIndicatorPlacement = 'corner' | 'start' | 'end' | 'none';
```
---
### `ValidationMessagePlacement`
Controls whether validation messages are exposed as tooltip text, inline text, both, or hidden.
```ts
/** Controls whether validation messages are exposed as tooltip text, inline text, both, or hidden. */
export type ValidationMessagePlacement = 'tooltip' | 'inline' | 'both' | 'none';
```
---
### `ValidationRendererContext`
Runtime context passed to advanced validation renderer callbacks.
```ts
interface ValidationRendererContext {
/** Cell value passed by RevoGrid into the cell renderer. */
value: any;
/** Full row model for the rendered cell. */
model?: any;
/** Column definition being rendered. */
column: ColumnRegular;
/** Whether `column.validate(value, model)` failed for this cell. */
invalid: boolean;
/** Resolved severity for the invalid cell. */
severity: ValidationSeverity
}
```
---
### `ValidationRendererOptions`
Options for `validationRenderer`.
Existing `template` and `invalidProperties` usage remains supported. New options let
applications choose severity-aware styling, move the validation indicator, and render
messages as tooltips, inline text, both, or neither.
```ts
interface ValidationRendererOptions {
/** Existing cell template to wrap with validation feedback. */
template?: ColumnRegular['cellTemplate'];
/** Extra cell properties applied only when the cell is invalid. */
invalidProperties?: ColumnRegular['cellProperties'];
/** Static or callback-driven severity for invalid cell styling. */
severity?: ValidationSeverity | ((context: Omit) => ValidationSeverity);
/** Where to render the validation indicator. Defaults to `corner`. */
indicatorPlacement?: ValidationIndicatorPlacement;
/** Where to expose the validation message. Defaults to `tooltip`. */
messagePlacement?: ValidationMessagePlacement;
/** Optional message resolver. Falls back to `column.validationTooltip`. */
message?: (context: ValidationRendererContext) => any
}
```
---
### `validationRenderer`
Extends the `ColumnRegular` interface from the RevoGrid framework to include
properties and functions for validating cell data and rendering validation feedback.
It facilitates cell validation and provides visual indicators for invalid cells.
**Features**:
- Extends `ColumnRegular` with:
- `softValidation`: An optional boolean indicating if a cell should be saved even when invalid.
- `validationTooltip`: A function to provide validation message text for invalid cells.
- `validationSeverity`: A function to choose `error`, `warning`, or `info` styling per invalid cell.
- `validate`: A function to determine if a cell's value is valid.
- The `invalidCellProps` function returns cell properties indicating whether a cell is invalid.
- The `validationRenderer` function returns a renderer with:
- `cellProperties`: Adds validation styles and properties to cells, highlighting them when invalid.
- `cellTemplate`: Renders invalid-cell indicators and optional tooltip or inline messages.
**Usage**:
- Integrate these extensions into the RevoGrid column configuration to enable cell validation.
- Customize cell templates and properties using `validationRenderer` to enhance the grid's UI.
### Example
```typescript
import { validationRenderer } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'num',
name: 'Linear',
validate: (value) => value >= 0 && value <= 40,
validationTooltip: (value) => `Value ${value} is out of range`,
...validationRenderer({
severity: 'error',
indicatorPlacement: 'corner',
messagePlacement: 'tooltip',
}),
},
];
```
This module is crucial for applications requiring data validation, improving user experience
by providing immediate feedback for data entry errors within the RevoGrid framework.
```ts
validationRenderer: ({ template, invalidProperties, severity, indicatorPlacement, messagePlacement, message, }?: ValidationRendererOptions) => Pick>>, "cellTemplate" | "cellProperties">;
```
---
### `validateNumber`
Validates if the value is a number.
**@param** value - The value to validate.
**@returns** `true` if the value is a number, otherwise `false`.
```ts
export function validateNumber(value: any): boolean;
```
---
### `validateBoolean`
Validates if the value is a boolean.
**@param** value - The value to validate.
**@returns** `true` if the value is a boolean, otherwise `false`.
```ts
export function validateBoolean(value: any): boolean;
```
---
### `validateString`
Validates if the value is a string.
**@param** value - The value to validate.
**@returns** `true` if the value is a string, otherwise `false`.
```ts
export function validateString(value: any): boolean;
```
---
### `validateInteger`
Validates if the value is an integer.
**@param** value - The value to validate.
**@returns** `true` if the value is an integer, otherwise `false`.
```ts
export function validateInteger(value: any): boolean;
```
---
### `validateDecimal`
Validates if the value is a decimal.
**@param** value - The value to validate.
**@returns** `true` if the value is a decimal, otherwise `false`.
```ts
export function validateDecimal(value: any): boolean;
```
---
### `validateRange`
Validates if the value is within a specific range.
**@param** value - The value to validate.
**@param** min - The minimum value.
**@param** max - The maximum value.
**@returns** `true` if the value is within the range, otherwise `false`.
```ts
export function validateRange(value: any, min: number, max: number): boolean;
```
---
### `validateArray`
Validates if the value is an array.
**@param** value - The value to validate.
**@returns** `true` if the value is an array, otherwise `false`.
```ts
export function validateArray(value: any): boolean;
```
---
### `validateObject`
Validates if the value is an object.
**@param** value - The value to validate.
**@returns** `true` if the value is an object, otherwise `false`.
```ts
export function validateObject(value: any): boolean;
```
---
### `validateNull`
Validates if the value is null.
**@param** value - The value to validate.
**@returns** `true` if the value is null, otherwise `false`.
```ts
export function validateNull(value: any): boolean;
```
---
### `validateUndefined`
Validates if the value is undefined.
**@param** value - The value to validate.
**@returns** `true` if the value is undefined, otherwise `false`.
```ts
export function validateUndefined(value: any): boolean;
```
---
### `validateDate`
Validates if the value is a date.
**@param** value - The value to validate.
**@returns** `true` if the value is a date, otherwise `false`.
```ts
export function validateDate(value: any): boolean;
```
---
### `validateRegex`
Validates if the value matches the provided regular expression.
**@param** value - The value to validate.
**@param** regex - The regular expression to test against.
**@returns** `true` if the value matches the regular expression, otherwise `false`.
```ts
export function validateRegex(value: any, regex: RegExp): boolean;
```
---
### `validateEnum`
Validates if the value is one of the allowed values.
**@param** value - The value to validate.
**@param** allowedValues - The array of allowed values.
**@returns** `true` if the value is one of the allowed values, otherwise `false`.
```ts
export function validateEnum(value: any, allowedValues: any[]): boolean;
```
---
### `validatePositive`
Validates if the value is a positive number.
**@param** value - The value to validate.
**@returns** `true` if the value is a positive number, otherwise `false`.
```ts
export function validatePositive(value: any): boolean;
```
---
### `validateNegative`
Validates if the value is a negative number.
**@param** value - The value to validate.
**@returns** `true` if the value is a negative number, otherwise `false`.
```ts
export function validateNegative(value: any): boolean;
```
---
### `validateFinite`
Validates if the value is a finite number.
**@param** value - The value to validate.
**@returns** `true` if the value is a finite number, otherwise `false`.
```ts
export function validateFinite(value: any): boolean;
```
---
### `validateEmptyString`
Validates if the value is an empty string.
**@param** value - The value to validate.
**@returns** `true` if the value is an empty string, otherwise `false`.
```ts
export function validateEmptyString(value: any): boolean;
```
---
### `validateNonEmptyString`
Validates if the value is a non-empty string.
**@param** value - The value to validate.
**@returns** `true` if the value is a non-empty string, otherwise `false`.
```ts
export function validateNonEmptyString(value: any): boolean;
```
---
### `validateInstance`
Validates if the value is an instance of a class.
**@param** value - The value to validate.
**@param** cls - The class to test against.
**@returns** `true` if the value is an instance of the class, otherwise `false`.
```ts
export function validateInstance(value: any, cls: any): boolean;
```
---
### `parseBoolean`
Utilities used in the RevoGrid project to assist in converting
various data types to a boolean value. This function is essential for data processing
tasks where boolean values may be represented in different formats.
**Features**:
- Accepts inputs of type boolean, number, or string, and attempts to convert them to
a boolean value.
- For numbers, returns `true` if the value is `1`, otherwise `false`.
- For strings, it normalizes the input to lowercase and trims whitespace, returning
`true` for common affirmative representations ('true', 'yes', '1') and `false` for
negative representations ('false', 'no', '0').
- Returns `undefined` if the input cannot be reliably converted to a boolean, allowing
for error handling or default value assignment in calling code.
**Usage**:
- Call `parseBoolean` when you need a flexible utility to standardize various input
representations into a boolean format, particularly useful in grid configuration
or data validation scenarios.
### Example
```typescript
const isEnabled = parseBoolean('yes'); // Returns true
const isDisabled = parseBoolean(0); // Returns false
```
```ts
export function parseBoolean(value: any): boolean | undefined;
```
---
# Chart Analysis API
URL: https://pro.rv-grid.com/api/chart-analysis/
Source: src/content/docs/api/chart-analysis.md
Description: Shared public contracts for analytical overlays, calculated references, facets, performance charts and source records.
The [Analysis guide](/guides/charts/analysis/) explains when to use each tool,
its calculation method and limitations. These APIs belong to
`@revolist/charts`; Pivot consumes the same contracts and native renderer.
## Imports
```ts
import {
resolveChartAnalysis,
resolveCalculatedReferences,
createParetoPreset,
getChartFacetDimensions,
chartDimensionFieldId,
type ChartAnalysis,
type ChartAnalysisResolution,
type ChartCalculatedReference,
type ChartFacets,
type ChartBullet,
type ChartParetoOptions,
type ChartUnderlyingRecordsProvider,
} from '@revolist/charts/core';
```
The same exports are available from `@revolist/charts`. Per-type factories accept
shared settings while retaining their fixed chart type. Use a multi-definition
factory to switch between registered chart types.
## Shared settings
| Setting | Contract | Behavior |
| --- | --- | --- |
| `analysis` | `ChartAnalysis` | Moving averages, regressions, calendar comparisons and linear forecasts. |
| `annotations.calculated` | `ChartCalculatedReference[]` | Mean, median, percentile lines and population-deviation bands. |
| `facets` | `ChartFacets` | Dimension-based line/bar/column/area panels and paging on `smallMultiples`. |
| `bullet` | `ChartBullet` | Qualitative performance ranges, authored bounds and target variance. |
| `axes.x.interval` | `'day' \| 'week' \| 'month' \| 'quarter' \| 'year'` | Minimum UTC tick cadence on time axes; cadence for explicit missing-period gaps. |
| `axes.x.missingPeriods` | `'gap' \| 'connect'` | Gap or connect absent periods; `gap` requires an interval. Explicit nulls always break paths. |
| `underlyingRecords` | `ChartUnderlyingRecordsProvider` | Runtime-only source-record loader for the shared paginated view. |
Settings are accepted at creation and through `chart.update`. Structured settings
replace whole objects, so spread fields you want to preserve. Undefined preserves
the current field during updates. Empty `analysis: {}`, `facets: {}` and
`bullet: {}` clear those settings; `annotations: {}` clears all annotations.
Invalid updates retain the previously accepted model and rendering.
## Analytical overlays
Every overlay has a unique `id`, source `seriesId`, and optional `label`/`color`.
At most 32 overlays are allowed across the arrays in `analysis`.
| Array | Additional configuration |
| --- | --- |
| `movingAverages` | `window` from 2 to 1000, optional `minPeriods` from 1 to window. Trailing source-position window; finite values only. |
| `regressions` | Optional `showConfidence`, `confidence` and `xSeriesId`. Confidence is `0.9`, `0.95` or `0.99`. |
| `periodComparisons` | `unit: 'week' \| 'month' \| 'quarter' \| 'year'`, optional `offset`, `partialPeriod: 'include' \| 'exclude'`, `asOf` UTC epoch. |
| `forecasts` | Calendar `unit: 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'`, `horizon` from 1 to 120, optional `confidence`. Requires time X. |
```ts
const result: ChartAnalysisResolution = resolveChartAnalysis(
dataset,
analysis,
axes,
{ bindings, chartType: 'line' },
);
```
The pure resolver returns separate overlays, statuses, points, value domains and
an optional future time domain. Regression diagnostics include sample count,
degrees of freedom, slope/intercept, R², RMSE and residual standard error.
Comparison points expose the matched reference category, difference, percentage
change and partial-period flag. Forecast points have no source category index.
Source rows and IDs are not rewritten. See the guide for supported families and
statistical assumptions.
## Calculated references
```ts
const reference: ChartCalculatedReference = {
id: 'p90', seriesId: 'revenue', statistic: 'percentile', percentile: 90,
scope: 'visible', label: '90th percentile',
};
```
The mean is an unweighted mean of the accepted chart cells, which may already
be Pivot aggregates; it is not implicitly a raw-record weighted average.
Statistics are `mean`, `median`, `percentile` and `standardDeviation`.
`percentile` uses a percentage from 0 to 100. `deviations` controls the
standard-deviation band multiplier and defaults to 1. Scopes are `all`, `visible`
and `selected`; omitted scope means `all`. Optional `axis` is `primary` or
`secondary`; otherwise the reference follows its source series assignment.
An unavailable secondary plot, unknown series or nonfinite computed band rejects.
IDs must be unique across references, notes and shapes.
`resolveCalculatedReferences(renderInput)` returns effective numeric references
for a validated chart render input. Authored calculations remain in the model;
the resolved numbers are transient.
## Facets and grouped distributions
```ts
const facets: ChartFacets = {
dimension: 'dimension:column:region',
type: 'column', scaleMode: 'shared', pageSize: 6, page: 0,
};
const dimensions = getChartFacetDimensions(dataset);
```
Use the actual discovered field IDs. Facets require provider-owned dimension
metadata and complete matrix-cell mappings. Without `dimension`, each measure
gets a panel. Page is zero-based, page size is bounded from 1 to 100, and actual
capacity also depends on the host. The shared controls keep later panels
reachable. Grouped box plots use multiple genuine distribution series; no new
chart type or synthetic samples are required.
## Bullet configuration
`ChartBullet` supports finite `minimum`/`maximum`, ascending
`ranges: { to, label?, color? }[]`, `showVariance` and
`varianceFormat: 'absolute' | 'percent'`. At most 20 qualitative ranges are
allowed. This complements the bullet chart's existing `actual` and `target`
bindings. Zero targets have no defined percentage variance.
## Pareto
```ts
const preset = createParetoPreset(dataset, {
seriesId: 'revenue', threshold: 80, limit: 10,
});
// await createChart({ container, ...preset });
```
`ChartParetoOptions` also accepts `othersLabel`, `cumulativeLabel` and
`thresholdLabel`. The preset returns a derived dataset plus combination-chart
axes, series assignments and threshold annotations. Contributions must be
nonnegative with a positive total. The full denominator includes Others.
Cumulative share and synthetic Others are inspection-only.
## Pivot integration
`PivotChartCreateParams`, `PivotChartUpdatePatch` and saved Pivot models accept
`analysis`, `facets`, `bullet`, `annotations.calculated` and time-axis policies.
Call `charts.getChartData(...)` to obtain source dataset/series IDs. A
`PivotChartRef` has no `getDataset()` method.
`charts.createParetoChart({ container, pareto, ...settings })` creates an unlinked
analytical snapshot. `charts.createChart({ ..., underlyingRecords: true })`
enables native source-record access for a current linked chart without an
independent query. A custom provider handles other scopes. Reattach providers
through creation or restore options; callbacks are never saved.
Native renderers advertise `capabilities.analysis` and
`capabilities.underlyingRecords`; unsupported backends reject those opt-ins.
These flags are separate from type-specific geometry and data requirements.
For the provider contract, see [Underlying records](/guides/charts/tools/underlying-records/).
---
# Charts
URL: https://pro.rv-grid.com/api/charts/
Source: src/content/docs/api/charts.md
## Module Extensions
### `ColumnRegular` (Extended from `@revolist/revogrid`)
```ts
interface ColumnRegular {
/**
* The position of the bars in the chart.
*/
barPosition?: 'top' | 'bottom';
/**
* Define badge styles for the cell.
*/
badgeStyles?: Record;
/**
* Whether to render the shape as a rectangular.
*/
rectangular?: boolean;
/**
* Maximum number of stars in the rating. Default is 5.
*/
maxStars?: number;
/**
* Minimum value for the progress line. Default is 0.
*/
minValue?: number;
/**
* Maximum value for the progress line. Default is 100.
*/
maxValue?: number;
/** Midpoint threshold used by custom three-stop heatmaps. */
midValue?: number;
/** Custom color at the heatmap minimum threshold. */
lowColor?: string;
/** Custom color at the heatmap midpoint threshold. */
midColor?: string;
/** Custom color at the heatmap maximum threshold. */
highColor?: string;
/** Legacy built-in heatmap palette used when custom colors are absent. */
colorMap?: 'heatmap' | 'coldmap';
/**
* Define thresholds for the cell.
*/
thresholds?: ThresholdConfig[];
/**
* Format the value of the cell.
*/
formatValue?: (value: number, column: ColumnRegular) => string;
/**
* Function to calculate progress based on row data.
* This allows connecting multiple fields to determine progress.
* @param config Configuration object containing model, data, and prop
* @returns A number between 0 and 100 representing progress
*/
progress?: (config: { model: DataType; data: DataType[]; prop: ColumnProp }) => number;
/** Visible timeline range. Invalid or inverted ranges fall back to 0–100. */
timelineRange?: { start: number; end: number };
/** Localized accessible name for a rendered timeline. */
timelineAriaLabel?: string | ((events: readonly TimelineEvent[]) => string);
/** Localized accessible name for a sparkline. */
sparklineAriaLabel?: string | ((values: readonly number[]) => string);
/** Localized accessible name for a rating visualization. */
ratingAriaLabel?: string | ((value: number, maximum: number) => string)
}
```
## Plugin API
### `progressLineRenderer`
The `progressLineRenderer` is a custom cell renderer for RevoGrid that provides a visual
representation of progress as a horizontal line within grid cells. This renderer effectively
communicates the completion level of a numeric value relative to a defined range.
**Features**:
- Displays a progress line that visually indicates the percentage completion within specified
minimum and maximum values.
- Shows a percentage label beside the progress bar, formatted to one decimal place for precision.
- Automatically normalizes values to ensure accurate representation within the defined range.
- Configurable through column properties such as `minValue` and `maxValue`.
- Supports a custom progress function to calculate progress based on row data.
**Usage**:
- Import `progressLineRenderer` and use it as the `cellTemplate` in your RevoGrid's column configuration.
- Specify `minValue` and `maxValue` for each column to define the range for progress calculation.
- Optionally provide a `progress` function to calculate progress based on row data.
### Example
```typescript
import { progressLineRenderer } from './charts/progress-line.renderer';
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'score',
name: 'Completion Rate',
minValue: 0,
maxValue: 100,
cellTemplate: progressLineRenderer,
// Optional: Custom progress calculation
progress: (config) => {
// Calculate progress based on multiple fields
const completed = config.model.completedTasks || 0;
const total = config.model.totalTasks || 1;
return (completed / total) * 100;
}
},
];
```
This renderer is suitable for applications where tracking progression or percentage completion
is necessary, such as performance dashboards, goal tracking systems, or any scenario where a
visual progress indicator is beneficial.
```ts
progressLineRenderer: CellTemplate>;
```
---
### `renderProgressBar`
Shared framework-neutral progress primitive used by chart cell renderers.
```ts
export function renderProgressBar(h: ProgressHyperFunc, {
value,
min = 0,
max = 100,
ariaLabel,
className,
fillClass,
}: ProgressBarProps);
```
---
### `ProgressBarProps`
```ts
interface ProgressBarProps {
value: number;
min?: number;
max?: number;
ariaLabel?: string;
className?: string;
fillClass?: string | Record
}
```
---
### `progressBarRenderer`
Compact progress-only RevoGrid cell renderer.
```ts
progressBarRenderer: CellTemplate>;
```
---
### `progressLineWithValueRenderer`
```ts
progressLineWithValueRenderer: CellTemplate>;
```
---
### `sparklineRenderer`
```ts
sparklineRenderer: CellTemplate> | undefined;
```
---
### `barChartRenderer`
RevoGrid Renderer
Generates a bar chart visualization based on the provided data
values and column configuration.
```ts
barChartRenderer: (h: HyperFunc, { value, column, }: { value?: any; column: Partial>>; }) => VNode;
```
---
### `heatmapRenderer`
```ts
heatmapRenderer: CellTemplate> | undefined;
```
---
### `badgeRenderer`
RevoGrid Renderer
Renderers a badge cell with custom background color, text
Supports both badgeStyles and thresholds for styling
```ts
badgeRenderer: CellTemplate> | undefined;
```
---
### `ratingStarRenderer`
```ts
ratingStarRenderer: CellTemplate>;
```
---
### `formatTimelineEvents`
Converts valid Timeline events to the same readable text used by tooltips
and accessibility labels.
**@param** value Timeline event array; invalid entries are ignored.
**@param** separator Text inserted between valid event descriptions.
**@returns** A human-readable schedule, or an empty string when no event is valid.
**Example:**
```typescript
formatTimelineEvents([
{ start: 8, end: 12, label: 'Build' },
{ start: 14, end: 18, label: 'Review' },
], '; ');
// "Build: 8–12; Review: 14–18"
```
```ts
export function formatTimelineEvents(value: unknown, separator = ', '): string;
```
---
### `TimelineEvent`
```ts
interface TimelineEvent {
readonly start: number;
readonly end: number;
readonly label?: string
}
```
---
### `timelineRenderer`
```ts
timelineRenderer: CellTemplate> | undefined;
```
---
### `changeRenderer`
RevoGrid Renderer
Formats a numeric cell value with corresponding styles and icons based on whether the value
is positive, negative, or zero,
and exports a cell template renderer for a grid that utilizes this formatting.
```ts
changeRenderer: CellTemplate>;
```
---
### `thumbsRenderer`
```ts
thumbsRenderer: CellTemplate> | undefined;
```
---
### `renderColumnTypeIcon`
Helper to render icons based on the columnType.
**@param** h Hyperscript function.
**@param** columnType The column type to determine the icon.
**@returns** A VNode containing the corresponding SVG icon.
```ts
export function renderColumnTypeIcon(
h: HyperFunc,
columnType: string
);
```
---
### `columnTypeRenderer`
```ts
columnTypeRenderer: ColumnTemplateFunc | undefined;
```
---
### `PieData`
```ts
interface PieData {
value: number;
color?: string;
name?: string
}
```
---
### `pieChartRenderer`
```ts
pieChartRenderer: (h: HyperFunc, { value, column, }: { value?: number[] | PieData[] | undefined; column?: Partial, ColumnRegular>, ColumnProp>> | undefined; }) => VNode;
```
---
### `summaryHeaderRenderer`
```ts
summaryHeaderRenderer: (h: HyperFunc, summary: Record, column?: { maxItems?: number | undefined; }) => VNode;
```
---
### `summaryAggregateRenderer`
```ts
summaryAggregateRenderer: (h: HyperFunc, summary: Record, column?: { showSum?: boolean | undefined; showAvg?: boolean | undefined; }) => VNode;
```
---
### `avatarRenderer`
```ts
avatarRenderer: CellTemplate> | undefined;
```
---
### `avatarTemplate`
```ts
export function avatarTemplate(h: any, options: AvatarTemplateOptions = {});
```
---
### `avatarWithTextRenderer`
```ts
avatarWithTextRenderer: CellTemplate> | undefined;
```
---
### `thresholdRenderer`
The `thresholdRenderer` is a custom cell renderer for RevoGrid that applies CSS classes based on defined thresholds.
It uses the existing threshold system to apply classes to cells based on their values.
**Features**:
- Supports multiple thresholds with custom CSS classes
- Simple and straightforward threshold-based styling
- Works with the existing threshold system
**Usage**:
```typescript
import { thresholdRenderer } from '@revolist/revogrid-pro'
const grid = document.createElement('revo-grid');
grid.columns = [
{
prop: 'score',
name: 'Score',
thresholds: [
{ value: 0, className: 'high' }, // Red for negative values
{ value: 50, className: 'medium' }, // Yellow for values 0-50
{ value: 100, className: 'low' }, // Green for values 50-100
],
cellProperties: thresholdRenderer,
},
];
```
```ts
thresholdRenderer: PropertiesFunc>;
```
---
### `circularProgressRenderer`
```ts
circularProgressRenderer: CellTemplate>;
```
---
### `normalizeProgressRange`
Normalize a numeric value into a finite, clamped 0–100 progress range.
```ts
export function normalizeProgressRange(
value: unknown,
configuredMin: unknown = 0,
configuredMax: unknown = 100,
): NormalizedProgressRange;
```
---
### `NormalizedProgressRange`
```ts
interface NormalizedProgressRange {
readonly min: number;
readonly max: number;
readonly percent: number
}
```
---
### `getThresholdClasses`
Get the threshold classes for a given value and column.
### Example
```typescript
const columns: ColumnRegular[] = [
{
name: 'Avg Rating',
prop: 'Average Rating',
filter: ['number', 'slider'],
sortable: true,
maxStars: 5,
maxValue: 5,
thresholds: [
{ value: 4, className: 'high' },
{ value: 3, className: 'medium' },
{ value: 0, className: 'low' },
],
},
];
```
```ts
export function getThresholdClasses(
value: number,
column: Partial,
);
```
---
### `ThresholdConfig`
The `ThresholdConfig` interface defines the structure of a threshold configuration object.
It specifies the value and the class name to be applied when the value exceeds or equals the threshold.
```ts
interface ThresholdConfig {
/**
* The value of the threshold.
*/
value: number;
/**
* The class name to be applied when the value exceeds or equals the threshold.
*/
className: string
}
```
---
# Checkbox
URL: https://pro.rv-grid.com/api/checkbox/
Source: src/content/docs/api/checkbox.md
### `renderCheckbox`
Render the shared native checkbox while keeping the caller's renderer and
event semantics. This works with both RevoGrid/Stencil templates and Preact.
```ts
export function renderCheckbox(h: PreactH, attributes?: SharedCheckboxAttributes): PreactVNode;;
```
---
### `Checkbox`
Preact adapter for the shared checkbox presentation. The native input stays
the interaction owner; the adapter only normalizes indeterminate state and
optional label/description composition.
```ts
export function Checkbox({
checked,
indeterminate = false,
label,
description,
ariaLabel,
controlClassName = '',
inputRef,
onChange,
...attributes
}: CheckboxProps): any;
```
---
### `SHARED_CHECKBOX_CLASS`
```ts
SHARED_CHECKBOX_CLASS: string;
```
---
### `SharedCheckboxAttributes` (Extended from `index.ts`)
```ts
export type SharedCheckboxAttributes = Record & {
class?: string;
className?: string;
};
```
---
### `CheckboxProps` (Extended from `index.ts`)
```ts
export type CheckboxProps = Omit & {
checked?: boolean;
indeterminate?: boolean;
label?: any;
description?: any;
ariaLabel?: string;
controlClassName?: string;
inputRef?: (input: HTMLInputElement | null) => void;
onChange?: (checked: boolean, event: Event & { currentTarget: HTMLInputElement }) => void;
};
```
---
# Choice Chips
URL: https://pro.rv-grid.com/api/choice-chips/
Source: src/content/docs/api/choice-chips.md
### `ChoiceChips`
Shared wrapping choice control for compact presets and granularities.
```ts
export function ChoiceChips({
value,
options,
onChange,
ariaLabel,
appearance = 'soft',
size = 'medium',
className = '',
itemClassName = '',
disabled = false,
testId,
}: ChoiceChipsProps): any;
```
---
### `ChoiceChipValue`
```ts
export type ChoiceChipValue = string | number;
```
---
### `ChoiceChipOption`
```ts
export type ChoiceChipOption = {
value: T;
label: any;
ariaLabel?: string;
title?: string;
disabled?: boolean;
className?: string;
style?: any;
testId?: string;
};
```
---
### `ChoiceChipsProps`
```ts
export type ChoiceChipsProps = {
value?: T | readonly T[];
options: readonly ChoiceChipOption[];
onChange: (value: T, option: ChoiceChipOption) => void;
ariaLabel: string;
appearance?: 'soft' | 'outlined';
size?: 'small' | 'medium';
className?: string;
itemClassName?: string;
disabled?: boolean;
testId?: string;
};
```
---
# Clipboard
URL: https://pro.rv-grid.com/api/clipboard/
Source: src/content/docs/api/clipboard.md
## Module Extensions
### `ClipboardConfig` (Extended from `@revolist/revogrid`)
```ts
interface ClipboardConfig {
/** Additional rich clipboard formats used by ClipboardPlugin. */
formats?: readonly ClipboardFormatHandler[];
/** Maximum decoded cells. Defaults to 1,000,000. */
maxCells?: number;
/** Maximum bytes inspected in one text representation. Defaults to 16 MiB. */
maxBytes?: number
}
```
---
### `HTMLRevoGridElementEventMap` (Extended from `global`)
```ts
interface HTMLRevoGridElementEventMap {
clipboardformaterror: ClipboardFormatDiagnostic
}
```
## Plugin API
### `ClipboardPlugin`
Browser-rich, Excel-compatible clipboard orchestration for RevoGrid Pro.
#### Dependencies
- **Optional** `DataGridFormattingPlugin`: Uses DataGridFormattingPlugin when available to import and apply rich formatting.
```ts
class ClipboardPlugin {
registerFormat(handler: ClipboardFormatHandler): () => void;
registerMetadataContributor(contributor: ClipboardMetadataContributor): () => void;
}
```
---
### `matrixToClipboardDocument`
Converts a value matrix into one dense clipboard-document range.
```ts
export function matrixToClipboardDocument(
matrix: readonly (readonly DataFormat[])[],
source = 'revogrid',
): ClipboardDocument;
```
---
### `clipboardDocumentToMatrix`
Materializes positioned clipboard ranges as one dense value matrix.
```ts
export function clipboardDocumentToMatrix(document: ClipboardDocument): DataFormat[][];
```
---
### `countClipboardCells`
Counts the rectangular cell capacity declared by a clipboard document.
```ts
export function countClipboardCells(document: ClipboardDocument);
```
---
### `isClipboardDocument`
Validates the supported clipboard document wire shape.
```ts
export function isClipboardDocument(value: unknown): value is ClipboardDocument;
```
---
### `createExcelHtmlClipboardFormat`
```ts
export function createExcelHtmlClipboardFormat(
formatting?: ClipboardExcelFormattingAdapter,
): ClipboardFormatHandler;
```
---
### `createGenericHtmlClipboardFormat`
```ts
export function createGenericHtmlClipboardFormat(
formatting?: ClipboardExcelFormattingAdapter,
): ClipboardFormatHandler;
```
---
### `extractCfHtml`
```ts
export function extractCfHtml(value: string): string;
```
---
### `parseHtmlClipboard`
```ts
export function parseHtmlClipboard(
raw: string,
options: {
source: string;
maxBytes: number;
maxCells: number;
formatting?: ClipboardExcelFormattingAdapter;
},
): ClipboardDocument | null;
```
---
### `serializeExcelHtml`
```ts
export function serializeExcelHtml(
document: ClipboardDocument,
formatting?: ClipboardExcelFormattingAdapter,
): string;
```
---
### `ClipboardExcelFormattingAdapter`
```ts
interface ClipboardExcelFormattingAdapter {
importFormat(declarations: readonly string[]): ClipboardCell['format'];
importValue(displayText: string, format?: ClipboardCell['format']): unknown;
exportFormat(format: NonNullable): string | undefined;
exportValue(
value: unknown,
format: NonNullable,
): { readonly value: unknown; readonly displayText: string }
}
```
---
### `excelHtmlClipboardFormat`
Value-only default. ClipboardPlugin supplies the formatting runtime adapter.
```ts
excelHtmlClipboardFormat: ClipboardFormatHandler;
```
---
### `genericHtmlClipboardFormat`
Value-only default. ClipboardPlugin supplies the formatting runtime adapter.
```ts
genericHtmlClipboardFormat: ClipboardFormatHandler;
```
---
### `parseDelimitedText`
```ts
export function parseDelimitedText(value: string, delimiter: string): string[][];
```
---
### `serializeDelimitedText`
```ts
export function serializeDelimitedText(
matrix: readonly (readonly unknown[])[],
delimiter: string,
): string;
```
---
### `plainTextClipboardFormat`
```ts
plainTextClipboardFormat: {
id: string;
mimeTypes: string[];
priority: number;
read: ({ transfer, maxBytes, maxCells }: ClipboardFormatReadContext) => ClipboardDocument;
write: ({ document }: ClipboardFormatWriteContext) => { type: string; data: string; }[];
};
```
---
### `ClipboardFormatRegistry`
```ts
class ClipboardFormatRegistry {
register(handler: ClipboardFormatHandler): () => void;
read(transfer: ClipboardDataReader, limits: { maxCells: number; maxBytes: number });
write(document: ClipboardDocument, transfer: ClipboardDataWriter): void;
}
```
---
### `resolveClipboardPluginConfig`
Resolves clipboard limits and custom formats from the grid configuration.
```ts
export function resolveClipboardPluginConfig(
useClipboard: boolean | ClipboardConfig | undefined,
): ResolvedClipboardPluginConfig;
```
---
### `REVOGRID_CLIPBOARD_MIME`
```ts
REVOGRID_CLIPBOARD_MIME: string;
```
---
### `REVOGRID_CLIPBOARD_VERSION`
```ts
REVOGRID_CLIPBOARD_VERSION: 2;
```
---
### `ClipboardCell`
```ts
interface ClipboardCell {
readonly value: T;
readonly displayText?: string;
readonly formula?: string;
/** `null` explicitly copies an ordinary RevoGrid cell with no effective format. */
readonly format?: DataGridCellFormat | null;
readonly metadata?: Readonly>
}
```
---
### `ClipboardRange`
```ts
interface ClipboardRange {
readonly row: number;
readonly col: number;
readonly width: number;
readonly height: number;
readonly cells: readonly ClipboardCell[];
readonly merges?: readonly MergeData[];
readonly columnWidths?: readonly (number | undefined)[];
readonly rowHeights?: readonly (number | undefined)[];
readonly rowType?: DimensionRows;
readonly colType?: DimensionCols;
readonly metadata?: Readonly>
}
```
---
### `ClipboardDocument`
```ts
interface ClipboardDocument {
readonly version: typeof REVOGRID_CLIPBOARD_VERSION;
readonly source: string;
readonly ranges: readonly ClipboardRange[];
readonly metadata?: Readonly>
}
```
---
### `ClipboardDataReader`
```ts
interface ClipboardDataReader {
readonly types: readonly string[];
getData(type: string): string
}
```
---
### `ClipboardDataWriter`
```ts
interface ClipboardDataWriter {
setData(type: string, value: string): void
}
```
---
### `ClipboardFormatReadContext`
```ts
interface ClipboardFormatReadContext {
readonly transfer: ClipboardDataReader;
readonly maxCells: number;
readonly maxBytes: number
}
```
---
### `ClipboardFormatWriteContext`
```ts
interface ClipboardFormatWriteContext {
readonly document: ClipboardDocument
}
```
---
### `ClipboardRepresentation`
```ts
interface ClipboardRepresentation {
readonly type: string;
readonly data: string
}
```
---
### `ClipboardFormatHandler`
```ts
interface ClipboardFormatHandler {
readonly id: string;
readonly mimeTypes: readonly string[];
readonly priority?: number;
canRead?(context: ClipboardFormatReadContext): boolean;
read(context: ClipboardFormatReadContext): ClipboardDocument | null;
write?(context: ClipboardFormatWriteContext): readonly ClipboardRepresentation[]
}
```
---
### `ClipboardMetadataCaptureContext`
```ts
interface ClipboardMetadataCaptureContext {
readonly document: ClipboardDocument;
readonly providers: PluginProviders
}
```
---
### `ClipboardMetadataApplyContext`
```ts
interface ClipboardMetadataApplyContext {
readonly document: ClipboardDocument;
readonly providers: PluginProviders
}
```
---
### `ClipboardMetadataContributor`
```ts
interface ClipboardMetadataContributor {
readonly id: string;
capture?(context: ClipboardMetadataCaptureContext): unknown;
apply?(metadata: unknown, context: ClipboardMetadataApplyContext): void
}
```
---
### `ClipboardFormatDiagnostic`
```ts
interface ClipboardFormatDiagnostic {
readonly phase: 'read' | 'write' | 'metadata';
readonly handlerId: string;
readonly error: unknown
}
```
---
### `ClipboardPluginConfig`
```ts
interface ClipboardPluginConfig {
readonly formats?: readonly ClipboardFormatHandler[];
readonly maxCells?: number;
readonly maxBytes?: number
}
```
---
### `ResolvedClipboardPluginConfig`
```ts
interface ResolvedClipboardPluginConfig {
readonly formats: readonly ClipboardFormatHandler[];
readonly maxCells: number;
readonly maxBytes: number
}
```
---
### `captureClipboardSelection`
Captures current grid selections as a rich clipboard document.
```ts
export function captureClipboardSelection(
context: ClipboardCaptureContext,
matrix?: DataFormat[][],
): ClipboardDocument | null;
```
---
### `getActiveClipboardSelection`
Reads the active provider-backed selection as normalized clipboard coordinates.
```ts
export function getActiveClipboardSelection(
providers: PluginProviders,
): ClipboardSelection[] | null;
```
---
### `getClipboardPasteStart`
Returns the focused viewport cell used as the paste origin.
```ts
export function getClipboardPasteStart(
providers: PluginProviders,
): ClipboardPasteStart | null;
```
---
### `getClipboardSelectionOffsets`
Converts viewport selections into offsets from their shared whole-grid origin.
```ts
export function getClipboardSelectionOffsets(
selections: readonly ClipboardSelection[],
providers: PluginProviders,
);
```
---
### `createClipboardCoordinateResolver`
Creates a cached coordinate resolver for one copy or paste operation.
```ts
export function createClipboardCoordinateResolver(
providers: PluginProviders,
start: ClipboardPasteStart,
): ClipboardCoordinateResolver;
```
---
### `forEachClipboardDocumentCell`
Visits every valid cell stored in a clipboard document without an intermediate array.
```ts
export function forEachClipboardDocumentCell(
document: ClipboardDocument,
visit: (entry: ClipboardDocumentCell) => boolean | void,
): boolean;
```
---
### `getSingleClipboardDocumentCell`
Returns the only stored document cell, or null when the count is not one.
```ts
export function getSingleClipboardDocumentCell(document: ClipboardDocument);
```
---
### `getClipboardRangeCellOffset`
Resolves one row-major range cell into its document-relative offset.
```ts
export function getClipboardRangeCellOffset(
range: ClipboardRange,
index: number,
): ClipboardCellOffset | null;
```
---
### `getClipboardDocumentBounds`
Computes clipboard-document bounds in one pass.
```ts
export function getClipboardDocumentBounds(document: ClipboardDocument);
```
---
### `getClipboardCellCount`
Counts the rectangular capacity declared by all clipboard ranges.
```ts
export function getClipboardCellCount(document: ClipboardDocument);
```
---
### `getClipboardMatrixData`
Flattens a matrix into a dense row-major clipboard range.
```ts
export function getClipboardMatrixData(
matrix: readonly (readonly DataFormat[])[],
);
```
---
### `createClipboardMatrix`
Builds a dense value matrix from positioned clipboard ranges.
```ts
export function createClipboardMatrix(document: ClipboardDocument): DataFormat[][];
```
---
### `getClipboardFormulaDestination`
Resolves the absolute destination used to restore an Excel formula.
```ts
export function getClipboardFormulaDestination(
resolver: ClipboardCoordinateResolver,
range: ClipboardRange,
cellIndex: number,
);
```
---
### `forEachClipboardRangeFillCell`
Visits every projected destination for a one-cell range-fill paste.
```ts
export function forEachClipboardRangeFillCell(
selection: ClipboardSelection,
start: ClipboardPasteStart,
cell: ClipboardCell,
visit: (entry: ClipboardDocumentCell) => boolean | void,
): boolean;
```
---
### `clipboardOutcomeIncludesTarget`
Checks whether a managed paste outcome includes one resolved target.
```ts
export function clipboardOutcomeIncludesTarget(
detail: {
type?: DimensionRows;
data?: Record>;
} | undefined,
target: ClipboardResolvedPasteTarget,
);
```
---
### `createClipboardTargetMerge`
Repositions clipboard merge metadata at a resolved viewport target.
```ts
export function createClipboardTargetMerge(
merge: MergeData,
target: ClipboardPasteStart,
): MergeData;
```
---
### `captureClipboardMerge`
Converts an in-selection merge into clipboard-relative coordinates.
```ts
export function captureClipboardMerge(
merge: MergeData,
area: RangeArea,
rowType: DimensionRows,
colType: DimensionCols,
): MergeData | null;
```
---
### `getClipboardMergeOffsets`
Returns the inclusive paste bounds of a clipboard merge.
```ts
export function getClipboardMergeOffsets(
range: ClipboardRange,
merge: MergeData,
);
```
---
### `forEachClipboardMergeCell`
Visits every document-relative cell covered by a clipboard merge.
```ts
export function forEachClipboardMergeCell(
range: ClipboardRange,
merge: MergeData,
visit: (offset: ClipboardCellOffset) => boolean | void,
): boolean;
```
---
### `clipboardMergesOverlap`
Checks whether two same-viewport merge rectangles overlap.
```ts
export function clipboardMergesOverlap(left: MergeData, right: MergeData);
```
---
### `ClipboardPasteStart`
```ts
export type ClipboardPasteStart = ViewportCellAddress;
```
---
### `ClipboardResolvedPasteTarget`
```ts
export type ClipboardResolvedPasteTarget = ResolvedViewportCell;
```
---
### `ClipboardMetadataTargetFilter`
```ts
export type ClipboardMetadataTargetFilter = (target: ClipboardResolvedPasteTarget) => boolean;
```
---
### `ClipboardSelection`
```ts
interface ClipboardSelection {
readonly rowType: DimensionRows;
readonly colType: DimensionCols;
readonly range: RangeArea
}
```
---
### `ClipboardCellOffset`
```ts
interface ClipboardCellOffset {
readonly rowOffset: number;
readonly colOffset: number
}
```
---
### `ClipboardDocumentCell` (Extended from `coordinates.ts`)
```ts
interface ClipboardDocumentCell {
readonly cell: ClipboardCell
}
```
---
### `ClipboardCoordinateResolver`
Resolves clipboard offsets through one cached provider-coordinate snapshot.
```ts
interface ClipboardCoordinateResolver {
/** Converts a document offset into a whole-grid row and column. */
getAbsoluteCell(offset: ClipboardCellOffset): { readonly row: number; readonly column: number };
/** Reads cached source and item mappings for one viewport pair. */
getViewportSources(rowType: DimensionRows, colType: DimensionCols): ClipboardViewportSources | null;
/** Resolves a document offset to its model-backed grid cell. */
resolveTarget(offset: ClipboardCellOffset): ClipboardResolvedPasteTarget | null;
/** Resolves a document offset to its viewport-local address. */
resolveTargetAddress(offset: ClipboardCellOffset): ClipboardPasteStart | null;
/** Converts a resolved cell from visible indexes to physical source indexes. */
getPhysicalCellAddress(target: ClipboardResolvedPasteTarget): {
readonly row: number;
readonly column: number;
readonly rowType: DimensionRows;
readonly colType: DimensionCols;
};
/** Converts a resolved column from its visible index to its physical source index. */
getPhysicalColumnAddress(target: ClipboardResolvedPasteTarget): {
readonly column: number;
readonly colType: DimensionCols;
};
/** Returns the cached physical row source for readonly evaluation. */
getTargetRowSource(target: ClipboardResolvedPasteTarget): DataType[]
}
```
---
### `resolveClipboardFormattingRuntime`
Finds the installed data-grid formatting runtime, when available.
```ts
export function resolveClipboardFormattingRuntime(
providers: PluginProviders,
): DataGridFormattingRuntime | undefined;
```
---
### `applyClipboardFormatting`
Applies rich clipboard formats to every accepted destination cell.
```ts
export function applyClipboardFormatting(
context: ClipboardFormattingContext,
document: ClipboardDocument,
start: ClipboardPasteStart,
targetFilter?: ClipboardMetadataTargetFilter,
): void;
```
---
### `isClipboardPasteValueCompatible`
Checks that pasted values match every effective destination editor domain.
```ts
export function isClipboardPasteValueCompatible(
context: ClipboardFormattingContext,
document: ClipboardDocument,
start: ClipboardPasteStart,
): boolean;
```
---
### `resolveClipboardPasteFormulas`
Rebases stored Excel R1C1 formulas against the current paste destination.
```ts
export function resolveClipboardPasteFormulas(
providers: PluginProviders,
document: ClipboardDocument,
start: ClipboardPasteStart,
): ClipboardDocument;
```
---
### `isWritableClipboardTarget`
Checks provider readonly policy for one resolved clipboard destination.
```ts
export function isWritableClipboardTarget(
resolver: ClipboardCoordinateResolver,
target: ClipboardResolvedPasteTarget,
): boolean;
```
---
### `clipboardDocumentToSparsePayload`
Converts multi-range rich data into the legacy sparse clipboard payload.
```ts
export function clipboardDocumentToSparsePayload(
document: ClipboardDocument,
): MultiRangeClipboardPayload | null;
```
---
### `captureClipboardMerges`
Captures merges fully contained by one copied viewport range.
```ts
export function captureClipboardMerges(
revogrid: HTMLRevoGridElement,
area: RangeArea,
rowType: DimensionRows,
colType: DimensionCols,
);
```
---
### `applyClipboardStructure`
Applies valid, writable clipboard merges at the paste destination.
```ts
export function applyClipboardStructure(
context: ClipboardStructureContext,
document: ClipboardDocument,
start: ClipboardPasteStart,
targetFilter?: ClipboardMetadataTargetFilter,
): void;
```
---
### `normalizeExcelClipboardFormula`
Convert browser-visible Excel R1C1 references into FormulaPlugin-compatible A1 references.
```ts
export function normalizeExcelClipboardFormula(
value: string,
rowIndex: number,
columnIndex: number,
): string;
```
---
### `toExcelClipboardFormula`
Convert FormulaPlugin-compatible A1 references into Excel HTML R1C1 references.
```ts
export function toExcelClipboardFormula(
value: string,
rowIndex: number,
columnIndex: number,
): string;
```
---
### `cloneClipboardDocument`
```ts
export function cloneClipboardDocument(document: ClipboardDocument): ClipboardDocument;
```
---
### `revogridJsonClipboardFormat`
```ts
revogridJsonClipboardFormat: {
id: string;
mimeTypes: string[];
priority: number;
read: ({ transfer, maxBytes, maxCells }: ClipboardFormatReadContext) => ClipboardDocument | null;
write: ({ document }: ClipboardFormatWriteContext) => { type: string; data: string; }[];
};
```
---
# Clipboard Validation
URL: https://pro.rv-grid.com/api/clipboard-validation/
Source: src/content/docs/api/clipboard-validation.md
## Module Extensions
### `ClipboardConfig` (Extended from `@revolist/revogrid`)
```ts
interface ClipboardConfig {
/**
* Resolves strict validation failures during paste.
*
* `atomic` rejects the complete paste when any destination is invalid.
* `valid-cells` applies accepted destinations and preserves rejected cells.
*
* @default 'atomic'
*/
validationResolution?: ClipboardValidationResolution
}
```
## Plugin API
### `ClipboardValidationResolution`
Validation policy used when clipboard data contains strict validation failures.
```ts
/** Validation policy used when clipboard data contains strict validation failures. */
export type ClipboardValidationResolution = 'atomic' | 'valid-cells';
```
---
# Collaborative Presence
URL: https://pro.rv-grid.com/api/collaborative-presence/
Source: src/content/docs/api/collaborative-presence.md
## Module Extensions
### `HTMLRevoGridElement` (Extended from `@revolist/revogrid`)
```ts
interface HTMLRevoGridElement {
collaborativePresence?: CollaborativePresenceConfig;
'collaborative-presence'?: CollaborativePresenceConfig
}
```
---
### `AdditionalData` (Extended from `@revolist/revogrid`)
```ts
interface AdditionalData {
collaborativePresence?: CollaborativePresenceConfig
}
```
## Plugin API
### `CollaborativePresencePlugin`
Renders remote collaborator focus and range presence without mutating local
grid focus, selection, data, or history.
#### Dependencies
- **Config integration** `additionalData.collaborativePresence`: Reads remote collaborator presence from grid.collaborativePresence or additionalData.collaborativePresence.
- **Event integration** `afterrender`: Refreshes visible remote focus and range markers after grid render updates.
```ts
class CollaborativePresencePlugin {
destroy();
}
```
---
### `CollaborativePresenceActivity`
```ts
export type CollaborativePresenceActivity = 'viewing' | 'editing' | 'idle';
```
---
### `CollaborativePresenceCellRef`
```ts
interface CollaborativePresenceCellRef {
x: number;
y: number;
colType?: DimensionCols;
rowType?: DimensionRows
}
```
---
### `CollaborativePresenceRangeRef` (Extended from `index.ts`)
```ts
interface CollaborativePresenceRangeRef {
colType?: DimensionCols;
rowType?: DimensionRows
}
```
---
### `CollaborativePresenceUser`
```ts
interface CollaborativePresenceUser {
id: string;
name: string;
initials?: string;
color?: string;
activity?: CollaborativePresenceActivity;
focus?: CollaborativePresenceCellRef | null;
range?: CollaborativePresenceRangeRef | null;
lastActiveAt?: number | string | Date
}
```
---
### `CollaborativePresenceRowPatch`
```ts
interface CollaborativePresenceRowPatch {
rowIndex: number;
data: Record;
rowType?: DimensionRows
}
```
---
### `CollaborativePresenceConfig`
```ts
interface CollaborativePresenceConfig {
enabled?: boolean;
showLabels?: boolean;
staleAfterMs?: number;
users?: CollaborativePresenceUser[];
remoteEdits?: CollaborativePresenceRowPatch[]
}
```
---
### `ResolvedCollaborativePresenceConfig`
```ts
interface ResolvedCollaborativePresenceConfig {
enabled: boolean;
showLabels: boolean;
staleAfterMs?: number;
users: CollaborativePresenceUser[];
remoteEdits: CollaborativePresenceRowPatch[]
}
```
---
### `CollaborativePresenceRect`
```ts
interface CollaborativePresenceRect {
left: number;
top: number;
width: number;
height: number
}
```
---
### `CollaborativePresenceMarker`
```ts
interface CollaborativePresenceMarker {
kind: 'focus' | 'range';
user: CollaborativePresenceUser;
rect: CollaborativePresenceRect;
colType: DimensionCols;
rowType: DimensionRows
}
```
---
### `normalizeCollaborativePresenceCell`
```ts
export function normalizeCollaborativePresenceCell(
cell: CollaborativePresenceCellRef,
): Required