PieChart: Proportional Compositions & Donut Badges

PieChart (drawlib.charts.pie) visualizes proportional compositions where slices represent parts of a whole (100%). It supports classic solid pie charts, modern donut rings with center KPI badges, outward slice explosion, custom angular orientations, and decoupled legend rendering.

pie_1
Standard Solid Pie Chart vs. Donut Chart with Center KPI
Source Code
from drawlib.canvas import save, setup
from drawlib.charts.pie import PieChart
from drawlib.lines import line
from drawlib.styles import Styles
from drawlib.text import text

setup(width=132, height=58)

# Left: Solid Pie Chart (hole_ratio=0.0) with Exploded Slice
solid_pie = PieChart(
    radius=17.5,
    hole_ratio=0.0,
    start_angle=90.0,
    title="1. Solid Pie (hole_ratio=0.0)",
    title_style=Styles.BlackBold.patch(text_size=11.0),
    value_text_style=Styles.BlackBold.patch(text_size=10.0),
    value_format="{:.0f}%",
)
solid_pie.add_slice("Core Platform", 45.0, style=Styles.PrimaryFlat, explode=1.8)
solid_pie.add_slice("AI Services", 35.0, style=Styles.PrimaryNeutral)
solid_pie.add_slice("Operations", 20.0, style=Styles.SecondaryNeutral)
solid_pie.draw(xy=(4.0, 5.0))

# Callout for start_angle=90° (12 o'clock)
pie_w, pie_h = solid_pie.get_size()
pie_cx = 4.0 + pie_w / 2.0
pie_top_y = 5.0 + (pie_h - 6.0) / 2.0 + 17.5
line((pie_cx, pie_top_y + 4.2), (pie_cx, pie_top_y + 0.4), arrow_head="->", style=Styles.DarkBold)
text(
    (pie_cx + 1.8, pie_top_y + 2.8),
    "start_angle=90° (12 o'clock)",
    style=Styles.DarkBold.patch(text_size=10.0, halign="left"),
)

# Right: Donut Chart (hole_ratio=0.62) with Center KPI
donut_pie = PieChart(
    radius=17.5,
    hole_ratio=0.62,
    center_text="$1.2B\nARR",
    center_text_style=Styles.DarkBold.patch(text_size=12.0),
    title="2. Donut Chart (hole_ratio=0.62)",
    title_style=Styles.BlackBold.patch(text_size=11.0),
    value_text_style=Styles.BlackBold.patch(text_size=10.0),
    value_format="{:.0f}%",
)
donut_pie.add_slice("Core Platform", 45.0, style=Styles.PrimaryFlat)
donut_pie.add_slice("AI Services", 35.0, style=Styles.PrimaryNeutral)
donut_pie.add_slice("Operations", 20.0, style=Styles.SecondaryNeutral)
donut_pie.draw(xy=(58.0, 5.0))
donut_pie.draw_legend(xy=(100.0, 32.0), text_style=Styles.Dark.patch(text_size=10.0))

save()

1. Core Concepts & Basic Usage

PieChart builds proportional sector compositions via .add_slice(...), anchoring by default at the Bottom-Left (x, y) corner of its container box (or directly on the pie center when align="center"). You can morph solid pies into hollow donut rings (hole_ratio, center_text), radially offset focal wedges (explode) while automatically suppressing cramped < 4% inline labels, and animate angular sweeps (draw_ratio) or slot-preserving visibility (show=False):

pie_2
Basic Usage & Parameter Tour: Slices & Anchor, Donut Center KPI, Explode & <4% Rule, and Sweep & Align (APNG Animation)
  • Slices, Anchor & Legend (xy, add_slice, draw_legend): Register proportional wedges via .add_slice(name, value, style=...), render inline percentage badges (value_text_style, value_format), and pair chart.draw(xy=...) (default align="bottom_left") with chart.draw_legend(...).
  • Solid Pie vs. Donut Center KPI (hole_ratio, center_text): Keep hole_ratio=0.0 for full sectors or set hole_ratio in (0.0, 0.9] (typically 0.5–0.7) to open a donut ring for multiline center_text + center_text_style KPI badges.
  • Radial Explode, Orientation & < 4% Rule (explode, start_angle, clockwise): Offset focal wedges radially via explode > 0.0, customize radial orientation (start_angle, clockwise), and rely on automatic < 4% inline label suppression (while keeping minor slices visible in draw_legend()).
  • Partial Sweep, Visibility & Center Pinning (draw_ratio, show, align="center"): Animate angular or radial progress via draw_ratio (draw_direction), toggle slice.show = False without rescaling sibling angles, or pass align="center" to pin xy=(cx, cy) directly at the pie center.

2. Donut Chart with Center KPI Badge

Donut charts are ideal for executive dashboards. Ground supporting slices in calm neutral tokens (Styles.PrimaryNeutral, Styles.SecondaryNeutral, Styles.Neutral) while reserving Styles.PrimaryFlat for the primary focal slice:

from drawlib.canvas import save, setup
from drawlib.charts.pie import PieChart
from drawlib.styles import Styles

setup(width=102, height=78)

