Skip to content

Pie and donut

Setting chart.type to 'pie' renders the series as pie slices instead of an x/y plot. The createPie helper builds the pieces from labelled values: every slice is its own series, so the legend, focus and filtering behave exactly like the other chart types.

ts
// createPie turns labelled values into pie pieces: every slice is its own
// series (so the legend lists the slices and clicking one filters it),
// and the data is a single row holding every slice value.
import { createPie } from '@mochart/core';
import type { MochartInputConfig } from '@mochart/core';

const pie = createPie(
  [
    { label: 'Subscriptions', value: 420 },
    { label: 'Services', value: 210 },
    { label: 'Hardware', value: 140 },
    { label: 'Licensing', value: 75 },
    { label: 'Support', value: 65 },
    { label: 'Other', value: 30 }
  ],
  // valuePercent puts each slice's share next to its value in the tooltip,
  // e.g. "420 (48.8%)"
  { valueFormat: ',.0f', tooltipValues: 'valuePercent' }
);

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Revenue by Product (fictional, $k)' },
  // chart.type 'pie' swaps the axis plot for the radial plot.
  chart: pie.chart,
  pie: pie.pie,
  categoryAxis: pie.categoryAxis,
  series: pie.series
};

export const data = pie.data;

How it works

  • createPie returns config fragments (like the other chart-type helpers): a chart fragment setting the type, a categoryAxis naming the single category column, and one series per slice. The data is a single row — { category: 'all', slice0: 420, slice1: 210, ... }.
  • Slices are series. Hovering a slice or its legend entry focuses it, and clicking a legend entry filters it — the remaining slices grow to fill the circle, animated with the usual animation timing. Slice colors come from the colorPalette by series index, or from an explicit per-item color.
  • On first load the pie sweeps in clockwise from the start angle over animation.initialDuration (slice labels appear once the sweep settles). Setting focusOffsetFraction "explodes" the focused slice away from the center, animated by the focus tween — try hovering the legend on the donut below.
  • Clicking the chart opens the tooltip with one row per slice. In pie mode tooltip.snapToCategory defaults to false, so the tooltip anchors at the click point, and tooltip.showCategory defaults to false — a pie has a single category, so its value (createPie writes 'all') would head every tooltip. Set showCategory: true with a meaningful categoryValue if you want that line back.
  • The category and value axes still exist structurally (the category column and value domains feed the data model and animations) but default to visible: false in pie mode. The crosshair does not apply.
  • Values must be non-negative: createPie clamps negatives to 0, and a zero-value slice simply doesn't render. Only the first data row is rendered.

Donut and slice labels

An inner radius via pie.innerRadiusFraction turns the pie into a donut — the helper's donut shorthand sets it to 0.6, and its own innerRadiusFraction option picks the exact value — and pie.showLabels puts value, percent or title labels at the slice centroids.

ts
// The donut option adds an inner radius, and tooltipValues 'percent' makes the
// tooltip show each slice's share instead of its raw value. The chart computes
// those percentages from the current slice shares, so — like the percent slice
// labels below — they renormalize as slices are filtered.
import { createPie } from '@mochart/core';
import type { MochartInputConfig } from '@mochart/core';

const donut = createPie(
  [
    { label: 'Chrome', value: 62 },
    { label: 'Safari', value: 20 },
    { label: 'Edge', value: 6 },
    { label: 'Firefox', value: 5 },
    { label: 'Opera', value: 3 },
    { label: 'Other', value: 4 }
  ],
  { donut: true, tooltipValues: 'percent' }
);

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Browser Market Share (fictional)' },
  chart: donut.chart,
  // showLabels puts percent labels at the slice centroids (slices thinner
  // than labelMinFraction hide theirs), and focusOffsetFraction explodes
  // the hovered slice away from the center.
  pie: { ...donut.pie, showLabels: true, labelType: 'percent', focusOffsetFraction: 0.05 },
  categoryAxis: donut.categoryAxis,
  series: donut.series
};

