Skip to content

Chart Customization

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.

DetailAPI
Default SVG line/grouped-bar transitionsrendererOptions.transitions (runtime preference)
Default series/category colorsrendererOptions.palette
Stable semantic colors, member/measure colors, gradients, conditional colorscolors
Series color, visibility, axis, mark type, stacking, errorsseriesOverrides[seriesId] (supported fields depend on the chart family)
Chart background, foreground, muted text, gridrendererOptions.theme (tokens or a callback)
Titles, description, legend placement, data labelspresentation
Every SVG label’s content, visibility, typography, class, accessible namerendererOptions.labels.styles and labels.formatter
Individual marks, fills, outlines, dashes, opacity and typographyrendererOptions.elements.styles and elements.formatter
Tooltips and their contentrendererOptions.tooltip, including formatter, className and timing
Domains, scales, tick formats, units, titlesaxes
Reference lines, ranges, text and drawing shapesannotations
Family-specific geometrychartOptions; inspect the selected catalog definition’s schema
Arbitrary SVG geometry, custom layers and remaining visual detailsrendererOptions.onRender
UI strings and number/date formattinglocaleText

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.

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.

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.

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.

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.

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 for the family-specific contracts.

Geographic charts have a separate provider lifecycle. All three types (choropleth, proportionalSymbolMap, geographicHeatmap) accept rendererOptions on the pack.render(input)/instance.update(input) input:

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

Section titled “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.

rendererOptions.sankey supplies sort comparators, node/link colors, value formatters and node/link/highlight callbacks; see the complete Sankey contract. 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.

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.

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.

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. 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.

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.

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.

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.

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.

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.

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. Histograms also support shared/crosshair/peer bin readouts and typed formatter metadata. Remote histogram loading uses complete-bin source summaries; typed metadata keeps global metrics separate from loaded raw rows.

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.

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.

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 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.

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

Section titled “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.

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.

FieldOmitted behavior / editor defaultAccepted values and units
outerRadiusFull available radius / 10.05–1, step 0.05; fraction of the fitted radius, after existing plot/legend layout.
paddingAngleNo angular gap / 00–45 degrees, step 0.5.
cornerRadiusSharp corners / 00–100 SVG pixels, step 1; bounded by sector angle and ring thickness during rendering.
arcLabelRadiusExisting adaptive per-ring label placement / editor preset 0.70–1, step 0.05; distance from center as a fraction of the individual slice’s actual outer radius.
arcLabelMinAngleNo additional angular filter / 00–360 degrees, step 1; minimum raw source sector angle for an on-arc label.
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.

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.

PolicyCoordinates
'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.
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.

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.

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 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.

Use chartOptions.sparkline for saved compact layout in Charts and Pivot, or createSparklineChart for a number-array source. See sparklines for every option, defaults, sizing, inspection and restore/export behavior.

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.

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:

import { createChart } from '@revolist/charts';
const chart = await createChart({
container: document.querySelector<HTMLElement>('#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.

OptionContract
transitionsOmitted or {}: 180 ms ease-out for supported SVG updates. false opts out. true, null, arrays, functions and unknown policy fields reject before mounting.
durationFinite 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.

ConfigurationBehavior
line with linear interpolation, ordinary SVGSource-point path and marker positions interpolate; runs/series without retained source points fade in.
groupedColumn, groupedBar, ordinary SVG, no stack groupsMatched source rects move/resize; new rects grow from the clipped zero baseline and fade in.
Removed/null observations or removed/hidden seriesRemoved 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 combinationsImmediate rendering. No chart-type morphing.
Batched SVG policyImmediate rendering, including when another customization causes that policy to fall back to individual SVG.
WebGL or third-party rendererNo transition support is promised by this native SVG option.
Stacked groups, non-linear line interpolation, any analysis configurationImmediate rendering.
Segment encoding, runtime datumStyle, elements or onRender customizationImmediate rendering; custom geometry is not interpolated.
Preview/sparkline, unavailable animation frames, reduced motionImmediate 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.

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.

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.

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.

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 }).
OptionBehavior
hostActions.label?Accessible button/menu label; default Chart actions.
hostActions.itemsAction 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, labelNonempty 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 childrenAction 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 for using the same canonical context around exports.

Renderer diagnostics and shared runtime options

Section titled “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.

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:

Reattach runtime configuration after restore. Accelerated renderers can reject or fall back for individual-DOM/CSS customizations; check batched SVG and WebGL before combining those policies with callbacks. Their capability matrix takes precedence over the general SVG customization examples.