Skip to content

ExpressiveCSS

Browse documentation

Pie chart

Parts of a whole drawn as slices from a data table, as a pie or a donut with its total.

View as Markdown

A <figure class="pie-chart"> draws a <table> of parts as slices of a whole. The first column of the table names each part and the second holds its value.

It takes the same markup as a column chart. Slices run clockwise from the top in table order, so put the largest part first. A legend lists every row with its cell and its share of the total. A tooltip follows the pointer round the pie, and the arrow keys move it when the chart has focus. Keep a pie to about five parts. For more, or to compare parts that are close in size, use a bar chart.

Revenue by plan, 2026
PlanRevenue
Basic plan$25,864
Premium plan$14,258
Enterprise plan$5,258
<figure class="pie-chart">
  <figcaption>Revenue by plan, 2026</figcaption>
  <table>
    <thead>
      <tr><th>Plan</th><th>Revenue</th></tr>
    </thead>
    <tbody>
      <tr><th>Basic plan</th><td>$25,864</td></tr>
      <tr><th>Premium plan</th><td>$14,258</td></tr>
      <tr><th>Enterprise plan</th><td>$5,258</td></tr>
    </tbody>
  </table>
</figure>

The legend and tooltip show each cell's text as written, so format values in the table. The chart works out the shares from the numbers.

Donut

Add donut to cut a hole in the middle. A <tfoot> row holds the total. Its value and label show in the hole, and the chart does not draw it as a slice. Write the total yourself, formatted as you want it read.

Lead sources, last six months
SourceLeads
Referrals558
Organic search434
Paid campaigns248
Total leads1,240
<figure class="pie-chart donut">
  <figcaption>Lead sources, last six months</figcaption>
  <table>
    <thead>
      <tr><th>Source</th><th>Leads</th></tr>
    </thead>
    <tbody>
      <tr><th>Referrals</th><td>558</td></tr>
      <tr><th>Organic search</th><td>434</td></tr>
      <tr><th>Paid campaigns</th><td>248</td></tr>
    </tbody>
    <tfoot>
      <tr><th>Total leads</th><td>1,240</td></tr>
    </tfoot>
  </table>
</figure>

Values

Add values to write each slice's share on it, in the slice color's on-color. Slices under 5% are too thin for a label. The legend still shows their share.

Top revenue sources
SourceRevenue
Enterprise clients$38M
Referral partners$10.9M
Retainer contracts$7.4M
Project-based work$4.1M
Strategic partnerships$2.7M
Total client revenue$63.1M
<figure class="pie-chart donut values">
  <figcaption>Top revenue sources</figcaption>
  <table>
    <thead>
      <tr><th>Source</th><th>Revenue</th></tr>
    </thead>
    <tbody>
      <tr><th>Enterprise clients</th><td data-value="38000000">$38M</td></tr>
      <tr><th>Referral partners</th><td data-value="10900000">$10.9M</td></tr>
      <!-- One row per source -->
    </tbody>
    <tfoot>
      <tr><th>Total client revenue</th><td>$63.1M</td></tr>
    </tfoot>
  </table>
</figure>

Labels

Add labels to write each slice's name outside it, so a reader can match slices to parts without the legend. Names on the right half run rightwards and those on the left run leftwards. Neighbours that would touch move apart, away from the middle. A line from each name back to its slice shows which slice a moved name belongs to. The pie shrinks to leave --md-comp-pie-chart-label-width on each side, or a quarter of a narrow figure. Longer names get cut short with an ellipsis. Combine it with values to show the shares too.

Leads by source
SourceLeads
Referrals558
Organic search434
Paid campaigns248
Total leads1,240
<figure class="pie-chart donut values labels">
  <figcaption>Leads by source</figcaption>
  <table>
    <!-- The same table as above -->
  </table>
</figure>

Small parts

Set data-other to a percentage to group every row under that share of the whole into one slice. The group comes last, so thin slices do not crowd the pie or push their names into the caption. It shows the rows' sum and share. The sum keeps the cells' currency and decimals, and is compact when they use data-value. Further columns do not add up, so the group shows no sum for them. Set data-other-label to name the group. It defaults to "Other". A single small row keeps its own slice, and the table keeps every row for screen readers.

