# StackedArea

> Layers stacked over time, for a part of a whole as it changes

Status: beta. Added in Charts 0.3.0.

Layers stacked over time, for a part of a whole as it changes.

## Import

```tsx
import { StackedArea } from '@oztix/roadie-charts/stacked-area'
```

## Examples

### Default

The top edge is the total. Each band is one gate's share of it, so you can read the crowd arriving and where it came in.

```tsx
<Chart label='Scans by gate' source='Oztix scans.' size='md'>
  <StackedArea
    data={Array.from({ length: 16 }, (_, i) => {
      const at = `2026-11-14T${String(18 + Math.floor(i / 4)).padStart(2, '0')}:${String((i % 4) * 15).padStart(2, '0')}`
      const crowd = Math.round(120 * Math.sin((Math.PI * (i + 1)) / 17))
      return ['Main gate', 'North gate', 'VIP', 'Box office'].map((gate, g) => ({ at, gate, scans: Math.round(crowd * [0.5, 0.3, 0.12, 0.08][g]) }))
    }).flat()}
    x='at'
    y='scans'
    series='gate'
    takeaway='Entry peaked around 8pm, mostly through the main gate'
  />
</Chart>
```

### Highlight

Name the layer that tells the story. It draws in the highlight and the rest step back.

```tsx
<Chart label='Tickets issued by pool' source='Oztix sales.' size='md'>
  <StackedArea
    data={['2026-10-01', '2026-10-08', '2026-10-15', '2026-10-22', '2026-10-29'].flatMap((day, i) => [
      { day, pool: 'GA', issued: 300 + i * 80 },
      { day, pool: 'Members', issued: 120 + i * 10 },
      { day, pool: 'Comps', issued: 20 }
    ])}
    x='day'
    y='issued'
    series='pool'
    highlight='Members'
  />
</Chart>
```

### Share

`mode='share'` stacks every time to 100%, so the make-up reads the same whether the crowd is big or small. The tooltip gives each layer's share and count, and the table holds shares.

```tsx
<Chart label='Tickets issued by pool' source='Oztix sales.' size='md'>
  <StackedArea
    data={['2026-10-01', '2026-10-08', '2026-10-15', '2026-10-22', '2026-10-29'].flatMap((day, i) => [
      { day, pool: 'GA', issued: 300 + i * 80 },
      { day, pool: 'Members', issued: 120 + i * 10 },
      { day, pool: 'Comps', issued: 20 }
    ])}
    x='day'
    y='issued'
    series='pool'
    mode='share'
    takeaway='GA is a growing share of tickets issued'
  />
</Chart>
```

### Today and annotations

`today` marks the top of the stack with a labelled dot that reads the total. `annotations` are dated markers, as on LineChart.

```tsx
<Chart label='Tickets issued by pool' source='Oztix sales.' size='md'>
  <StackedArea
    data={['2026-10-01', '2026-10-08', '2026-10-15', '2026-10-22', '2026-10-29'].flatMap((day, i) => [
      { day, pool: 'GA', issued: 300 + i * 80 },
      { day, pool: 'Members', issued: 120 + i * 10 },
      { day, pool: 'Comps', issued: 20 }
    ])}
    x='day'
    y='issued'
    series='pool'
    today='2026-10-22'
    annotations={[{ at: '2026-10-08', label: 'Members presale' }]}
  />
</Chart>
```

### Selecting a range

`onRangeSelect` lets someone drag across dated x to pick a range, as on LineChart. The range is inclusive, in the venue's wall time: `'2026-10-03'` for days, or `'2026-10-03T18:00'` for times. On a dashboard, [`selectsPeriod`](https://ticketsolutionsptyltd.github.io/roadie/charts/dashboards.md#set-the-period-from-a-chart) passes it to `onPeriodChange`.

```tsx
function Zoom() {
  const [range, setRange] = useState(null)
  return (
    <div className='grid gap-4'>
      <Chart label='Tickets sold by channel' source='Oztix sales.' size='md'>
        <StackedArea
          data={[120, 180, 150, 240, 310, 260, 400, 380].flatMap((sold, i) => {
            const day = `2026-10-0${i + 1}`
            return [
              { day, channel: 'Email', sold },
              { day, channel: 'Social', sold: Math.round(sold * 0.6) },
              { day, channel: 'Direct', sold: Math.round(sold * 0.3) }
            ]
          })}
          x='day'
          y='sold'
          series='channel'
          onRangeSelect={setRange}
        />
      </Chart>
      <p className='text-sm text-subtle'>Range: {range ? `${range.start} to ${range.end}` : 'none yet'}</p>
    </div>
  )
}

render(<Zoom />)
```

## Guidelines

- Use StackedArea when both the total and its make-up matter over time, such as scans by gate. To compare the layers' own trends, use a LineChart; for a few categories rather than a run of time, use StackedBars.
- The layer with the largest total stacks at the bottom, where its edge is flat and easiest to read. Other always stacks on top.
- Past 6 layers, the smallest roll into Other. The table keeps a column for every layer, plus the total.
- A missing reading counts as zero, so the layers above it stay joined.
- The x axis reads like LineChart's: dates, numbers, or `xFormat='daysOut'` and `xFormat='eventTime'`.
- With `onRangeSelect`, releasing a drag reports the range and leaves it drawn until a click on the plot, Escape while the chart has focus, or `data` with different x values. Touch only scrolls and shows the tooltip, so pair the chart with a `DateRangePicker` for phones.

## Accessibility

- The plot is one tab stop. Left and right arrows move between times, and up and down between layers at the same time. Each value is read out in plain words, such as "Saturday, 14 November 2026, 7:00pm, 48 scans, Main gate". In `share` mode it ends with the layer's share, such as "71% of the total".
- With `onRangeSelect`, Shift with the left or right arrow selects from the focused time, and Shift with Home or End selects to either end. The range is read out, such as "Selected Friday, 2 to Sunday, 4 October 2026, 3 days", and Enter applies it. Once there's a range, its start and end handles are sliders in the tab order. Their arrows move them, and Enter applies. Escape clears the range.
- The tooltip opens with the total, then lists each layer with a value from the top of the stack down, matching the legend.
- The Table tab holds every value, with a Total column. In `share` mode it holds shares, and the total stays a count.

## API reference

### StackedArea

```tsx
import { StackedArea } from '@oztix/roadie-charts/stacked-area'
```

- `data`: `PlotRow[]`. Required. Rows of plain values, one per time and series. Dates are ISO strings in venue wall time.
- `x`: `string`. Required. Field along the bottom: an ISO date or a number.
- `y`: `string`. Required. Field for the value each series adds to the stack.
- `series`: `string`. Required. Field that names each layer. Past 6, the smallest roll into Other.
- `highlight`: `string | string[]`. Layers that tell the story, drawn in the highlight. The rest are context.
- `palette`: `"categorical" | "pair" | "trio"`. Defaults to `'categorical', or the pair or trio set at 2 or 3 layers`.
- `format`: `"number" | "compact" | "percent" | "currency" | "compactCurrency" | "points" | "index"`. Defaults to `'number'`.
- `xFormat`: `"number" | "compact" | "percent" | "currency" | "compactCurrency" | "points" | "index" | "daysOut" | "eventTime"`. Defaults to `'number'`. How a numeric x reads, as on LineChart.
- `takeaway`: `string`. The headline sentence. It becomes the plot's accessible name.
- `mode`: `"count" | "share"`. Defaults to `'count'`. `share` stacks every time to 100%.
- `annotations`: `{ at: string | number; label: string; }[]`. Dated markers with labels of 20 characters at most.
- `today`: `string | number`. Marks today with a labelled dot on the top of the stack, read as the total.
- `onRangeSelect`: `((range: AbsoluteRange) => void)`. Lets a drag across dated x select a range, inclusive and in venue wall time.
- `className`: `string`.

Docs updated 23 Oct 2026.
