Skip to content

Appearance and Formatting

Make the chart readable before adding decoration. Start with a clear title, an appropriate number format, a useful legend and enough space for labels.

Try changing the title and legend, then turn value labels on or off. Use the chart’s Display controls for appearance. The code below shows the corresponding model settings; shared renderer styling applies across charts.

For chart data choices use Layout. For buttons, translated text or a settings drawer use controls and localization.

Interactive example

Chart presentation

Change titles, legend placement, and data labels to compare the chart's presentation.

The example loads when visible.

await chart.update({
presentation: {
title: 'Quarterly sales',
subtitle: 'Committed orders only',
legendPosition: 'bottom',
dataLabels: true,
xAxisTitle: 'Region',
yAxisTitle: 'Revenue',
},
theme: 'material',
});

legendPosition accepts top, right, bottom, left, or none. theme accepts auto or a renderer-supported theme string. The standard UI offers follow-grid, default, material, compact, dark material, and dark compact choices, plus high contrast and dark high contrast.

The catalog generates a typed option schema for every chart:

OptionAvailabilityValid values
showGridLinesCharts with axes.Boolean.
markOpacityEvery built-in type.0.2–1 in 0.05 steps.
strokeWidthLine, area, combination, radar line, and radar area.1–6 in 0.5 steps.
fillOpacityArea-family, area-line, radar area, confidence band, and range area.0.1–0.9 in 0.01 steps.
facetScaleModeSmall multiples.independent (default) or shared.
await chart.update({
chartOptions: {
showGridLines: false,
markOpacity: 0.9,
strokeWidth: 2.5,
},
});

Unsupported keys, out-of-range values, and values that do not align with the declared step are rejected during update and restore.

Small multiples use independent value domains by default. This emphasizes each series’ trend; equal panel heights can represent very different values. Choose shared scales when comparing magnitudes:

await chart.update({
chartType: 'smallMultiples',
chartOptions: { facetScaleMode: 'shared' },
});

Panels show their scale mode and value ticks. Shared domains use the complete visible series data, including values omitted by path sampling or outside the current viewport. Independent domains use each series’ complete values. Changing visibility can change these domains. Reference lines and notes follow the same panel scales as the plotted data.

Portable colors settings complement the renderer palette. The default paletteMode: 'index' preserves palette order; opt into 'semantic' to assign colors by measure and field-qualified member identity across reordering and refresh. Labels do not determine identity. Semantic mode requires field metadata for nonempty member paths; providers supply it through hierarchy definitions or selection fields. Missing metadata is rejected instead of reverting to an unstable index color.

Configured measure IDs keep semantic colors stable when measures are reordered. Without a configured ID, measure identity includes its original configured index to distinguish repeated measures; reordering that configuration can therefore change its palette assignment.

await chart.update({
colors: {
paletteMode: 'semantic',
measures: [{
measure: { prop: 'sales', aggregator: 'sum', valueIndex: 0 },
color: '#2563eb',
}],
members: [{
axis: 'row',
path: [{ field: 'region', value: 'North' }],
color: '#f97316',
pattern: 'diagonal',
}],
},
});

Member overrides match the exact field-qualified path; they do not implicitly color descendants or synthetic Other members with a matching label. Authored colors accept #RGB or #RRGGBB. Unavailable identities remain dormant so styling can return when a member or series reappears. When measure selectors overlap, the first matching measure-color entry wins. Duplicate exact member paths or measure selectors are rejected.

Supported discrete marks can map their analytical value to color:

await chart.update({
colors: {
valueMap: {
type: 'diverging',
min: -100,
midpoint: 0,
max: 100,
colors: ['#2563eb', '#ffffff', '#dc2626'],
},
},
});

continuous maps between two colors. conditional applies the first matching numeric gt, lt, equal, or inclusive between rule, with an optional fallback color. Mappings use analytical values, preserve missing observations, and clamp outside explicit domains. Automatic domains use the visible series’ full data, independently of viewport sampling.

Numeric mappings display a separate color key even when the ordinary legend is hidden. Gradient keys show their domain; conditional keys show a bounded set of swatches and the number of additional rules. The Display controls retain the complete rule list.

