Density Lanes
Show when things happen, one lane of dots per kind, swelling where it's busy, on one scale with a highlight and a marker.
import { DensityLane, DensityLanes, DensityLanesTick } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use density lanes for "when" across a few kinds of thing: alerts by hour per source, orders by half-hour per channel, commits by weekday per team. Each lane's shape tells when it's busy; the shares under the names tell how much of the whole it is. For one series of counts use a Unit Chart; for a year of days, a Calendar Heatmap. The Hour lanes card is built on it.
import { DensityLanes } from '@youj/react'
<DensityLanes
label="Alerts by hour"
columns={hours}
lanes={[
{ id: 'identity', label: 'Identity', values: identityByHour, tone: 'chart-3' },
{ id: 'network', label: 'Network', values: networkByHour, tone: 'chart-2' },
]}
highlight={[[0, 6], [19, 23]]}
marker={{ column: 12, label: 'Peak 12:00 · 504 alerts' }}
/>columns names each column (in its tooltip and spoken), and every lane gives one value per column. A column swells depth dots (4 by default) above and below the lane's spine at max (by default the largest value, one scale for every lane), and a column with nothing draws no dots, only the faint grid. ticks places labels at column edges (at 0 is the start of the first column), by default one at each quarter. Lanes take the chart colors in order unless they set a tone; shares={false} drops the shares under their names; formatValue writes values with their unit. No lanes, or nothing in them, shows the empty state.
Description
A made-up café's orders through a day, by channel. Each lane swells where it's busy, and every lane uses the same scale, so the counter's morning rush and the evening's deliveries compare at a glance.
highlight keeps column ranges in tone and quiets the rest (here the hours outside the office), and marker flags one column through every lane.
Code
density-lanes-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propslanes, columns, depth, max, highlight, marker, ticks…
Props
| Prop | Type | |
|---|---|---|
lanesRequired | readonly DensityLane[] | – |
columnsRequired | readonly string[]Names each column, in its tooltip and spoken: "12:30". | – |
depth | numberHow many dots a column can swell above and below the spine. default 4 | 4 |
maxDot | numberThe largest a dot grows, in px from one dot to the next. Past it a wide plot stops growing taller and its columns spread apart. default 16 | 16 |
max | numberThe value at full swell, one scale for every lane. Default: the largest value. | – |
highlight | readonly (readonly [number, number])[]Column ranges, first and last index, kept in each lane's tone; the rest go quiet. | – |
marker | { column: number; label: string }A dashed rule through every lane at one column, with a flag above it. | – |
ticks | readonly DensityLanesTick[]Labels under the plot at column edges. Default: a label at each quarter, from | – |
shares | booleanEach lane's share of the total under its name. default true | true |
formatValue | (value: number) => stringFormats values in the tooltip and the spoken text. Default: the locale's number format. | – |
locale | stringDefault: the YoujProvider locale. | – |
label | stringAccessible name of the chart. Default: the labels' "Density lanes". | – |
config | Readonly<Record<string, { color?: string }>>A ChartConfig shared with your other charts: a lane whose id is a key takes its color, so one series matches across chart types. | – |
Other props pass through: extends ComponentProps<'div'>. | ||
StylingParts and data attributes
Styling
Parts: density-lanes, density-lanes-grid, density-lanes-lane, density-lanes-label, density-lanes-plot, density-lanes-column, density-lanes-cells, density-lanes-cell, density-lanes-band, density-lanes-flag-row, density-lanes-flag, density-lanes-marker, density-lanes-axis, density-lanes-tick, density-lanes-tooltip, density-lanes-summary and density-lanes-empty. A column in a highlight has data-highlighted. A lane's color is --density-lanes-tone on its plot, and an empty dot's is --density-lanes-off.
MotionReveal, highlight · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Revealthe dots light column by column from the start when a third of the chart scrolls into view | duration.deliberateease.standard560ms · cubic-bezier(0.2, 0, 0, 1) | 560mscubic-bezier(0.2, 0, 0, 1) | |
| Highlightcolumns leaving or entering a highlight recolor between the tone and the quiet gray | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
Drawn in place; changes land at once.
Reduced motion
The dots are drawn in place, and a new highlight lands at once.
AccessibilityKeyboard, focus, screen readers
Accessibility
- A named
grid(yourlabel, or the labels' "Density lanes") described by a summary: how many lanes, their total, and the busiest column with its value. - A row per lane, its header the lane's name and share; every cell is named with its column and value, and says when it is in the highlight ("12:00: 504 alerts, highlighted").
- One tab stop. Left and Right walk the columns (mirrored in RTL), Up and Down the lanes, Home and End go to a lane's ends. Focus shows the cell's tooltip and marks its column through every lane, as hovering does.
- The dots, flag and axis are decorative; the cells say the same in words. Color is never the only cue: lanes are named, and a highlight is spoken.
- Its built-in text comes from the labels, so
<YoujProvider labels>translates it.