Box Plot

Pro

Distribution chart with Tukey 1.5×IQR whiskers, outlier dots, an optional mean marker, raw-samples or server-computed summaries, and a value axis that is deliberately not zero-based. Pure SVG.

</>sv-box-plot-chart chartbox plotdistributionquartilestatistics
import { SvBoxPlotChartComponent } from '@styloviz-pro/charts';

Demo & usage

Basic usage

Pro

Usage example — open the gallery for the full interactive demo.

Pro

An interactive demo for this component lives in the gallery.

Open live demo

More examples

Raw samples

Pro

Server-computed summaries

Pro

Reconciling with Excel QUARTILE.EXC

Pro

Small-sample threshold

Pro

Fixed axis

Pro

Horizontal layout

Pro

When to use

  • Comparing the SHAPE of several distributions — latency by service, price by category.
  • Spotting outliers and skew that an average hides completely.
  • For a single distribution with enough samples to show shape, a histogram is clearer.

Async states

  • loading
  • empty
  • error
  • retry

Inputs

NameTypeDefaultDescription
loadingbooleanfalseTrue while the distributions are being fetched.
errorSvDataError | string | nullnullLoad failure. Use `toSvDataError()` from `@styloviz/core` to normalize an `HttpErrorResponse`.
emptyMessagestring'No data to plot'Copy for the no-data state.
filteredbooleanfalseTrue when the emptiness is caused by a filter rather than an empty set.
boxesreadonly BoxPlotBox[][]Distributions to plot, one box each.
orientationBoxPlotOrientation'vertical'Layout direction.
iqrFactornumber1.5Multiplier on the IQR that sets the outlier fences. `1.5` is Tukey's rule; `3` marks only far outliers. Ignored for boxes that supply `stats`.
quartileMethodSvQuartileMethod'linear'Which sample-quantile definition to compute quartiles with, and the one the chart names under the plot. There are at least nine defensible definitions and they disagree, most visibly on small samples. Unnamed, this chart was unfalsifiable: a reader holding a different `q3` from their own BI tool had no way to tell whether the chart was wrong or merely using another rule, and they will trust the tool they already had. `exclusive` is the rule Excel's `QUARTILE.EXC`, Tableau and Minitab use; `linear` is R, NumPy, Excel `QUARTILE`, Sheets. Ignored for boxes that supply `stats` — those were computed elsewhere, and the chart says so rather than claiming a rule it did not apply.
minReliableSamplesnumber5Below this many samples a box is marked as too small for its quartiles to carry much meaning: drawn with a dashed edge, named under the plot, and flagged in its accessible name and in the data table. `0` turns the check off. The default is 5 because below it `q1` and `q3` are pure interpolation between two adjacent points and the IQR fences that define an outlier are derived from that interpolation — the box is a picture of arithmetic rather than of a distribution, and it looks exactly like a box over four thousand samples.
axisMinnumber | nullnullForce the low end of the value axis. `null` (the default) rounds one out of the data — a box plot's axis is deliberately not zero-based.
axisMaxnumber | nullnullForce the high end of the value axis.
widthnumber520Chart width in pixels.
heightnumber300Chart height in pixels.
showOutliersbooleantrueDraw individual samples that fall outside the whiskers.
showMeanbooleanfalseDraw a marker at the arithmetic mean.
showGridbooleantrueDraw the value-axis grid lines and tick labels.
boxRationumber0.6Box thickness as a fraction of the slot width, clamped to `0.1…0.95`.
titlestring''Chart title, rendered above the plot.
ariaLabelstring''Accessible name override. Falls back to a generated summary.
customClassstring''Extra CSS classes applied to the root wrapper.

Outputs

EventPayload typeDescription
retryvoidThe host should re-run the failed load.
boxHoverBoxPlotBoxEventEmitted when a box is hovered or keyboard-focused.
boxSelectBoxPlotBoxEventEmitted when a box is clicked or activated with Enter/Space.

Wire it to your API

This component is headless about transport: it renders what you give it and reports what the user asks for. Nothing below requires @styloviz/data — drive it from whatever you already use.

Template

Pro

Bound to the names this component actually exposes.

Host class

Pro

Drive the three async inputs from your own fetch.

  • This component does not emit a query, so it renders what you give it. Fetch on your own trigger and bind `boxes`, `loading` and `error`.
  • Set `filtered` when a search or filter is active. It is the difference between "you have no orders" and "no orders match this filter" — telling a customer with 4,000 orders the first one reads as a bug.
  • Normalize failures with `toSvDataError()` from `@styloviz/core`. It derives `retryable` from the status, so a 404 does not get a "Try again" button that can only fail again.

Full walkthrough: Connecting to a backend.

