Skip to content

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.

Interactive example

Simulated chart providers

Load chart data through a simulated server, then explore delay, failure, cancellation, and retry.

The example loads when visible.

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.

PivotChartDataRequest includes:

FieldPurpose
requestIdUnique request identity that the response must echo.
viewId and fieldsVersionIdentify the remote analytical view and field registry.
loadOptionsCommitted Pivot filters, sorting, grouping, and engine options.
uiStateOptional Pivot UI hints.
axisStateExpanded row and column paths.
chart.seriesIdsRequested series selection.
chart.categoryMeasureValues-on-rows measure selection.
chart.totalsRequested totals mode.
chart.limitsEffective category, series, and point limits.
chart.bindingsStable semantic field-to-channel bindings.
chart.scopeExact committed revision, row/column paths, and immediate-child depths.
chart.queryOptional 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.

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.

The client rejects:

  • a response whose requestId differs from the request;
  • any change to revision, row/column paths, or requested depths;
  • partial frames whose returned and total counts disagree;
  • value/display matrices with wrong dimensions;
  • duplicate or unstable category/series IDs;
  • malformed hierarchy levels or member paths;
  • incompatible bindings, selected series, or values-on-rows measures;
  • categories, series, or point counts above the effective limits;
  • data that violates the selected chart’s semantic constraints.

There is no visible-window fallback. A partial response would create a misleading chart and is treated as an error.

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.

Interactive example

Requests and dense data

Load chart data through a simulated server, then explore delay, failure, cancellation, and retry.

The example loads when visible.

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.

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.

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 overrides
await chart.update({ limits: {} }); // inherit plugin limits again

A limits update replaces the authored object; undefined leaves it unchanged. Values must be finite and at least 1, and are rounded down to safe integers. Unknown keys and non-plain objects are rejected. Limits are budgets, not data transformations: exceeding one reports LIMIT_EXCEEDED and retains the prior chart. A limits-only update validates the current frame before requesting data; a combined selection update validates the newly selected frame. Unlinked charts validate their captured data without requiring an active Pivot.

Matrix points count every category/series cell, including null cells. Statistical payloads have a separate point budget: raw sample counts, or one summary plus its outlier and density entries. Both budgets must fit maxDataPoints; they are not added together. Providers receive the effective limits for root, refresh, and drill requests. The plugin validates returned payloads before caching/rendering.

Within those limits, the renderer degrades deliberately:

  • every legend position reserves a valid plot rectangle;
  • long legends show a deterministic subset and localized hidden count;
  • category ticks and data labels are thinned without removing data;
  • final text layout measures localized and customized fonts, fits long names with ellipses and full hover captions, and omits labels that cannot fit without overlapping another label. Numeric data labels are omitted rather than shortened;
  • Cartesian marks retain exact values;
  • density scatter, histograms, strip plots, and large heatmaps can aggregate observations into rendered bins; those bins expose inspection descriptions without claiming an individual observation’s selection or drill identity;
  • unstacked line/area families, combination line/area layers, annotated time series, and small multiples sample dense paths while retaining real source points, extrema, and separate finite runs;
  • charts with more than 240 rendered inspection targets use one Tab stop with arrow-key navigation. Dense canonical marks remain reachable; source matrix dimensions alone do not determine the keyboard policy.

Sampling affects rendered geometry, not the persisted normalized dataset. Exceeding configured projection limits fails rather than silently truncating the model.

KPI, gauge and progress-gauge views limit visible tiles to the container’s readable capacity. A localized sampling footer and accessible description report the source and rendered row counts. Resizing recalculates the capacity; View data and exports retain the complete dataset. The card itself remains an inspection target even when its optional text cannot fit.

Interactive example

Rendering dense charts

Inspect a long series with retained extrema, null gaps, and a bounded drawing budget.

The example loads when visible.

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.

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.

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.

Interactive example

Geographic and statistical providers

Explore schematic provider-owned regions. The example does not include real map geometry.

The example loads when visible.

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.

  • 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

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.