Value mapping takes precedence over explicit series/member colors, followed by measure colors and the palette. Mapping is supported for grouped bar and column, heatmap/calendar heatmap, and pie/donut/semi-donut charts. Line colors represent series; numeric mapping of an entire line is unsupported. Aggregated heatmap buckets cannot receive canonical member or value mappings.

For independently visible series, seriesOverrides can persist color and visible. Line-bearing charts also support lineDash and marker:

await chart.update({
seriesOverrides: {
[forecastSeriesId]: {
color: '#475569',
visible: true,
lineDash: 'dash',
marker: 'diamond',
},
},
presentation: { legendAction: 'toggleVisibility' },
});

Visibility removes a series from plotted geometry, scales, stacks, tooltips, and rendered exports while retaining the canonical chart dataset. Hidden series remain available in the legend and Display controls. Hiding every series leaves a recoverable empty plot. Appearance and visibility changes do not request new provider data or change the shared Pivot.

Series visibility is supported by bar/column and line/area families, combination charts, annotated time series, and small multiples. Required relationship or financial channels cannot be hidden as independent series. Unsupported authored settings are rejected according to chart capabilities.

colors and seriesOverrides updates replace their whole authored objects; omit them to preserve settings or pass {} to reset. Custom renderers must opt into the new appearance and visibility capabilities.

Persist thresholds and notes in annotations. References use logical axes: category, primary, or secondary. On horizontal bars, the primary value axis runs horizontally. Secondary references require a combination chart.

await chart.update({
annotations: {
references: [
{
id: 'target',
kind: 'line',
axis: 'primary',
value: 2500,
label: 'Sales target',
color: '#dc2626',
lineDash: 'dash',
},
{
id: 'expected-range',
kind: 'band',
axis: 'primary',
min: 1000,
max: 2000,
color: '#2563eb',
opacity: 0.2,
},
],
notes: [{
id: 'launch',
target: {
row: [{ field: 'region', value: 'North' }],
column: [],
measure: { prop: 'sales', aggregator: 'sum' },
},
text: 'New product launch',
placement: 'above',
}],
},
});

Category lines use at; category bands use from and to. Each coordinate is either { scale: 'category', path: [{ field, value }], measure? } or { scale: 'time', value: utcEpochMilliseconds }. Time coordinates require an actual time axis. Category bands span the centers of their semantic endpoints in the current order. They do not create extra category buckets.

References use the existing plot domain: off-domain lines disappear and bands are clipped. They never stretch the scale. Numeric bounds must be finite and ordered; references on logarithmic axes must be positive. In small multiples, a primary reference applies the same authored value to each panel’s own scale.

Notes match exact field-qualified row and column paths plus a measure, rather than labels or array indexes. Use a configured measure ID to distinguish repeated measures; an explicit valueIndex further constrains the match. Group and collapsed aggregate points can have notes. Subtotals, grand totals, synthetic Other members, and ambiguous targets are unavailable.

A missing, hidden, null, off-screen, or omitted-panel target keeps its authored note dormant. When that exact observation returns, the note follows its current value. Drilling to children never reassigns a parent’s note. Nonempty paths require provider field metadata. Notes can anchor observations omitted by visual sampling, and do not intercept chart selection or drill actions.

The Display tab applies valid edits automatically and provides Clear controls for editing references and notes. Annotation edits preserve provider data and survive refresh, unlink, and JSON restoration. Pass annotations: {} to clear them; omit the field to preserve them. Updates replace the whole annotation object. Each array accepts up to 32 entries, IDs up to 128 characters, reference labels up to 256 characters, and nonblank note text up to 512 characters.

Supported families are grouped bar/column, unstacked line/area variants, combination charts, annotated time series, and small multiples. Unsupported families reject authored annotations. Custom renderers opt in through capabilities.annotations. Existing automatic extrema labels are independent of authored notes. Rendered references and notes follow the chart’s label, theme, and image/document export pipeline. The label formatter receives annotationId and annotationKind (reference or note) for authored labels. Notes retain their canonical datum context; numeric reference labels expose the threshold value without pretending to be a data observation. Long labels wrap within the panel, with complete text retained in accessible titles.

