|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | | -import altair as alt |
8 | | -import pandas as pd |
| 7 | +import importlib |
| 8 | +import os |
| 9 | +import sys |
9 | 10 |
|
10 | 11 |
|
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 | + |
12 | 25 | 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]} |
14 | 27 | ) |
15 | 28 |
|
16 | | -# Calculate percentages for labels |
17 | | -total = data["value"].sum() |
| 29 | +total = int(data["value"].sum()) |
18 | 30 | data["percentage"] = (data["value"] / total * 100).round(1) |
19 | 31 | data["label"] = data["percentage"].astype(str) + "%" |
20 | 32 |
|
21 | | -# Create donut chart using arc mark |
22 | | -chart = ( |
| 33 | +arc = ( |
23 | 34 | 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) |
25 | 36 | .encode( |
26 | 37 | theta=alt.Theta(field="value", type="quantitative", stack=True), |
27 | 38 | color=alt.Color( |
28 | 39 | field="category", |
29 | 40 | 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 |
33 | 44 | ), |
34 | | - legend=alt.Legend(title="Category", titleFontSize=20, labelFontSize=16, orient="right", symbolSize=300), |
35 | 45 | ), |
| 46 | + order=alt.Order(field="value", sort="descending"), |
36 | 47 | 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"), |
40 | 51 | ], |
41 | 52 | ) |
42 | | - .properties( |
43 | | - width=1600, height=900, title=alt.Title(text="donut-basic · altair · pyplots.ai", fontSize=28, anchor="middle") |
44 | | - ) |
45 | 53 | ) |
46 | 54 |
|
47 | | -# Add percentage labels on segments |
48 | | -text = ( |
| 55 | +labels = ( |
49 | 56 | alt.Chart(data) |
50 | | - .mark_text(radius=200, fontSize=20, fontWeight="bold") |
| 57 | + .mark_text(radius=390, fontSize=22, fontWeight="bold", color="#FFFFFF") |
51 | 58 | .encode( |
52 | 59 | theta=alt.Theta(field="value", type="quantitative", stack=True), |
| 60 | + order=alt.Order(field="value", sort="descending"), |
53 | 61 | text=alt.Text("label:N"), |
54 | | - color=alt.value("white"), |
55 | 62 | ) |
56 | 63 | ) |
57 | 64 |
|
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") |
63 | 69 | ) |
64 | 70 |
|
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 | +) |
67 | 83 |
|
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