Skip to content

ExpressiveCSS

Browse documentation

Bar chart

Ranked or long-labelled categories drawn as horizontal bars from a data table, grouped, stacked or as a meter.

View as Markdown

A <figure class="bar-chart"> draws a <table> of values as horizontal bars that grow from zero. The first column of the table labels each row. Each further column is a series named by its header cell.

It takes the same markup as a column chart. Use bars when the labels are long, when the rows are a ranking, or when there are more rows than fit across the page. The labels sit at the inline start of each row. The plot grows with the number of rows. A band and a tooltip follow the pointer, and the up and down arrows move them when the chart has focus.

Monthly revenue, first half
MonthRevenue
Jan$4,400
Feb$5,500
Mar$4,100
Apr$3,700
May$2,200
Jun$4,300
<figure class="bar-chart">
  <figcaption>Monthly revenue, first half</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>$5,500</td></tr>
      <tr><th>Mar</th><td>$4,100</td></tr>
      <!-- One row per month -->
    </tbody>
  </table>
</figure>

Several series

Each value column after the first adds a bar to every row, and a legend appears. Series take primary, tertiary, secondary and on-surface-variant in turn. Two or three series per row stay readable. Add toggle to the figure to make each legend item a button that shows or hides its series, as on the line chart.

Subscriptions by plan
MonthBasicPremium
Jan5,5004,900
Feb2,2002,800
Mar5,2001,800
Apr3,5003,200
May2,5004,000
Jun6,2005,800
<figure class="bar-chart">
  <figcaption>Subscriptions by plan</figcaption>
  <table>
    <thead>
      <tr><th>Month</th><th>Basic</th><th>Premium</th></tr>
    </thead>
    <tbody>
      <tr><th>Jan</th><td>5,500</td><td>4,900</td></tr>
      <!-- One row per month -->
    </tbody>
  </table>
</figure>

Stacked

Add stacked to draw one bar per row, with each series after the ones before it. The bar's length is the total. Only the last segment rounds its end. Stack values that add up, and keep them positive. An empty cell leaves the total unknown, so the chart leaves out the segments after it.

Subscriptions by plan, stacked
MonthBasicPremium
Jan5,5004,900
Feb2,2002,800
Mar5,2001,800
Apr3,5003,200
May2,5004,000
Jun6,2005,800
<figure class="bar-chart stacked">
  <figcaption>Subscriptions by plan, stacked</figcaption>
  <table><!-- … --></table>
</figure>

Track and values

Add track to draw a full-width track behind each row and round both ends of its bars. Add values to write each cell's text from its bar's base, so a ranking reads without the tooltip. Over the bar, the text takes the series' on-color. A value longer than a short bar runs on past its end in the label color. Put the number in data-value when the text is abbreviated.

Visitors by source
SourceVisitors
Search900K
Instagram650K
Facebook420K
YouTube260K
Newsletter27K
<figure class="bar-chart track values" style="
    --md-comp-bar-chart-row-height: 48px;
    --md-comp-bar-chart-shape: 12px;
    --md-comp-bar-chart-grid-color: transparent">
  <figcaption>Visitors by source</figcaption>
  <table>
    <thead>
      <tr><th>Source</th><th>Visitors</th></tr>
    </thead>
    <tbody>
      <tr><th>Search</th><td data-value="900000">900K</td></tr>
      <tr><th>Instagram</th><td data-value="650000">650K</td></tr>
      <!-- One row per source -->
    </tbody>
  </table>
</figure>

Meter

One stacked row with a track and a data-max is a meter. The segments are what is used and the track is what is left. Add split to separate the segments with a gap and round them at each joint. The legend names the segments, and the tooltip lists every cell. In a stacked bar, values keeps each segment's text inside it and cuts what does not fit.

