Skip to content

Datum styling and encoding

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.

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.

Native familyDatum fill/strokeShape/radiusDirected segment stroke
line, smoothLine, steppedLinePainted markersYesYes
area, steppedArea, stackedArea, normalizedAreaPainted markers; default style also paints areaYesYes, outline only
groupedColumn, stackedColumn, normalizedColumn, groupedBar, stackedBar, normalizedBarYesRejectedRejected
scatterYesYesRejected
Combinations, bubble, range, polar, financial, heatmap, other familiesRejectedRejectedRejected
Batched SVGWhole-scene fallback to individual SVG with data-chart-renderer-fallback="datum-style"Same fallbackSame 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.

ChartDatumEncoding supports only:

FieldMeaning and limits
styleDefault ChartDatumStyle for this series. Path fill applies to the area, stroke to its outline; marker fields apply to painted markers.
datumsUp 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.
segmentsUp to 10,000 { from, to, style } entries, unique directed ID pairs. Same ID limits. Line/area stroke fields only.

ChartDatumStyle supports only:

FieldValues
fill, strokeExisting hexadecimal colors, or the paint objects below. No external URLs or arbitrary SVG.
strokeWidthFinite 0–64 SVG units.
strokeDasharraynone or nonnegative decimal lengths separated by commas/spaces; at most 128 characters.
fillOpacity, strokeOpacityFinite 0–1.
markercircle, square, diamond, triangle, cross, star, wye.
markerSizeFinite radius 0–128 SVG units; zero paints no visible glyph.

Paint objects:

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.

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.

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.

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.