|
1 | 1 | """ anyplot.ai |
2 | 2 | radar-multi: Multi-Series Radar Chart |
3 | | -Library: pygal 3.1.0 | Python 3.13.13 |
4 | | -Quality: 86/100 | Updated: 2026-05-07 |
| 3 | +Library: pygal 3.1.3 | Python 3.13.15 |
| 4 | +Quality: 90/100 | Updated: 2026-08-17 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import pygal |
8 | 10 | from pygal.style import Style |
9 | 11 |
|
10 | 12 |
|
| 13 | +# Theme tokens (see prompts/default-style-guide.md "Background" + "Theme-adaptive Chrome") |
| 14 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 15 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 16 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 17 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 18 | + |
| 19 | +# Imprint categorical palette — position 1 is always the brand green |
| 20 | +IMPRINT_PALETTE = ("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314") |
| 21 | + |
11 | 22 | # Data - Team performance comparison across skill dimensions |
12 | 23 | categories = ["Communication", "Technical", "Leadership", "Creativity", "Teamwork", "Problem Solving"] |
13 | | -teams = { |
14 | | - "Alpha Team": [85, 90, 75, 80, 88, 82], |
15 | | - "Beta Team": [78, 72, 85, 90, 70, 88], |
16 | | - "Gamma Team": [65, 88, 70, 75, 92, 78], |
17 | | -} |
| 24 | +teams = [ |
| 25 | + ("Alpha Team", [85, 90, 75, 80, 88, 82]), |
| 26 | + ("Beta Team", [78, 72, 85, 90, 70, 88]), |
| 27 | + ("Gamma Team", [65, 88, 70, 75, 92, 78]), |
| 28 | +] |
18 | 29 |
|
19 | | -# Custom style for 3600x3600 canvas (square for radar) |
| 30 | +# Highest overall average becomes the visual "hero" series — a bolder, solid |
| 31 | +# stroke and larger dots build a focal point so the viewer immediately spots |
| 32 | +# the top all-round team, while the other two recede behind a thinner dashed |
| 33 | +# outline. |
| 34 | +hero_name, _ = max(teams, key=lambda t: sum(t[1]) / len(t[1])) |
| 35 | + |
| 36 | +# Style — theme-adaptive chrome, Imprint palette, sizing tuned for the 2400x2400 canvas |
20 | 37 | custom_style = Style( |
21 | | - background="white", |
22 | | - plot_background="white", |
23 | | - foreground="#333333", |
24 | | - foreground_strong="#333333", |
25 | | - foreground_subtle="#666666", |
26 | | - colors=("#306998", "#FFD43B", "#E74C3C"), |
27 | | - title_font_size=72, |
28 | | - label_font_size=42, |
29 | | - major_label_font_size=36, |
30 | | - legend_font_size=42, |
31 | | - value_font_size=32, |
| 38 | + font_family='"Liberation Sans", "DejaVu Sans", Arial, sans-serif', |
| 39 | + background=PAGE_BG, |
| 40 | + plot_background=PAGE_BG, |
| 41 | + foreground=INK, |
| 42 | + foreground_strong=INK, |
| 43 | + foreground_subtle=INK_MUTED, |
| 44 | + colors=IMPRINT_PALETTE, |
| 45 | + title_font_size=66, |
| 46 | + label_font_size=56, |
| 47 | + major_label_font_size=44, |
| 48 | + legend_font_size=44, |
| 49 | + value_font_size=36, |
32 | 50 | opacity=0.25, |
33 | 51 | opacity_hover=0.5, |
34 | 52 | ) |
35 | 53 |
|
36 | | -# Create radar chart |
| 54 | +# Plot — square canvas for the symmetric radar layout. Legend moved to the |
| 55 | +# bottom (rather than the default left column) so the polar plot area is no |
| 56 | +# longer squeezed toward the top-right of the canvas, balancing whitespace. |
37 | 57 | chart = pygal.Radar( |
38 | | - width=3600, |
39 | | - height=3600, |
| 58 | + width=2400, |
| 59 | + height=2400, |
40 | 60 | style=custom_style, |
41 | | - title="radar-multi · pygal · pyplots.ai", |
| 61 | + title="radar-multi · python · pygal · anyplot.ai", |
42 | 62 | show_legend=True, |
43 | | - legend_at_bottom=False, |
44 | | - legend_box_size=30, |
45 | | - dots_size=12, |
46 | | - stroke_style={"width": 5}, |
| 63 | + legend_at_bottom=True, |
| 64 | + legend_at_bottom_columns=3, |
47 | 65 | show_dots=True, |
48 | 66 | fill=True, |
49 | 67 | inner_radius=0.1, |
50 | 68 | range=(0, 100), |
| 69 | + margin=30, |
| 70 | + spacing=16, |
51 | 71 | ) |
52 | | - |
53 | | -# Set axis labels |
54 | 72 | chart.x_labels = categories |
55 | 73 |
|
56 | | -# Add data series |
57 | | -for team_name, values in teams.items(): |
58 | | - chart.add(team_name, values) |
| 74 | +# Add data series — hero team gets a bold solid stroke + larger dots, the |
| 75 | +# other two recede behind a thinner dashed outline. |
| 76 | +for team_name, values in teams: |
| 77 | + is_hero = team_name == hero_name |
| 78 | + chart.add( |
| 79 | + team_name, |
| 80 | + values, |
| 81 | + dots_size=12 if is_hero else 7, |
| 82 | + stroke_style={"width": 6, "linecap": "round", "linejoin": "round"} |
| 83 | + if is_hero |
| 84 | + else {"width": 2.5, "linecap": "round", "linejoin": "round", "dasharray": "12,6"}, |
| 85 | + ) |
59 | 86 |
|
60 | | -# Save outputs |
61 | | -chart.render_to_png("plot.png") |
62 | | -chart.render_to_file("plot.html") |
| 87 | +# Save |
| 88 | +chart.render_to_png(f"plot-{THEME}.png") |
| 89 | +with open(f"plot-{THEME}.html", "wb") as f: |
| 90 | + f.write(chart.render()) |
0 commit comments