Skip to content

Commit 3793f9b

Browse files
feat(seaborn): implement donut-basic (#5337)
## Implementation: `donut-basic` - python/seaborn Implements the **python/seaborn** version of `donut-basic`. **File:** `plots/donut-basic/implementations/python/seaborn.py` **Parent Issue:** #733 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24873516282)* --------- 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 7eff378 commit 3793f9b

2 files changed

Lines changed: 248 additions & 155 deletions

File tree

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,87 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
donut-basic: Basic Donut Chart
3-
Library: seaborn 0.13.2 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: seaborn 0.13.2 | Python 3.14.4
4+
Quality: 86/100 | Updated: 2026-04-24
55
"""
66

7+
import os
8+
79
import matplotlib.pyplot as plt
10+
import numpy as np
811
import pandas as pd
912
import seaborn as sns
1013

1114

12-
# Data - Budget allocation by department
13-
data = pd.DataFrame(
15+
# Theme tokens
16+
THEME = os.getenv("ANYPLOT_THEME", "light")
17+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
18+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
19+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
20+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
21+
22+
# Okabe-Ito palette — first series always #009E73
23+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9"]
24+
25+
# Data — departmental budget allocation (ordered largest → smallest for readability)
26+
budget = pd.DataFrame(
1427
{
15-
"category": ["Marketing", "Engineering", "Operations", "Sales", "HR", "R&D"],
16-
"value": [25000, 45000, 18000, 32000, 12000, 28000],
28+
"department": ["Engineering", "Sales", "Operations", "Marketing", "R&D", "HR"],
29+
"amount": [520_000, 310_000, 240_000, 150_000, 95_000, 45_000],
1730
}
1831
)
32+
total = budget["amount"].sum()
33+
budget["share"] = budget["amount"] / total * 100
1934

20-
# Calculate percentages
21-
total = data["value"].sum()
22-
data["percentage"] = (data["value"] / total * 100).round(1)
23-
24-
# Set seaborn style
25-
sns.set_theme(style="white")
35+
# Theme — register Okabe-Ito as the default seaborn palette so wedge colors come from sns.color_palette
36+
sns.set_palette(sns.color_palette(OKABE_ITO))
37+
sns.set_theme(
38+
context="talk",
39+
style="white",
40+
palette=sns.color_palette(OKABE_ITO),
41+
rc={"figure.facecolor": PAGE_BG, "axes.facecolor": PAGE_BG, "text.color": INK},
42+
)
43+
wedge_colors = sns.color_palette(n_colors=len(budget)).as_hex()
2644

27-
# Create figure (square for symmetric donut)
28-
fig, ax = plt.subplots(figsize=(12, 12))
45+
# Plot — standard 3600x3600 square canvas (12in @ 300dpi)
46+
fig, ax = plt.subplots(figsize=(12, 12), facecolor=PAGE_BG)
47+
ax.set_facecolor(PAGE_BG)
2948

30-
# Define colors using seaborn color palette
31-
colors = sns.color_palette("Set2", n_colors=len(data))
49+
# Emphasize the dominant Engineering segment with a subtle explode offset
50+
explode = [0.03 if i == 0 else 0.0 for i in range(len(budget))]
3251

33-
# Create donut chart using matplotlib pie (seaborn is built on matplotlib)
34-
wedges, texts, autotexts = ax.pie(
35-
data["value"],
36-
labels=data["category"],
37-
autopct="%1.1f%%",
52+
wedges, _ = ax.pie(
53+
budget["amount"],
54+
colors=wedge_colors,
3855
startangle=90,
39-
colors=colors,
40-
wedgeprops={"width": 0.5, "edgecolor": "white", "linewidth": 2},
41-
pctdistance=0.75,
42-
labeldistance=1.15,
56+
counterclock=False,
57+
explode=explode,
58+
wedgeprops={"width": 0.38, "edgecolor": PAGE_BG, "linewidth": 3},
4359
)
4460

45-
# Style the text for large canvas
46-
for text in texts:
47-
text.set_fontsize(20)
48-
text.set_fontweight("medium")
49-
50-
for autotext in autotexts:
51-
autotext.set_fontsize(16)
52-
autotext.set_fontweight("bold")
53-
autotext.set_color("white")
61+
# External two-line labels (department + share) placed along wedge bisector.
62+
# Small segments get a larger radius to prevent crowding near the top.
63+
for wedge, dept, share in zip(wedges, budget["department"], budget["share"], strict=True):
64+
angle = np.deg2rad((wedge.theta2 + wedge.theta1) / 2)
65+
radius = 1.28 if share < 8 else 1.18
66+
x_label = radius * np.cos(angle)
67+
y_label = radius * np.sin(angle)
68+
ha = "left" if np.cos(angle) >= 0 else "right"
69+
ax.text(x_label, y_label, f"{dept}\n{share:.1f}%", ha=ha, va="center", fontsize=20, color=INK, linespacing=1.3)
5470

55-
# Add center text with total
56-
center_text = f"Total\n${total:,.0f}"
57-
ax.text(0, 0, center_text, ha="center", va="center", fontsize=28, fontweight="bold", color="#333333")
71+
# Center metric: label, thin separator, and bold total
72+
ax.text(0, 0.14, "Total Budget", ha="center", va="center", fontsize=20, color=INK_SOFT)
73+
ax.plot([-0.12, 0.12], [0.02, 0.02], color=INK_SOFT, linewidth=1.2, solid_capstyle="round")
74+
ax.text(0, -0.14, f"${total / 1_000_000:.2f}M", ha="center", va="center", fontsize=44, fontweight="bold", color=INK)
5875

5976
# Title
60-
ax.set_title("donut-basic · seaborn · pyplots.ai", fontsize=24, fontweight="bold", pad=20)
77+
ax.set_title(
78+
"FY2026 Budget Allocation · donut-basic · seaborn · anyplot.ai", fontsize=24, fontweight="medium", color=INK, pad=28
79+
)
6180

62-
# Equal aspect ratio for circular shape
6381
ax.set_aspect("equal")
82+
ax.set_xlim(-1.5, 1.5)
83+
ax.set_ylim(-1.5, 1.5)
84+
ax.set_axis_off()
6485

6586
plt.tight_layout()
66-
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
87+
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)

0 commit comments

Comments
 (0)