Basic Donut Chart — D3.js

A donut chart (ring chart) showing proportions of categorical data as segments of a ring, with a hollow center. Similar to pie charts but the empty center can display summary statistics or additional information. The ring format can be easier to read when comparing segment sizes.

Basic Donut Chart rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// donut-basic: Basic Donut Chart
// Library: d3 7.9.0 | JavaScript 22.22.3
// Quality: 90/100 | Created: 2026-06-25

//# anyplot-orientation: square

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;

// Choose readable text color for a given fill hex (maximizes contrast)
function labelColor(hex) {
  const r = parseInt(hex.slice(1, 3), 16) / 255;
  const g = parseInt(hex.slice(3, 5), 16) / 255;
  const b = parseInt(hex.slice(5, 7), 16) / 255;
  const lin = (v) => (v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);
  const L = 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
  return L > 0.179 ? "#1A1A17" : "#F0EFE8";
}

// --- Data: Annual Budget Allocation by Department ---
const segments = [
  { label: "Product Dev",      value: 34 },
  { label: "Marketing",        value: 23 },
  { label: "Operations",       value: 17 },
  { label: "Customer Success", value: 13 },
  { label: "HR & People",      value: 8 },
  { label: "Finance & Legal",  value: 5 },
];

// --- Layout ---
const titleH     = 70;
const legendRowH = 46;
const legendPad  = 36;
const legendH    = 2 * legendRowH + legendPad;
const chartTop   = titleH + 28;
const chartH     = height - chartTop - legendH;
const cx         = width / 2;
const cy         = chartTop + chartH / 2;
const outerRadius = (Math.min(chartH, width) / 2) * 0.86;
const innerRadius = outerRadius * 0.54;

// --- SVG root ---
const svg = d3
  .select("#container")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

// --- Pie + arc generators ---
const pie = d3.pie().value((d) => d.value).sort(null).padAngle(0.022);

const arc = d3.arc()
  .innerRadius(innerRadius)
  .outerRadius(outerRadius)
  .cornerRadius(5);

const midArc = d3.arc()
  .innerRadius(innerRadius + (outerRadius - innerRadius) * 0.52)
  .outerRadius(innerRadius + (outerRadius - innerRadius) * 0.52);

const slices = pie(segments);
const g = svg.append("g").attr("transform", `translate(${cx},${cy})`);

// --- Donut segments ---
g.selectAll("path")
  .data(slices)
  .join("path")
  .attr("d", arc)
  .attr("fill", (d, i) => t.palette[i])
  .attr("stroke", t.pageBg)
  .attr("stroke-width", 3);

// --- Percentage labels inside each segment ---
g.selectAll(".pct")
  .data(slices)
  .join("text")
  .attr("class", "pct")
  .attr("transform", (d) => `translate(${midArc.centroid(d)})`)
  .attr("text-anchor", "middle")
  .attr("dominant-baseline", "middle")
  .attr("fill", (d, i) => labelColor(t.palette[i]))
  .style("font-size", (d) => (d.data.value >= 10 ? "17px" : "13px"))
  .style("font-weight", "600")
  .text((d) => `${d.data.value}%`);

// --- Center annotation ---
g.append("text")
  .attr("text-anchor", "middle")
  .attr("y", -16)
  .attr("fill", t.inkSoft)
  .style("font-size", "15px")
  .text("Annual Budget");

g.append("text")
  .attr("text-anchor", "middle")
  .attr("y", 22)
  .attr("fill", t.ink)
  .style("font-size", "30px")
  .style("font-weight", "700")
  .text("$2.4M");

// --- Title ---
svg
  .append("text")
  .attr("x", width / 2)
  .attr("y", 44)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", "22px")
  .style("font-weight", "600")
  .text("donut-basic · javascript · d3 · anyplot.ai");

// --- Legend: 2 rows × 3 columns ---
const swatchW   = 18;
const swatchGap = 10;
const legendW   = width * 0.75;
const colW      = legendW / 3;
const lgX0      = (width - legendW) / 2;
const lgY0      = height - legendH + 20;

segments.forEach((seg, i) => {
  const col = i % 3;
  const row = Math.floor(i / 3);
  const lx  = lgX0 + col * colW;
  const ly  = lgY0 + row * legendRowH;

  const item = svg.append("g").attr("transform", `translate(${lx},${ly})`);

  item
    .append("rect")
    .attr("width", swatchW)
    .attr("height", swatchW)
    .attr("rx", 4)
    .attr("fill", t.palette[i]);

  item
    .append("text")
    .attr("x", swatchW + swatchGap)
    .attr("y", swatchW / 2)
    .attr("dominant-baseline", "middle")
    .attr("fill", t.inkSoft)
    .style("font-size", "14px")
    .text(seg.label);
});

Part of Basic Donut Chart on anyplot.ai.

Other implementations