The Kit/Components

Donut

Proportional donut / ring chart from summed segment values. Pure geometry + colour props; center is free for a label.

loading…
Donut.tsx
import * as React from "react";

export interface DonutSegment {
  value: number;
  color: string;
}

export interface DonutProps {
  segments: DonutSegment[];
  /** px size of the square SVG. */
  size?: number;
  /** ring thickness in viewBox units (viewBox is 120). */
  thickness?: number;
  trackColor?: string;
  className?: string;
  children?: React.ReactNode;
}

/**
 * Generic donut/ring chart. Segments are drawn proportionally to their summed value.
 * Pure geometry + colour props — design-agnostic.
 */
export function Donut({
  segments,
  size = 106,
  thickness = 15,
  trackColor = "rgba(255,255,255,0.06)",
  className,
}: DonutProps) {
  const r = 60 - thickness / 2 - 2;
  const C = 2 * Math.PI * r;
  const total = segments.reduce((s, x) => s + Math.max(0, x.value), 0) || 1;
  let offset = 0;
  return (
    <svg className={className} width={size} height={size} viewBox="0 0 120 120" style={{ flex: "0 0 auto" }}>
      <circle cx="60" cy="60" r={r} fill="none" stroke={trackColor} strokeWidth={thickness} />
      {segments.map((seg, i) => {
        const frac = Math.max(0, seg.value) / total;
        const len = frac * C;
        const el = (
          <circle
            key={i}
            cx="60"
            cy="60"
            r={r}
            fill="none"
            stroke={seg.color}
            strokeWidth={thickness}
            strokeDasharray={`${len} ${C - len}`}
            strokeDashoffset={-offset}
            transform="rotate(-90 60 60)"
          />
        );
        offset += len;
        return el;
      })}
    </svg>
  );
}
← The whole kit