Visits by channel
ChannelVisits
Search4,210
Direct3,120
Social2,480
Email310
Podcasts140
Print90
Radio60
<figure class="pie-chart labels" data-other="5">
  <figcaption>Visits by channel</figcaption>
  <table>
    <thead>
      <tr><th>Channel</th><th>Visits</th></tr>
    </thead>
    <tbody>
      <tr><th>Search</th><td>4,210</td></tr>
      <tr><th>Email</th><td>310</td></tr>
      <!-- One row per channel; those under 5% become "Other" -->
    </tbody>
  </table>
</figure>

More columns

The slices come from the first value column. Further columns add lines to the tooltip, such as a change from last year, and stay in the table for screen readers.

Revenue by plan, with change
PlanRevenueChange
Basic plan$25,864+10%
Premium plan$14,258+4%
Enterprise plan$5,258−2%
<figure class="pie-chart">
  <figcaption>Revenue by plan, with change</figcaption>
  <table>
    <thead>
      <tr><th>Plan</th><th>Revenue</th><th>Change</th></tr>
    </thead>
    <tbody>
      <tr><th>Basic plan</th><td>$25,864</td><td>+10%</td></tr>
      <!-- One row per plan -->
    </tbody>
  </table>
</figure>

Chart cards

Put a chart in a card with the figures it explains. The card adds no chart styles. Size the pie with --md-comp-pie-chart-height, which sets its diameter.

Total revenue
$45,380
+10% from last year
Revenue by plan
PlanRevenue
Basic plan$25,864
Premium plan$14,258
Enterprise plan$5,258
<article class="outlined">
  <div class="p-4">
    <dl class="stats my-0">
      <div>
        <dt>Total revenue</dt>
        <dd>$45,380</dd>
        <dd class="stat-change up">+10% from last year</dd>
      </div>
    </dl>
    <figure class="pie-chart donut" style="--md-comp-pie-chart-height: 200px">
      <figcaption class="visually-hidden">Revenue by plan</figcaption>
      <table><!-- … --></table>
    </figure>
  </div>
</article>

Use sparkline for a small pie beside a figure that already explains it. It is a 48px pie with no legend or tooltip.

Values and colors

Cells read the same way as in a column chart. A cell's number is its data-value, or its text without currency signs, percent signs or grouping commas. Text that is not a single number, such as "$38M" without a data-value, counts as nothing. So do zero and negative values. Their rows stay in the legend and the tooltip with no slice and no share. A chart whose cells are all gaps shows its table.

Slices take primary, tertiary, secondary, on-surface-variant, and then primary-container and tertiary-container mixed with on-surface, in turn. The mix keeps every slice at 3:1 or more against the surface in light and dark themes. A seventh row would take the first row's color beside it, so it takes the second. Past that, the colors repeat. Group small parts into one "Other" row instead.

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 chart hides the drawn pie, values, total and legend from assistive technology.

The plot is one Tab stop, named by the caption. The arrow keys move round the pie. Right and down go to the next row, and left and up go to the one before. Past the last row they go on to the first. Home and End jump to the first and last rows, and Escape hides the tooltip. On a touch screen, tap a slice to show it. In a right-to-left page the slices still run clockwise, and so do the arrows. The tooltip is a polite live region, so a screen reader hears each row's values and share as it moves. A sparkline is not focusable. In forced colors the slices keep their colors so the legend still keys them.

JavaScript

AutoInit() starts every .pie-chart. To start one yourself, call PieChart.init. After changing its table, call update() to redraw it in place, or add data-watch to the figure so it redraws itself. It has the line chart's methods. The min and max options do not apply.

const chart = Expressive.PieChart.init(document.querySelector('.pie-chart'));
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

Tokens

Override these on the .pie-chart.

Token Default
--md-comp-pie-chart-height240px, the pie's diameter; 48px for a sparkline
--md-comp-pie-chart-hole0.6, the donut hole's share of the diameter
--md-comp-pie-chart-label-width96px, the room on each side of the pie for labels, at most a quarter of the figure's width
--md-comp-pie-chart-gap1.5, the gap between slices in hundredths of the plot's width
--md-comp-pie-chart-color-1 … -4primary, tertiary, secondary, on-surface-variant
--md-comp-pie-chart-color-5, -6primary-container, tertiary-container, each mixed 45% with on-surface
--md-comp-pie-chart-on-color-1 … -6on-primary, on-tertiary, on-secondary, surface, surface, surface; text from values

For a selective Sass build, include "pie-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