diff --git a/plots/streamgraph-basic/implementations/python/bokeh.py b/plots/streamgraph-basic/implementations/python/bokeh.py index edc3d1f7f2..28d557585e 100644 --- a/plots/streamgraph-basic/implementations/python/bokeh.py +++ b/plots/streamgraph-basic/implementations/python/bokeh.py @@ -1,15 +1,31 @@ -""" pyplots.ai +""" anyplot.ai streamgraph-basic: Basic Stream Graph -Library: bokeh 3.8.1 | Python 3.13.11 -Quality: 91/100 | Created: 2025-12-23 +Library: bokeh 3.9.0 | Python 3.13.13 +Quality: 86/100 | Updated: 2026-05-06 """ +import os +import time +from pathlib import Path + import numpy as np import pandas as pd -from bokeh.io import export_png, output_file, save -from bokeh.models import ColumnDataSource, Legend +from bokeh.io import output_file, save +from bokeh.models import ColumnDataSource, HoverTool, Legend from bokeh.plotting import figure +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + + +# Theme tokens +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 — first series always #009E73 +COLORS = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9"] # Data: monthly streaming hours by music genre over two years np.random.seed(42) @@ -17,11 +33,10 @@ months = pd.date_range(start="2022-01-01", periods=24, freq="ME") categories = ["Pop", "Rock", "Hip-Hop", "Electronic", "Jazz", "Classical"] -# Generate streaming data for each genre with natural variations n_points = len(months) base = np.linspace(0, 4 * np.pi, n_points) -data = { +raw = { "Pop": 45 + 18 * np.sin(base) + np.random.randn(n_points) * 3, "Rock": 38 + 12 * np.sin(base + 0.8) + np.random.randn(n_points) * 2.5, "Hip-Hop": 35 + 22 * np.sin(base + 1.6) + np.random.randn(n_points) * 4, @@ -30,24 +45,18 @@ "Classical": 14 + 6 * np.sin(base + 4.0) + np.random.randn(n_points) * 1.5, } -# Ensure all values are positive for cat in categories: - data[cat] = np.maximum(data[cat], 5) + raw[cat] = np.maximum(raw[cat], 5) -# Convert to DataFrame -df = pd.DataFrame(data) -df["month"] = months +df = pd.DataFrame(raw) -# Calculate stacked values with symmetric baseline (centered around 0) +# Symmetric baseline — center the stack around zero values = df[categories].values -cumulative = np.cumsum(values, axis=1) -total = cumulative[:, -1] - -# Center the baseline: offset so the middle is at 0 +total = values.sum(axis=1) baseline_offset = total / 2 + y_bottom = np.zeros_like(values) y_top = np.zeros_like(values) - for i in range(len(categories)): if i == 0: y_bottom[:, i] = -baseline_offset @@ -56,86 +65,108 @@ y_bottom[:, i] = y_top[:, i - 1] y_top[:, i] = y_bottom[:, i] + values[:, i] -# Create smooth interpolated curves for flowing appearance using numpy +# Smooth interpolation for flowing curves x_numeric = np.arange(n_points) n_smooth = n_points * 10 x_smooth = np.linspace(0, n_points - 1, n_smooth) -# Convert smooth indices back to datetime -date_min = months.min() -date_max = months.max() -months_smooth = pd.date_range(start=date_min, end=date_max, periods=n_smooth) - +months_smooth = pd.date_range(start=months.min(), end=months.max(), periods=n_smooth) y_bottom_smooth = np.zeros((n_smooth, len(categories))) y_top_smooth = np.zeros((n_smooth, len(categories))) -# Use polynomial interpolation for smooth curves for i in range(len(categories)): - # Fit polynomial and interpolate for smooth curves - poly_bottom = np.polyfit(x_numeric, y_bottom[:, i], deg=min(10, n_points - 1)) - poly_top = np.polyfit(x_numeric, y_top[:, i], deg=min(10, n_points - 1)) - y_bottom_smooth[:, i] = np.polyval(poly_bottom, x_smooth) - y_top_smooth[:, i] = np.polyval(poly_top, x_smooth) + deg = min(10, n_points - 1) + y_bottom_smooth[:, i] = np.polyval(np.polyfit(x_numeric, y_bottom[:, i], deg), x_smooth) + y_top_smooth[:, i] = np.polyval(np.polyfit(x_numeric, y_top[:, i], deg), x_smooth) -# Colors - Python Blue first, then harmonious colorblind-safe palette -colors = ["#306998", "#FFD43B", "#E07A5F", "#81B29A", "#F2CC8F", "#3D405B"] - -# Create figure +# Plot p = figure( width=4800, height=2700, - title="streamgraph-basic · bokeh · pyplots.ai", + title="streamgraph-basic · bokeh · anyplot.ai", x_axis_label="Time", y_axis_label="Streaming Hours (relative)", x_axis_type="datetime", ) -# Style title and axes for large canvas +# Font sizes for 4800×2700 px canvas p.title.text_font_size = "32pt" +p.title.text_color = INK p.xaxis.axis_label_text_font_size = "24pt" p.yaxis.axis_label_text_font_size = "24pt" p.xaxis.major_label_text_font_size = "18pt" p.yaxis.major_label_text_font_size = "18pt" -# Create patches for each category (streamgraph layers) with smooth curves -legend_items = [] +# Theme-adaptive chrome +p.background_fill_color = PAGE_BG +p.border_fill_color = PAGE_BG +p.outline_line_color = None +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 +p.xgrid.grid_line_color = INK +p.ygrid.grid_line_color = INK +p.xgrid.grid_line_alpha = 0.10 +p.ygrid.grid_line_alpha = 0.10 + +# Draw streamgraph patches x_values = months_smooth.values +legend_items = [] +hover_renderers = [] for i, cat in enumerate(categories): - # Create polygon coordinates for smooth stacked area - # Go forward along top, then backward along bottom xs = np.concatenate([x_values, x_values[::-1]]) ys = np.concatenate([y_top_smooth[:, i], y_bottom_smooth[:, i][::-1]]) - source = ColumnDataSource(data={"x": xs, "y": ys}) - + source = ColumnDataSource(data={"x": xs, "y": ys, "genre": [cat] * len(xs)}) renderer = p.patch( - x="x", y="y", source=source, fill_color=colors[i], fill_alpha=0.85, line_color=colors[i], line_width=2 + x="x", y="y", source=source, fill_color=COLORS[i], fill_alpha=0.85, line_color=COLORS[i], line_width=2 ) legend_items.append((cat, [renderer])) + hover_renderers.append(renderer) -# Add legend outside the plot +# HoverTool — shows genre name on hover +hover = HoverTool(renderers=hover_renderers, tooltips=[("Genre", "@genre")]) +p.add_tools(hover) + +# Legend outside the plot area legend = Legend(items=legend_items, location="center") -legend.label_text_font_size = "20pt" -legend.glyph_height = 35 -legend.glyph_width = 35 -legend.spacing = 12 +legend.label_text_font_size = "22pt" +legend.label_text_color = INK_SOFT +legend.glyph_height = 40 +legend.glyph_width = 40 +legend.spacing = 15 +legend.background_fill_color = ELEVATED_BG +legend.border_line_color = INK_SOFT p.add_layout(legend, "right") -# Grid styling - subtle -p.grid.grid_line_alpha = 0.3 -p.grid.grid_line_dash = [6, 4] - -# Keep y-axis visible but subtle -p.yaxis.visible = True -p.outline_line_color = None - -# Toolbar placement p.toolbar_location = "above" -# Save as PNG -export_png(p, filename="plot.png") - -# Also save interactive HTML -output_file("plot.html") +# Save interactive HTML +output_file(f"plot-{THEME}.html") save(p) + +# Screenshot with headless Chrome (export_png unavailable in this environment) +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) +driver.save_screenshot(f"plot-{THEME}.png") +driver.quit() diff --git a/plots/streamgraph-basic/metadata/python/bokeh.yaml b/plots/streamgraph-basic/metadata/python/bokeh.yaml index 1b8920543a..34a1ed5120 100644 --- a/plots/streamgraph-basic/metadata/python/bokeh.yaml +++ b/plots/streamgraph-basic/metadata/python/bokeh.yaml @@ -1,169 +1,199 @@ library: bokeh +language: python specification_id: streamgraph-basic created: '2025-12-23T21:55:42Z' -updated: '2025-12-23T22:02:08Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20472383566 -issue: 0 -python_version: 3.13.11 -library_version: 3.8.1 -preview_url: https://storage.googleapis.com/anyplot-images/plots/streamgraph-basic/bokeh/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/streamgraph-basic/bokeh/plot.html -quality_score: 91 -impl_tags: - dependencies: [] - techniques: - - html-export - patterns: - - data-generation - - matrix-construction - dataprep: [] - styling: - - alpha-blending +updated: '2026-05-06T22:02:23Z' +generated_by: claude-sonnet +workflow_run: 25356018905 +issue: 856 +python_version: 3.13.13 +library_version: 3.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/streamgraph-basic/python/bokeh/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/streamgraph-basic/python/bokeh/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/streamgraph-basic/python/bokeh/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/streamgraph-basic/python/bokeh/plot-dark.html +quality_score: 86 review: strengths: - - Excellent implementation of the centered baseline algorithm creating proper symmetric - streamgraph - - Smooth polynomial interpolation (degree 10) creates flowing, organic curves that - look professional - - Colorblind-safe palette starting with Python Blue (#306998), distinct adjacent - colors - - Proper use of Bokeh patch method for filled polygon areas - - Well-sized text elements appropriate for 4800x2700 canvas - - Realistic music streaming data scenario that matches specification perfectly + - 'Complete spec compliance: symmetric baseline streamgraph with smooth flowing + curves, correct centered y=0 baseline, and all required features implemented' + - 'Correct Okabe-Ito palette in canonical order with brand green #009E73 as first + series (Pop), all 6 positions used correctly' + - 'Full theme adaptation: PAGE_BG, ELEVATED_BG, INK, INK_SOFT tokens applied to + background, text, axis lines, tick marks, and legend — both renders theme-correct' + - HoverTool displaying genre name on hover showcases Bokeh's distinctive interactivity; + HTML artifact produced alongside PNG + - 'Clean reproducible flat script: np.random.seed(42), no functions or classes, + all imports used, correct Selenium/Chrome screenshot pattern' weaknesses: - - Legend text could be slightly larger for the canvas size - - Missing HoverTool for interactivity which is a key Bokeh strength - - Y-axis label says relative but could benefit from units - image_description: 'The plot displays a streamgraph (centered stacked area chart) - showing "Streaming Hours (relative)" over time from January 2022 to January 2024. - Six music genres are shown as flowing, stacked layers: Pop (Python blue #306998), - Rock (golden yellow #FFD43B), Hip-Hop (coral/salmon #E07A5F), Electronic (muted - teal/green #81B29A), Jazz (cream/tan #F2CC8F), and Classical (dark gray-blue #3D405B). - The baseline is centered around 0 on the y-axis, creating a symmetric river-like - appearance. The layers have smooth, flowing curves showing seasonal variations - over the 2-year period. A legend is positioned on the right side identifying each - genre. The title "streamgraph-basic · bokeh · pyplots.ai" appears at the top left. - X-axis shows time periods (Jan 2022 through Jan 2024), Y-axis shows "Streaming - Hours (relative)" ranging from approximately -100 to +100.' + - Polynomial fitting (degree 10 via np.polyfit/np.polyval) used for smooth curves + rather than true spline interpolation — high-degree polynomials risk Runge phenomenon + edge oscillations at leftmost and rightmost time points + - Streams extend close to the top and bottom viewport edges with minimal breathing + room — y-range could be expanded slightly or a padding parameter added to give + the data more visual space + - 'No focal-point emphasis or data storytelling annotation: no callout highlighting + the dominant genre trend, no emphasis on the category that drives the most visual + interest' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white consistent with #FAF8F1 — not pure white, not dark. Correct light surface fills both the plot area and the surrounding border. + Chrome: Title "streamgraph-basic · bokeh · anyplot.ai" visible at top-left in dark ink (#1A1A17). Y-axis label "Streaming Hours (relative)" clearly readable on the left (rotated, dark text). Tick labels (-100, -50, 0, 50, 100) in INK_SOFT (#4A4A44). Legend positioned to the right of the plot on an ELEVATED_BG (#FFFDF6) panel with readable labels. Bokeh toolbar icons at top-right. X-axis datetime labels visible at bottom. + Data: Six smooth flowing streaming bands — Pop (#009E73 brand green, bottommost), Rock (#D55E00 vermillion), Hip-Hop (#0072B2 blue), Electronic (#CC79A7 pink), Jazz (#E69F00 orange/gold), Classical (#56B4E9 sky blue, topmost). fill_alpha=0.85 gives slight depth. Symmetric baseline correctly centered around y=0. A small annotation box reading "Pop — most-streamed" is visible in the lower-center of the plot (appears to originate from a prior iteration's code or captured hover state; not present in current code). + Legibility verdict: PASS — all visible text readable against the warm off-white background; no light-on-light failures detected. + + Dark render (plot-dark.png): + Background: Dark near-black consistent with #1A1A17 — warm near-black, not pure black. Both plot area and border fill correctly set to PAGE_BG (#1A1A17). + Chrome: Title in light warm-white (#F0EFE8) — clearly readable against dark background. Axis labels in INK (#F0EFE8). Tick labels in INK_SOFT (#B8B7B0). Legend text is light-colored against ELEVATED_BG (#242420) panel — no dark-on-dark failure. The same small "Pop — most-streamed" annotation box appears (consistent with light render). No black-on-black text detected. + Data: All six Okabe-Ito stream colors are IDENTICAL to the light render — only chrome (backgrounds, text, legend frame) flipped. Data colors are theme-independent as required. + Legibility verdict: PASS — all text clearly readable against the dark background; no dark-on-dark failures observed. criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 28 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 10 - max: 10 + score: 7 + max: 8 passed: true - comment: Title at 32pt, axis labels at 24pt, tick labels at 18pt - all perfectly - readable at full resolution + comment: 'Font sizes explicitly set (32pt title, 24pt axis labels, 18pt ticks, + 22pt legend). All text readable in both themes with correct INK/INK_SOFT + token mapping. Minor: x-axis ''Time'' label small relative to large canvas, + hard to confirm at thumbnail scale.' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements, all labels clearly separated + comment: Streaming bands flow smoothly with no element collisions. Legend + placed fully outside plot area. No text or band overlap detected. - id: VQ-03 name: Element Visibility - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: Stream layers are well-sized, smooth curves visible with good fill - alpha (0.85) + comment: All 6 bands clearly distinguishable at fill_alpha=0.85 with matching + line borders. Each Okabe-Ito color is distinct. - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Colorblind-safe palette with good contrast between adjacent layers - (no red-green reliance) + comment: Okabe-Ito palette is peer-reviewed CVD-safe. No red-green pairing + as sole signal. - id: VQ-05 - name: Layout Balance + name: Layout & Canvas score: 3 - max: 5 + max: 4 passed: true - comment: Good proportions, though the legend spacing from the main plot could - be tighter + comment: 'Good 16:9 proportions at 4800x2700. Legend correctly placed outside. + Minor: streams extend very close to top/bottom edges with minimal breathing + room; Bokeh toolbar at top shares space with title.' - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 passed: true - comment: Descriptive labels ("Time", "Streaming Hours (relative)") but no - units on y-axis + comment: Title formatted as 'streamgraph-basic · bokeh · anyplot.ai'. Y-axis + labelled 'Streaming Hours (relative)'. X-axis labelled 'Time'. - id: VQ-07 - name: Grid & Legend - score: 1 + name: Palette Compliance + score: 2 max: 2 passed: true - comment: Grid is subtle (alpha 0.3), legend well-placed but could be slightly - larger + comment: 'First series (Pop) = #009E73 (brand green). Full Okabe-Ito order + positions 1-6. Light background #FAF8F1, dark background #1A1A17. Both renders + theme-correct with adaptive chrome tokens.' + design_excellence: + score: 11 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Clean professional streamgraph with Okabe-Ito palette, alpha blending, + well-placed external legend. Raised above default (4) for deliberate design + choices: outline_line_color=None, subtle grid, ordered streams from dominant + to minor genre. Not dramatically sophisticated beyond spec requirements.' + - id: DE-02 + name: Visual Refinement + score: 3 + max: 6 + passed: true + comment: outline_line_color=None removes figure border box. Grid at 10% alpha + is appropriately subtle. Legend has custom glyph sizes (40x40) and spacing. + No explicit spine removal (Bokeh default kept). Raised from default (2). + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: true + comment: Smooth flowing curves communicate genre trend evolution over 2 years. + Symmetric baseline creates visual balance and enables relative comparison. + No callout annotations or focal-point emphasis on dominant genre. Raised + from default (2). spec_compliance: - score: 25 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct streamgraph with centered baseline - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Time on x-axis, stacked values centered around 0 - - id: SC-03 + comment: Correct streamgraph with symmetric baseline centered at y=0 and smooth + flowing curves. + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Smooth interpolation, symmetric baseline, distinct colors, legend - present - - id: SC-04 - name: Data Range + comment: Smooth interpolation (polynomial), symmetric baseline, distinct Okabe-Ito + colors, legend — all present. + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All data visible, axes encompass full range - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: All 6 genre labels correctly identified - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: X=datetime (24 months), Y=streaming hours (relative, centered), 6 + categories within spec's 3-8 range. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Correct format: "streamgraph-basic · bokeh · pyplots.ai"' + comment: Title matches required format. Legend labels match category names + (Pop, Rock, Hip-Hop, Electronic, Jazz, Classical). data_quality: - score: 20 - max: 20 + score: 15 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: Shows variation over time, different category sizes, seasonal patterns + comment: 'All key streamgraph aspects demonstrated: 6 categories, 24 time + points, varying trend shapes, symmetric stacking.' - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Music streaming by genre is a perfect real-world application + comment: Monthly music genre streaming hours over 2 years — realistic, relatable, + culturally neutral domain. - id: DQ-03 name: Appropriate Scale - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: 24 months of data with 6 genres, realistic streaming hour values + comment: Base values 14-45 hours/month per genre with sinusoidal variation. + 2-year range appropriate for trend analysis. code_quality: - score: 7 + score: 10 max: 10 items: - id: CQ-01 @@ -171,42 +201,69 @@ review: score: 3 max: 3 passed: true - comment: 'Linear script: imports → data → plot → save' + comment: No functions or classes. Clean top-to-bottom flat script. - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: np.random.seed(42) set + comment: np.random.seed(42) set. - id: CQ-03 name: Clean Imports - score: 1 + score: 2 max: 2 - passed: false - comment: 'All imports used, but minor issue: Legend imported separately when - could be simplified' + passed: true + comment: All imported symbols actively used (os, time, Path, numpy, pandas, + bokeh.io, bokeh.models, bokeh.plotting, selenium). - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Using current Bokeh API + comment: No fake UI. Matrix-based y_bottom/y_top computation is clear. Section + comments guide readability. - id: CQ-05 - name: Output Correct - score: 0 + name: Output & API + score: 1 max: 1 - passed: false - comment: Saves as plot.png but also creates plot.html (minor) - library_features: - score: 3 - max: 5 + passed: true + comment: Saves plot-{THEME}.html and plot-{THEME}.png correctly. Selenium/Chrome + pattern follows bokeh.md prescription. + library_mastery: + score: 7 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: ColumnDataSource per patch, p.patch() for filled polygons, HoverTool + via add_tools(), Legend model via add_layout() — canonical Bokeh patterns + used throughout. Raised above default (3). + - id: LM-02 + name: Distinctive Features score: 3 max: 5 passed: true - comment: Uses ColumnDataSource, figure, patch, Legend, and export_png/save - appropriately, but doesn't leverage HoverTool for interactivity which would - be a distinctive Bokeh feature + comment: Interactive HoverTool with genre tooltip, HTML artifact alongside + PNG, externally-placed Legend model with custom sizing. Raised above default + (1). Callbacks/widgets not leveraged to push higher. verdict: APPROVED +impl_tags: + dependencies: + - selenium + techniques: + - hover-tooltips + - html-export + - custom-legend + patterns: + - data-generation + - iteration-over-groups + - columndatasource + - matrix-construction + dataprep: + - time-series + - interpolation + styling: + - alpha-blending