|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | line-loss-training: Training Loss Curve |
3 | | -Library: pygal 3.1.0 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-31 |
| 3 | +Library: pygal 3.1.0 | Python 3.13.13 |
| 4 | +Quality: 92/100 | Updated: 2026-05-14 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
7 | 10 | import numpy as np |
8 | | -import pygal |
9 | | -from pygal.style import Style |
10 | 11 |
|
11 | 12 |
|
| 13 | +# Remove local directory from sys.path to avoid shadowing the pygal package |
| 14 | +_local_dir = os.path.abspath(os.path.dirname(__file__)) |
| 15 | +sys.path = [p for p in sys.path if os.path.abspath(p) != _local_dir] |
| 16 | + |
| 17 | +import pygal # noqa: E402 |
| 18 | +from pygal.style import Style # noqa: E402 |
| 19 | + |
| 20 | + |
| 21 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 22 | + |
| 23 | +# Theme tokens (from default-style-guide.md) |
| 24 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 25 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 26 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 27 | + |
| 28 | +# Okabe-Ito palette (first series = brand green #009E73) |
| 29 | +OKABE_ITO = ("#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9", "#F0E442") |
| 30 | + |
12 | 31 | # Data: Simulated training loss curves showing typical overfitting behavior |
13 | 32 | np.random.seed(42) |
14 | 33 | epochs = np.arange(1, 51) |
15 | 34 |
|
16 | 35 | # Training loss: Steadily decreasing with some noise |
17 | 36 | train_loss = 2.5 * np.exp(-0.08 * epochs) + 0.1 + np.random.normal(0, 0.02, len(epochs)) |
18 | 37 |
|
19 | | -# Validation loss: Decreases then increases (overfitting after epoch ~30) |
| 38 | +# Validation loss: Decreases then increases (overfitting after epoch ~25) |
20 | 39 | val_loss = 2.3 * np.exp(-0.07 * epochs) + 0.15 + 0.003 * np.maximum(0, epochs - 25) ** 1.5 |
21 | 40 | val_loss += np.random.normal(0, 0.03, len(epochs)) |
22 | 41 |
|
23 | 42 | # Find minimum validation loss epoch for annotation |
24 | 43 | min_val_epoch = int(epochs[np.argmin(val_loss)]) |
25 | 44 | min_val_loss = float(np.min(val_loss)) |
26 | 45 |
|
27 | | -# Custom style for large canvas |
| 46 | +# Custom theme-adaptive style |
28 | 47 | custom_style = Style( |
29 | | - background="white", |
30 | | - plot_background="white", |
31 | | - foreground="#333333", |
32 | | - foreground_strong="#333333", |
33 | | - foreground_subtle="#666666", |
34 | | - colors=("#306998", "#FFD43B"), # Python Blue, Python Yellow |
35 | | - title_font_size=48, |
36 | | - label_font_size=36, |
37 | | - major_label_font_size=32, |
38 | | - legend_font_size=32, |
39 | | - value_font_size=28, |
40 | | - stroke_width=4, |
41 | | - opacity=0.9, |
42 | | - opacity_hover=1.0, |
| 48 | + background=PAGE_BG, |
| 49 | + plot_background=PAGE_BG, |
| 50 | + foreground=INK, |
| 51 | + foreground_strong=INK, |
| 52 | + foreground_subtle=INK_MUTED, |
| 53 | + colors=OKABE_ITO, |
| 54 | + title_font_size=28, |
| 55 | + label_font_size=22, |
| 56 | + major_label_font_size=18, |
| 57 | + legend_font_size=16, |
| 58 | + value_font_size=14, |
| 59 | + stroke_width=3, |
43 | 60 | ) |
44 | 61 |
|
45 | 62 | # Create line chart |
46 | 63 | chart = pygal.Line( |
47 | 64 | width=4800, |
48 | 65 | height=2700, |
49 | 66 | style=custom_style, |
50 | | - title="line-loss-training · pygal · pyplots.ai", |
| 67 | + title="line-loss-training · pygal · anyplot.ai", |
51 | 68 | x_title="Epoch", |
52 | 69 | y_title="Cross-Entropy Loss", |
53 | | - show_x_guides=False, |
| 70 | + show_x_guides=True, |
54 | 71 | show_y_guides=True, |
55 | 72 | dots_size=6, |
56 | | - stroke_style={"width": 4}, |
| 73 | + stroke_style={"width": 3}, |
57 | 74 | legend_at_bottom=False, |
58 | 75 | legend_box_size=24, |
59 | | - margin=50, |
| 76 | + margin=80, |
60 | 77 | x_label_rotation=0, |
61 | 78 | truncate_label=-1, |
62 | 79 | show_dots=True, |
|
69 | 86 | chart.add("Training Loss", list(train_loss)) |
70 | 87 | chart.add("Validation Loss", list(val_loss)) |
71 | 88 |
|
72 | | -# Save as PNG and HTML |
73 | | -chart.render_to_png("plot.png") |
74 | | -chart.render_to_file("plot.html") |
| 89 | +# Save as PNG and HTML with theme suffix |
| 90 | +chart.render_to_png(f"plot-{THEME}.png") |
| 91 | +with open(f"plot-{THEME}.html", "wb") as f: |
| 92 | + f.write(chart.render()) |
0 commit comments