Skip to content

Commit c93f260

Browse files
feat(altair): implement donut-basic (#5340)
## Implementation: `donut-basic` - python/altair Implements the **python/altair** version of `donut-basic`. **File:** `plots/donut-basic/implementations/python/altair.py` **Parent Issue:** #733 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24873943198)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 0d053f1 commit c93f260

2 files changed

Lines changed: 239 additions & 158 deletions

File tree

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,85 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
donut-basic: Basic Donut Chart
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-23
3+
Library: altair 6.1.0 | Python 3.14.4
4+
Quality: 91/100 | Updated: 2026-04-24
55
"""
66

7-
import altair as alt
8-
import pandas as pd
7+
import importlib
8+
import os
9+
import sys
910

1011

11-
# Data - Budget allocation by department
12+
# Drop script directory from sys.path so the `altair` package resolves, not this file
13+
sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != os.path.dirname(os.path.abspath(__file__))]
14+
alt = importlib.import_module("altair")
15+
pd = importlib.import_module("pandas")
16+
17+
THEME = os.getenv("ANYPLOT_THEME", "light")
18+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
19+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
20+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
21+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
22+
23+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00"]
24+
1225
data = pd.DataFrame(
13-
{"category": ["Marketing", "Development", "Operations", "Sales", "Support"], "value": [28, 35, 18, 12, 7]}
26+
{"category": ["Engineering", "Marketing", "Operations", "Sales", "Support"], "value": [480, 210, 155, 125, 55]}
1427
)
1528

16-
# Calculate percentages for labels
17-
total = data["value"].sum()
29+
total = int(data["value"].sum())
1830
data["percentage"] = (data["value"] / total * 100).round(1)
1931
data["label"] = data["percentage"].astype(str) + "%"
2032

21-
# Create donut chart using arc mark
22-
chart = (
33+
arc = (
2334
alt.Chart(data)
24-
.mark_arc(innerRadius=120, outerRadius=280, stroke="white", strokeWidth=3)
35+
.mark_arc(innerRadius=260, outerRadius=520, stroke=PAGE_BG, strokeWidth=4)
2536
.encode(
2637
theta=alt.Theta(field="value", type="quantitative", stack=True),
2738
color=alt.Color(
2839
field="category",
2940
type="nominal",
30-
scale=alt.Scale(
31-
domain=["Marketing", "Development", "Operations", "Sales", "Support"],
32-
range=["#306998", "#FFD43B", "#4B8BBE", "#646464", "#8FBC8F"],
41+
scale=alt.Scale(domain=list(data["category"]), range=OKABE_ITO),
42+
legend=alt.Legend(
43+
title="Department", titleFontSize=22, labelFontSize=18, orient="right", symbolSize=400, padding=16
3344
),
34-
legend=alt.Legend(title="Category", titleFontSize=20, labelFontSize=16, orient="right", symbolSize=300),
3545
),
46+
order=alt.Order(field="value", sort="descending"),
3647
tooltip=[
37-
alt.Tooltip("category:N", title="Category"),
38-
alt.Tooltip("value:Q", title="Budget ($M)"),
39-
alt.Tooltip("percentage:Q", title="Percentage", format=".1f"),
48+
alt.Tooltip("category:N", title="Department"),
49+
alt.Tooltip("value:Q", title="Budget ($K)"),
50+
alt.Tooltip("percentage:Q", title="Share (%)", format=".1f"),
4051
],
4152
)
42-
.properties(
43-
width=1600, height=900, title=alt.Title(text="donut-basic · altair · pyplots.ai", fontSize=28, anchor="middle")
44-
)
4553
)
4654

47-
# Add percentage labels on segments
48-
text = (
55+
labels = (
4956
alt.Chart(data)
50-
.mark_text(radius=200, fontSize=20, fontWeight="bold")
57+
.mark_text(radius=390, fontSize=22, fontWeight="bold", color="#FFFFFF")
5158
.encode(
5259
theta=alt.Theta(field="value", type="quantitative", stack=True),
60+
order=alt.Order(field="value", sort="descending"),
5361
text=alt.Text("label:N"),
54-
color=alt.value("white"),
5562
)
5663
)
5764

58-
# Add center text showing total
59-
center_text = (
60-
alt.Chart(pd.DataFrame({"text": [f"Total: ${total}M"]}))
61-
.mark_text(fontSize=36, fontWeight="bold", color="#306998")
62-
.encode(text="text:N")
65+
center = (
66+
alt.Chart(pd.DataFrame({"line": ["Total budget", f"${total:,}K"], "y": [0.08, -0.08]}))
67+
.mark_text(fontSize=36, fontWeight="bold", color=INK, align="center")
68+
.encode(y=alt.Y("y:Q", axis=None, scale=alt.Scale(domain=[-1, 1])), text="line:N")
6369
)
6470

65-
# Combine layers
66-
final_chart = alt.layer(chart, text, center_text).configure_view(strokeWidth=0)
71+
final_chart = (
72+
alt.layer(arc, labels, center)
73+
.properties(
74+
width=1200,
75+
height=1200,
76+
background=PAGE_BG,
77+
title=alt.Title(text="donut-basic · altair · anyplot.ai", fontSize=28, anchor="middle", color=INK, offset=20),
78+
padding={"left": 40, "right": 40, "top": 20, "bottom": 20},
79+
)
80+
.configure_view(fill=PAGE_BG, stroke=None)
81+
.configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK, cornerRadius=6)
82+
)
6783

68-
# Save outputs
69-
final_chart.save("plot.png", scale_factor=3.0)
70-
final_chart.save("plot.html")
84+
final_chart.save(f"plot-{THEME}.png", scale_factor=3.0)
85+
final_chart.save(f"plot-{THEME}.html")

0 commit comments

Comments
 (0)