# 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. --- # 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 ``` ## 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 {chartTools.map(([slug, label, group]) => )}
ToolArea
{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.
Excel-style selection filter with a mini-filter, checked Select All control, collapsed year values, a Blanks option, and Apply and Cancel buttons.
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.
Excel-style selection filter searching for ap, with Select All Search Results, Add current selection to filter, and matching Apple and Grapes options.
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)**.
Excel-style date checklist with 2025 and February expanded to day 03, and mixed-state checkboxes on Select All and the 2025 parent.
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: `..