Circular Charts

RareCharts provides three circular chart types that share a common API and theme system: Donut, Pie, and Gauge. Each answers a different question.

Donut

The default circular chart. A ring with a center area — the center gives you a place to put something useful: the total, a headline number, or a short label. That makes donut the better default for dashboards, where charts rarely live alone and context matters.

new RareCharts.Donut('#chart', {
  title:       'Revenue by Product',
  source:      'Source: Internal accounting',
  legend:      segments.map(d => ({ label: d.label, type: 'bar' })),
  height:      300,
  centerText:  data => '$' + d3.format(',.0f')(d3.sum(data, d => d.value)) + 'K',
  centerLabel: 'Revenue',
}).setData(segments);

Legend to the right

Pass legendPosition: 'right' to place the legend in a vertical column beside the chart instead of above it. Works well when you have many categories or want to keep the chart area compact.

new RareCharts.Donut('#chart', {
  legend:         segments.map(d => ({ label: d.label, type: 'bar' })),
  legendPosition: 'right',
  height:         340,
}).setData(segments);

Drill-down (hierarchy mode)

Hand Donut a tree — a single root object instead of a flat array — and it becomes an interactive drill-down. It opens on the root’s direct children; click a slice that has children to descend into it, and click the center (or a breadcrumb crumb) to come back up. A leaf slice just shows its value. It is the same tree contract as the hierarchical bar — one data shape, two views: the bar lays the whole hierarchy out at once, the donut walks it one ring at a time.

Two composition details carry over from the tree contract. A parent whose children don’t add up to its stated value has a remainder — the undisclosed gap — which draws as a muted slice once you surface it with showRemainder: true or a per-node remainderLabel. And a named-but-undisclosed item (value: null) has no magnitude, so it cannot be a slice; the ring notes it as “N not disclosed” beneath the chart instead. See the hierarchical bar page for the full node shape and the value tri-state.

new RareCharts.Donut('#chart', {
  showRemainder: true,
}).setData({
  label: 'Portfolio', value: 1400,
  children: [
    { label: 'World Liberty Financial', value: 536.4, remainderLabel: 'Other', children: [
      { label: 'Ethereum Key', value: 106 },
      { label: 'USDC Key',     value: 56 },
    ] },
    { label: 'Stablecoin proceeds', value: 196.9 },
    { label: 'NFT INT, LLC',        value: null },   // undisclosed → noted, not drawn
  ],
});

Pie

A pie is a donut with innerRadius: 0. The class name is DonutPie is an alias. Same API, same options, same behavior.

A pie chart is a blunt instrument: it answers “how is the total split” when there are only a few categories and the differences are obvious. It breaks down quickly when slices are similar in size, when there are many categories, or when the reader needs precision.

new RareCharts.Pie('#chart', {
  height:     300,
  showLabels: true,
}).setData(segments);

Outer labels

Enable with showLabels: true. Each slice gets a leader line and two-line text (category name + percent). Slices below labelMinPct (default: 4%) are skipped automatically.

Control what appears with labelContent:

  • 'both' — category name + percentage (default)
  • 'label' — category name only
  • 'percent' — percentage only
new RareCharts.Pie('#chart', {
  showLabels:   true,
  labelContent: 'percent',   // just percentages
  labelMinPct:  0.05,        // hide labels below 5%
}).setData(segments);

Gauge

An arc-based progress chart. Shows a value relative to a maximum along a partial arc. Use it for goal completion, budget usage, KPI progress — anywhere the question is "how far along are we?"

setData() accepts a plain number, or an object to override max and min per render:

// 73 out of 100 (default max)
new RareCharts.Gauge('#chart', {
  centerLabel: 'Complete',
}).setData(73);

// 50 achieved out of 80 plan — fills to 62.5%
new RareCharts.Gauge('#chart', {
  max:         80,
  color:       '#00c97a',
  centerText:  (value, max) => `${value}/${max}`,
  centerLabel: 'Achieved',
}).setData(50);

// Override max at render time
gauge.setData({ value: 50, max: 80 });

The arc geometry is fully configurable:

