# Column chart

> Values compared across categories or periods, drawn as columns from a data table, grouped or stacked.

## Anatomy

Put a `<table>` of values in a `<figure class="column-chart">` to draw them as columns that grow from zero. The first column of the table labels each group. Each further column is a series named by its header cell.

It takes the same markup as a [line chart](line-chart.html). JavaScript draws the chart from the table and hides the table visually, so a screen reader still reads the data as a table. Without JavaScript, the table shows. A band and a tooltip follow the pointer, and the arrow keys move them when the chart has focus.

```
<figure class="column-chart">
  <figcaption>Monthly revenue, 2026</figcaption>
  <table>
    <thead>
      <tr><th>Month</th><th>Revenue</th></tr>
    </thead>
    <tbody>
      <tr><th>Jan</th><td>$4,400</td></tr>
      <tr><th>Feb</th><td>$3,400</td></tr>
      <tr><th>Mar</th><td>$3,700</td></tr>
      <!-- One row per month -->
    </tbody>
  </table>
</figure>
```

The tooltip shows each cell's text as written, so format values in the table.

## Several series

Each value column after the first adds a column to every group, and a legend appears. Series take `primary`, `tertiary`, `secondary` and `on-surface-variant` in turn. Keep a grouped chart to two or three series. Past that, the columns get too thin to compare. Add `toggle` to the figure to make each legend item a button that shows or hides its series, as on the [line chart](line-chart.html#toggle).

```
<figure class="column-chart">
  <figcaption>Subscriptions by plan, first half</figcaption>
  <table>
    <thead>
      <tr><th>Month</th><th>Basic</th><th>Premium</th><th>Enterprise</th></tr>
    </thead>
    <tbody>
      <tr><th>Jan</th><td>5,500</td><td>3,500</td><td>3,200</td></tr>
      <!-- One row per month -->
    </tbody>
  </table>
</figure>
```

## Ranges

Give a series header the class `range` and each of its cells two numbers, such as `120–480`. The column floats from the lower to the higher, rounded at both ends. Write the ends with an en dash, a spaced hyphen or "to", or put them in `data-value="120 480"`. Any other cell is a gap. The tooltip shows the cell as written, and other series draw from zero beside the range as usual. A bar chart reads ranges the same way.

```
<figure class="column-chart axis">
  <figcaption>Response time (ms), fastest to slowest and median</figcaption>
  <table>
    <thead>
      <tr><th>Day</th><th class="range">Fastest to slowest</th><th>Median</th></tr>
    </thead>
    <tbody>
      <tr><th>Mon</th><td>120–480</td><td>210</td></tr>
      <!-- … -->
    </tbody>
  </table>
</figure>
```

## Stacked

Add `stacked` to draw one column per group, with each series on top of the ones before it, so the column's height is the total. Only the top segment rounds its end. The tooltip still shows each cell. Stack values that add up and keep them positive. An empty cell leaves the total unknown, so the chart leaves out the segments above it.

```
<figure class="column-chart stacked">
  <figcaption>Subscriptions by plan, stacked</figcaption>
  <table><!-- … --></table>
</figure>
```

## Fade and track

Add `fade` to fade each column toward its base. Add `track` to draw a full-height track behind each group and round both ends of its columns. Narrow the columns with `--md-comp-column-chart-column-width` to make them bars on a rail.

```
<figure class="column-chart fade">
  <figcaption>Revenue, first half</figcaption>
  <table><!-- … --></table>
</figure>

<figure class="column-chart track" style="
    --md-comp-column-chart-column-width: 24%;
    --md-comp-column-chart-shape: 8px;
    --md-comp-column-chart-grid-color: transparent">
  <figcaption>Revenue, second half</figcaption>
  <table><!-- … --></table>
</figure>
```

## Sparkline

`sparkline` makes a 48px chart with no grid, labels, legend or tooltip. Use it for a count beside a figure. Hide its caption with `visually-hidden` when the tile already names it.

```
<dl class="stats">
  <div>
    <dt>Signups this week</dt>
    <dd>353</dd>
    <dd class="stat-change up">+12% from last week</dd>
    <dd>
      <figure class="column-chart sparkline my-0">
        <figcaption class="visually-hidden">Signups per day this week</figcaption>
        <table><!-- … --></table>
      </figure>
    </dd>
  </div>
</dl>
```

## Chart cards

Put a chart in a card with the figures it explains, and put the period switch beside the stat. When the period changes:

1.  Update the stat.
2.  Swap the table rows.
3.  Call `update()` to redraw. If the figure has `data-watch`, skip the call.

The card adds no chart styles, so size a chart with its tokens. A card pads only its headings and paragraphs, so wrap the stat and chart in `<div class="p-4">` to keep them off the border. A connected button group fills its container, so give it a `<div>` of its own to keep it beside the stat.

```
<article class="outlined" id="profit-card">
  <div class="p-4">
    <div class="flex flex-wrap justify-between align-start gap-3">
      <dl class="stats my-0">
        <div>
          <dt>Revenue</dt>
          <dd>$54,500</dd>
          <dd class="stat-change">34.9% profit margin</dd>
        </div>
      </dl>
      <div>
        <div class="button-group connected xsmall" data-selection="single"
             data-selection-required role="group" aria-label="Period">
          <button type="button" class="button tonal" aria-pressed="true"
                  data-period="12" data-total="$54,500"
                  data-change="34.9% profit margin">Year</button>
          <button type="button" class="button tonal" aria-pressed="false"
                  data-period="6" data-total="$28,500"
                  data-change="36.8% profit margin">6 months</button>
        </div>
      </div>
    </div>
    <figure class="column-chart stacked">
      <figcaption class="visually-hidden">Expenses and profit per month</figcaption>
      <table><!-- … --></table>
    </figure>
  </div>
</article>

<script>
  const card = document.getElementById('profit-card');
  const figure = card.querySelector('.column-chart');
  const body = figure.querySelector('tbody');
  const rows = [...body.rows];
  card.querySelectorAll('[data-period]').forEach((button) => {
    button.addEventListener('click', () => {
      body.replaceChildren(...rows.slice(-Number(button.dataset.period)));
      card.querySelector('.stats dd').textContent = button.dataset.total;
      card.querySelector('.stat-change').textContent = button.dataset.change;
      Expressive.ColumnChart.getInstance(figure).update();
    });
  });
</script>
```

For a compact card, set the height, widen the columns and clear the grid. A sparkline with `flex-1` beside the figures fills the rest of a short tile.

```
<article class="outlined">
  <div class="p-4">
    <dl class="stats my-0">…</dl>
    <figure class="column-chart" style="
        --md-comp-column-chart-height: 120px;
        --md-comp-column-chart-column-width: 80%;
        --md-comp-column-chart-grid-color: transparent">
      <figcaption class="visually-hidden">Signups per day this week</figcaption>
      <table><!-- … --></table>
    </figure>
  </div>
</article>

<article class="outlined">
  <div class="p-4">
    <div class="flex align-center justify-between gap-3">
      <dl class="stats my-0">…</dl>
      <figure class="column-chart sparkline flex-1 my-0"
              style="--md-comp-column-chart-height: 72px">
        <figcaption class="visually-hidden">Monthly revenue, 2026</figcaption>
        <table><!-- … --></table>
      </figure>
    </div>
  </div>
</article>
```

## Values and scale

Cells read the same way as in a line chart. A cell's number is its text without currency signs, percent signs or grouping commas. Text that is not a single number is a gap. For values like `4.4k` or `4.400 €`, put the number in `data-value`. In a grouped chart, a gap leaves an empty slot, so the other series stay in place.

The y axis always includes zero, with 10% of the span to spare above and, when there are negative values, below. Negative columns hang below zero and round their lower end. Set `data-min` or `data-max` on the figure to fix either end. When zero is off the scale, columns start at the bottom of the plot.

Add `axis` to label the y axis at round values, with a grid line at each. It widens an end the chart chose to the next round value, so the labels start and end on the plot's edges. The labels repeat any currency or percent sign the cells are written with and shorten large numbers, such as `$1.5K`. An end set with `data-min` or `data-max` stays where it is. The labels are hidden from assistive technology, and the table still carries the values.

```
<figure class="column-chart axis">
  <figcaption>Net cash flow, first half</figcaption>
  <table>
    <thead><tr><th>Month</th><th>Net</th></tr></thead>
    <tbody>
      <tr><th>Jan</th><td>$1,200</td></tr>
      <tr><th>Feb</th><td>−$400</td></tr>
      <!-- One row per month -->
    </tbody>
  </table>
</figure>
```

## Accessibility

The table is the chart's text alternative. Give it a header row and a header cell per row, and name the chart with a `<figcaption>`. The drawn columns, labels and legend are hidden from assistive technology.

The plot is one Tab stop named by the caption. Left and right arrows move between groups, Home and End jump to the ends, and Escape hides the tooltip. In a right-to-left page the groups run from right to left, and the arrows still move the band the way they point. The tooltip is a polite live region, so a screen reader hears each group's values as it moves. A sparkline is not focusable. In forced colors, the columns keep their series colors so the legend still matches them.

## JavaScript

`AutoInit()` starts every `.column-chart`. To start one yourself, call `ColumnChart.init`. After changing its table, call `update()` to redraw it in place, or put `data-watch` on the figure and it redraws itself. The columns move from their old values to the new ones, and a new column grows from zero. With reduced motion, they appear at once. The column chart takes the line chart's options and methods.

```
const chart = Expressive.ColumnChart.init(document.querySelector('.column-chart'), {
  max: 10000
});
chart.show(2);   // highlight the third group; -1 hides the tooltip
chart.update();  // redraw from the table; focus and the highlighted row stay
chart.destroy(); // remove the drawing and show the table again
```

| Option | Default | Description |
| --- | --- | --- |
| `min` | `null` | Lowest y value. `data-min` overrides it. |
| `max` | `null` | Highest y value. `data-max` overrides it. |

## Tokens

Override these on the `.column-chart`.

| Token | Default |
| --- | --- |
| `--md-comp-column-chart-height` | 240px, 48px for a sparkline |
| `--md-comp-column-chart-column-width` | 60% of each group's width |
| `--md-comp-column-chart-shape` | 4px |
| `--md-comp-column-chart-track-color` | `--md-sys-color-surface-container-highest` |
| `--md-comp-column-chart-grid-color` | `--md-sys-color-outline-variant` |
| `--md-comp-column-chart-axis-width` | 48px, the room beside the plot for `axis` labels |
| `--md-comp-column-chart-color-1` | `--md-sys-color-primary` |
| `--md-comp-column-chart-color-2` | `--md-sys-color-tertiary` |
| `--md-comp-column-chart-color-3` | `--md-sys-color-secondary` |
| `--md-comp-column-chart-color-4` | `--md-sys-color-on-surface-variant` |

For a selective Sass build, include `"column-chart"` in `$components`.
