AreaChart

A filled trend over time — stacked to show a total, normalized to show share, or overlapping for two or three series. Strokes the top edge of each band so a stack stays readable where two fills meet, and takes `gradient` to fade the fill out toward its baseline.

vue
<UiAreaChart :data="..." :categoryKey="..." :series="..." :ariaLabel="..." />

Needs the charts module

This component ships in @jgastager/bundy-ui/charts, a separate module so projects that never draw a chart don't carry the paint classes these need. Nothing extra to install — just add the module entry. See Charts.


Options

One block per prop — what it does, and what it looks like set.

Data

required

data is one object per x position with one field per series — the wide shape your API already returns, so nothing needs melting into long form first.

  • Desktop
  • Mobile
  • Tablet

data stays in the wide shape an API returns ({ month: "Jan", desktop: 120, mobile: 80 }); the component folds it into the long rows the marks want. Rows whose value isn't a finite number are dropped rather than zeroed, so a gap in the data reads as a gap.

There's no animate prop. The marks are plain SVG elements Vue patches directly, so a data change re-renders rather than tweening; what motion there is comes from CSS transitions on hover, which this chart doesn't have.

Category key

required

categoryKey sets which field on each row is its x value.

Series

required

series is field names, or { key, label, color } objects when the legend needs better words than your column names. Order assigns the default colours and, when stacked, the band order — so put the series that needs reading accurately first, since only the bottom band keeps a flat baseline.

Colors are the library's own semantic ones — primary, secondary, success, warning, error, info, pending, neutral — and any shade of them, primary-400 through primary-950, exactly as the bg-* utilities spell them. Set color per series (or per item) to pin one; leave it unset to take the next slot of the default sequence. neutral and secondary are never handed out automatically, so an unlabelled series never lands on them.

ARIA label

required

ariaLabel sets what the chart shows, in one sentence. Required — an unlabelled chart is an unreadable one.

  • Desktop
  • Mobile
  • Tablet

ARIA description

ariaDescription is the detail the label shouldn't carry — the trend a sighted reader gets from the shape.

Total visits grow from 2,270 in January to 3,700 in June, with mobile overtaking desktop in June.
  • Desktop
  • Mobile
  • Tablet

Variant

variant decides what the stack means. stacked sits the bands on each other so the top edge is the total — usually the question an area chart is being asked. normalized stacks to a full 100% at every position, turning it into a share-over-time read where the total disappears. overlapped starts every band at zero so they share a baseline; readable for two or three series and no more.

stacked
  • Desktop
  • Mobile
  • Tablet
overlapped
  • Desktop
  • Mobile
  • Tablet
normalized
  • Desktop
  • Mobile
  • Tablet

variant decides the baseline, and that decides what's readable. stacked (the default) gives only the bottom band a flat baseline; overlapped gives every band one but only survives two or three series; normalized shows share and hides the total entirely.

Curve

curve sets how the top edge gets between points. smooth is monotone cubic — it rounds corners without bulging past a value the data never contained.

Line

line strokes the *top* edge of each band only — not an outline around the fill, which would trace the baseline and sides too and turn a stack into a set of boxes. Turn it off for a softer read.

  • Desktop
  • Mobile
  • Tablet
  • Desktop
  • Mobile
  • Tablet

line strokes the *top* edge only, not an outline around the fill. That needs the bands pre-stacked: areaY's own boundary stroke would trace the baseline and sides too, and a plain lineY over the raw values wouldn't stack at all.

Gradient

gradient fades each band out toward its baseline, so it reads as weight under the line rather than as a solid slab. Separate from variant on purpose — painting and stacking are different questions, and any variant can be gradient-filled.

  • Desktop
  • Mobile
  • Tablet

gradient fades each band out toward its baseline. It's a separate prop from variant because it's about how a band is painted, not how bands relate — stacked, overlapped and normalized can each be gradient-filled or flat.

Fill opacity

fillOpacity applies to the flat fill under each line. Drop it when the bands overlap and you need to see through them; it does nothing under a gradient fill.

  • Desktop
  • Mobile
  • Tablet
  • Desktop
  • Mobile
  • Tablet

X label

xLabel names what the x ticks are counting, for the cases the surrounding copy doesn't.

Y label

yLabel is the same, for the value axis.

  • Desktop
  • Mobile
  • Tablet

X tick size

xTickSize is the box the x-tick slot draws into, and the room reserved for it below the plot. Anything you can draw in SVG goes in it — a boxed initial here, an icon or an <image> just as easily.

  • Desktop
  • Mobile
  • Tablet

Y tick size

yTickSize is the same for the y-tick slot, and on the left axis it *is* the margin — a tick drawing its own content leaves no text for the chart to estimate a width from. Size it for the widest thing you draw, formatted label included.

Value format

valueFormat covers the y ticks and the tooltip together, so a formatted axis can never disagree with the panel beside it. A normalized chart formats its own axis as percentages and ignores this for the ticks, since the numbers there are shares rather than amounts.

