diff --git a/plots/scatter-map-geographic/implementations/python/bokeh.py b/plots/scatter-map-geographic/implementations/python/bokeh.py index 474d74d19a..addce38e92 100644 --- a/plots/scatter-map-geographic/implementations/python/bokeh.py +++ b/plots/scatter-map-geographic/implementations/python/bokeh.py @@ -1,18 +1,30 @@ -""" pyplots.ai +""" anyplot.ai scatter-map-geographic: Scatter Map with Geographic Points -Library: bokeh 3.8.2 | Python 3.13.11 -Quality: 91/100 | Created: 2026-01-10 +Library: bokeh 3.9.0 | Python 3.13.13 +Quality: 84/100 | Updated: 2026-05-18 """ +import os +import time +from pathlib import Path + import numpy as np -from bokeh.io import export_png, save +from bokeh.io import output_file, save from bokeh.models import ColorBar, ColumnDataSource, LinearColorMapper, WMTSTileSource from bokeh.plotting import figure -from bokeh.resources import CDN +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" +BRAND = "#009E73" # Data - Major cities with population and elevation -np.random.seed(42) cities = { "name": [ "New York", @@ -179,8 +191,7 @@ # Convert lat/lon to Web Mercator projection (required for tile maps) def lat_lon_to_mercator(lat, lon): - """Convert latitude/longitude to Web Mercator (EPSG:3857) coordinates.""" - k = 6378137 # Earth radius in meters + k = 6378137 x = lon * (k * np.pi / 180.0) y = np.log(np.tan((90 + lat) * np.pi / 360.0)) * k return x, y @@ -192,7 +203,7 @@ def lat_lon_to_mercator(lat, lon): # Normalize size based on population (scaled for visibility on large canvas) populations = np.array(cities["population"]) -sizes = 25 + (populations / populations.max()) * 55 # Range: 25-80 for 4800x2700 +sizes = 25 + (populations / populations.max()) * 55 # Create color mapper for elevation elevations = np.array(cities["elevation"]) @@ -218,7 +229,7 @@ def lat_lon_to_mercator(lat, lon): height=2700, x_axis_type="mercator", y_axis_type="mercator", - title="World Cities · scatter-map-geographic · bokeh · pyplots.ai", + title="scatter-map-geographic · python · bokeh · anyplot.ai", tools="pan,wheel_zoom,box_zoom,reset,hover,save", tooltips=[ ("City", "@name"), @@ -228,7 +239,7 @@ def lat_lon_to_mercator(lat, lon): ], ) -# Add map tiles (CartoDB Positron for clean basemap) +# Add map tiles tile_url = "https://tiles.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png" tile_source = WMTSTileSource(url=tile_url) p.add_tile(tile_source) @@ -241,7 +252,7 @@ def lat_lon_to_mercator(lat, lon): size="size", fill_color={"field": "elevation", "transform": color_mapper}, fill_alpha=0.75, - line_color="#306998", + line_color=BRAND, line_width=2.5, ) @@ -252,7 +263,7 @@ def lat_lon_to_mercator(lat, lon): title_text_font_size="22pt", major_label_text_font_size="18pt", label_standoff=15, - border_line_color=None, + border_line_color=INK_SOFT, location=(0, 0), width=40, height=600, @@ -260,19 +271,42 @@ def lat_lon_to_mercator(lat, lon): ) p.add_layout(color_bar, "right") +# Add size legend for population by creating reference points + +legend_items = [("1M", 1.0), ("10M", 10.0), ("25M", 24.9)] +legend_x_pos = -15000000 +legend_y_start = 7500000 +y_offset = 800000 + +for i, (label, pop) in enumerate(legend_items): + size = 25 + (pop / populations.max()) * 55 + y = legend_y_start - (i * y_offset) + p.scatter(x=[legend_x_pos], y=[y], size=size, fill_color=BRAND, fill_alpha=0.75, line_color=BRAND, line_width=2.5) + p.text(x=[legend_x_pos + 2000000], y=[y], text=[label], text_font_size="18pt", text_color=INK_SOFT) + # Styling -p.title.text_font_size = "32pt" -p.title.text_color = "#306998" -p.xaxis.axis_label = "Longitude" -p.yaxis.axis_label = "Latitude" -p.xaxis.axis_label_text_font_size = "24pt" -p.yaxis.axis_label_text_font_size = "24pt" +p.title.text_font_size = "28pt" +p.title.text_color = INK +p.xaxis.axis_label = "Longitude (°)" +p.yaxis.axis_label = "Latitude (°)" +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" -p.xaxis.axis_line_width = 2 -p.yaxis.axis_line_width = 2 -p.xaxis.major_tick_line_width = 2 -p.yaxis.major_tick_line_width = 2 +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 + +# Background and border +p.background_fill_color = PAGE_BG +p.border_fill_color = PAGE_BG +p.outline_line_color = INK_SOFT +p.outline_line_width = 2 # Set view to show entire world p.x_range.start = -15000000 @@ -280,12 +314,25 @@ def lat_lon_to_mercator(lat, lon): p.y_range.start = -6000000 p.y_range.end = 8500000 -# Background and border -p.background_fill_color = None -p.border_fill_color = "#ffffff" -p.outline_line_color = "#306998" -p.outline_line_width = 2 +# Save as interactive HTML +output_file(f"plot-{THEME}.html") +save(p) -# Save as PNG and HTML -export_png(p, filename="plot.png") -save(p, filename="plot.html", title="World Cities · scatter-map-geographic · bokeh", resources=CDN) +# 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) +driver.save_screenshot(f"plot-{THEME}.png") +driver.quit() diff --git a/plots/scatter-map-geographic/metadata/python/bokeh.yaml b/plots/scatter-map-geographic/metadata/python/bokeh.yaml index a3f41c0f41..a6c13b342e 100644 --- a/plots/scatter-map-geographic/metadata/python/bokeh.yaml +++ b/plots/scatter-map-geographic/metadata/python/bokeh.yaml @@ -1,216 +1,229 @@ library: bokeh +language: python specification_id: scatter-map-geographic created: '2026-01-10T04:04:15Z' -updated: '2026-01-10T04:06:57Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20872457397 +updated: '2026-05-18T18:06:42Z' +generated_by: claude-haiku +workflow_run: 26050543079 issue: 3617 -python_version: 3.13.11 -library_version: 3.8.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/scatter-map-geographic/bokeh/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/scatter-map-geographic/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/scatter-map-geographic/python/bokeh/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-map-geographic/python/bokeh/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-map-geographic/python/bokeh/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-map-geographic/python/bokeh/plot-dark.html +quality_score: 84 review: strengths: - - Excellent use of Bokeh's tile mapping capabilities with WMTSTileSource and CartoDB - basemap - - Interactive tooltips showing city name, population, elevation, and coordinates - - Clean Viridis colormap for colorblind-safe elevation encoding - - Appropriate Web Mercator projection conversion for accurate positioning - - Good marker sizing that differentiates population levels - - Both static PNG and interactive HTML outputs generated + - Excellent spec compliance with all required features present and functional + - Perfect data quality with realistic cities and accurate geographic coordinates + - Strong dual encoding of size for population and color for elevation + - Proper Web Mercator projection with interactive zoom/pan capabilities + - Comprehensive hover tooltips with city name, population, elevation, and coordinates + - 'Light render perfectly theme-compliant with warm #FAF8F1 background' + - Clean code structure with explicit font sizing for high-DPI rendering weaknesses: - - 'Missing size legend to explain population encoding (spec notes: "Add a size legend - when size varies")' - - Title includes "World Cities" prefix instead of starting directly with spec-id - - Axis labels lack degree symbols/units - - Helper function slightly breaks KISS structure (though necessary for projection) + - Dark render canvas background is pure black (#000000) instead of required warm + dark (#1A1A17) - identical to Attempt 1, fix was not applied + - Bokeh HTML page background not using theme-adaptive color - bokeh background_fill_color + may only apply to plot area, not HTML page element + - Generic styling with default colors and no design sophistication + - Minimal visual refinement - no spine customization, no grid styling, no whitespace + enhancement image_description: |- - The plot displays a world map using CartoDB Positron basemap tiles showing 30 major cities across all continents. Cities are represented as scatter points with: - - Position: Correctly placed by latitude/longitude (converted to Web Mercator projection) - - Size encoding: Point sizes vary from small (~25px) to large (~80px) representing population (millions) - Tokyo, Shanghai, Beijing, Mumbai show largest circles - - Color encoding: Viridis colormap from purple (low elevation) to yellow (high elevation) - Mexico City appears yellow-green (2240m), São Paulo and Johannesburg show greenish tones, most coastal cities are purple/blue - - Color bar: Located on right side showing "Elevation (m)" scale from 0 to ~2200m - - Title: "World Cities · scatter-map-geographic · bokeh · pyplots.ai" in blue (#306998) at top-left - - Axis labels: "Longitude" (x-axis) and "Latitude" (y-axis) - - Basemap: Clean light gray CartoDB tiles with country names and boundaries visible - - Layout: World view from approximately -150° to +180° longitude, -60° to +70° latitude + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 - correct theme token applied + Chrome: Title (28pt, dark #1A1A17), axis labels (22pt), tick labels (18pt) - all clearly readable with excellent contrast + Data: Viridis colormap for elevation (dark purple to yellow), brand green borders on scatter points, size varies 25-80 for population encoding + Legibility verdict: PASS - all text perfectly readable, theme colors correctly applied + + Dark render (plot-dark.png): + Background: PURE BLACK #000000 - FAILED - should be warm dark #1A1A17 + Chrome: Title, axis labels, tick labels appear in light colors (INK #F0EFE8, INK_SOFT #B8B7B0) and are technically readable but compromised by harsh black background + Data: Viridis colors correctly identical to light render (no recoloring), brand green borders intact + Legibility verdict: FAIL - dark-on-dark theme violation; pure black canvas background is not the warm dark (#1A1A17) required by style guide. This is identical to Attempt 1 - the issue was not fixed. criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 25 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 9 - max: 10 - passed: true - comment: Title at 32pt, axis labels at 24pt, tick labels at 18pt, colorbar - title at 22pt - all readable, though colorbar title could be slightly larger + score: 7 + max: 8 + passed: false + comment: Light render perfect; dark render compromised by background issue - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements, markers well-spaced globally + comment: Clean spacing; no text collisions - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Markers sized 25-80px appropriate for 30 points, alpha=0.75 handles - sparse overlap well + comment: 30 points perfectly adapted; size range 25-80 optimal - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Viridis colormap is colorblind-safe, good contrast against light - basemap + comment: Viridis perceptually uniform; brand green high contrast - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 + name: Layout & Canvas + score: 4 + max: 4 passed: true - comment: World map fills canvas well, colorbar positioned appropriately on - right + comment: Balanced proportions; nothing cut off - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 passed: true - comment: Labels say Longitude and Latitude but lack units (could say Longitude - (°)) + comment: Descriptive with units - id: VQ-07 - name: Grid & Legend - score: 1 + name: Palette Compliance + score: 0 max: 2 + passed: false + comment: 'FAILED - dark render canvas pure black instead of #1A1A17' + design_excellence: + score: 10 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 + passed: false + comment: Generic defaults; no design refinement + - id: DE-02 + name: Visual Refinement + score: 2 + max: 6 + passed: false + comment: No spine customization; minimal styling + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 passed: true - comment: Colorbar well-placed; no explicit size legend for population encoding + comment: Clear dual encoding guides understanding 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 geographic scatter plot on map tiles - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Lat/lon correctly converted to Mercator, mapped to x/y - - id: SC-03 + comment: Correct scatter-map with Web Mercator + - id: SC-02 name: Required Features score: 4 - max: 5 + max: 4 passed: true - comment: Has basemap, color encoding, size encoding, tooltips; missing explicit - size legend - - id: SC-04 - name: Data Range + comment: 'All features present: basemap, pan/zoom, tooltips, legends' + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: World view shows all 30 cities without clipping - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Colorbar correctly labeled Elevation (m) - - id: SC-06 - name: Title Format - score: 1 - max: 2 + comment: X/Y correct; Web Mercator transforms properly + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: Title has extra World Cities prefix instead of starting directly - with spec-id + comment: Correct title format; legends present 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 cities across all continents, varies population (0.7M-24.9M) - and elevation (0-2240m) + comment: 6 continents; elevation 0-2240m; population 0.7-24.9M - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Real world cities with actual population and elevation data - neutral, - educational context + comment: Real cities with accurate data; neutral - id: DQ-03 name: Appropriate Scale score: 4 - max: 5 + max: 4 passed: true - comment: Populations and elevations are realistic; some elevation values approximate + comment: Values factually correct code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 name: KISS Structure - score: 2 + score: 3 max: 3 passed: true - comment: Mostly linear flow, but includes a helper function lat_lon_to_mercator - (necessary for projection) + comment: Linear flow; no unnecessary functions - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: Uses np.random.seed(42) though data is deterministic + comment: Hard-coded data; deterministic - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used appropriately + 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: Using current Bokeh APIs + comment: Proper ColumnDataSource; idiomatic patterns - id: CQ-05 - name: Output Correct + name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png and plot.html - library_features: - score: 5 - max: 5 + comment: Saves as plot-{THEME}.png and .html + library_mastery: + score: 9 + max: 10 items: - - id: LF-01 - name: Distinctive Features + - id: LM-01 + name: Idiomatic Usage score: 5 max: 5 passed: true - comment: 'Excellent use of Bokeh features: WMTSTileSource for tile maps, ColumnDataSource, - interactive tooltips, mercator axis types, ColorBar, both PNG and HTML export' - verdict: APPROVED + comment: 'Expert bokeh patterns: figure, ColumnDataSource, ColorMapper, WMTS + tiles' + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: false + comment: Good use of ColorBar and custom legend; could leverage more bokeh-specific + features + verdict: REJECTED impl_tags: - dependencies: [] + dependencies: + - selenium techniques: - colorbar + - custom-legend - hover-tooltips - html-export patterns: - - data-generation - columndatasource + - data-generation dataprep: [] - styling: - - custom-colormap - - alpha-blending - - edge-highlighting + styling: []