Pie chart

_images/light-donut.svg _images/dark-donut.svg
  1. Load data

# Source : https://observablehq.com/@d3/donut-chart/2
import polars as pl # for data manipulation
import detroit as d3

URL = (
    "https://static.observableusercontent.com/files/bee673b386dd058ab8d2cf353acbedc6aa0"
    "1ebd1e6f63e2a9ab1b4273c7e6efd1eeea526345e4be7f0012d5db3ec743ef39ad9e6a043c196670bf"
    "9658cb02e79?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27popul"
    "ation-by-age.csv"
)

data = pl.read_csv(URL)
shape: (18, 2)
┌───────┬──────────┐
│ name  ┆ value    │
│ ---   ┆ ---      │
│ str   ┆ i64      │
╞═══════╪══════════╡
│ <5    ┆ 19912018 │
│ 5-9   ┆ 20501982 │
│ 10-14 ┆ 20679786 │
│ 15-19 ┆ 21354481 │
│ 20-24 ┆ 22604232 │
│ …     ┆ …        │
│ 65-69 ┆ 14547446 │
│ 70-74 ┆ 10587721 │
│ 75-79 ┆ 7730129  │
│ 80-84 ┆ 5811429  │
│ ≥85   ┆ 5938752  │
└───────┴──────────┘
  1. Make the pie chart

# Declare the chart dimensions.
width = 500
height = min(width, 500)
radius = min(width, height) / 2

# Declare the arc generator with its dimensions.
arc = d3.arc().set_inner_radius(radius * 0.67).set_outer_radius(radius - 1)

# Declare pie generator with its dimensions.
pie = d3.pie().set_pad_angle(1 / radius).set_sort(None).set_value(lambda d: d[1])

# Declare color scale.
color = (
    d3.scale_ordinal()
    .set_domain(data["name"].to_list())
    .set_range(
        d3.quantize(
            lambda t: d3.interpolate_spectral(t * 0.8 + 0.1),
            data.height,
        )[::-1]
    )
)

# Create the SVG container.
svg = (
    d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", f"{-width / 2} {-height / 2} {width} {height}")
    .attr("style", "max-width: 100% height: auto;")
)

# Generate pies and add arcs to the chart.
(
    svg.append("g")
    .select_all()
    .data(pie(data.iter_rows()))
    .join("path")
    .attr("fill", lambda d: color(d["data"][0]))
    .attr("d", arc)
    .append("title")
    .text(lambda d: f"{d['data'][0]}: {d['data'][1]}")
)

# Generate pies and add text on them.
(
    svg.append("g")
    .attr("font-family", "sans-serif")
    .attr("font-size", 12)
    .attr("text-anchor", "middle")
    .select_all()
    .data(pie(data.iter_rows()))
    .join("g")
    .attr(
         "transform", lambda d: f"translate({arc.centroid(d)[0]}, {arc.centroid(d)[1]})"
    )
    .call(
        lambda g: (
            g.append("text")
            .append("tspan")
            .attr("y", "-0.4em")
            .attr("font-weight", "bold")
            .text(lambda d: d["data"][0])
        )
    )
    .call(
        lambda g: (
            g.filter(lambda d: (d["end_angle"] - d["start_angle"]) > 0.25)
            .select_all("text")
            .append("tspan")
            .attr("x", 0)
            .attr("y", "0.7em")
            .attr("fill-opacity", 0.7)
            .text(lambda d: str(d["data"][1]))
        )
    )
)
  1. Save your chart

with open("donut.svg", "w") as file:
    file.write(str(svg))