Category format

categoryFormat rewrites the x tick labels without touching your data — how a key like 2025-03 becomes Mar.

X type

xType decides whether the x values are names or quantities. A category axis gives every row an equal slot, so six samples taken at ragged intervals come out evenly spaced and the band's shape is a fiction. number places each one where its value falls, and the widths become the reading.

A number axis changes three things beyond the spacing. Rows are drawn in value order rather than data order, so an unsorted array no longer doubles the band back across the plot. A row whose category isn't a number is dropped, the way a row with a non-finite value already is — there's nowhere on the scale to put it. And categoryFormat is never called: the ticks are numbers the scale chose, so xFormat formats them, and the tooltip's title with them. Everything else — folding, focus, the focusChange payload — still speaks in the category string your data held.

X format

xFormat is categoryFormat for a numeric axis: it takes the number and returns the label, on the ticks and in the tooltip's title. Without it the axis prints the raw quantity — 1.25 where the sample was taken at 1:15.

X min

xMin holds the left end of a numeric axis at a value no row reaches. It's yMax's counterpart on the other axis, and gives way the same way: a row further left still widens the scale rather than being drawn outside the plot.

X max

xMax does the same at the right end, and the pair is what makes the band a timeline: midnight to noon, with the samples where they landed and the quiet stretches drawn at their real width.

X ticks

xTicks names the ticks outright, for when the scale's idea of a round number isn't yours — every three hours here rather than the 0, 2, 4 the arithmetic prefers.

Grid

grid is the horizontal rules from the y ticks. Dropping them keeps the axes.

  • Desktop
  • Mobile
  • Tablet

Y max

yMax holds the y axis open to a number the data needn't reach — quota used against the whole quota, where the space left is the point. Without it the band always tops out at the plot's edge, which makes 62% and 98% of a quota look the same.

There's no yMin to pair with yMax: an area is read from its baseline, so the axis starts at zero and moving it would make the fills lie about their proportions. yMax is also ignored by normalized, which is 0–100% by construction.

Tooltip

tooltip is the crosshair and the panel that follows it, dropped together.

  • Desktop
  • Mobile
  • Tablet

Animate

animate is on by default: the bands rise out of the baseline on the first draw and ease from old values to new. Roll new values to compare it with the chart on the right, which has it off. Only the marks move — axes, tooltips and labels always read the real numbers — and a reader who has asked their system for reduced motion gets no motion either way.

  • Desktop
  • Mobile
  • Tablet
  • Desktop
  • Mobile
  • Tablet

Legend

legend is on by default from two series up, and off for one — a single band needs no key. Force it off when the surrounding heading already names what's plotted.

Legend position

legendPosition moves the key above or below the plot. top reads as part of a heading when the chart sits directly under one.

  • Desktop
  • Mobile
  • Tablet

Legend align

legendAlign places the key horizontally — start lines it up with the plot's left edge, which is where the eye already is after reading the axis.

  • Desktop
  • Mobile
  • Tablet
  • Desktop
  • Mobile
  • Tablet
  • Desktop
  • Mobile
  • Tablet

Legend orientation

legendOrientation flows the entries in a row or a column. vertical gives long labels their own line, at the cost of height.

  • Desktop · 8,680 visits
  • Mobile · 7,630 visits
  • Tablet · 1,430 visits

Legend group

legendGroup names a group that a standalone UiChartLegend with the same group belongs to, so the legend can sit anywhere — here above the plot — and still dim the other bands while you hover an entry. The chart hands the legend its entries, colors included, so it needs no items. Switch the chart's own legend off if you don't want two.

Guides

guides drops the axes and the margins they reserve, so the bands fill the box. That plus a small height is the sparkline shape — an area sparkline reads as volume where a line reads as direction.

Visits3,700

guides="false" drops the axes, ticks, labels and grid *and* the margins they reserve, so the marks fill the whole box. That plus a small height is the inline-sparkline shape — there's no separate component for it.

Height

height is the plot's height in pixels. Ignored once aspectRatio is set.

Aspect ratio

aspectRatio sizes the chart from its container's width instead of a fixed height, so it keeps its proportions across breakpoints.

  • Desktop
  • Mobile
  • Tablet

Loading

loading is a skeleton at the chart's exact height. An empty data array gets the empty state instead — both hold the height, so a dashboard doesn't reflow as its panels resolve.

  • Desktop
  • Mobile
  • Tablet
No data to display
  • Desktop
  • Mobile
  • Tablet

Examples

The component in real use: compositions that answer "how do I build X with this", rather than one prop at a time.

@focus-change

The category under the pointer, or null once it leaves — for driving a readout, a linked row, or a second chart scrubbing alongside this one.

Month:—

  • Desktop
  • Mobile
  • Tablet

API Reference

Generated from the component's source — props, slots and emits as the component actually declares them.

Props

