Skip to main content

DistributionChart

Distributionchart

One numeric variable, optionally by group, read four ways on one shared scale — histogram, box, violin or strip.

EMEA: 48 records, median 23.5, IQR 10.575 (16 to 26.575); AMER: 48 records, median 24.05, IQR 10.1 (20.425 to 30.525); APAC: 48 records, median 30.65, IQR 16.15 (19.85 to 36); LATAM: 48 records, median 35.05, IQR 32.625 (19.825 to 52.45)
import { DistributionChart } from "@elabs-ai/components-charts";
Install
npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts

Skipping the @source line is the most common first failure: Tailwind does not scan node_modules, so every component renders unstyled. The one-time wiring

Prompt for your coding agent

Paste it into Claude Code, Cursor, Codex or any other coding agent. Text in [brackets] is yours to fill in.

Add the brand-ui DistributionChart chart (from @elabs-ai/components-charts) to this project.

1. Read https://elabs-ai.com/llms.txt first: it maps every package and the rules. If you can use MCP servers, connect https://elabs-ai.com/mcp and use its search, docs and tokens tools. If not, run the same lookups in the terminal: `npx -y @elabs-ai/components-cli search "<what you need>"` and `npx -y @elabs-ai/components-cli docs <Component>`.
2. Look DistributionChart up before writing code: `npx -y @elabs-ai/components-cli docs DistributionChart` prints its real props, variants, what it pairs with and what to avoid.
3. If @elabs-ai/components-charts is not installed yet: `pnpm add @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts` (or `npm install @elabs-ai/components-tokens @elabs-ai/components-ui @elabs-ai/components-charts`). If this is the first brand-ui package in the project, wire it once: import `@elabs-ai/components-tokens/styles.css` plus the `themes/light.css` and `themes/dark.css` stylesheets in the CSS entry, add one Tailwind `@source` line per installed @elabs-ai package (Tailwind does not scan node_modules, so without it everything renders unstyled), and wrap the app root in `ThemeProvider` from `@elabs-ai/components-tokens`.
4. Use it here: [the screen or file, and what it should show]
5. Rules: use only @elabs-ai/components-* components and the semantic tokens — no raw colours, no raw font sizes, no arbitrary Tailwind values. Look up each component's real props with the docs tool before you use it; never guess a prop.
6. Done means: the typecheck passes, `npx -y @elabs-ai/components-cli audit src --strict` reports nothing, and you have opened the app and looked at every screen you touched in the light and the dark theme.

Use it for

  • How is it spread? Every record, by group.
  • How is it spread? Five-number summary.
  • the spread of one measure, optionally grouped — a histogram, box plot or strip plot

Avoid

  • a single summary number would do — use a metric card
  • kind="violin" on a handful of records — a density estimate from ~12 points draws the bandwidth, not the data; below roughly 50 per group use kind="strip", where every record is its own mark.
  • Leaving the Silverman default bandwidth on visibly bimodal data — the rule of thumb over-smooths two modes into one bulge and loses ~2% of the mass off the grid. Pass `bandwidth` when the shape is the point.
  • kind="box" where the reader needs the individual records — the five-number summary hides bimodality completely. Under ~150 per group, kind="strip" shows the same numbers without discarding them.
  • Comparing a violin's axis against another kind's as if it were the same scale — violin is the one kind that widens the domain (by 1.6 bandwidths, so the silhouette is not clipped into a bar); histogram, box and strip share the data's own extent.
  • Passing `bins` as an edge list that does not cover the data — an explicit array is the FULL edge list, and a value outside it is DROPPED (with a dev warning), never absorbed into an end bucket. Pass a bin COUNT unless the edges carry business meaning.

Works with

Examples

9 live examples

F14 · Rung histogram

**F14 — rung histogram.** `unit` turns each bin into COUNTABLE rungs instead of a bar: one rung is five tickets (`unitLabel` renders that legend), so the reader can count the tall bin rather than measure it against an axis. The rung pitch is the plot's own count scale, so the stack fills the room it is given. The dashed flag is the median. Reach for a histogram when the bin EDGES carry meaning — here the first bucket is "under 15 minutes", which is the queue's actual promise.

Loading

`status="loading"` (RM-185): a skeleton fills the same plot box the ready chart would use, at every width, so nothing moves once the data lands.

F15 · Tick box