Customization & theming

  • Vertical or horizontal layout.
  • Tukey fence multiplier via `iqrFactor`.
  • Outliers, mean marker and grid each toggleable.
  • Box thickness via `boxRatio`.
  • `quartileMethod` picks the sample-quantile definition: `linear` (type 7 — R, NumPy, Excel `QUARTILE`, Sheets) or `exclusive` (type 6 — Excel `QUARTILE.EXC`, Tableau, Minitab, SPSS). They agree on the median and diverge on `q1`/`q3`, most visibly on small samples. Whichever is in force is **printed under the plot** along with the whisker convention: there are at least nine defensible quartile rules, so a chart that keeps its own to itself cannot be reconciled against anything, and a reader holding a different `q3` from their BI tool will trust the tool they already had. A summary supplied through `stats` was computed elsewhere, so the chart says so rather than claiming a rule it never applied — unless the summary declares its own `method`. When supplied summaries disagree, the line reports exactly what each declared, per rule with the boxes named (e.g. "1 linear (C), 2 exclusive (A, B), 1 not stated (D)"), and warns that boxes under different rules are not directly comparable. A supplied `count` or `dropped` that is not a whole number is left out and disclosed rather than printed as `n = NaN`.
  • `minReliableSamples` (default 5) marks a box with too few samples for its quartiles to describe a distribution: a dashed edge, a sentence under the plot naming which boxes and their n, the caveat in the accessible name, and a note in the data table. Below five, `q1` and `q3` are pure interpolation between two adjacent points and the fences that define an outlier are derived from that interpolation. `0` turns the check off.
  • A `stats` summary is checked against itself before it is drawn, because it is the one input here with nothing else to cross-check against. Out-of-order figures, or one that cannot be read as a number, mean the box is **not drawn** and is named under the plot with the reason — inverted quartiles used to draw at the right size in the wrong place, and a `NaN` coordinate makes an SVG element silently fail to paint, which on screen is identical to a category that had no data. Nonsense extras are gentler: a mean that cannot be read loses its marker, an "outlier" sitting between the whiskers is dropped, and the box itself still renders.
  • Sample size is drawn on the category axis as `n = …`, and it is the count actually **used**: samples that could not be read are excluded from it and reported separately, so the figure never overstates how much data is behind a box.
  • Raw-sample cost, measured rather than guessed. Aggregating `values` is dominated by one sort per box and runs inside a single `computed()`, so it re-runs when the `boxes` array identity changes and **not** on hover, focus, resize or a theme flip. On a desktop machine: 12 boxes × 1,000 samples ≈ 3 ms, × 10,000 ≈ 30 ms, × 50,000 ≈ 190 ms, × 100,000 ≈ 460 ms. Treat **250,000 samples in total** as the ceiling for passing raw `values` — that is around the point a recompute stops fitting in a frame on a desktop, and a mid-range phone is several times slower again. Above it, aggregate server-side and pass `stats` with `count` and `method`; the wire cost of shipping the raw array is usually the worse half of the problem anyway.
  • The value axis is **linear only** — there is no log-scale input, and `axisMin` / `axisMax` are read as linear bounds. A log axis would move the box, the median line and both whiskers independently, so the box would no longer be the IQR of anything you could measure off the axis, and the 1.5×IQR fences that define an outlier would land in a different place than the ones the summary was computed with. For heavily skewed data, transform the samples before passing them in and say so in the `title` (`Response time (log₁₀ ms)`). A quantile is order-preserving, so the median and any quartile landing on a real sample are the log of the untransformed one; only the interpolated quartiles shift, because the interpolation then happens in log space. The **mean** is the exception and the one to watch: the mean of the logs is a geometric mean, not the log of the arithmetic mean, so `showMean` on transformed samples is answering a different question — turn it off, or state which mean it is.

Best practices

  • Do: Aggregate server-side past roughly 250,000 samples in total across all boxes, and pass `stats` instead of `values`. Below that the arithmetic is not the problem — see the measured figures under Customization.
  • Do: Pin `axisMin` / `axisMax` when the chart reloads, so successive renders stay comparable.
  • Don't: Do not force the axis to zero out of habit — it flattens a tight distribution into a smear.
  • Do: Set `count` on a server-computed `stats` summary. Without it the chart cannot say how much data a box stands for, and a box over four samples looks exactly like a box over four thousand.
  • Do: Set `method` on a server-computed `stats` summary too, so the chart names the real rule instead of reporting that the rule is not stated.
  • Do: Match `quartileMethod` to whatever the report your reader is holding was built with — `exclusive` for Excel `QUARTILE.EXC`, Tableau or Minitab; `linear` for R, NumPy, Excel `QUARTILE` or Sheets.
  • Don't: Do not pass a `stats` summary straight from an untrusted aggregation without checking it. The chart rejects a self-contradictory one, but a rejected box is a box your reader does not see.
  • Tip: Quartiles default to the type-7 (linear interpolation) definition, the same as R, NumPy and Excel `QUARTILE`, so the chart reconciles with the workbook the data came from — and it prints which rule it used under the plot.

Accessibility

  • Each box has a slot-sized `role="button"` hit area named with its median, quartiles, range, mean, outlier count and sample size — the mean marker is a bare circle and n used to be on the hover tooltip alone, so neither reached a keyboard.
  • The plot is a labelled `role="group"`; Enter/Space emits `boxSelect` and focus emits `boxHover`.
  • A visually-hidden table lists each distribution with its sample size, five-number summary, mean and the individual outlier **values** — an outlier is drawn as a ring carrying no label at all. The quartile rule is stated in the table's own overview, so the alternative is no more unfalsifiable than the picture.
  • Anything the chart dropped, repaired or hedged is stated in the rendered output rather than logged: a rejected box, samples that could not be read, and a sample too small to support a quartile. The dashed edge on an unreliable box is never the only cue.

Feedback on Box Plot

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.