Interactive example

Chart styling and labels

Compare label formatting, typography, and presentation in the chart.

The example loads when visible.

const renderer = createPivotChartsRenderer({
className: 'sales-chart',
fontFamily: '"IBM Plex Sans", sans-serif',
categorySeparator: ' / ',
palette: ['#2563eb', '#f97316', '#16a34a'],
theme: ({ theme }) => ({
...theme,
background: '#fffdf8',
foreground: '#292524',
grid: '#e7e5e4',
}),
});

The theme callback receives the resolved light/dark base theme and current render input. Renderer classes, theme colors, palette, fonts, labels, and visible chart options are retained in image and document exports.

Every visible label passes through one formatter. Kinds are title, subtitle, axisTitle, axisTick, category, legend, data, annotation, summary, and specialized.

const renderer = createPivotChartsRenderer({
labels: {
styles: {
title: { fontSize: 20, fontWeight: 700 },
category: { fill: '#475569', fontSize: 11 },
data: { fontWeight: 600 },
},
formatter(context) {
if (context.kind === 'legend') {
return `Measure: ${context.defaultText}`;
}
if (context.kind === 'axisTick' && context.axis === 'y') {
return null;
}
if (context.kind === 'data' && context.formattedValue) {
return {
text: context.formattedValue,
ariaLabel:
`${context.category?.label}: ${context.formattedValue}`,
className: 'sales-chart-value',
};
}
return undefined;
},
},
});

Return a string, a structured result, null to suppress one label, or undefined to retain the default. Structured results can override text, accessible text, classes, safe SVG typography, opacity, and visibility.

Annotated time series automatically arranges endpoint and extrema labels inside the plot after applying your formatter and typography. Labels move apart with leaders pointing to their observations. If no clear position fits, the optional label is omitted; the full value remains available on the point’s tooltip and accessible label. Zooming uses the visible observations. Return null for context.kind === 'annotation' to hide these labels and their leaders.

const renderer = createPivotChartsRenderer({
tooltip: {
showDelayMs: 100,
hideDelayMs: 60,
followPointer: true,
className: 'sales-chart-tooltip',
formatter(context, defaultContent) {
return {
...defaultContent,
footer: context.drillable
? 'Open the next sales level'
: 'Leaf value',
};
},
},
});

The formatter receives raw and formatted values, category and series metadata, relationship channels, mark color, and drillability. Return structured content, plain text, an HTMLElement, or null to suppress one tooltip. Set tooltip: false to disable tooltips.

Interactive example

Data labels

Change titles, legend placement, and data labels to compare the chart's presentation.

The example loads when visible.

getPivotChartDefinition(type).interactions.labels advertises support for optional presentation.dataLabels text. The Display checkbox is available only when this capability is true. Switching to a chart without optional labels clears dataLabels: true while preserving other compatible presentation settings.

Unsupported chart types reject dataLabels: true with INVALID_DATA_SHAPE in the model parser, plugin, standalone API, and native renderer. false and omitted values remain valid. This also applies to saved models: clear an unsupported true value before restoring an older model; restoration does not silently rewrite it. Intrinsic category labels and primary KPI values remain visible independently of optional data labels. Supported labels remain subject to the chart’s density and available-space policies.

Customize the rejection text with localeText.renderer.dataLabelsUnsupported(chartType). When parsing a model directly, pass a resolved locale as the optional second argument to parsePivotChartModel(model, createPivotChartsLocaleText(overrides)); locale callbacks are never persisted inside the model. Direct renderer calls also reject non-boolean dataLabels values; customize that diagnostic with localeText.renderer.invalidDataLabels(). Model parsing retains its structural validation error for malformed presentation values.

  • Give the chart enough space before adding labels. Dense charts may suppress text to keep the result readable.
  • Use number formats for percentages or currency; changing display text does not change the underlying measure.
  • Use controls policy to hide editing controls; changing renderer colors does not restrict editing.

Continue with exporting, save and restore or advanced chart options.