Skip to content

feat(plotly): implement map-tile-background#7755

Merged
MarkusNeusinger merged 6 commits into
mainfrom
implementation/map-tile-background/plotly
May 27, 2026
Merged

feat(plotly): implement map-tile-background#7755
MarkusNeusinger merged 6 commits into
mainfrom
implementation/map-tile-background/plotly

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

Implementation: map-tile-background - python/plotly

Implements the python/plotly version of map-tile-background.

File: plots/map-tile-background/implementations/python/plotly.py

Parent Issue: #3756


🤖 impl-generate workflow

github-actions Bot and others added 2 commits May 27, 2026 15:20
Regen from quality 91. Addressed:
- Canvas: corrected from width=1600,height=900,scale=3 (4800×2700) to width=800,height=450,scale=4 (3200×1800)
- Theme support: added ANYPLOT_THEME env var, dark/light mode, saves plot-{THEME}.png + plot-{THEME}.html
- Colorscale: replaced custom blue-to-yellow with anyplot imprint_seq (green→blue)
- Map style: theme-adaptive (open-street-map light / carto-darkmatter dark)
- Label size: increased from 12px to 14px for better readability
- Chrome: fully theme-adaptive title, colorbar, label colors
- autosize=False added per library spec
- sys.path fix for plotly.py filename shadowing installed package
- pyproject.toml: added plotly.py to ruff E402 per-file-ignores (same as pygal/altair/bokeh etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 27, 2026

AI Review - Attempt 1/3

Image Description

Light render (plot-light.png): The plot shows a 3200×1800 landscape map with OpenStreetMap tiles covering Europe. The outer paper background is the correct warm off-white (#FAF8F1). Fifteen bubble markers are scattered across Europe, sized proportionally (15–50px diameter) and colored using the anyplot imprint_seq colorscale (#009E73 green for low-visitor, #4467A3 blue for high-visitor landmarks). A vertical colorbar on the right with an #FFFDF6 elevated background labels "Annual Visitors" with comma-formatted tick values. Landmark name labels (14px, dark ink, top-center position) are visible but show crowding in the Netherlands/Belgium/France cluster (Big Ben, Anne Frank House, Manneken Pis, Eiffel Tower) and significant overlap between the two Prague landmarks (Charles Bridge and Old Town Square whose coordinates differ by only ~0.001°). Title "European Landmarks by Visitor Count" (16px) and subtitle "map-tile-background · python · plotly · anyplot.ai" (12px) are both clearly readable. All text is readable against the light background. OSM attribution visible bottom-right.

Dark render (plot-dark.png): The same plot is rendered with CartoDB Dark Matter tiles. The outer paper background is the correct warm near-black (#1A1A17). Title and subtitle text are rendered in #F0EFE8 (light ink) — clearly readable against the dark background. Colorbar text is also light-colored on the #242420 elevated background — readable. Data colors are identical to the light render: same imprint_seq green→blue colorscale with no theme-related drift. Landmark name labels are light (#F0EFE8) against the dark map — readable throughout. No dark-on-dark failures detected. CARTO attribution visible bottom-right. Same Prague cluster overlap present.

Both paragraphs are required. A review that only describes one render is invalid.

Score: 85/100

Category Score Max
Visual Quality 24 30
Design Excellence 12 20
Spec Compliance 15 15
Data Quality 15 15
Code Quality 10 10
Library Mastery 9 10
Total 85 100

Visual Quality (24/30)

  • VQ-01: Text Legibility (6/8) — Font sizes explicitly set throughout; all text readable in both themes. Deduction for label crowding in the western Europe cluster and Prague landmark overlap.
  • VQ-02: No Overlap (4/6) — Charles Bridge (lat=50.0865/lon=14.4114) and Old Town Square (lat=50.0870/lon=14.4208) are virtually co-located at zoom 3.5, causing marker+label overlap. Additional crowding in the Big Ben/Anne Frank House/Manneken Pis/Eiffel Tower cluster.
  • VQ-03: Element Visibility (5/6) — Good 15–50px marker range with 0.85 opacity; dual encoding makes hierarchy clear. Smallest markers (Manneken Pis at 15px) are still discernible.
  • VQ-04: Color Accessibility (2/2) — imprint_seq (green→blue) is CVD-safe; adequate contrast on both light and dark tile backgrounds.
  • VQ-05: Layout & Canvas (3/4) — Map fills canvas well; margins l=20/r=20 are tighter than recommended but acceptable for a map. Colorbar well-placed within bounds.
  • VQ-06: Axis Labels & Title (2/2) — Descriptive title; colorbar labeled "Annual Visitors" with comma-formatted ticks. N/A for axis labels on map type.
  • VQ-07: Palette Compliance (2/2) — imprint_seq correctly used for single-polarity continuous data; paper_bgcolor #FAF8F1 (light) / #1A1A17 (dark); theme-adaptive chrome and map styles; data colors identical across themes.

Design Excellence (12/20)

  • DE-01: Aesthetic Sophistication (5/8) — Above well-configured default: dual size+color encoding, theme-adaptive tile styles (open-street-map / carto-darkmatter), custom colorbar with matching theme colors, clean anyplot colorscale. Lacks a focal annotation highlighting the top attraction.
  • DE-02: Visual Refinement (3/6) — Custom colorbar styling (bgcolor, bordercolor, borderwidth, thickness), explicit opacity, theme-adaptive backgrounds. No unnecessary clutter. Solid but not fully polished.
  • DE-03: Data Storytelling (4/6) — Size variation creates natural visual hierarchy; Eiffel Tower and Colosseum immediately stand out as top attractions. Geographic clustering vs sparsity is visible. No explicit annotation or emphasis on the most-visited landmark to anchor the story.

Spec Compliance (15/15)

  • SC-01: Plot Type (5/5) — Correct tile-based map using go.Scattermap with open-street-map and carto-darkmatter providers.
  • SC-02: Required Features (4/4) — Multiple tile providers, appropriate zoom (3.5 over Europe), markers with opacity/contrast, attribution visible, interactive HTML, color+size encoding. Clustering not needed for 15 sparse points.
  • SC-03: Data Mapping (3/3) — lat/lon correctly mapped; visitor count encoded in both size and color; all 15 points visible.
  • SC-04: Title & Legend (3/3) — Optional descriptive prefix "European Landmarks by Visitor Count" on line 1; "map-tile-background · python · plotly · anyplot.ai" on line 2 via HTML <br>. All required components present. showlegend=False correct (colorbar used).

Data Quality (15/15)

  • DQ-01: Feature Coverage (6/6) — Geographic distribution, size encoding, color encoding, text labels, hover tooltips — all spec data dimensions used.
  • DQ-02: Realistic Context (5/5) — Real European landmarks with recognizable names and plausible visitor counts. Neutral tourism/culture topic.
  • DQ-03: Appropriate Scale (4/4) — Visitor counts (500K–7.4M) align with real-world figures; Eiffel Tower ~7M and Colosseum ~7.4M match known data; coordinates accurate.

Code Quality (10/10)

  • CQ-01: KISS Structure (3/3) — Linear flow: imports → data → figure → layout → save. No functions or classes.
  • CQ-02: Reproducibility (2/2) — np.random.seed(42) set; data is hardcoded so deterministic.
  • CQ-03: Clean Imports (2/2) — os, sys, numpy, plotly.graph_objects — all used.
  • CQ-04: Code Elegance (2/2) — Clean Pythonic code; sys.path manipulation is a justified pattern to prevent plotly.py from shadowing the package. No fake UI elements.
  • CQ-05: Output & API (1/1) — Saves plot-{THEME}.png and plot-{THEME}.html. Uses current go.Scattermap API (not deprecated Scattermapbox).

Library Mastery (9/10)

  • LM-01: Idiomatic Usage (5/5) — Expert use of go.Scattermap with map layout key (current idiomatic Plotly map API); theme-adaptive map style switching is idiomatic.
  • LM-02: Distinctive Features (4/5) — Uses Plotly-distinctive tile map layers, interactive HTML export, custom hovertemplate, and colorbar. Minor deduction as the implementation is solid but doesn't push into advanced Plotly territory (e.g., no choropleth layers, no map projections, no density_mapbox).

Score Caps Applied

  • None

Strengths

  • Theme-adaptive tile map styles (open-street-map / carto-darkmatter) — genuinely Plotly-distinctive feature well-utilized
  • Dual size+color encoding for visitor count creates clear visual hierarchy with minimal effort
  • Correct anyplot imprint_seq colorscale with fully theme-adaptive chrome in both renders
  • Perfect spec compliance: tile background, zoom, attribution, interactive output, all data fields used
  • Realistic dataset with verifiable real-world data (European landmark visitor counts)

Weaknesses

  • Prague label/marker overlap: Charles Bridge (lat=50.0865/lon=14.4114) and Old Town Square (lat=50.0870/lon=14.4208) differ by only ~0.001° — at zoom 3.5 they render on top of each other. Replace one with a landmark from a different city (e.g., Dubrovnik City Walls, Plitvice Lakes, Alhambra, Uffizi Gallery).
  • Western Europe label crowding: Big Ben, Anne Frank House, Manneken Pis, and Eiffel Tower labels crowd at zoom 3.5. Reduce textfont size to 11px or alternate textposition (some "top center", some "bottom center") to spread labels.
  • Missing focal annotation: No visual emphasis on the top-visited attraction. Add an annotation or data label callout (e.g., "Most visited: Colosseum 7.4M") to anchor the story and improve DE-03.

Issues Found

  1. VQ-02 MODERATE: Two Prague landmarks with near-identical coordinates (~0.001° apart) cause marker and label overlap at zoom 3.5
    • Fix: Replace one Prague landmark (e.g., swap Old Town Square for a geographically distant Czech/European attraction such as Alhambra, Uffizi, or Dubrovnik Old Town)
  2. DE-01/DE-03 BELOW THRESHOLD: No focal point annotation; story is implicit in size variation only
    • Fix: Add fig.add_annotation() callout for the highest-visitor landmark to create an explicit focal point and improve storytelling

AI Feedback for Next Attempt

Fix the geographic overlap first: replace one of the two nearly co-located Prague landmarks with a site from a different European city so markers don't stack. Reduce textfont size to 11px and alternate textposition between "top center" and "bottom center" for the crowded western Europe cluster. Add a fig.add_annotation() callout on the most-visited landmark (Colosseum or Eiffel Tower) pointing to its marker with the formatted visitor count — this creates a clear focal point, improves DE-03, and demonstrates Plotly's annotation layer as a distinctive feature.

Verdict: REJECTED

@github-actions github-actions Bot added quality:85 Quality score 85/100 ai-rejected Quality not OK, triggers update labels May 27, 2026
@github-actions github-actions Bot added ai-attempt-1 First repair attempt and removed ai-rejected Quality not OK, triggers update labels May 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor Author

🔧 Repair Attempt 1/4

Applied fixes based on AI review feedback.

Status: Repair completed, re-triggering review...


🤖 impl-repair

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 27, 2026

AI Review - Attempt 2/3

Image Description

Light render (plot-light.png): Warm off-white (#FAF8F1) paper background surrounds OpenStreetMap tile area showing street-level Europe. Two-line title "European Landmarks by Visitor Count" (16px dark ink) + subtitle "map-tile-background · python · plotly · anyplot.ai" (12px) both clearly readable at top. Colorbar on the right labelled "Annual Visitors" with dark INK/INK_SOFT tick labels — fully legible. Annotation callout box "★ Top attraction / Colosseum · 7.4M visitors/yr" with #009E73 border and #FFFDF6 elevated fill sits cleanly in lower-center. 15 circular markers sized 15–50 px, colored via imprint_seq (green→blue), positioned accurately across Europe. Alternating top/bottom label placement mitigates NW-Europe cluster crowding. Attribution "© OpenStreetMap contributors" bottom-right. All text readable against the light background — legibility verdict: PASS.

Dark render (plot-dark.png): Warm near-black (#1A1A17) background surrounding Carto Dark Matter tiles. Title and subtitle rendered in light #F0EFE8 INK — clearly readable against dark surface. Colorbar title and ticks in light #B8B7B0 INK_SOFT — fully legible. Landmark text labels use light INK (#F0EFE8) at 11px against the dark map — readable. Annotation box with green border and #242420 elevated fill provides good contrast. Data marker colors are identical to the light render (imprint_seq green→blue unchanged). Attribution updated to "© CARTO, © OpenStreetMap contributors". No dark-on-dark failures detected — legibility verdict: PASS.

Both paragraphs required — both renders confirmed inspected.

Score: 87/100

Category Score Max
Visual Quality 26 30
Design Excellence 15 20
Spec Compliance 15 15
Data Quality 15 15
Code Quality 8 10
Library Mastery 8 10
Total 87 100

Visual Quality (26/30)

  • VQ-01: Text Legibility (6/8) — All sizes explicitly set (title 16px, subtitle 12px, labels 11px, colorbar 10–12px); readable in both themes. Minor: 11px landmark labels slightly small for NW-Europe cluster density.
  • VQ-02: No Overlap (5/6) — Two-trace alternating textposition approach effectively reduces crowding. Minor residual density near Big Ben/Manneken Pis but no critical occlusion.
  • VQ-03: Element Visibility (5/6) — Markers 15–50 px at 0.85 opacity are well visible on both tile styles. Minor: some smaller markers blend slightly with busy tile backgrounds.
  • VQ-04: Color Accessibility (2/2) — imprint_seq (green→blue) is CVD-safe. Size as redundant encoding adds second channel.
  • VQ-05: Layout & Canvas (4/4) — Canvas gate passed. Map fills canvas well; appropriate margins; colorbar properly placed. No content cut off.
  • VQ-06: Axis Labels & Title (2/2) — Map has no traditional axes; colorbar labelled "Annual Visitors"; descriptive main title. Appropriate for this chart type.
  • VQ-07: Palette Compliance (2/2) — imprint_seq [[0.0,"#009E73"],[1.0,"#4467A3"]] is the correct sequential cmap. Backgrounds #FAF8F1/#1A1A17 correct. Theme chrome fully adaptive in both renders.

Design Excellence (15/20)

  • DE-01: Aesthetic Sophistication (6/8) — Strong design: theme-adaptive map tile providers (OSM vs Carto Dark Matter), dual size+color encoding, styled annotation callout, refined colorbar. Clearly above library defaults.
  • DE-02: Visual Refinement (4/6) — Good: colorbar styled with border/bgcolor/len, opacity 0.85, themed annotation border. Could improve with thin marker outline strokes for legibility on busy tile areas.
  • DE-03: Data Storytelling (5/6) — Excellent: size+color dual encoding creates immediate visual hierarchy, annotation callout highlights top attraction, geographic context itself narrates the tourism distribution story.

Spec Compliance (15/15)

  • SC-01: Plot Type (5/5) — Correct: scatter map with tile backgrounds (OSM light, Carto Dark Matter dark). Multiple providers demonstrated.
  • SC-02: Required Features (4/4) — All spec features present: tile background, markers on tiles, zoom/center, tile attribution, interactive hover, color+size encoding.
  • SC-03: Data Mapping (3/3) — Lat/lon correctly mapped; visitor count correctly encoded in size and color channels.
  • SC-04: Title & Legend (3/3) — Descriptive prefix + mandated map-tile-background · python · plotly · anyplot.ai subtitle. Format accepted.

Data Quality (15/15)

  • DQ-01: Feature Coverage (6/6) — Full range of visitor magnitudes (500K–7.4M), geographic spread across Europe, continental zoom, dual encoding demonstrated.
  • DQ-02: Realistic Context (5/5) — Real European landmarks, accurate names, neutral non-controversial tourism topic.
  • DQ-03: Appropriate Scale (4/4) — GPS coordinates geographically accurate; visitor counts align with known real-world figures.

Code Quality (8/10)

  • CQ-01: KISS Structure (2/3) — Mostly flat but _select() helper function and sys.path manipulation introduce minor non-flat elements.
  • CQ-02: Reproducibility (2/2) — np.random.seed(42) set.
  • CQ-03: Clean Imports (2/2) — All imports used.
  • CQ-04: Code Elegance (1/2) — Acceptable but slightly verbose: two-trace approach duplicates the marker dict. A single trace with per-point textposition list would be more elegant.
  • CQ-05: Output & API (1/1) — Saves plot-{THEME}.png + plot-{THEME}.html; uses go.Scattermap (current API).

Library Mastery (8/10)

  • LM-01: Idiomatic Usage (4/5) — Strong: go.Scattermap, update_layout map config, colorbar dict, hovertemplate, HTML export. Minor deduction for two-trace pattern where one trace with textposition array suffices.
  • LM-02: Distinctive Features (4/5) — Distinctly Plotly: tile-based map with go.Scattermap, theme-adaptive map styles (open-street-map vs carto-darkmatter), interactive hover, HTML export — features not replicable in other libraries.

Score Caps Applied

  • None — no zero categories, DE-01 = 6 > 2 so cap does not apply.

Strengths

  • Theme-adaptive map tile providers (OpenStreetMap for light, Carto Dark Matter for dark) is a sophisticated choice that makes both renders feel native to their theme
  • Double encoding of visitor count via marker size AND imprint_seq colorscale creates strong, immediate visual hierarchy
  • "★ Top attraction" annotation callout with themed border highlights the Colosseum as Add workflow diagram for new prototype discovery #1 — excellent data storytelling
  • Real European landmarks with accurate GPS coordinates and factually correct visitor counts
  • Full spec compliance: tile background, size/color encoding, attribution, interactive hover, HTML export

Weaknesses

  • Landmark label font size (11px) is slightly small for the NW-Europe cluster density — 12–13px would improve legibility at mobile widths
  • _select() helper function and two-trace approach slightly violate KISS — a single trace with a per-point textposition list is cleaner and avoids marker dict duplication
  • Thin marker outline strokes (e.g. line=dict(width=1, color='white')) would improve contrast of smaller markers against busy tile backgrounds

Issues Found

  1. CQ-01/CQ-04 MINOR: _select() helper + two-trace duplication — Fix: single go.Scattermap trace with textposition=[...] list per-point
  2. VQ-01/DE-02 MINOR: 11px labels slightly small + no marker outlines — Fix: increase label size to 12px, add marker.line=dict(width=1, color='rgba(255,255,255,0.6)') for definition on busy tiles

AI Feedback for Next Attempt

Implementation is high quality and meets the approval threshold at attempt 2. If a further repair is needed: consolidate two traces into one using per-point textposition list, increase label fontsize from 11 to 12px, and add a subtle white outline (marker.line) to improve marker legibility on busy OSM tile backgrounds. Design excellence is already strong — focus repairs on code elegance (KISS) and minor visual refinements.

Verdict: APPROVED

@github-actions github-actions Bot added quality:87 Quality score 87/100 ai-approved Quality OK, ready for merge and removed quality:85 Quality score 85/100 labels May 27, 2026
@MarkusNeusinger MarkusNeusinger merged commit ee764f4 into main May 27, 2026
@MarkusNeusinger MarkusNeusinger deleted the implementation/map-tile-background/plotly branch May 27, 2026 15:40
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-approved Quality OK, ready for merge ai-attempt-1 First repair attempt quality:87 Quality score 87/100

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant