diff --git a/plots/line-loss-training/implementations/python/bokeh.py b/plots/line-loss-training/implementations/python/bokeh.py new file mode 100644 index 0000000000..ba1ca3071e --- /dev/null +++ b/plots/line-loss-training/implementations/python/bokeh.py @@ -0,0 +1,224 @@ +""" anyplot.ai +line-loss-training: Training Loss Curve +Library: bokeh 3.9.0 | Python 3.13.13 +Quality: 95/100 | Created: 2026-05-14 +""" + +import os +import sys +import time +from pathlib import Path + + +# Prevent script name from shadowing bokeh module +script_dir = str(Path(__file__).parent) +if script_dir in sys.path: + sys.path.remove(script_dir) + +import numpy as np # noqa: E402 +import pandas as pd # noqa: E402 +from bokeh.io import output_file, save # noqa: E402 +from bokeh.models import ColumnDataSource, HoverTool, Label, Quad # noqa: E402 +from bokeh.plotting import figure # noqa: E402 +from selenium import webdriver # noqa: E402 +from selenium.webdriver.chrome.options import Options # noqa: E402 + + +# Theme tokens (see prompts/default-style-guide.md) +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" + +# Okabe-Ito palette +TRAIN_COLOR = "#009E73" # bluish green - first series (brand) +VAL_COLOR = "#D55E00" # vermillion - second series + +# Generate realistic neural network training data +np.random.seed(42) +n_epochs = 150 + +# Training loss: smooth exponential decay with noise +epochs = np.arange(1, n_epochs + 1) +train_loss_base = 2.5 * np.exp(-0.015 * (epochs - 1)) + 0.15 +train_loss = train_loss_base + np.random.normal(0, 0.02, n_epochs) +train_loss = np.maximum(train_loss, 0.15) # ensure positive + +# Validation loss: slightly noisier, higher baseline, potential overfitting +val_loss_base = 2.5 * np.exp(-0.012 * (epochs - 1)) + 0.2 +val_loss = val_loss_base + np.random.normal(0, 0.035, n_epochs) +# Add slight overfitting effect in later epochs +val_loss[80:] += np.linspace(0, 0.08, n_epochs - 80) +val_loss = np.maximum(val_loss, 0.18) + +# Find minimum validation loss epoch (for annotation) +min_val_idx = np.argmin(val_loss) +min_val_epoch = epochs[min_val_idx] +min_val_loss = val_loss[min_val_idx] + +# Create DataFrame +df = pd.DataFrame({"epoch": epochs, "train_loss": train_loss, "val_loss": val_loss}) + +# Create Bokeh figure +title_text = "line-loss-training · bokeh · anyplot.ai" +p = figure( + width=4800, + height=2700, + title=title_text, + x_axis_label="Epoch", + y_axis_label="Loss (Cross-Entropy)", + toolbar_location="right", +) + +# Set up data sources +train_source = ColumnDataSource(df[["epoch", "train_loss"]]) +val_source = ColumnDataSource(df[["epoch", "val_loss"]]) + +# Create a shaded region to highlight potential overfitting area (after epoch 80) +overfitting_start = 80 +max_loss = max(df["val_loss"].max(), df["train_loss"].max()) +overfitting_quad = p.quad( + left=[overfitting_start], + right=[n_epochs], + bottom=[0], + top=[max_loss], + fill_alpha=0.08, + fill_color=VAL_COLOR, + line_color=None, + level="underlay", +) + +# Plot lines +train_line = p.line( + x="epoch", + y="train_loss", + source=train_source, + line_width=4, + color=TRAIN_COLOR, + legend_label="Training Loss", + muted_color=TRAIN_COLOR, + muted_alpha=0.15, +) + +val_line = p.line( + x="epoch", + y="val_loss", + source=val_source, + line_width=4, + color=VAL_COLOR, + legend_label="Validation Loss", + muted_color=VAL_COLOR, + muted_alpha=0.15, +) + +# Add circle markers at data points +p.scatter( + x="epoch", + y="train_loss", + source=train_source, + size=5, + color=TRAIN_COLOR, + alpha=0.6, + hover_color=TRAIN_COLOR, + hover_alpha=1.0, +) + +p.scatter( + x="epoch", + y="val_loss", + source=val_source, + size=5, + color=VAL_COLOR, + alpha=0.6, + hover_color=VAL_COLOR, + hover_alpha=1.0, +) + +# Mark the epoch with minimum validation loss - larger marker for emphasis +optimal_marker = p.scatter( + x=[min_val_epoch], + y=[min_val_loss], + size=20, + color=VAL_COLOR, + line_color=INK, + line_width=3, + alpha=1.0, + legend_label=f"Optimal epoch: {min_val_epoch}", +) + +# Add annotation label at the optimal epoch +label = Label( + x=min_val_epoch, + y=min_val_loss, + text=f" Epoch {min_val_epoch}\n Loss {min_val_loss:.4f}", + text_color=INK, + text_font_size="14pt", + text_baseline="middle", + text_align="left", +) +p.add_layout(label) + +# Add detailed hover tool +hover = HoverTool(tooltips=[("Epoch", "@epoch{0}"), ("Loss", "@y{0.0000}")], mode="vline") +p.add_tools(hover) + +# Apply text sizing +p.title.text_font_size = "28pt" +p.xaxis.axis_label_text_font_size = "22pt" +p.yaxis.axis_label_text_font_size = "22pt" +p.xaxis.major_label_text_font_size = "18pt" +p.yaxis.major_label_text_font_size = "18pt" + +# Apply theme-adaptive chrome colors +p.background_fill_color = PAGE_BG +p.border_fill_color = PAGE_BG +p.outline_line_color = INK_SOFT + +p.title.text_color = INK +p.xaxis.axis_label_text_color = INK +p.yaxis.axis_label_text_color = INK +p.xaxis.major_label_text_color = INK_SOFT +p.yaxis.major_label_text_color = INK_SOFT +p.xaxis.axis_line_color = INK_SOFT +p.yaxis.axis_line_color = INK_SOFT +p.xaxis.major_tick_line_color = INK_SOFT +p.yaxis.major_tick_line_color = INK_SOFT + +# Y-axis grid (for line charts per style guide) +p.ygrid.grid_line_color = INK +p.ygrid.grid_line_alpha = 0.10 +p.xgrid.grid_line_color = INK +p.xgrid.grid_line_alpha = 0.05 + +# Configure legend +p.legend.background_fill_color = ELEVATED_BG +p.legend.border_line_color = INK_SOFT +p.legend.label_text_color = INK_SOFT +p.legend.location = "top_right" +p.legend.click_policy = "mute" +p.legend.label_text_font_size = "16pt" + +# Save HTML +output_file(f"plot-{THEME}.html") +save(p) + +# Screenshot with headless Chrome using Selenium +W, H = 4800, 2700 +opts = Options() +for arg in ( + "--headless=new", + "--no-sandbox", + "--disable-dev-shm-usage", + "--disable-gpu", + f"--window-size={W},{H}", + "--hide-scrollbars", +): + opts.add_argument(arg) + +driver = webdriver.Chrome(options=opts) +driver.set_window_size(W, H) +driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") +time.sleep(3) # let bokeh's JS render the canvas +driver.save_screenshot(f"plot-{THEME}.png") +driver.quit() diff --git a/plots/line-loss-training/metadata/python/bokeh.yaml b/plots/line-loss-training/metadata/python/bokeh.yaml new file mode 100644 index 0000000000..2d954d7718 --- /dev/null +++ b/plots/line-loss-training/metadata/python/bokeh.yaml @@ -0,0 +1,235 @@ +library: bokeh +language: python +specification_id: line-loss-training +created: '2026-05-14T05:36:32Z' +updated: '2026-05-14T05:45:57Z' +generated_by: claude-haiku +workflow_run: 25843704791 +issue: 2860 +python_version: 3.13.13 +library_version: 3.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/line-loss-training/python/bokeh/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/line-loss-training/python/bokeh/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/line-loss-training/python/bokeh/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/line-loss-training/python/bokeh/plot-dark.html +quality_score: 95 +review: + strengths: + - Excellent theme support with perfect legibility in both light and dark renders + - Perfect Okabe-Ito palette compliance with identical data colors across themes + - Smart visual design with shaded overfitting region and optimal epoch annotation + - Interactive features (HoverTool, mutable legend, hover states) enhance usability + - All text explicitly sized for high-resolution canvas (4800×2700 px) + - Realistic synthetic data with proper exponential decay, noise, and overfitting + effect + weaknesses: [] + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct theme surface + Chrome: Title "line-loss-training · bokeh · anyplot.ai" in bold dark text; X-axis "Epoch" and Y-axis "Loss (Cross-Entropy)" clearly readable; tick labels visible; legend in top right with "Training Loss", "Validation Loss", "Optimal epoch: 82" — all readable in 16pt text + Data: Two distinct lines — training loss (green #009E73) and validation loss (orange #D55E00) declining over 150 epochs; shaded beige region from epoch 80 onward highlights overfitting area; marker and label at epoch 82 show optimal stopping point; grid lines subtle + Legibility verdict: PASS — all text clearly readable against light background + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct dark theme surface + Chrome: All text rendered in light colors; title and axis labels clearly visible against dark background; tick labels readable; legend text light-colored and clear; no dark-on-dark failures detected + Data: Same two lines with identical colors to light render (#009E73 green, #D55E00 orange); shaded overfitting region visible as darker overlay; marker and label at epoch 82 clearly visible + Legibility verdict: PASS — all text readable against dark background; data colors identical to light render; only chrome is theme-adapted + criteria_checklist: + visual_quality: + score: 30 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: All font sizes explicitly set; title 28pt, labels 22pt, ticks 18pt, + legend 16pt, annotation 14pt + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping elements; legend positioned away from data; annotation + well-placed + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Lines (width=4) and markers (size=5, optimal=20) optimally scaled + for 150-point dataset + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Okabe-Ito palette is CVD-safe; strong luminance contrast between + series + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Perfect layout with balanced margins; plot fills 60% of canvas; whitespace + generous + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: 'Descriptive with units: ''Loss (Cross-Entropy)'' and ''Epoch''' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73, second #D55E00 (Okabe-Ito pos. 1–2); backgrounds + #FAF8F1/#1A1A17 correct; data colors identical across themes' + design_excellence: + score: 16 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: false + comment: Intentional choices (shaded region, optimal epoch annotation); professional; + clear above defaults but not exceptional + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: false + comment: Clean layout, subtle grid, generous whitespace, well-styled legend; + could be more minimal + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: false + comment: Clear hierarchy, color contrast guides viewer, shaded region directs + to overfitting zone, optimal epoch is focal point + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct line plot with dual series + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Training/validation curves, distinct colors, legend, epochs, loss + label, optimal point marked + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X-axis epoch, Y-axis loss; axes span all data + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title 'line-loss-training · bokeh · anyplot.ai' correct; legend labels + accurate + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Shows training, validation, overfitting, stopping point, 150 epochs + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Neural network training is realistic; exponential decay; validation + > training; overfitting effect + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Loss 0.15–2.5 sensible for cross-entropy; 150 epochs typical; scale + makes all features visible + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: No functions/classes; linear flow + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) ensures determinism + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imports used; no dead code + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean, Pythonic, no fake UI or over-engineering + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot-{THEME}.png + plot-{THEME}.html; current API + library_mastery: + score: 9 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 5 + max: 5 + passed: true + comment: ColumnDataSource, figure API, proper theme handling, idiomatic color + application + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: false + comment: Uses HoverTool, legend muting, Label annotations, Quad shading, hover + states; strong use of distinctive features + verdict: APPROVED +impl_tags: + dependencies: + - selenium + techniques: + - annotations + - hover-tooltips + - html-export + patterns: + - data-generation + - columndatasource + dataprep: [] + styling: + - grid-styling