Spending limit
$4,200
of $10,000 this month
Spending by category, April
MonthAdsSoftwarePayroll toolsTeam events
Apr$1,450$1,120$890$740
<article class="outlined">
  <div class="p-4">
    <dl class="stats my-0">
      <div>
        <dt>Spending limit</dt>
        <dd>$4,200</dd>
        <dd class="stat-change">of $10,000 this month</dd>
      </div>
    </dl>
    <figure class="bar-chart stacked split track" data-max="10000" style="
        --md-comp-bar-chart-bar-height: 100%;
        --md-comp-bar-chart-shape: 8px;
        --md-comp-bar-chart-grid-color: transparent">
      <figcaption class="visually-hidden">Spending by category, April</figcaption>
      <table>
        <thead>
          <tr><th>Month</th><th>Ads</th><th>Software</th><th>Payroll tools</th><th>Team events</th></tr>
        </thead>
        <tbody>
          <tr><th>Apr</th><td>$1,450</td><td>$1,120</td><td>$890</td><td>$740</td></tr>
        </tbody>
      </table>
    </figure>
  </div>
</article>

Use sparkline for a bar beside a figure that already explains it. It drops the label, grid, legend and tooltip.

Chart cards

Put a chart in a card with the figures it explains. Labels take up to 40% of the width and end in an ellipsis past it. The card adds no chart styles, so size a chart with its tokens.

Total revenue
$24,200
+14.5% from last year
Revenue by country
CountryRevenue
United States$4,400
India$5,500
France$4,100
Canada$3,700
United Kingdom$2,200
Turkey$4,300
<article class="outlined">
  <div class="p-4">
    <dl class="stats my-0">…</dl>
    <figure class="bar-chart values"
            style="--md-comp-bar-chart-grid-color: transparent">
      <figcaption class="visually-hidden">Revenue by country</figcaption>
      <table><!-- … --></table>
    </figure>
  </div>
</article>

Values and scale

Cells read as in a column 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. In a grouped chart, a gap leaves an empty slot, so the other series stay in place.

The value axis always includes zero. It leaves 10% of the span to spare past the longest bar, and before the shortest when there are negative values. Negative bars grow toward the inline start and round that end. Set data-min or data-max on the figure to fix either end.

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

Net cash flow, first half
MonthNet
Jan$1,200
Feb−$400
Mar$800
Apr$1,500
May−$900
Jun$600
<figure class="bar-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 bars, values, labels and legend are hidden from assistive technology.

The plot is one Tab stop named by the caption. Up and down arrows move between rows. Home and End jump to the ends, and Escape hides the tooltip. On a touch screen, tap a row to show it. In a right-to-left page, the labels sit on the right and the bars grow leftwards. The rows keep their order. The tooltip is a polite live region, so a screen reader hears each row's values as it moves. A sparkline is not focusable. In forced colors the bars keep their series colors so the legend still keys them.

JavaScript

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

const chart = Expressive.BarChart.init(document.querySelector('.bar-chart'), {
  max: 10000
});
chart.show(2);   // highlight the third row; -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
OptionDefaultDescription
minnullLowest value. data-min overrides it.
maxnullHighest value. data-max overrides it.

Tokens

Override these on the .bar-chart. The plot is as tall as its rows, so the shared --md-comp-bar-chart-height has no effect.

Token Default
--md-comp-bar-chart-row-height40px, 24px for a sparkline
--md-comp-bar-chart-bar-height60% of each row's height
--md-comp-bar-chart-shape4px
--md-comp-bar-chart-track-color--md-sys-color-surface-container-highest
--md-comp-bar-chart-grid-color--md-sys-color-outline-variant
--md-comp-bar-chart-color-1--md-sys-color-primary
--md-comp-bar-chart-color-2--md-sys-color-tertiary
--md-comp-bar-chart-color-3--md-sys-color-secondary
--md-comp-bar-chart-color-4--md-sys-color-on-surface-variant
--md-comp-bar-chart-on-color-1 … -4on-primary, on-tertiary, on-secondary, surface; text from values

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

No matching pages

  • Source color

    The seed every generated ramp derives from. Pick one and browse the docs — the whole theme follows. Error does not: it is a fixed hue.

    Default source: Mist Iris.

    ExpressiveCSS presets: Lagoon, Harbor, Breeze, Cobalt, Iris.

    Bold

    Soft

    Mist

    Glow