D3.js Pie & Doughnut Charts Tutorial
This tutorial covers how to compose pie and doughnut charts with D3.js in React, including labels and legends.
Pie Chart
import React, { useEffect, useRef } from 'react'
import * as d3 from 'd3'
function PieChart() {
const containerRef = useRef(null)
useEffect(() => {
const data = [ { label: 'Apples', value: 30 }, { label: 'Bananas', value: 20 } ]
const width = 640, height = 360
const radius = Math.min(width, height) / 2 - 16
const root = d3.select(containerRef.current)
root.selectAll('*').remove()
const svg = root.append('svg').attr('viewBox', `0 0 ${width} ${height}`).style('max-width', '100%').style('height', 'auto')
const g = svg.append('g').attr('transform', `translate(${width / 2}, ${height / 2 - 12})`)
const color = d3.scaleOrdinal().domain(data.map(d => d.label)).range(['#3b82f6', '#10b981'])
const arcs = d3.pie().value(d => d.value).sort(null)(data)
const arc = d3.arc().innerRadius(0).outerRadius(radius)
g.selectAll('path').data(arcs).join('path').attr('d', arc).attr('fill', d => color(d.data.label))
}, [])
return <div ref={containerRef} />
}
Doughnut Chart
import React, { useEffect, useRef } from 'react'
import * as d3 from 'd3'
function DoughnutChart() {
const containerRef = useRef(null)
useEffect(() => {
const data = [ { label: 'North', value: 35 }, { label: 'South', value: 20 } ]
const width = 640, height = 360
const radius = Math.min(width, height) / 2 - 16
const root = d3.select(containerRef.current)
root.selectAll('*').remove()
const svg = root.append('svg').attr('viewBox', `0 0 ${width} ${height}`).style('max-width', '100%').style('height', 'auto')
const g = svg.append('g').attr('transform', `translate(${width / 2}, ${height / 2})`)
const color = d3.scaleOrdinal().domain(data.map(d => d.label)).range(['#3b82f6', '#10b981'])
const arcs = d3.pie().value(d => d.value).sort(null)(data)
const arc = d3.arc().innerRadius(radius * 0.6).outerRadius(radius)
g.selectAll('path').data(arcs).join('path').attr('d', arc).attr('fill', d => color(d.data.label))
}, [])
return <div ref={containerRef} />
}
Tips
- Use
d3.pie
to compute arc angles andd3.arc
to render slices. - For doughnuts, set
innerRadius
to 50–70% of theouterRadius
. - Add a center label to communicate totals or key metrics.
When to Use Pie vs. Doughnut Charts
Pie and doughnut charts represent parts of a whole at a single point in time. They shine when you have a small number of categories (ideally 3–6) and the goal is to communicate composition quickly. A doughnut chart is a pie chart with a hole; it provides space for a center label (such as the total) and often improves legibility by reducing clutter near the center. If your data contains many tiny categories or if exact comparisons are crucial, prefer a bar chart. Bars are easier to sort and compare precisely, while pies rely on angle and area, which are harder to judge visually.
Data Modeling and Percentages
Model data as { label, value }
. To display percentages, compute the total and use value / total
. Format with d3.format('.0%')
or d3.format('.1%')
to control rounding. D3’s pie layout returns arcs with start and end angles but does not round for you—only round for display to avoid mismatches between labels and slice geometry. If categories can overlap (e.g., respondents selecting multiple choices), a pie chart may mislead because it implies a complete partition of a whole. In those cases, pick a different visualization (like stacked bars or grouped bars) that better encodes overlaps.
Labeling Strategies
Labels are often the hardest part of pie/doughnut charts. Common approaches include:
- Inside labels placed at arc centroids; best when slices are large enough.
- Outside labels with leader lines; useful when slices are small.
- Legends placed below or to the side; helpful for dense categories or responsive layouts.
If labels collide, reduce font size slightly, abbreviate category names, move labels outside with lines, or show labels on hover to keep the base chart clean while retaining a clear legend for reference.
Responsiveness and Layout
Use an SVG viewBox
so the chart scales inside responsive containers. On small screens, slightly reduce outer radius and increase vertical padding for labels and legends. Consider changing the labeling approach at mobile breakpoints (e.g., legend-only labels). Keep minimum touch target sizes in mind if slices trigger interactions or navigation.
Interactivity and Tooltips
Thoughtful interactivity increases comprehension without distraction:
- Highlight the hovered slice and show a tooltip with raw value and percentage.
- Allow toggling categories via the legend to focus attention on specific segments.
- For doughnuts, use the center label to display the hovered slice’s label/value for compact, glanceable feedback.
Limit animation to brief, purposeful transitions (on load or hover). Avoid exaggerated easing or “exploding” effects unless they communicate meaning.
Color and Accessibility
Choose a categorical palette with good contrast. Do not rely on color alone—pair with text labels or tooltips. Provide <title>
elements for screen readers and ensure sufficient contrast ratios. If categories are critical and must be distinguishable in print or for users with color-vision deficiencies, consider adding patterns or textures. Limit the number of distinct colors; group minor categories under an “Other” slice when necessary and document the grouping rule.
Performance Notes
Pie and doughnut charts typically render a small number of arcs and are fast by default. If you update data frequently, update the pie layout and arc paths instead of recreating the entire SVG to minimize DOM churn. In dashboards with many charts, memoize color scales and arc generators to avoid repeated allocations on every render.
Common Pitfalls
- Too many slices: readability drops beyond 6–8 categories.
- 3D pies and heavy gradients: distort areas and harm accuracy.
- Unsorted categories: consider sorting by value or a logical order (alphabetic, domain-specific) to support quick scanning.
- Values that do not sum to a meaningful whole: use bars or another composition graphic to avoid implying completeness.
FAQs
How do I show percentage labels on slices? Compute value / total
and format with d3.format('.0%')
. Place inside with arc.centroid(d)
when slices are large; otherwise, position labels outside with leader lines.
How can I group small categories? Aggregate them into an “Other” slice to reduce clutter. Document the grouping rule in the legend or a footnote.
Can I transition between pie and doughnut? Yes—animate innerRadius
from 0 to a fraction of outerRadius
. Keep duration short (300–500ms) and easing subtle.