Server Data and Performance
A remote Pivot viewport is only a window into the analytical result. Pivot
Charts never treat the visible rows or columns as the complete dataset.
Configure PivotChartDataProvider to return the complete immediate-child frame
for every requested semantic scope.
Configure a provider
Section titled “Configure a provider”Simulated chart providers
Load chart data through a simulated server, then explore delay, failure, cancellation, and retry.
grid.pivotCharts = { renderer: createPivotChartsRenderer(), dataProvider: { async load(request, { signal }) { const response = await fetch('/api/pivot/chart-data', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(request), signal, });
if (!response.ok) { throw new Error(`Chart data failed: ${response.status}`); } return response.json(); }, },};The provider is used for root projection, explicit scoped data, linked
refreshes, and drill frames. Local and server Pivots produce the same
PivotChartDataset contract after projection and validation.
Request contract
Section titled “Request contract”PivotChartDataRequest includes:
| Field | Purpose |
|---|---|
requestId | Unique request identity that the response must echo. |
viewId and fieldsVersion | Identify the remote analytical view and field registry. |
loadOptions | Committed Pivot filters, sorting, grouping, and engine options. |
uiState | Optional Pivot UI hints. |
axisState | Expanded row and column paths. |
chart.seriesIds | Requested series selection. |
chart.categoryMeasure | Values-on-rows measure selection. |
chart.totals | Requested totals mode. |
chart.limits | Effective category, series, and point limits. |
chart.bindings | Stable semantic field-to-channel bindings. |
chart.scope | Exact committed revision, row/column paths, and immediate-child depths. |
chart.query | Optional normalized chart-local slice, member filters, sort, and ranking. |
The semantic scope is absolute:
interface PivotChartSemanticScope { revision: number; rowPath: PivotPath; columnPath: PivotPath; rowDepth: number; columnDepth: number;}The response must contain every immediate child for that scope, not a viewport-sized subset.
Response contract
Section titled “Response contract”interface PivotChartDataResponse { requestId: string; scope: PivotChartSemanticScope; categories: PivotChartCategory[]; series: PivotChartSeries[]; values: Array<Array<number | null>>; displayValues?: Array<Array<string | null>>; hierarchies: { row?: PivotChartHierarchyDefinition; column?: PivotChartHierarchyDefinition; }; totalCategoryCount: number; totalSeriesCount: number;}categories.length and series.length describe the returned complete frame.
The total counts let the client prove that the response is complete and within
limits. Hierarchy definitions and member hierarchy metadata are required when
the response contains drillable members.
Strict validation
Section titled “Strict validation”The client rejects:
- a response whose
requestIddiffers from the request; - any change to revision, row/column paths, or requested depths;
- partial frames whose returned and total counts disagree;
- value/display matrices with wrong dimensions;
- duplicate or unstable category/series IDs;
- malformed hierarchy levels or member paths;
- incompatible bindings, selected series, or values-on-rows measures;
- categories, series, or point counts above the effective limits;
- data that violates the selected chart’s semantic constraints.
There is no visible-window fallback. A partial response would create a misleading chart and is treated as an error.
Chart-local query providers
Section titled “Chart-local query providers”A provider that executes chart-local analytical queries must opt in with
capabilities: { query: true }. Existing providers continue serving charts
without an authored query.
dataProvider: { capabilities: { query: true }, async load(request, { signal }) { // Execute the complete analytical query on the backend. return loadCompleteChartFrame(request, signal); },}For shaped requests, the response must include query matching the normalized
request.chart.query, alongside the existing request and semantic scope
identities. Missing or different query echoes are rejected. Nonempty frames
must include each requested measure for every semantic member, including null
values, unless an explicit series or category-measure selection narrows them.
The inherited
loadOptions remain part of the request; chart-local filters intersect them.
The provider executes ranking globally within the requested semantic scope before returning complete counts. It builds Other from source transactions and reaggregates the configured measures. Ranking a remote page or adding its displayed averages and distinct counts cannot satisfy this contract. Statistical distributions must describe the same shaped source.
charts.getDataShapingCapabilities() reports current support and reasons.
Provider opt-in establishes an integration contract; the frontend cannot
independently prove backend aggregation correctness.
Cancellation and stale results
Section titled “Cancellation and stale results”Requests and dense data
Load chart data through a simulated server, then explore delay, failure, cancellation, and retry.
Every provider call receives an AbortSignal. Abort underlying HTTP, query,
and decoding work when possible. Root loads, linked refreshes, and drill
transitions carry monotonically increasing revisions. The plugin ignores a
completion that is no longer current even if the provider did not stop after
abort.
During drill, the last successful frame remains rendered. During a linked refresh, the UI exposes a loading state and commits the replacement only after data validation and renderer update succeed.
Caching
Section titled “Caching”The plugin caches immutable datasets by committed Pivot revision, effective data options, bindings, and semantic scope. Concurrent equivalent requests share the same promise. Successful drill scopes are cached per chart so breadcrumb navigation does not repeatedly load unchanged data.
Replacing grid.pivotCharts, changing the Pivot model/source/filter/sort/group
state, or advancing the committed revision invalidates the relevant cache.
Application/server caches should use the same request identity inputs and must
still return the exact requested scope.
Dense-data behavior
Section titled “Dense-data behavior”Defaults are:
const DEFAULT_PIVOT_CHART_LIMITS = { maxCategories: 1_000, maxSeries: 250, maxDataPoints: 50_000,};Chart requests use a 10,000-category acquisition default instead. Categorical charts without scrolling automatically choose a Top N from chart type and available area, plus a synthetic Others group, recalculated from source records. The chart explains the reduction. Ordered, relationship and statistical charts keep their own rendering policies. The built-in renderer automatically shows a native scrollbar when categories no longer fit: horizontal for columns, vertical for bars. It draws only the visible category window and keeps the value axis based on all categories. Scrolling preserves datum identities; CSV/data exports retain the full dataset, and images capture the visible plot. Scroll position is local display state, not persisted chart-model state. The raw projector retains the defaults above when no chart type is supplied.
Effective limits apply in this order: built-in defaults, plugin limits, then
chart-authored limits. The chart model stores only authored overrides, so
omitted keys inherit the current plugin configuration on later operations.
const chart = await charts.createChart({ container, limits: { maxCategories: 2_000, maxDataPoints: 100_000 },});await chart.update({ limits: { maxCategories: 3_000 } }); // replace overridesawait chart.update({ limits: {} }); // inherit plugin limits againA limits update replaces the authored object; undefined leaves it unchanged.
Values must be finite and at least 1, and are rounded down to safe integers.
Unknown keys and non-plain objects are rejected. Limits are budgets, not data
transformations: exceeding one reports LIMIT_EXCEEDED and retains the prior
chart. A limits-only update validates the current frame before requesting data;
a combined selection update validates the newly selected frame. Unlinked charts
validate their captured data without requiring an active Pivot.
Matrix points count every category/series cell, including null cells. Statistical
payloads have a separate point budget: raw sample counts, or one summary plus its
outlier and density entries. Both budgets must fit maxDataPoints; they are not
added together. Providers receive the effective limits for root, refresh, and
drill requests. The plugin validates returned payloads before caching/rendering.
Within those limits, the renderer degrades deliberately:
- every legend position reserves a valid plot rectangle;
- long legends show a deterministic subset and localized hidden count;
- category ticks and data labels are thinned without removing data;
- final text layout measures localized and customized fonts, fits long names with ellipses and full hover captions, and omits labels that cannot fit without overlapping another label. Numeric data labels are omitted rather than shortened;
- Cartesian marks retain exact values;
- density scatter, histograms, strip plots, and large heatmaps can aggregate observations into rendered bins; those bins expose inspection descriptions without claiming an individual observation’s selection or drill identity;
- unstacked line/area families, combination line/area layers, annotated time series, and small multiples sample dense paths while retaining real source points, extrema, and separate finite runs;
- charts with more than 240 rendered inspection targets use one Tab stop with arrow-key navigation. Dense canonical marks remain reachable; source matrix dimensions alone do not determine the keyboard policy.
Sampling affects rendered geometry, not the persisted normalized dataset. Exceeding configured projection limits fails rather than silently truncating the model.
KPI, gauge and progress-gauge views limit visible tiles to the container’s readable capacity. A localized sampling footer and accessible description report the source and rendered row counts. Resizing recalculates the capacity; View data and exports retain the complete dataset. The card itself remains an inspection target even when its optional text cannot fit.
Bounded paths and data tables
Section titled “Bounded paths and data tables”Rendering dense charts
Inspect a long series with retained extrema, null gaps, and a bounded drawing budget.
Sampled SVG paths share a chart-wide budget of at most 4,800 canonical point marks. Each series also has a pixel-based budget and its family’s existing cap. Sampling retains actual first, minimum, maximum, and last points in each retained bucket in source order when the allocated budget can represent them. Null values split paths before sampling. When there are more finite runs than the budget can represent, the renderer keeps a bounded set of separate runs and reports omitted observations; it never draws a line across an omitted gap.
Sampling changes drawing work. Canonical values, scale domains, saved data, supported semantic annotations, and shared crosshair lookup remain complete. Localized chart descriptions report the source and rendered counts. Column rectangles and stacked/normalized geometry retain their complete visible cells, so their DOM cost still grows with the dataset. Projection limits remain the safety boundary for those families.
View data uses RevoGrid’s native row and column virtualization, keeping every canonical cell reachable by scrolling. Sorting applies to the complete frame, with stable ties and null values last in both directions. The grid is read-only; selection and column resizing remain available.
Optional ECharts Canvas renderer
Section titled “Optional ECharts Canvas renderer”Applications can inject a modular ECharts runtime through the existing renderer configuration. Pivot does not import or fetch ECharts at runtime:
import * as echarts from 'echarts/core';import { BarChart, LineChart } from 'echarts/charts';import { GridComponent, LegendComponent, TitleComponent, TooltipComponent } from 'echarts/components';import { CanvasRenderer } from 'echarts/renderers';import { createPivotChartsEChartsRenderer } from '@revolist/pivot';
echarts.use([ BarChart, LineChart, GridComponent, LegendComponent, TitleComponent, TooltipComponent, CanvasRenderer,]);
grid.pivotCharts = { renderer: createPivotChartsEChartsRenderer(echarts),};Choose the renderer before creating a chart. The adapter supports
groupedColumn, groupedBar, and line, basic presentation, grid lines, and
PNG export. Pointer activation uses the original canonical cell. A bounded HTML
inspector provides keyboard access to finite values without creating one DOM
node per value. Null line values remain gaps.
The adapter rejects authored axes, colors, series overrides, navigation, annotations, selection, cross-highlighting, and unsupported chart options. It preserves the complete dataset and existing projection limits. It does not provide automatic aggregation, SVG/vector export, or an automatic backend switch. Use the default SVG renderer when those features are required.
Renderer preview performance
Section titled “Renderer preview performance”The gallery uses the same chart definitions and renderer functions as full
charts, but with renderMode: 'preview'. Preview mode is decorative and omits
tooltips and interactive focus targets. Intersection-based mounting defers a
preview until its card approaches the viewport. SVG snapshots are cached by
type, renderer, theme, and locale. Canvas previews are recreated on remount,
because cloning DOM nodes does not copy a Canvas bitmap. Their compact layout
hides axes, titles, legends, and labels.
Geographic capability
Section titled “Geographic capability”Geographic and statistical providers
Explore schematic provider-owned regions. The example does not include real map geometry.
Geographic views are opt-in because map geometry, projections, tiles, and licenses belong to the provider:
const pack = createPivotChartGeographicCapabilityPack(provider);
const host = await pack.render({ container, chartType: 'choropleth', dataset, bindings: { geographic: ['dimension:country'], value: ['measure:sales'] },});The pack exposes choropleth, proportionalSymbolMap, and
geographicHeatmap. These views are not built-in gallery types and do not
change PivotChartModel. Importing Pivot Charts alone loads no geographic
assets or map dependency.
Production checklist
Section titled “Production checklist”- Return complete immediate-child frames from the analytical engine.
- Preserve stable field, category, series, hierarchy, and path identities.
- Echo the request ID and semantic scope exactly.
- Forward abort signals to network and query work.
- Set limits that match renderer and server budgets.
- Include formatted display values when chart and Pivot formatting must match.
- Log provider errors with request/view IDs without exposing raw sensitive source rows.
- Exercise root, linked refresh, drill, retry, breadcrumb, and stale-response paths before release.
Previous: Headless API and persistence · Pivot Charts overview · Remote Pivot guide · API reference
Statistical provider data
Section titled “Statistical provider data”Return source distributions in response.analyticalFrame.derived.distributions
when serving box plots or violin charts. Each entry is keyed by returned
categoryId and seriesId and contains either finite kind: 'samples'
observations or a kind: 'summary' with ordered quartiles and whiskers. Violin
summaries additionally require ordered nonnegative density points. Raw sample
arrays, summary points, outliers, and density allocations are checked against
the chart’s maxDataPoints budget. The provider must supply the complete requested
frame; it must not substitute aggregated Pivot cells for raw observations.
See Source distributions for the public data shape and statistical conventions.