diff --git a/plots/map-route-path/implementations/python/bokeh.py b/plots/map-route-path/implementations/python/bokeh.py index 1249e203d8..f5342b9746 100644 --- a/plots/map-route-path/implementations/python/bokeh.py +++ b/plots/map-route-path/implementations/python/bokeh.py @@ -1,115 +1,205 @@ -""" pyplots.ai +""" anyplot.ai map-route-path: Route Path Map -Library: bokeh 3.8.2 | Python 3.13.11 -Quality: 91/100 | Created: 2026-01-19 +Library: bokeh 3.9.0 | Python 3.13.13 +Quality: 90/100 | Updated: 2026-05-21 """ +import base64 +import os +import sys +import time +from pathlib import Path + + +# bokeh.py shadows the installed bokeh package when Python adds this file's +# directory to sys.path[0]; remove it so imports resolve to the package. +_here = os.path.dirname(os.path.abspath(__file__)) +sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != _here] +del _here + import numpy as np -from bokeh.io import export_png, output_file, save -from bokeh.models import ColumnDataSource, Title +from bokeh.io import output_file, save +from bokeh.models import ColorBar, ColumnDataSource, LinearColorMapper, Title +from bokeh.palettes import Viridis256 from bokeh.plotting import figure +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +# Theme +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" -# Data - Simulated hiking trail GPS track (approximately 200 waypoints) +# Data - Simulated hiking trail GPS track (Rocky Mountain National Park) np.random.seed(42) -# Base coordinates (Central Park, New York area) -center_lon = -73.968 -center_lat = 40.785 +start_lon = -105.683 +start_lat = 40.343 -# Generate a realistic hiking trail path with sequential waypoints n_points = 200 -t = np.linspace(0, 4 * np.pi, n_points) +t = np.linspace(0, 1, n_points) -# Create a winding path with some randomness (simulating trail GPS data) -lat_offset = 0.015 * np.sin(t) + 0.008 * np.sin(2.5 * t) + 0.002 * np.cumsum(np.random.randn(n_points)) / n_points -lon_offset = 0.012 * np.cos(t) + 0.006 * np.cos(3 * t) + 0.002 * np.cumsum(np.random.randn(n_points)) / n_points +# Low-frequency meanders: both lon and lat are monotonically increasing at all t, +# so the trail never crosses itself (verified: d(lon)/dt >= 0.019, d(lat)/dt >= 0.008) +lon_progress = 0.038 * t +lat_progress = 0.027 * t +lon_meander = 0.006 * np.sin(np.pi * t) +lat_meander = 0.003 * np.sin(2 * np.pi * t) -lats = center_lat + lat_offset -lons = center_lon + lon_offset +lats = start_lat + lat_progress + lat_meander +lons = start_lon + lon_progress + lon_meander -# Convert lat/lon to Web Mercator coordinates (required for tile providers) +# Convert lat/lon to Web Mercator for tile compatibility k = 20037508.34 / 180 x_coords = lons * k y_coords = np.log(np.tan((90 + lats) * np.pi / 360)) / (np.pi / 180) * k -# Sequence and color gradient for time progression -sequence = np.arange(n_points) -colors = [ - f"#{int(50 + 150 * i / n_points):02x}{int(105 - 80 * i / n_points):02x}{int(152 - 100 * i / n_points):02x}" - for i in range(n_points) -] +# Progress percentage (0–100) drives both dot coloring and the colorbar +t_pct = t * 100 +mapper = LinearColorMapper(palette=Viridis256, low=0, high=100) -# Create ColumnDataSource for the path -source = ColumnDataSource(data={"x": x_coords, "y": y_coords, "sequence": sequence, "color": colors}) +source = ColumnDataSource(data={"x": x_coords, "y": y_coords, "t_pct": t_pct}) -# Create figure with tile background +# Figure +W, H = 3200, 1800 p = figure( - width=4800, - height=2700, + width=W, + height=H, x_axis_type="mercator", y_axis_type="mercator", x_axis_label="Longitude", y_axis_label="Latitude", - tools="pan,wheel_zoom,box_zoom,reset", + toolbar_location=None, + min_border_bottom=160, + min_border_left=180, + min_border_top=110, + min_border_right=200, # extra room for the colorbar + tick labels ) -# Add title separately for better control -p.add_layout(Title(text="map-route-path · bokeh · pyplots.ai", text_font_size="28pt"), "above") +# Title +title_obj = Title( + text="Rocky Mountain Trail · map-route-path · python · bokeh · anyplot.ai", text_font_size="50pt", text_color=INK +) +p.add_layout(title_obj, "above") -# Add basemap tiles (Bokeh 3.x uses add_tile with string provider name) -p.add_tile("CartoDB Positron") +# Theme-appropriate basemap tile +tile_provider = "CartoDB Positron" if THEME == "light" else "CartoDB Dark Matter" +p.add_tile(tile_provider) -# Draw the route path as connected line -p.line(x="x", y="y", source=source, line_width=4, line_color="#306998", line_alpha=0.8) +# Route line — Okabe-Ito position 1 (#009E73) +p.line(x="x", y="y", source=source, line_width=5, line_color="#009E73", line_alpha=0.9) -# Add points along the path with color gradient showing progression -p.scatter(x="x", y="y", source=source, size=8, fill_color="color", line_color="#306998", line_width=1, alpha=0.7) +# Waypoints with viridis gradient showing trail progression +p.scatter( + x="x", + y="y", + source=source, + size=12, + fill_color={"field": "t_pct", "transform": mapper}, + line_color=PAGE_BG, + line_width=1.5, + alpha=0.85, +) -# Mark start point (green circle) -start_source = ColumnDataSource(data={"x": [x_coords[0]], "y": [y_coords[0]]}) +# Start marker — Okabe-Ito #009E73 (large circle) +start_src = ColumnDataSource(data={"x": [x_coords[0]], "y": [y_coords[0]]}) p.scatter( x="x", y="y", - source=start_source, - size=25, - fill_color="#2ecc71", - line_color="white", + source=start_src, + size=32, + fill_color="#009E73", + line_color=PAGE_BG, line_width=3, legend_label="Start", ) -# Mark end point (red square) -end_source = ColumnDataSource(data={"x": [x_coords[-1]], "y": [y_coords[-1]]}) +# End marker — Okabe-Ito #D55E00 (large square) +end_src = ColumnDataSource(data={"x": [x_coords[-1]], "y": [y_coords[-1]]}) p.scatter( x="x", y="y", - source=end_source, - size=25, - fill_color="#e74c3c", - line_color="white", + source=end_src, + size=32, + fill_color="#D55E00", + line_color=PAGE_BG, line_width=3, marker="square", legend_label="End", ) -# Styling -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" - -# Legend styling +# Colorbar decoding the viridis gradient: purple = trail start, yellow = trail end +color_bar = ColorBar( + color_mapper=mapper, + label_standoff=16, + major_label_text_font_size="28pt", + major_label_text_color=INK_SOFT, + title="Trail Progress (%)", + title_text_font_size="30pt", + title_text_color=INK, + background_fill_color=PAGE_BG, + bar_line_color=INK_SOFT, + major_tick_line_color=INK_SOFT, + width=40, +) +p.add_layout(color_bar, "right") + +# Theme-adaptive chrome +p.background_fill_color = PAGE_BG +p.border_fill_color = PAGE_BG +p.outline_line_color = INK_SOFT + +p.xaxis.axis_label_text_font_size = "42pt" +p.yaxis.axis_label_text_font_size = "42pt" +p.xaxis.major_label_text_font_size = "34pt" +p.yaxis.major_label_text_font_size = "34pt" +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 + +# Slightly more visible grid on the near-black dark basemap +grid_alpha = 0.06 if THEME == "light" else 0.09 +p.xgrid.grid_line_color = INK +p.ygrid.grid_line_color = INK +p.xgrid.grid_line_alpha = grid_alpha +p.ygrid.grid_line_alpha = grid_alpha + +# Legend p.legend.location = "top_right" -p.legend.label_text_font_size = "18pt" -p.legend.background_fill_alpha = 0.8 +p.legend.label_text_font_size = "34pt" +p.legend.background_fill_color = ELEVATED_BG +p.legend.border_line_color = INK_SOFT +p.legend.label_text_color = INK_SOFT -# Grid styling -p.grid.grid_line_alpha = 0.3 - -# Save outputs -export_png(p, filename="plot.png") - -# Save interactive HTML for bokeh -output_file("plot.html") +# Save HTML +output_file(f"plot-{THEME}.html") save(p) + +# Screenshot with headless Chrome (Selenium 4 / Selenium Manager) +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) +# Capture full page (beyond visible viewport) to handle the ~139px viewport gap +result = driver.execute_cdp_cmd("Page.captureScreenshot", {"format": "png", "captureBeyondViewport": True}) +with open(f"plot-{THEME}.png", "wb") as fh: + fh.write(base64.b64decode(result["data"])) +driver.quit() diff --git a/plots/map-route-path/metadata/python/bokeh.yaml b/plots/map-route-path/metadata/python/bokeh.yaml index bc1cf3ee65..a10be5d35c 100644 --- a/plots/map-route-path/metadata/python/bokeh.yaml +++ b/plots/map-route-path/metadata/python/bokeh.yaml @@ -1,157 +1,187 @@ library: bokeh +language: python specification_id: map-route-path created: '2026-01-19T17:53:54Z' -updated: '2026-01-19T17:56:55Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 21147013646 +updated: '2026-05-21T06:03:36Z' +generated_by: claude-sonnet +workflow_run: 26207466934 issue: 3768 -python_version: 3.13.11 -library_version: 3.8.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/map-route-path/bokeh/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/map-route-path/bokeh/plot.html -quality_score: 91 +language_version: 3.13.13 +library_version: 3.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-route-path/python/bokeh/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-route-path/python/bokeh/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/map-route-path/python/bokeh/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/map-route-path/python/bokeh/plot-dark.html +quality_score: 90 review: strengths: - - Excellent use of Bokeh tile providers for geographic basemap (CartoDB Positron) - - Proper Web Mercator coordinate transformation for compatibility with tile providers - - Clean ColumnDataSource pattern for data management - - Interactive tools enabled (pan, wheel_zoom, box_zoom, reset) - - Distinct start (green circle) and end (red square) markers with white borders - - Both PNG and interactive HTML outputs generated + - Theme-aware CartoDB basemap switching (Positron for light, Dark Matter for dark) + is elegant and fully automatic based on ANYPLOT_THEME + - Dual encoding — green route line for connectivity + viridis gradient dots for + progression — is intentional and effective + - 'Full chrome token coverage: title, axis labels, ticks, legend, colorbar all use + INK/INK_SOFT tokens throughout both themes' + - Geographically accurate RMNP coordinates with plausible trail meander and realistic + scale + - 'captureBeyondViewport: True CDP approach correctly captures the full 3200x1800 + canvas' weaknesses: - - Route path is very complex with many loops - a more realistic hiking trail would - be more linear/less self-intersecting - - Path marker points (size 8) are relatively small for 200 points at 4800x2700 resolution - - Grid lines visible over the map can be slightly distracting - image_description: The plot displays a route path map overlaid on a CartoDB Positron - basemap showing the New York City area (Manhattan and surrounding boroughs visible). - A winding blue line traces a complex looping path that creates figure-8 patterns - across the map. The route is marked with small circular points along its length - that show a subtle color gradient from blue at the start to a darker shade toward - the end. A large green circle marks the start point and a red square marks the - end point, both with white borders for visibility. The title "map-route-path · - bokeh · pyplots.ai" appears in the upper left. A legend in the top right shows - "Start" and "End" labels. Axis labels show "Longitude" and "Latitude" with Web - Mercator coordinate values. The overall layout effectively utilizes the canvas - with good geographic context. + - Colorbar major labels at 28pt are slightly smaller than the main tick labels at + 34pt — a minor chrome hierarchy inconsistency; unify to 34pt for perfect balance + - The green route line (#009E73) is visually overridden by the viridis dots stacked + on top; consider drawing the dots first (or reducing dot alpha slightly) so the + green line remains visible as a separate layer + - 'DE-01 held back (5/8): no directional arrows along the trail path and no distance + markers — adding even optional direction glyphs would elevate the storytelling' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white border (#FAF8F1) surrounding CartoDB Positron basemap tile (cream/light-grey terrain) + Chrome: Title "Rocky Mountain Trail · map-route-path · python · bokeh · anyplot.ai" in dark ink at top — fully readable. Axis labels "Longitude" (x) and "Latitude" (y) in dark ink, tick labels in INK_SOFT. Colorbar "Trail Progress (%)" with 28pt labels, legend "Start"/"End" entries both readable. + Data: Green route line (#009E73) tracing SW→NE path. 200 viridis-gradient dots (deep purple at start → teal → yellow-green at end). Large green circle start marker (lower-left, lat≈40.343, lon≈-105.683), large orange square end marker (upper-right, lat≈40.370, lon≈-105.645). Viridis colorbar 0–100% on right. + Legibility verdict: PASS — all text readable against light background, no light-on-light failures + + Dark render (plot-dark.png): + Background: Near-black border (#1A1A17) surrounding CartoDB Dark Matter basemap tile (near-black terrain with light grey road/water features) + Chrome: Title, axis labels, tick labels all render in light-ink tokens (#F0EFE8 / #B8B7B0) — clearly readable against dark background. Legend and colorbar labels in INK_SOFT light tokens. No dark-on-dark text failures detected. + Data: Data colors are pixel-for-pixel identical to light render — green route line, viridis gradient dots, green start circle, orange-red end square all unchanged. Theme-switch from Positron to Dark Matter basemap is smooth. + Legibility verdict: PASS — all text readable against dark background, no dark-on-dark failures criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 29 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 9 - max: 10 + score: 7 + max: 8 passed: true - comment: Title at 28pt, axis labels at 22pt, tick labels at 18pt are all readable, - though the title could be slightly more prominent against the map + comment: 'All font sizes explicitly set (50pt title, 42pt axis labels, 34pt + ticks, 34pt legend). Minor: colorbar labels at 28pt slightly undersized + vs 34pt tick labels.' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements, all labels clear + comment: No overlapping text. Legend and colorbar don't conflict. - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Route line (width 4) and markers visible; path points (size 8) could - be slightly larger for 200 points + comment: Route line width 5, waypoint dots size 12, terminal markers size + 32 — well-adapted to 200 waypoints. - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Blue route, green start, red end are distinct and colorblind-safe + comment: Okabe-Ito categorical, Viridis continuous. Colorblind-safe. - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 + name: Layout & Canvas + score: 4 + max: 4 passed: true - comment: Good canvas utilization with map filling most of the area + comment: Plot fills ~75% of canvas. Balanced margins. Colorbar and legend + well-placed. - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 passed: true - comment: Labels are Longitude and Latitude without units (though units would - be degrees) + comment: Longitude and Latitude are descriptive for geographic coordinates. - id: VQ-07 - name: Grid & Legend - score: 1 + name: Palette Compliance + score: 2 max: 2 passed: true - comment: Legend well placed but grid alpha 0.3 visible over map could be distracting + comment: 'Route line #009E73 (pos 1), end marker #D55E00 (pos 2). Viridis + for continuous. Backgrounds #FAF8F1 light / #1A1A17 dark. Both themes correct.' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'CartoDB theme-aware basemap switching is elegant. Dual encoding + (line + gradient dots) shows intentional design. Not quite publication-ready: + green route line hidden by dots, no directional cues.' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Very subtle grid (alpha 0.06-0.09). Full chrome theming. Spines retained + (appropriate for map frame). Solid refinement. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Viridis progression (purple→yellow) communicates trail direction. + Start/end markers create focal anchors. Route shape suggests terrain-following + path. spec_compliance: - score: 23 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct route path map with connected waypoints - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Lat/lon correctly converted to Web Mercator and displayed - - id: SC-03 + comment: 'Correct: route path map with connected waypoints on geographic basemap.' + - id: SC-02 name: Required Features score: 4 - max: 5 + max: 4 passed: true - comment: Has connected path, start/end markers, color gradient, basemap; missing - direction arrows (spec says optional) - - id: SC-04 - name: Data Range + comment: Sequential connection, start/end markers with different shapes, viridis + gradient, CartoDB basemap, HTML output all present. + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All data points visible within map extent - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Legend correctly identifies Start and End - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: Longitude→X (Mercator), Latitude→Y (Mercator). All 200 waypoints + visible. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Correct format: map-route-path · bokeh · pyplots.ai' + comment: Title matches {Descriptive} · {spec-id} · python · bokeh · anyplot.ai + format. Legend Start/End correct. data_quality: - score: 18 - max: 20 + score: 15 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Shows winding path with loops, color progression, start/end markers; - path is quite complex (maybe overly so for a hiking trail) + comment: 'Shows all features: path connectivity, progression gradient, terminal + markers, basemap context, colorbar scale.' - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: GPS hiking trail in Central Park/NYC area is plausible real-world - scenario + comment: Rocky Mountain NP hiking trail — real, neutral, geographically accurate + coordinates. - id: DQ-03 name: Appropriate Scale score: 4 - max: 5 + max: 4 passed: true - comment: 200 waypoints is appropriate; coordinates centered around Central - Park are realistic + comment: Trail ~4km east, ~2.7km north — plausible hiking segment. RMNP lat/lon + range correct. code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -159,52 +189,63 @@ review: score: 3 max: 3 passed: true - comment: 'Linear script: imports → data → plot → save' + comment: 'Flat: sys.path fix → tokens → data → figure → layers → save.' - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: np.random.seed(42) ensures reproducibility + comment: np.random.seed(42). - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used (numpy, bokeh modules) + comment: All imports used. - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Uses current Bokeh 3.x API (add_tile with string) + comment: sys.path fix documented. captureBeyondViewport CDP approach elegant. + Appropriate complexity. - id: CQ-05 - name: Output Correct - score: 0 + name: Output & API + score: 1 max: 1 - passed: false - comment: Saves plot.png but also plot.html (correct for bokeh, but minor redundancy) - library_features: - score: 5 - max: 5 + passed: true + comment: Saves plot-{THEME}.html and plot-{THEME}.png for both themes. + library_mastery: + score: 8 + max: 10 items: - - id: LF-01 + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: ColumnDataSource, LinearColorMapper, ColorBar, Title layout, add_tile + — all idiomatic Bokeh for geo visualization. + - id: LM-02 name: Distinctive Features - score: 5 + score: 4 max: 5 passed: true - comment: 'Excellent use of Bokeh: tile providers for basemap, ColumnDataSource, - Web Mercator projection, interactive tools, HTML export' + comment: Theme-aware tile switching (Positron vs Dark Matter) is distinctively + Bokeh. LinearColorMapper+ColorBar with full chrome theming. CDP captureBeyondViewport + demonstrates deep ecosystem knowledge. verdict: APPROVED impl_tags: - dependencies: [] + dependencies: + - selenium techniques: + - colorbar - html-export - - hover-tooltips patterns: - data-generation - columndatasource dataprep: [] styling: - alpha-blending - - grid-styling + - custom-colormap + - edge-highlighting