export const data = donut.data;
  • labelType picks the label content: a single part ('value', 'percent' or 'title') or a combination — 'valuePercent' for 420 (45%), 'percentValue' for 45% (420), 'titleValue' for Subscriptions: 420 and 'titlePercent' for Subscriptions: 45%. labelValueFormat and labelPercentFormat format the two numeric parts independently (percent parts format the fraction, so specifiers like '.1%' apply). Slices thinner than labelMinFraction hide their labels. Label colors reuse the per-series labelTextStyle style — each slice is a series, so a slice's label is painted by its own series entry. When slices are filtered via the legend, percent labels renormalize against the remaining slices — set adjustLabelsForFiltering to false to keep every slice's share of the full total instead.
  • tooltipValues does the same for the tooltip rows: 'value' (the default), 'percent', or the 'valuePercent' / 'percentValue' combinations. The value part keeps its per-series formatting (valueFormat, valuePrefix, valueSuffix) — the helper's valueFormat option stamps one format onto every slice's series; the percent part is formatted by tooltipPercentFormat. The helper's tooltipValues option forwards straight to it.
  • Tooltip percentages are computed from the same slice shares as the labels, so they renormalize as slices are filtered — set tooltip.adjustForFiltering to false to keep every slice's share of the full total (the tooltip equivalent of adjustLabelsForFiltering). A filtered slice's own row shows the usual filteredValueCharacter placeholder in place of both parts.
  • Geometry knobs: startAngle rotates the first slice's starting edge, padAngle opens a gap between slices, and cornerRadius rounds the slice corners.

Half pies and gauges

endAngle defaults to startAngle + 360 (a full circle, so rotating with startAngle alone never truncates the pie); setting it explicitly confines the slices to a partial span. With startAngle: -90 and endAngle: 90 the pie becomes a half-donut gauge — an endAngle smaller than startAngle runs counterclockwise.

ts
// startAngle/endAngle confine the slices to a partial span — here a half
// donut — and the pie center can carry a label plus a live total that counts
// along with value changes and filtering.
import { createPie } from '@mochart/core';
import type { MochartInputConfig } from '@mochart/core';

const gauge = createPie(
  [
    { label: 'Promoters', value: 540 },
    { label: 'Passives', value: 280 },
    { label: 'Detractors', value: 180 }
  ],
  // percentValue pairs each segment's share with its response count, e.g.
  // "54.0% (540)"
  { tooltipValues: 'percentValue' }
);

export const config: MochartInputConfig = {
  version: '1.0.0',
  title: { text: 'Customer Sentiment (fictional survey)' },
  chart: gauge.chart,
  pie: {
    ...gauge.pie,
    startAngle: -90,
    endAngle: 90,
    innerRadiusFraction: 0.55,
    // a small gap and rounded corners separate the segments
    padAngle: 1,
    cornerRadius: 3,
    showLabels: true,
    labelType: 'title',
    // the center total tracks the unfiltered slices, so clicking a legend
    // entry counts it down; the negative Y offset lifts it off the gauge
    // pivot into the hole
    centerLabel: 'responses',
    showCenterTotal: true,
    centerTotalFormat: ',.0f',
    centerOffsetYFraction: -0.25
  },
  categoryAxis: gauge.categoryAxis,
  series: gauge.series
};

export const data = gauge.data;
  • centerLabel puts a text line at the circle center, and showCenterTotal adds the live total of the unfiltered slice values, formatted by centerTotalFormat — it counts along with value tweens and filtering (click a legend entry). Set adjustCenterTotalForFiltering to false to keep the full total while slices are filtered.
  • The center content sits at the circle center (the gauge pivot); centerOffsetXFraction and centerOffsetYFraction nudge it by fractions of the outer radius — the example's -0.25 lifts it into the hole.
  • The center text is styled by centerLabelTextStyle and centerTotalTextStyle, both defaulting to fillColor: 'currentColor' so the text follows the host page's CSS color. It can also be restyled directly: CSS wins over the presentation attribute, so targeting .mochart-pie-center text works (the demo dark theme does exactly this).
  • The layout fits the configured span's bounding box into the plot, so a half pie uses the space its missing half would waste (and the radius grows accordingly) instead of staying centered in an empty square. The span comes from the config, so the fit holds still while values animate.

Released under the BSD-3-Clause License.