Box Plot
import { BoxPlot } from '@absoluteui/components/box-plot';import '@absoluteui/components/box-plot/style.css';Using Angular? See Box Plot for Angular.
What it does
Section titled “What it does”An accessible SVG box plot for comparing distributions across categories. It provides:
- one box per category and series, from the lower to the upper quartile, with a median line, capped whiskers, and outlier dots;
- data as precomputed statistics (
{ min, q1, median, q3, max, outliers? }) or raw values; raw values use type-7 quantiles (linear interpolation, as in spreadsheets) and Tukey whiskers at 1.5 IQR (whiskers: 'tukey') or min/max whiskers without outliers ('minmax'); - several series grouped side by side, with a legend of toggle buttons and textures (with
patterns) so series never differ by color alone; - keyboard-reachable boxes (one tab stop, arrow keys) whose names read the five-number summary and outlier count, with a tooltip on focus and hover;
- a generated summary and a data table fallback with every statistic and the outlier values;
- the module functions
boxStatsandquantile, to precompute statistics with the same rules.
When to use
Section titled “When to use”- Comparing the spread, skew, and outliers of a measure across categories (for example latency per region).
- Summarizing many raw values per category when individual values do not matter.
When not to use
Section titled “When not to use”- Few values per category: show them directly (Scatter Chart) instead of quartiles.
- Audiences unfamiliar with quartiles: a Bar Chart of medians or a Histogram may read faster.
- The shape of one distribution in detail (bimodal data): use Histogram.
Examples
Section titled “Examples”Raw values and precomputed statistics, two series
Section titled “Raw values and precomputed statistics, two series”import { BoxPlot, type BoxPlotSeries } from '@absoluteui/components/box-plot';
const series: BoxPlotSeries[] = [ { id: 'prod', label: 'Production', data: [[120, 135, 150, 160, 180, 420], { min: 90, q1: 110, median: 125, q3: 140, max: 170, outliers: [310] }] }, { id: 'staging', label: 'Staging', data: [[140, 150, 170, 190, 210], null] },];
const host = document.createElement('div');document.body.append(host);BoxPlot.create(host, { label: 'Request latency by region', categories: ['Europe', 'North America'], series, valueAxis: { label: 'Milliseconds' }, valueFormat: { maximumFractionDigits: 0 },});Precompute statistics with boxStats and use min/max whiskers
Section titled “Precompute statistics with boxStats and use min/max whiskers”import { BoxPlot, boxStats, quantile } from '@absoluteui/components/box-plot';
const samples = [4.1, 3.8, 5.2, 4.7, 9.9, 4.4, 4.0];const stats = boxStats(samples);const p90 = quantile([...samples].sort((a, b) => a - b), 0.9);
const host = document.querySelector<HTMLElement>('#build-times');if (host) { const chart = BoxPlot.create(host, { label: 'Build time', description: `90th percentile: ${p90.toFixed(1)} min`, categories: ['main'], series: [{ id: 'ci', label: 'CI', data: [stats] }], }); chart.update({ whiskers: 'minmax', series: [{ id: 'ci', label: 'CI', data: [samples] }] });}import { BoxPlot } from '@absoluteui/components/box-plot';import '@absoluteui/components/box-plot/style.css';
const host = document.querySelector<HTMLElement>('#scores');if (host) { const chart = BoxPlot.create(host, { label: 'Test scores by class', categories: ['Class A', 'Class B'], series: [ { id: 'math', label: 'Math', data: [[62, 70, 71, 78, 85, 99], [55, 60, 68, 72, 74]] }, { id: 'reading', label: 'Reading', data: [[66, 72, 75, 80, 88], [58, 64, 70, 76, 82]] }, ], valueAxis: { label: 'Score', min: 0, max: 100 }, }); chart.host.addEventListener('aui-series-toggle', (e) => { console.log(e); });}More usage
Section titled “More usage”import { BoxPlot, boxStats } from '@absoluteui/components/box-plot';
const host = document.createElement('div');document.body.append(host);BoxPlot.create(host, { label: 'Latency', categories: ['EU', 'US'], series: [{ id: 'prod', label: 'Prod', data: [[10, 12, 14, 16, 90], boxStats([8, 10, 11, 13, 15])] }],});Anatomy
Section titled “Anatomy”<div class="aui-chart aui-box-plot [aui-chart--patterns]"> <div class="aui-chart__header">title, description</div> <ul class="aui-chart__legend" aria-label="Legend">toggle buttons with texture swatches (more than one series)</ul> <div class="aui-chart__body"> <svg class="aui-chart__svg" role="group" aria-roledescription="box plot" aria-labelledby="T" aria-describedby="S"> <g class="aui-chart__grid" aria-hidden="true"/> <g class="aui-chart__axis …" aria-hidden="true"/> <g class="aui-box-plot__boxes"> <g class="aui-box-plot__series aui-chart__series--c1"> <g class="aui-box-plot__whiskers" aria-hidden="true"> <path class="aui-box-plot__whisker"/> <circle class="aui-box-plot__outlier"/> </g> <rect class="aui-box-plot__box aui-chart__mark" role="img" aria-label="EU, Prod: median 14, quartiles 12 to 16, whiskers 10 to 16, 1 outlier"/> <rect class="aui-chart__pattern" aria-hidden="true"/> (second and later series) <line class="aui-box-plot__median" aria-hidden="true"/> </g> </g> </svg> <div class="aui-chart__tooltip" aria-hidden="true" hidden/> </div> <p class="aui-chart__summary" hidden/> <div class="aui-chart__data">Show data table, table</div></div>API reference
Section titled “API reference”Factory
Section titled “Factory”BoxPlot.create(host: HTMLElement, options: BoxPlotOptions): BoxPlotGlobal configuration key: boxPlot (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
label |
string |
Chart title: visible text and the accessible name of the plot (aria-labelledby). Must not be blank. Required. | |
hideLabel |
boolean |
false |
Visually hide the title; it stays the accessible name. |
description |
string |
Visible description below the title, also referenced by aria-describedby (text only). | |
summary |
string |
Replaces the generated accessible summary (series and category counts, per-series lowest and highest median, hidden series). | |
valueFormat |
Intl.NumberFormatOptions |
Number format for statistics in box names, the tooltip, the data table, the summary, and axis ticks (ticks use fixed fraction digits). | |
formatValue |
(value: number) => string |
Custom value text; overrides valueFormat everywhere except axis ticks. | |
hiddenSeries |
readonly string[] |
Ids of series hidden initially; update() replaces the hidden set. | |
categories |
readonly string[] |
Category labels, one per box group (text only). Required. | |
series |
readonly BoxPlotSeries[] |
Series: { id, label, data: BoxPlotDatum[], color?: 1-8 }, one datum per category. A datum is precomputed statistics { min, q1, median, q3, max, outliers? } (finite, min <= q1 <= median <= q3 <= max; min and max are the whisker ends), raw values number[] (non-finite values ignored), or null (no box). Required. | |
categoryAxis |
{ label?: string } |
Category axis title; also the first data table column header (default “Category”). | |
valueAxis |
BoxPlotValueAxis ({ label?: string; min?: number; max?: number; tickCount?: number; includeZero?: boolean }) |
Value axis title, fixed domain (max > min; marks are clamped to it), and approximate tick count (2-20). includeZero is validated but has no effect on a box plot; the automatic domain spans every whisker and outlier. | |
legend |
'auto' | 'show' | 'hide' |
"auto" |
auto: legend (toggle buttons) when there is more than one series. Globally configurable. |
dataTable |
'toggle' | 'visible' | 'hidden' | 'none' |
"toggle" |
Data table fallback (one row per category and series: Minimum, Lower quartile, Median, Upper quartile, Maximum, Outliers): behind a Show data table button (built on first open), always visible, visually hidden (screen readers only), or none. Globally configurable. |
tooltip |
boolean |
true |
Tooltip on pointer hover and keyboard focus (maximum, quartiles, median, minimum, outlier count). Globally configurable. |
patterns |
boolean |
false |
Texture fills on the boxes (and legend keys) of the second and later series so series never differ by color alone (always on in forced colors). Globally configurable. |
height |
number |
300 |
Plot height in CSS px (integer 120-2000); the width follows the container. Globally configurable. |
whiskers |
'tukey' | 'minmax' |
"tukey" |
Whiskers computed from raw values: tukey (whiskers end at the most extreme values within 1.5 IQR of the quartiles; values beyond are outliers) or minmax (whiskers at the minimum and maximum, no outliers). Precomputed statistics are drawn as given. Globally configurable. |
locale |
string |
"en-US" |
Number, list, and message locale. Globally configurable. Inherited from common configuration. |
direction |
'ltr' | 'rtl' |
"ltr" |
rtl mirrors the category axis and series order, moves the value axis to the right, and swaps ArrowLeft/ArrowRight. Globally configurable. Inherited from common configuration. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
The host element. |
element |
SVGSVGElement |
The plot (svg role=“group”), named by the title and described by the summary. |
summary |
string |
The generated (or custom) accessible summary. |
hiddenSeries |
string[] |
Ids of hidden series, in data order. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
update(options: Partial<BoxPlotOptions>): void |
Merge, validate, and re-render. Invalid options throw and keep the previous state. Keyboard focus stays on the same box (series id and category). |
setSeriesVisible(id: string, visible: boolean): void |
Show or hide a series programmatically; emits no event. |
destroy(): void |
Remove rendering, host classes, listeners, the resize observer, and the configuration subscription. |
boxStats(values: readonly number[], whiskers?: BoxPlotWhiskers): BoxPlotStats | null |
Module function (not an instance method), exported from @absoluteui/components/box-plot: five-number summary and outliers of raw values with the same rules the chart uses (non-finite values ignored; whiskers default ‘tukey’). Returns null when no finite value remains. Use it to precompute statistics for large samples. |
quantile(sorted: readonly number[], p: number): number |
Module function (not an instance method): quantile p (0-1) of ascending values with linear interpolation (Hyndman-Fan type 7, as in spreadsheets). NaN for an empty array. The input must already be sorted. |
Events
Section titled “Events”| Event | Detail | Bubbles | Cancelable | Description |
|---|---|---|---|---|
aui-series-toggle |
{ id: string; visible: boolean; hidden: string[] } |
Yes | No | The user toggled a legend entry (pointer or keyboard). Not emitted for update() or setSeriesVisible(). |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-chart |
Host (shared chart chrome). |
aui-box-plot |
Host modifier for this chart type. |
aui-chart--patterns |
patterns option on: textures shown. |
aui-chart__header / __title / __title--hidden / __description / __toolbar |
Title and description; the toolbar stays hidden. |
aui-chart__legend / __legend-entry / __legend-item / __legend-item--hidden / __legend-label |
Legend list and toggle buttons. |
aui-chart__swatch |
aria-hidden legend and tooltip key (fill and texture). |
aui-chart__body / __svg / __layer |
Plot container (height from –aui-chart-height), SVG, and drawing layer. |
aui-chart__grid / __gridline / __zero-line / __axis / __axis-line / __tick-label / __axis-title |
Axes (aria-hidden). |
aui-chart__series--c1 ... --c8 |
Palette slot classes; set –aui-chart-color from color-chart-<n>. |
aui-chart__pattern / aui-chart__pattern-tile |
Texture overlays on boxes. |
aui-chart__mark / __mark--active / __mark--hover |
Focusable box and its states. |
aui-chart__tooltip / __tooltip-title / __tooltip-rows / __tooltip-row / __tooltip-label / __tooltip-value |
Tooltip (aria-hidden; the focused box carries the same text). |
aui-chart__empty |
Empty state text (No data, All series are hidden). |
aui-chart__summary |
Hidden summary referenced by aria-describedby. |
aui-chart__data / __data--hidden / __table-toggle / __table-scroll / __table / __cell--numeric |
Data table fallback. |
aui-box-plot__boxes / __series |
All boxes, and one group per box (series color slot). |
aui-box-plot__box |
The focusable box (lower to upper quartile). |
aui-box-plot__median |
Median line (aria-hidden). |
aui-box-plot__whiskers / __whisker / __outlier |
Decorative (aria-hidden) whiskers with caps and outlier dots. |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-border-width | --aui-color-border | --aui-color-border-strong | --aui-color-chart-1 | --aui-color-chart-2 | --aui-color-chart-3 | --aui-color-chart-4 | --aui-color-chart-5 | --aui-color-chart-6 | --aui-color-chart-7 | --aui-color-chart-8 | --aui-color-chart-axis | --aui-color-chart-grid | --aui-color-focus-ring | --aui-color-hover | --aui-color-link | --aui-color-surface | --aui-color-surface-sunken | --aui-color-text | --aui-color-text-muted | --aui-duration-fast | --aui-easing-standard | --aui-focus-ring-offset | --aui-focus-ring-width | --aui-font-family-sans | --aui-font-size-md | --aui-font-size-sm | --aui-font-size-xs | --aui-font-weight-medium | --aui-font-weight-regular | --aui-font-weight-semibold | --aui-line-height-normal | --aui-line-height-tight | --aui-radius-md | --aui-radius-sm | --aui-shadow-md | --aui-space-1 | --aui-space-2 | --aui-space-3 | --aui-tooltip-max-width | --aui-touch-target-min
TypeScript definitions
Section titled “TypeScript definitions”type BoxPlotWhiskers = 'tukey' | 'minmax';interface BoxPlotConfig extends ChartBaseConfig { whiskers: BoxPlotWhiskers }interface BoxPlotStats { min: number; q1: number; median: number; q3: number; max: number; outliers?: readonly number[] }type BoxPlotDatum = BoxPlotStats | readonly number[] | null;interface BoxPlotSeries { id: string; label: string; data: readonly BoxPlotDatum[]; color?: ChartColor }interface BoxPlotOptions extends ChartCommonOptions, Partial<BoxPlotConfig> { categories: readonly string[]; series: readonly BoxPlotSeries[]; categoryAxis?: { label?: string }; valueAxis?: BoxPlotValueAxis; // { label?; min?; max?; tickCount?; includeZero? }}type BoxPlotSeriesToggleDetail = { id: string; visible: boolean; hidden: string[] };function boxStats(values: readonly number[], whiskers?: BoxPlotWhiskers): BoxPlotStats | null;function quantile(sorted: readonly number[], p: number): number;Accessibility
Section titled “Accessibility”Pattern: SVG box plot as a named, described group (role=“group” with aria-roledescription “box plot”) of role=“img” boxes with roving focus; data table fallback (native table with every statistic and the outlier values).
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| ArrowLeft / ArrowRight | Box focused | Previous / next category in the same series (mirrored in RTL); categories without a box are skipped. |
| ArrowUp / ArrowDown | Box focused, more than one visible series | Same category in the previous / next visible series (nearest box when missing). |
| ArrowUp / ArrowDown | Box focused, one visible series | Previous / next category. |
| Home / End | Box focused | First / last box of the series. |
| PageUp / PageDown | Box focused | Ten boxes back / forward. |
| Escape | Tooltip visible | Hides the tooltip without moving focus (WCAG 1.4.13); it returns on the next move. |
| Tab / Shift+Tab | Any | Legend buttons, then one tab stop for the plot (the active box), then the Show data table button and table region. |
| Enter / Space | Legend button | Shows or hides the series (native button). |
| Element | Role |
|---|---|
| svg.aui-chart__svg | group with aria-roledescription (localized “box plot”), named by the title |
| boxes | img with an aria-label: category, series, five-number summary, outlier count |
| legend | list (ul) named “Legend”; entries are toggle buttons (aria-pressed) |
| data table | table (native) with caption, column headers, and row headers; wide tables scroll in a focusable region |
| axes, whiskers, median lines, outlier dots, textures, tooltip | aria-hidden (decorative duplicates of the accessible data) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-labelledby |
svg | id of the visible (or visually hidden) title |
aria-describedby |
svg | description id (when provided) and the generated summary id |
aria-roledescription |
svg | Localized chart type, “box plot” |
aria-label |
each box | Localized “{category}, {series}: median {median}, quartiles {q1} to {q3}, whiskers {min} to {max}”, followed by “, {n} outlier(s)” when there are outliers (for example “EU, Prod: median 14, quartiles 12 to 16, whiskers 10 to 16, 1 outlier”) |
tabindex |
active box | 0 on exactly one box (roving); other boxes have none |
aria-pressed |
legend button | true while the series is visible |
aria-expanded / aria-controls |
Show data table button | Table open state and the table region id |
Roving focus: one box is in the tab sequence and remembers the last visited box (series id and category), also across re-renders (resize, update). Toggling a legend entry keeps focus on its button; toggling the table keeps focus on the table button.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Each box name reads the five-number summary (median, quartiles, whisker ends) and the outlier count.
- Outliers are decorative dots; their values are listed in the Outliers column of the data table.
- The generated summary lists the series and category counts, each series’ lowest and highest median with their categories, and hidden series.
- Meaning never depends on color alone: textures on the boxes of the second and later series (patterns option), plus legend keys that repeat them.
- Box outlines are drawn in the series color (3:1 against surfaces); the median line uses the text color.
- Forced colors: medians CanvasText, outlier dots Canvas-filled; focus and hover use Highlight.
- No animation: nothing moves on render; hover and focus emphasis transitions are removed under prefers-reduced-motion and animation: none.
- Manual screen-reader verification (NVDA, JAWS, VoiceOver, TalkBack) is pending.
- aria-roledescription support varies; screen readers without it announce “group”.
- Individual outliers are not focusable; their values are available in the data table only.
Behavior
Section titled “Behavior”States
Section titled “States”default, empty, all series hidden, series hidden, box missing, box focused (tooltip), box hovered, table open.
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| any | legend button | series hidden or shown; aui-series-toggle; domain rescales |
| plot not focused | Tab | active box focused, tooltip shown |
| box focused | arrow keys, Home, End, PageUp, PageDown | another box focused |
| tooltip visible | Escape | tooltip hidden |
| table closed | Show data table | table shown |
| any | update({ whiskers }) |
raw-value boxes recomputed |
Accessibility notes
Section titled “Accessibility notes”- The box name carries the full five-number summary and the outlier count; outliers themselves are decorative dots, and their values are in the table’s Outliers column.
- Textures are the non-color cue for series; keep
patternson with several series. - Box outlines use the series color (3:1 against surfaces); the translucent fill is decorative.
- Forced colors: medians
CanvasText, outliersCanvas-filled; focus and hover useHighlight. - No animation. Manual screen-reader verification is pending.
Responsive behavior
Section titled “Responsive behavior”Width follows the container; each category gets a band shared by its series’ boxes. Category labels are truncated or thinned. Reflows at 320 CSS px.
Right-to-left
Section titled “Right-to-left”The category axis runs right to left, series order within a category is mirrored, the value axis moves to the right, and ArrowLeft/ArrowRight swap.
Theming
Section titled “Theming”Series use color-chart-1..8 (3:1 against surface, background, and sunken surfaces in all themes). Gridlines color-chart-grid, axes color-chart-axis, labels color-text-muted.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { boxPlot: { whiskers: 'minmax', legend: 'show' } } });Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
Timezones
Section titled “Timezones”Not applicable (categories are text).
Security
Section titled “Security”All text uses textContent; SVG uses createElementNS; colors are classes; texture fills reference per-instance pattern ids through the fill attribute. No innerHTML or style attributes.
Data handling
Section titled “Data handling”Raw values are summarized in memory on each render; nothing is persisted, sent, or logged.
Performance
Section titled “Performance”Budget: 4 series x 50 categories from raw values (100 values each) < 80 ms in headless Chromium (e2e performance test), including the first render. Raw values are sorted per box on every render; pass boxStats results for large samples.
Supported data scale
Section titled “Supported data scale”Up to about 200 boxes (each is one focusable node plus whiskers, a median, an optional texture overlay, and one dot per outlier). Aggregate or precompute larger data.
Edge cases
Section titled “Edge cases”- No categories or series, or every datum empty: “No data”; every series hidden: “All series are hidden”.
nulldatum, or raw values with no finite number: no box; “No value” in every table cell of that row.- A single raw value: a flat box (quartiles, median, and whiskers equal).
- Precomputed statistics are drawn as given (
whiskersdoes not apply);outliersdefaults to none. - A fixed
valueAxisdomain clamps boxes, whiskers, and outliers to its edges. - Long category labels are truncated; the full text stays in box names, tooltip, and table.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
BOX_PLOT_OPTIONS |
configuration | Blank label; categories not strings; series not an array; empty or duplicate id; label not a string; color not 1-8; data length differs from categories; raw values not numbers; statistics not finite or not ordered min <= q1 <= median <= q3 <= max; outliers not finite numbers; invalid categoryAxis or valueAxis (max <= min, tickCount outside 2-20). |
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION |
configuration | Invalid or unknown configuration (for example whiskers: 'iqr'). |
A failed construction leaves the host untouched.
Known limitations
Section titled “Known limitations”- Vertical boxes only; one datum per category per series.
- Outliers are not individually focusable.
valueAxis.includeZerohas no effect.- Manual screen-reader verification is pending.
Unsupported scenarios
Section titled “Unsupported scenarios”- Horizontal, notched, or violin plots; mean markers.
- Logarithmic or broken axes.
- HTML in labels.