**F15 — tick box.** The five-number summary, three queues, one scale. The median is cut through the capsule in the paper colour rather than drawn as a darker line, so it reads at a glance without adding a fourth ink. Marks past 1.5 × IQR are hollow. This is the compact reading: it answers "which queue is slower" in one look — and hides Billing's two populations completely. That is the trade, and it is why the violin below exists.

G19 · Violin

**G19 — violin.** The same three queues as a mirrored density silhouette. Billing's two bulges are the fact the box plot above cannot show: a fast path and an escalation path, not one slow queue. Each violin is scaled to its own band, so WIDTH is shape, never n — the group label and the text summary carry the record count instead.

G15 · Jitter strip

**G15 — jitter strip.** Every ticket, one dot, jittered off the row's centre line by a SEEDED offset so the same record lands in the same place on every render. Nothing is aggregated away — the two Billing populations are visible as two clouds, and each dot is a real record you can click. Reach for a strip up to roughly 150 records per group; past that the ink saturates and the violin is the honest reading.

Vertical Sequential

Vertical orientation and a sequential palette: with `palette="sequential"` a box's shade is its MEDIAN RANK, so the ordering is carried by the fill as well as by position — useful when the groups are read out of order.

Dense Strip

Two thousand replies, one queue. Past roughly 150 records per group a strip's ink saturates and the violin is the honest reading — this story exists to prove the interaction budget rather than to recommend the picture. The hover is measured in the browser, where 16 ms is a frame: the marks are memoized behind a stable hover callback, so moving the pointer costs one tooltip, never a redraw of two thousand circles.

Reference Line

A fixed threshold (an SLA, a spec limit) drawn across the shared value axis via `referenceLines` — dashed and haloed so it stays legible over a box body, and its fact ("SLA: 60 min at 60") is folded into the chart's own accessible description rather than riding on the dashed line alone.

Selection states

A host's `selectionStates` paints Support selected, Onboarding associated and Billing excluded, keyed by the `team` group — every kind paints the same whole-lane outline/dim, never per record.

API

DistributionChart