new RareCharts.Gauge('#chart', {
  startAngle:   -Math.PI * 0.75,   // -135° (default)
  endAngle:      Math.PI * 0.75,   // +135° (default, 270° sweep)
  thickness:    0.18,              // ring thickness as fraction of radius
  cornerRadius: 6,
  trackColor:   '#e8e8e8',         // background arc
  color:        '#ff3b5c',         // fill arc
});

Speedometer — half-circle and needle

Two ingredients make the automotive-indicator look:

  • Sweep. startAngle / endAngle set the arc in radians, clockwise from 12 o'clock. The default ±Math.PI * 0.75 is a 270° horseshoe; -Math.PI / 2 … Math.PI / 2 is the flat 180° half-circle. The gauge re-centers vertically for any sweep and reserves room for the center text when the arc is shallow, so nothing clips.
  • Needle. needle: true draws a pointer pivoting on a hub at the dial center, animated to the value together with the fill. The center readout moves below the hub, dial-style. Color it with needleColor (default: theme text).
new RareCharts.Gauge('#chart', {
  startAngle:  -Math.PI / 2,   // 9 o'clock
  endAngle:     Math.PI / 2,   // 3 o'clock — a 180° sweep
  needle:       true,
  max:          240,
  centerText:   v => v,
  centerLabel:  'km/h',
}).setData(87);

Data format

All three types use the same segment data structure:

[
  { label: 'Subscriptions', value: 42000, color: '#00c97a' },
  { label: 'Services',      value: 18000 },
  { label: 'Other',         value:  6000 }
]

Only positive finite values are rendered. Zero and negative values are filtered out.

Passing a single root object instead of a flat array switches Donut into drill-down mode, where the data is a tree — see the Drill-down section above.

Colors can be provided per item. If omitted, the chart uses the active theme palette in order.

Slice geometry

padAngle controls the gap between slices (a small default keeps separation without turning the chart into a flower). cornerRadius rounds slice corners. Both have sensible defaults and adjust slightly between Pie and Donut mode.

Hover interaction expands the hovered slice outward and shows a tooltip. Tooltip content is fully customizable:

tooltipFormat: ({ label, value, percent, color }) => `
  <div style="color:${color}">${label}</div>
  <div>${d3.format(',.0f')(value)}</div>
  <div style="color:#888">${d3.format('.1%')(percent)}</div>
`

Circular charts options

Option Default Description
Donut / Pie
height 280 Chart height px
innerRadius 0.58 Hole size as fraction of outer radius; 0 = Pie
padAngle 0.018 Gap between slices (radians)
cornerRadius 3 Rounded slice corners px
showLabels false Show outer leader-line labels
labelContent 'both' 'both' / 'label' / 'percent'
labelMinPct 0.04 Hide label when slice < this fraction
legendPosition 'right' to place legend in vertical aside
showCenter true (donut) Show center text hole
centerText formatted total String or function(data) => string
centerLabel 'Total' Secondary line below center text
valueFormat locale number function(value) => string — center total and default tooltip value
percentFormat .1% function(pct) => string — outer-label and tooltip percents
animate true Animate on first render
duration 650 Animation duration ms
ease 'cubicOut' 'cubicOut' / 'cubicInOut' / 'linear'
tooltipFormat built-in function({label, value, percent, color}) => html
showRemainder false Drill-down mode: draw a muted slice for each node’s undisclosed remainder (value − Σ children)
remainderLabel 'Other' Drill-down mode: fallback label for a surfaced remainder
strict false Drill-down mode: throw when a node’s children exceed its stated value (default: warn, leave the negative remainder undrawn)
Gauge
height 220 Chart height px
min 0 Minimum value
max 100 Maximum value
startAngle -¾π Arc start (radians, clockwise from top)
endAngle +¾π Arc end (270° sweep by default)
thickness 0.18 Ring thickness as fraction of outer radius
cornerRadius 6 Arc end rounding px
needle false Speedometer pointer pivoting at the dial center; the center readout moves below the hub
needleColor theme text Needle and hub color
color theme.accent Fill arc color
trackColor theme.grid Background arc color
showCenter true Show center text
centerText '63%' String or function(value, max, min) => string
centerLabel Secondary line below center text
animate true Animate fill on first render
duration 800 Animation duration ms
ease 'cubicOut' 'cubicOut' / 'cubicInOut' / 'linear'
tooltipFormat built-in function({value, max, min, percent}) => html