Prop
Type
Default
Description
datarequired
Record<string, unknown>[]
—
One object per x position, with one field per series.
categoryKeyrequired
string
—
Field holding each row's x value.
seriesrequired
ChartSeriesInput[]
—
The series to draw. Order assigns default colors and, when stacked, the band order.
ariaLabelrequired
string
—
What the chart shows, for screen readers. Required: an unlabelled chart is unreadable.
string
—
A longer accessible description, for detail the label shouldn't carry.
"stacked" | "overlapped" | "normalized"
'stacked'
How the bands relate to each other. - `stacked` — bands sit on each other and the top edge is the total. - `overlapped` — every band starts at zero, so all of them keep a shared baseline. Readable for two or three series and no more. - `normalized` — stacked to a full 100%, showing share rather than amount.
ChartCurveName
'linear'
How the edge gets between points.
boolean
true
Strokes the top edge of each band, keeping a stack legible where two fills meet.
boolean
false
Fades each band toward its baseline instead of filling it flat.
number
0.24
Fill opacity, when not using a gradient.
string
—
Title for the x axis.
string
—
Title for the y axis.
number
24
Side of the square box each x tick's slot content draws into, in pixels — and the room reserved for it below the plot. Ignored without the `x-tick` slot: text ticks size themselves.
number
24
The same for the `y-tick` slot, reserved out of the left margin instead.
(value: number) => string
—
Formats y-axis ticks and tooltip values.
(value: string) => string
—
Formats x-axis tick labels.
"category" | "number"
'category'
How the x axis reads the category values. `category` is a name per row, evenly spaced — twelve months, five channels. `number` reads them as quantities and places each row where its value falls, so the gaps between readings are drawn to scale: a morning's readings at 4:43, 5:34, 8:00 and 11:25 sit where they happened rather than a quarter of the plot apart. The ticks stop being the readings too — they become the round numbers the scale wants, bounded by `xMin`/`xMax` and named outright by `xTicks`. A row whose category isn't a number is dropped on a `number` axis, the same way a row with no value is: there's nowhere on the scale to put it.
(value: number) => string
—
Formats a `number` axis's ticks and the tooltip title — 8.5 into `8:30`. Replaces `categoryFormat`, which a numeric axis never calls.
number
—
Lowest value the x axis reaches on a `number` axis, whether or not a row gets there — a shift charted across the whole morning rather than across its first and last reading. Data outside it still widens the axis. Ignored on a `category` axis, which has no numbers to bound.
number
—
Highest value the x axis reaches, on the same terms.
number[]
—
Tick values for a `number` axis, when the round ones aren't the useful ones — hours across a timeline held in minutes, where the scale's idea of a round number is 100 and yours is 60. They're labels only: they neither move the marks nor bound the axis.
boolean
true
Draws horizontal grid rules from the y ticks.
number
—
Highest value the y axis reaches, whether or not the data gets there — `y-max="100"` gives a percentage chart its whole scale when the data peaks at 62%, so the reader sees the headroom instead of a band that fills the plot at every magnitude. A bound holds the axis open; it doesn't cap the data. A value above it still widens the axis, because a band drawn outside the plot is a worse answer than a scale that grew. Ignored by `normalized`, which is 0–100% by construction. There's no `yMin` to pair with it: an area is read from its baseline, so the axis starts at zero. Moving that baseline makes the fills lie about their proportions.
boolean
true
Shows the crosshair and tooltip on hover.
boolean
true
Raises the bands out of the baseline on the first draw, and eases them from old values to new whenever the data changes. Only the marks move: the axes and the tooltip always read the real numbers. Readers who ask their system for reduced motion get no animation either way.
boolean
—
Shows the legend. Defaults to on for two or more series.
"top" | "bottom"
'bottom'
Where the legend sits relative to the plot.
"start" | "center" | "end"
'start'
Horizontal placement of the legend.
"horizontal" | "vertical"
'horizontal'
How the legend entries flow.
string
—
Names the group a `<UiChartLegend group="…">` elsewhere on the page belongs to, so hovering its entries dims this chart's other marks. Set it — usually with `:legend="false"` — to place the legend anywhere you like instead of under the plot.
boolean
true
Draws the axes and reserves their margins.
number
320
Height of the plot in pixels. Ignored when `aspectRatio` is set.
number
—
Width-to-height ratio, sizing the chart from its container's width.
boolean
false
Replaces the plot with a skeleton of the same height.

Slots

Slot
default
x-tick
Receives `{ tick, index, x, y, size, box }`.
y-tick
Receives `{ tick, index, x, y, size, box }`.

Emits

Event
Payload
Description
focusChange
[category: string | null]
The category under the pointer, or `null` once it leaves.

Types

ts
interface ChartPlot {
    x: number;
    y: number;
    width: number;
    height: number;
    /** Right edge, since marks need it more often than the width. */
    right: number;
    /** Bottom edge, which is also the x axis' baseline. */
    bottom: number;
}
ts
type ChartSeriesInput = string | ChartSeries;
ts
type ChartCurveName = "linear" | "smooth" | "step" | "step-before" | "step-after";