chart = PieChart(
    radius=24.0,
    hole_ratio=0.62,
    center_text="$1.2B\nARR",
    center_text_style=Styles.BlackBold.patch(text_size=12.0),
    title="Revenue Contribution by Product Line",
    title_style=Styles.BlackBold.patch(text_size=13.0),
    value_text_style=Styles.BlackBold.patch(text_size=10.5),
)
chart.add_slice("Cloud Infrastructure", 540.0, style=Styles.PrimaryFlat)
chart.add_slice("AI Developer Tools", 340.0, style=Styles.PrimaryNeutral)
chart.add_slice("Security Suite", 220.0, style=Styles.SecondaryNeutral)
chart.add_slice("Legacy Support", 100.0, style=Styles.Neutral)

chart.draw(xy=(8.0, 8.0))
chart.draw_legend(xy=(64.0, 48.0), text_style=Styles.Dark.patch(text_size=10.5))
save()
pie_3
Revenue Contribution Donut Chart

3. Exploded Slice Allocation & Callable Formatter

Setting explode > 0.0 radially displaces a slice away from the center origin. Here we also pass a callable value_format and include a minor 3% slice whose inline label is automatically suppressed by the < 4% threshold rule while remaining visible in the legend:

from drawlib.canvas import save, setup
from drawlib.charts.pie import PieChart
from drawlib.styles import Styles

setup(width=102, height=78)

chart = PieChart(
    radius=24.0,
    title="R&D Budget Allocation (2026)",
    title_style=Styles.BlackBold.patch(text_size=13.0),
    value_text_style=Styles.BlackBold.patch(text_size=10.5),
    value_format=lambda pct: f"{pct:.0f}%",
)
chart.add_slice("Generative AI Models", 45.0, style=Styles.PrimaryFlat, explode=2.8)
chart.add_slice("Core Infrastructure", 28.0, style=Styles.PrimaryNeutral)
chart.add_slice("DevOps & Tooling", 16.0, style=Styles.SecondaryNeutral)
chart.add_slice("Compliance & Audit", 8.0, style=Styles.Neutral)
chart.add_slice("Experimental Labs (3%)", 3.0, style=Styles.Muted)

chart.draw(xy=(8.0, 8.0))
chart.draw_legend(xy=(64.0, 50.0), text_style=Styles.Dark.patch(text_size=10.5))
save()
pie_4
Budget Allocation with Exploded Hero Slice and Callable Formatter

4. API Reference

Constructor (PieChart)

All constructor arguments are keyword-only (*):

from drawlib.charts.pie import DrawDirection, FormatterType, PieChart, Slice

chart = PieChart(
    *,
    radius: float = 20.0,
    hole_ratio: float = 0.0,
    start_angle: float = 90.0,
    clockwise: bool = True,
    value_text_style: Style | None = None,
    value_format: FormatterType = "{:.1f}%",
    center_text: str = "",
    center_text_style: Style | None = None,
    background_style: Style | None = None,
    title: str = "",
    title_style: Style | None = None,
    width: float | None = None,
    height: float | None = None,
)
Parameter Type Default Description
radius float 20.0 Outer radius of the pie circle in canvas units.
hole_ratio float 0.0 Inner hole radius ratio (0.0 for solid pie; clamped to [0.0, 0.9] for donut rings).
start_angle float 90.0 Starting radial angle in degrees (90.0 is 12 o'clock, 0.0 is 3 o'clock).
clockwise bool True When True, slices progress clockwise from start_angle; when False, counter-clockwise.
value_text_style Style | None None Style for percentage badges drawn inside wedges (>= 4% share). If None, badges are omitted.
value_format FormatterType "{:.1f}%" Format string or Callable[[float], str] receiving the slice's percentage (0.0–100.0).
center_text str "" Text rendered inside the donut hole (supports \n multiline strings; requires hole_ratio > 0).
center_text_style Style | None None Typography style for center_text. Required for center badge text to render.
background_style Style | None None Style for the outer chart background card. If None, card is omitted.
title str "" Chart title text rendered at the top of the container.
title_style Style | None None Typography style for title. Required for the title to render.
width / height float | None None Explicit container dimensions. If None, computed automatically from radius and title.

Methods & Properties

  • add_slice(name: str, value: float, style: Style, explode: float = 0.0, legend_text_style: Style | None = None, *, show: bool = True, draw_ratio: float = 1.0, draw_direction: DrawDirection = "left_to_right") -> Slice: Registers a proportional wedge and returns the mutable Slice instance.
    • explode: Outward radial offset distance along the slice's mid-angle (0.0 = flush with center).
    • legend_text_style: Optional custom Style for this slice's label in draw_legend().
    • show: Toggles slice visibility while preserving the total pie sum across all slices so remaining wedges do not rescale.
    • draw_ratio & draw_direction: Partial spatial rendering ("left_to_right" sweeps angularly across span * draw_ratio; "bottom_to_top" grows radially outward from the inner edge toward radius).
  • get_size() -> tuple[float, float]: Returns the computed (width, height) bounding box of the chart container.
  • draw(xy: tuple[float, float] = (0.0, 0.0), *, radius: float | None = None, width: float | None = None, height: float | None = None, align: Literal["bottom_left", "center"] = "bottom_left", scale: float = 1.0) -> None: Renders the chart anchored at xy ("bottom_left" by default or "center"), with optional temporary radius/width/height overrides and uniform scale.
  • draw_legend(xy: tuple[float, float], text_style: Style, orientation: Literal["vertical", "horizontal"] = "vertical", swatch_width: float = 2.4, swatch_height: float = 1.2, item_gap: float = 4.0, *, scale: float = 1.0) -> None: Renders the decoupled slice legend at xy.
  • Properties:
    • chart.slices -> list[Slice]: Returns a copy of registered Slice objects.