PropTypeDescription
messagesChartMessagesmessages group (RM-187): this chart's own words, keyed by the ui catalogue's `charts.*` message keys. A key set here wins over the `LocaleProvider`; every other key reads the catalogue as before.
datarequiredDistributionRow[]RECORD-level rows — one per observation, NOT pre-aggregated buckets. The container does the aggregating; handing it counts defeats the point.
valueKeyrequiredstringThe numeric column. Rows whose value is not finite are dropped, and counted.
groupKeystringThe grouping column. Omit for a single, ungrouped distribution.
kindrequiredDistributionKindWhich mark to draw. See the table in this file's header for how to pick.
orientationDistributionOrientationWhich screen axis the VALUE runs along. `"horizontal"` (default) puts groups in rows, which is what long group labels want; `"vertical"` puts them in columns.
binsnumber | number[]Histogram only: a bin COUNT hint (nice, approximate) or the FULL ordered edge list. Edges must be meaningful — a value outside an explicit list is dropped with a dev warning rather than absorbed by an end bucket. See `bins.ts`.
bandwidthnumberViolin only: KDE bandwidth. Unset uses Silverman's rule of thumb.
showMedianbooleanDraw the median: a dashed flag on a histogram, a paper tick on a box/violin. Default `true`.
showOutliersbooleanBox only: hollow marks beyond 1.5 × IQR. Default `true`.
unitnumberHistogram only: records per rung. Set it to draw each bin as COUNTABLE rungs (`F14`) instead of a bar — the number is the legend ("one rung = 5 tickets"), which is why it is a count and not a boolean.
unitLabelstringHistogram with `unit` only: the legend that makes the rungs decodable — "one rung = 5 tickets". Rendered as a caption under the plot and folded into the accessible description. Ignored without `unit`.
paletteChartPaletteColour family. With `"sequential"`, a box/violin's shade is its MEDIAN RANK — the darkest group has the highest median — so the ordering is carried by the fill as well as by position.
valueFormatChartValueFormatValue formatting for ticks, tooltips and the text summary.
currencystringISO 4217 code when `valueFormat="currency"`.
referenceLinesDistributionReferenceLine[]Fixed thresholds drawn across the shared value axis (an SLA, a spec limit, a target) — dashed `--chart-foreground`, haloed so it stays legible over a box/violin body. Each labelled line's fact is folded into the chart's own accessible description, so it is never colour-only or visual-only. Default none — omitting it renders byte-identical to before this prop existed.
analyticsreadonly ChartAnalytic[]Computed lines and bands on the value axis (RM-138, ADR 0040 §1): an average, a percentile, `{ spread: { ci: 0.95 } }`, `{ spread: { stddev: 1 } }` … over `valueKey`, drawn like `referenceLines` and restated in the accessible description. Unset: no change.
plotHeightResponsive<ChartPlotHeight>The plot's own height (ADR 0039): px, or `{ aspect }` (width ÷ height), optionally per breakpoint. Unset (default): fills the height its parent gives it, exactly as before this prop existed.
marginnumber | Partial<DistributionMargin>Chart margins: one number for every side, or per side. Merged over the orientation's own default (`HORIZONTAL_MARGIN`/`VERTICAL_MARGIN`).
statusChartStatusLoading vs ready (RM-185). `"loading"` shows a skeleton in the plot box the chart will fill, with one polite status message, until the data is ready. Default: `"ready"`.
classNamestring
styleCSSProperties
onDatapointClickChartDatapointClickHandler<Record<string, unknown>>Fires when a datapoint is activated by pointer OR keyboard. Setting it is what enables the interaction layer — with it unset the chart renders exactly as before, with no extra DOM and no new focusables.
copyValueOnActivatebooleanPut the datapoint's EXACT value on the clipboard when it is activated — the recovery path for compact axis labels and tooltips, reachable by mouse AND keyboard because activation rides `ChartDatapointLayer`'s real buttons. Default `false` on a raw chart container: turning it on mounts the interaction layer, and the documented guarantee is that a chart with no interaction props renders byte-identical DOM. `AutoChart` — the spec-driven surface, where the caller never sees the axis config — turns it on by default. A consumer-supplied `onDatapointClick` always wins; the two never both run.
datapointLabelChartDatapointLabel<Record<string, unknown>>Override the accessible name of each keyboard target.
maxInteractiveDatapointsnumberDev-warning threshold on the number of keyboard targets. Default 500. NOT a functional cap — every plotted point stays reachable, so a keyboard user can reach exactly what a mouse user can click.
accessibleLabelstringAccessible name for the chart region. AT announces this when the container receives focus or is read in flow. Example: "Monthly revenue bar chart"
accessibleDescriptionstringSupplemental description (series names, value ranges, last values, etc.). Rendered as a visually-hidden `<span>` associated via `aria-describedby`. Example: "Series: Revenue, Expenses. Range: 0–25,000."
selectionGesturesreadonly ChartSelectionGesture[]Gestures to enable. Unset → no gesture layer, DOM byte-identical to today.
onSelectionIntentChartSelectionIntentHandler<Record<string, unknown>>Fires with every intent. Required for the gesture layer to mount.
selectionConfirmChartSelectionConfirmDefault `"immediate"`.
selectionFieldstringThe field name carried in intents. Default: the chart's `xDataKey`.
selectionHitRuleChartSelectionHitRuleRect / lasso hit rule. Default `"overlap"` (the report-builder BI suite).
selectionToolbar"auto" | "none"`"auto"` (default): a toolbar appears when gestures are enabled; `"none"` hides it.
selectionStatesChartSelectionStatesResolver<Record<string, unknown>>Maps a mark (category, optional series key, optional raw row) to its selection state. Unset → the chart renders exactly as before.
dimExcludedbooleanDim `excluded` marks and add their non-hue channel (the dashed frame). Default `true`; `false` keeps the `data-selection` attribute (so a host can style it) but paints nothing.

Also accepts: ChartInteractionProps, ChartA11yProps, ChartSelectionGestureProps, ChartSelectionProps, FrameSizeGroupProps, Pick<ChartStateGroupProps, "status">

Theming

The tokens each state resolves to. Change the token and every theme follows.

StateToken
group--chart-1 … --chart-12 via resolvePalette; palette="sequential" shades a box/violin by MEDIAN RANK, not by group order
mediancut in the paper colour (--chart-background) through a box/violin waist; a dashed --chart-foreground flag + text-meta label on a histogram
outlierhollow past 1.5 × IQR — fill=none with the group's own stroke, never a filled dot
axis--chart-grid gridlines, --chart-label value ticks, --chart-foreground group labels, all text-meta
focuson a strip every record is a real button in ChartDatapointLayer carrying focus-ring — the compound indicator: a ring-2 in --ring plus a 1px --ring-contour outline (#67)