diff --git a/plots/waffle-basic/implementations/python/bokeh.py b/plots/waffle-basic/implementations/python/bokeh.py index d386bd13c4..251b44ab97 100644 --- a/plots/waffle-basic/implementations/python/bokeh.py +++ b/plots/waffle-basic/implementations/python/bokeh.py @@ -1,7 +1,7 @@ -""" anyplot.ai +"""anyplot.ai waffle-basic: Basic Waffle Chart -Library: bokeh 3.9.0 | Python 3.13.13 -Quality: 88/100 | Updated: 2026-05-05 +Library: bokeh 3.9.2 | Python 3.13.12 +Quality: 88/100 | Updated: 2026-07-26 """ import os @@ -15,56 +15,72 @@ from selenium.webdriver.chrome.options import Options -# Theme tokens +# Theme tokens (see prompts/default-style-guide.md "Theme-adaptive Chrome") 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 brand green) -PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] +# Imprint palette (first series always brand green) +IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233"] -# Data - Survey results on renewable energy adoption +# Data - survey results on renewable energy adoption categories = ["Solar", "Wind", "Hydro", "Geothermal"] values = [48, 25, 18, 9] # Percentages summing to 100 +leader = categories[0] # highest share drives the focal-point emphasis below -# Build waffle grid (10x10 = 100 squares) +# Build waffle grid (10x10 = 100 squares, one square per percentage point) grid_size = 10 total_squares = grid_size * grid_size -# Assign category to each square based on percentages square_categories = [] -color_map = [] -for i, (cat, val) in enumerate(zip(categories, values, strict=True)): +for cat, val in zip(categories, values, strict=True): square_categories.extend([cat] * val) - color_map.extend([PALETTE[i]] * val) # Create grid coordinates (fill left-to-right, bottom-to-top) -x_coords = [] -y_coords = [] -for idx in range(total_squares): - x_coords.append(idx % grid_size) - y_coords.append(idx // grid_size) +x_coords = [idx % grid_size for idx in range(total_squares)] +y_coords = [idx // grid_size for idx in range(total_squares)] -source = ColumnDataSource(data={"x": x_coords, "y": y_coords, "category": square_categories, "color": color_map}) +# Title — mandated format, fontsize scaled to the exact rendered length +title = "Renewable Energy Sources · waffle-basic · python · bokeh · anyplot.ai" +title_fontsize = round(50 * (67 / len(title) if len(title) > 67 else 1.0)) +title_fontsize = max(title_fontsize, 34) -# Create figure +# Create figure — width/height are the TOTAL canvas (bokeh.md "Canvas — hard rule") p = figure( - width=4800, - height=2700, - title="Renewable Energy Sources · waffle-basic · bokeh · anyplot.ai", - x_range=(-0.7, grid_size - 0.3), - y_range=(-0.7, grid_size - 0.3), + width=3200, + height=1800, + title=title, + x_range=(-1.15, grid_size - 1 + 1.15), + y_range=(-1.15, grid_size - 1 + 1.15), tools="", toolbar_location=None, + min_border_top=140, + min_border_bottom=60, + min_border_left=60, + min_border_right=60, ) -# Draw squares with small gap between them +# Framing card behind the grid — a light compositional touch that gives the +# waffle a sense of place on the canvas instead of loose floating squares. +card_center = (grid_size - 1) / 2 +card_span = grid_size - 1 + 1.5 +p.rect( + x=card_center, + y=card_center, + width=card_span, + height=card_span, + fill_color=ELEVATED_BG, + line_color=INK_SOFT, + line_width=1.5, +) + +# Draw squares with a small gap between them, one renderer per category so +# hover + legend can target each group independently. square_size = 0.88 renderers = [] -for i, (cat, color) in enumerate(zip(categories, PALETTE, strict=True)): - # Filter data for this category +for i, (cat, color) in enumerate(zip(categories, IMPRINT_PALETTE, strict=True)): indices = [j for j, c in enumerate(square_categories) if c == cat] cat_source = ColumnDataSource( data={ @@ -81,42 +97,46 @@ height=square_size, source=cat_source, fill_color=color, + fill_alpha=1.0 if cat == leader else 0.85, # focal-point emphasis on the leading share line_color=PAGE_BG, line_width=2.5, - fill_alpha=1.0, ) renderers.append((f"{cat} ({values[i]}%)", [r])) -# Add hover tool +# Hover tool hover = HoverTool(tooltips=[("Category", "@category"), ("Percentage", "@percentage%")]) p.add_tools(hover) -# Add legend +# Legend — click an entry to toggle its squares on/off legend = Legend(items=[LegendItem(label=label, renderers=rends) for label, rends in renderers]) -legend.label_text_font_size = "18pt" -legend.glyph_height = 35 -legend.glyph_width = 35 -legend.spacing = 12 -legend.padding = 15 +legend.title = "Category (share)" +legend.title_text_font_size = "24pt" +legend.title_text_color = INK_SOFT +legend.label_text_font_size = "34pt" +legend.glyph_height = 50 +legend.glyph_width = 50 +legend.spacing = 16 +legend.padding = 24 legend.location = "top_right" legend.background_fill_color = ELEVATED_BG legend.border_line_color = INK_SOFT legend.label_text_color = INK_SOFT +legend.click_policy = "hide" p.add_layout(legend, "right") -# Style the plot -p.title.text_font_size = "28pt" +# Style the title +p.title.text_font_size = f"{title_fontsize}pt" p.title.align = "center" p.title.text_color = INK -# Hide axes +# Hide axes — a waffle chart carries no axis meaning p.xaxis.visible = False p.yaxis.visible = False p.xgrid.visible = False p.ygrid.visible = False p.outline_line_color = None -# Set background +# Background p.background_fill_color = PAGE_BG p.border_fill_color = PAGE_BG @@ -124,8 +144,10 @@ output_file(f"plot-{THEME}.html") save(p) -# Screenshot with headless Chrome -W, H = 4800, 2700 +# Screenshot with headless Chrome — Selenium 4 / Selenium Manager auto-resolves +# a working driver. Do NOT use bokeh.io.export_png (requires a chromedriver +# binary that isn't reliably available in this environment). +W, H = 3200, 1800 opts = Options() for arg in ( "--headless=new", @@ -139,6 +161,12 @@ driver = webdriver.Chrome(options=opts) driver.set_window_size(W, H) driver.get(f"file://{Path(f'plot-{THEME}.html').resolve()}") -time.sleep(3) +# Pin the viewport exactly via CDP — headless Chrome's --window-size sets the +# OUTER window, which still reserves a phantom title-bar height even headless, +# so innerHeight (and the screenshot) comes out short of H without this. +driver.execute_cdp_cmd( + "Emulation.setDeviceMetricsOverride", {"width": W, "height": H, "deviceScaleFactor": 1, "mobile": False} +) +time.sleep(3) # let bokeh's JS render the canvas driver.save_screenshot(f"plot-{THEME}.png") driver.quit() diff --git a/plots/waffle-basic/metadata/python/bokeh.yaml b/plots/waffle-basic/metadata/python/bokeh.yaml index b3e6b3f6af..10eeace9b2 100644 --- a/plots/waffle-basic/metadata/python/bokeh.yaml +++ b/plots/waffle-basic/metadata/python/bokeh.yaml @@ -1,232 +1,21 @@ +# Per-library metadata for bokeh implementation of waffle-basic +# Auto-generated by impl-generate.yml + library: bokeh language: python specification_id: waffle-basic created: '2025-12-24T09:51:07Z' -updated: '2026-05-05T18:14:26Z' -generated_by: claude-haiku -workflow_run: 25393236128 +updated: '2026-07-26T10:29:22Z' +generated_by: claude-sonnet +workflow_run: 30198192112 issue: 998 -python_version: 3.13.13 -library_version: 3.9.0 +language_version: 3.13.14 +library_version: 3.9.2 preview_url_light: https://storage.googleapis.com/anyplot-images/plots/waffle-basic/python/bokeh/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/waffle-basic/python/bokeh/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/waffle-basic/python/bokeh/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/waffle-basic/python/bokeh/plot-dark.html -quality_score: 88 +quality_score: null review: - strengths: - - Excellent visual quality with proper color contrast and readability in both light - and dark themes - - Clean, minimal design with thoughtful square styling and clear spacing - - Correct waffle chart implementation with proper data mapping (100 squares for - 100%) - - Proper theme-adaptive tokens throughout (backgrounds, text colors) - - Idiomatic bokeh patterns using ColumnDataSource and custom Legend styling - - Interactive hover tooltips for category and percentage information - weaknesses: - - Title format includes extra context prefix beyond {spec-id} · {library} · anyplot.ai - spec requirement - - Design excellence is competent but lacks visual sophistication or strategic emphasis - - Legend styling is functional but could be more refined (consider custom borders - or slightly elevated background emphasis) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correctly applied, not pure white - Chrome: Title "Renewable Energy Sources · waffle-basic · bokeh · anyplot.ai" in 28pt dark text (#1A1A17), clearly centered and readable. Legend in top-right with dark label text on elevated background (#FFFDF6), category labels and percentages all readable. - Data: 10×10 waffle grid with colors: Solar in brand green (#009E73) dominating 48 squares, Wind in orange (#D55E00) at 25 squares, Hydro in blue (#0072B2) at 18 squares, Geothermal in pink (#CC79A7) at 9 squares. Square borders in background color with 0.88 width and 2.5pt gaps create excellent separation and readability. - Legibility verdict: PASS — all text readable, all data clearly visible, excellent contrast throughout - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correctly applied, not pure black - Chrome: Title in light text (#F0EFE8) readable against dark background. Legend text (#B8B7B0) readable on elevated dark background (#242420). No dark-on-dark failures detected. - Data: Identical colors to light render (Solar #009E73, Wind #D55E00, Hydro #0072B2, Geothermal #CC79A7). Square borders now darker/black, still providing clear separation. Layout and proportions identical to light render. - Legibility verdict: PASS — all text readable, no dark-on-dark issues, brand green (#009E73) clearly visible, all elements distinguishable - criteria_checklist: - visual_quality: - score: 30 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: Title 28pt, clearly readable in both themes, no size issues - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: All text and elements well-spaced, no collisions - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: All 100 squares clearly visible and distinguishable by color - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Okabe-Ito palette CVD-safe, strong contrast between categories - - id: VQ-05 - name: Layout & Canvas - score: 4 - max: 4 - passed: true - comment: Good proportions, generous margins, nothing cut off - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Clear descriptive title, no axes needed for waffle - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'First series #009E73 (brand), Okabe-Ito order followed, correct - backgrounds, both themes proper' - design_excellence: - score: 12 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: false - comment: Clean minimal design, adequate styling but lacks sophisticated polish - or custom refinement - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Axes hidden, thoughtful square gaps, minimal chrome, could be more - refined - - id: DE-03 - name: Data Storytelling - score: 3 - max: 6 - passed: false - comment: Clear proportion display through color, but no additional visual - hierarchy or emphasis - spec_compliance: - score: 14 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct 10×10 waffle chart - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: Proportions in grid, distinct colors, legend with percentages - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X/Y correctly mapped to grid, all data visible, sums to 100 - - id: SC-04 - name: Title & Legend - score: 2 - max: 3 - passed: false - comment: Title includes extra context prefix beyond spec format requirement - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: All 4 categories shown, 100% coverage - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Plausible renewable energy percentages, neutral topic - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Percentages 0-100%, grid 10×10 = 100 squares, appropriate - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Simple direct implementation, no unnecessary abstraction - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Hardcoded deterministic data, fully reproducible - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only necessary imports, all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Elegant structure, appropriate complexity, no fake UI - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Correct output files plot-{THEME}.png and .html - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Proper ColumnDataSource, figure creation with parameters, HoverTool, - custom Legend - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: false - comment: HoverTool and Selenium screenshot show library awareness but somewhat - standard patterns - verdict: APPROVED -impl_tags: - dependencies: - - selenium - techniques: - - hover-tooltips - - custom-legend - - html-export - patterns: - - data-generation - - iteration-over-groups - dataprep: [] - styling: - - minimal-chrome + strengths: [] + weaknesses: []