diff --git a/plots/contour-basic/implementations/python/altair.py b/plots/contour-basic/implementations/python/altair.py index ea6f158014..50c4514c23 100644 --- a/plots/contour-basic/implementations/python/altair.py +++ b/plots/contour-basic/implementations/python/altair.py @@ -1,104 +1,146 @@ -""" pyplots.ai +""" anyplot.ai contour-basic: Basic Contour Plot -Library: altair 6.0.0 | Python 3.13.11 -Quality: 98/100 | Created: 2025-12-23 +Library: altair 6.1.0 | Python 3.14.4 +Quality: 86/100 | Updated: 2026-04-24 """ -import altair as alt -import numpy as np -import pandas as pd +import importlib +import os +import sys -# Data - 2D Gaussian function on meshgrid -np.random.seed(42) -x = np.linspace(-3, 3, 80) -y = np.linspace(-3, 3, 80) +# Drop script directory from sys.path so the `altair` package resolves, not this file +sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != os.path.dirname(os.path.abspath(__file__))] +alt = importlib.import_module("altair") +np = importlib.import_module("numpy") +pd = importlib.import_module("pandas") + + +# 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" + +# Data — simulated topographic elevation map of a 10km x 10km mountain region +x = np.linspace(0, 10, 80) +y = np.linspace(0, 10, 80) X, Y = np.meshgrid(x, y) -# Two overlapping Gaussian peaks for interesting contour patterns -Z = np.exp(-((X - 1) ** 2 + (Y - 1) ** 2)) + 0.8 * np.exp(-((X + 1) ** 2 + (Y + 0.5) ** 2)) +elevation = ( + 850 * np.exp(-((X - 7) ** 2 + (Y - 7) ** 2) / 4.0) + + 550 * np.exp(-((X - 2.5) ** 2 + (Y - 3) ** 2) / 3.0) + - 180 * np.exp(-((X - 5) ** 2 + (Y - 5) ** 2) / 8.0) + + 12 * X + + 350 +) -# Flatten to DataFrame for filled contour background -df_fill = pd.DataFrame({"x": X.ravel(), "y": Y.ravel(), "z": Z.ravel()}) +df_fill = pd.DataFrame({"x": X.ravel(), "y": Y.ravel(), "elevation": elevation.ravel()}) -# Extract contour line segments using marching squares algorithm -levels = np.linspace(0.1, 1.6, 10) +# Contour line segments via marching squares +levels = np.arange(400, 1251, 100) segments = [] for level in levels: for i in range(len(y) - 1): for j in range(len(x) - 1): - # Get 4 corners of cell - z00, z10, z01, z11 = Z[i, j], Z[i + 1, j], Z[i, j + 1], Z[i + 1, j + 1] + z00, z10, z01, z11 = elevation[i, j], elevation[i + 1, j], elevation[i, j + 1], elevation[i + 1, j + 1] x0, x1, y0, y1 = x[j], x[j + 1], y[i], y[i + 1] - # Calculate which corners are above/below level (binary case) case = int(z00 >= level) | (int(z10 >= level) << 1) | (int(z01 >= level) << 2) | (int(z11 >= level) << 3) if case == 0 or case == 15: continue - # Find edge crossings via linear interpolation edges = [] - if (case & 1) != (case >> 1) & 1: # Bottom edge (z00 to z10) + if (case & 1) != (case >> 1) & 1: t = (level - z00) / (z10 - z00) if z10 != z00 else 0.5 edges.append((x0, y0 + t * (y1 - y0))) - if (case >> 1) & 1 != (case >> 3) & 1: # Right edge (z10 to z11) + if (case >> 1) & 1 != (case >> 3) & 1: t = (level - z10) / (z11 - z10) if z11 != z10 else 0.5 edges.append((x0 + t * (x1 - x0), y1)) - if (case >> 2) & 1 != (case >> 3) & 1: # Top edge (z01 to z11) + if (case >> 2) & 1 != (case >> 3) & 1: t = (level - z01) / (z11 - z01) if z11 != z01 else 0.5 edges.append((x1, y0 + t * (y1 - y0))) - if (case & 1) != (case >> 2) & 1: # Left edge (z00 to z01) + if (case & 1) != (case >> 2) & 1: t = (level - z00) / (z01 - z00) if z01 != z00 else 0.5 edges.append((x0 + t * (x1 - x0), y0)) - # Connect edge crossings as line segments if len(edges) >= 2: segments.append( - {"x1": edges[0][0], "y1": edges[0][1], "x2": edges[1][0], "y2": edges[1][1], "level": level} + {"x1": edges[0][0], "y1": edges[0][1], "x2": edges[1][0], "y2": edges[1][1], "level": float(level)} ) - if len(edges) == 4: # Saddle point case + if len(edges) == 4: segments.append( - {"x1": edges[2][0], "y1": edges[2][1], "x2": edges[3][0], "y2": edges[3][1], "level": level} + { + "x1": edges[2][0], + "y1": edges[2][1], + "x2": edges[3][0], + "y2": edges[3][1], + "level": float(level), + } ) df_lines = pd.DataFrame(segments) -# Filled contour background using rect marks +# Plot — filled contour background filled = ( alt.Chart(df_fill) .mark_rect() .encode( - x=alt.X("x:Q", bin=alt.Bin(maxbins=80), title="X Value"), - y=alt.Y("y:Q", bin=alt.Bin(maxbins=80), title="Y Value"), + x=alt.X("x:Q", bin=alt.Bin(maxbins=80), title="Distance East (km)"), + y=alt.Y("y:Q", bin=alt.Bin(maxbins=80), title="Distance North (km)"), color=alt.Color( - "mean(z):Q", + "mean(elevation):Q", scale=alt.Scale(scheme="viridis"), - title="Z Value", - legend=alt.Legend(titleFontSize=20, labelFontSize=18, gradientLength=500, gradientThickness=30), + title="Elevation (m)", + legend=alt.Legend(titleFontSize=22, labelFontSize=18, gradientLength=600, gradientThickness=28), ), ) ) -# Contour lines overlaid with contrasting white color for visibility +# Thin contour lines (all levels) lines = ( alt.Chart(df_lines) - .mark_rule(strokeWidth=3, opacity=1.0, color="white") + .mark_rule(strokeWidth=1.2, opacity=0.35, color="white") .encode(x="x1:Q", y="y1:Q", x2="x2:Q", y2="y2:Q") ) -# Combine filled regions and contour lines (lines layered on top) -chart = ( - (filled + lines) - .properties(width=1420, height=785, title="contour-basic · altair · pyplots.ai") - .configure_title(fontSize=32, anchor="middle") - .configure_axis(labelFontSize=20, titleFontSize=24, tickSize=10) - .configure_view(strokeWidth=0) +# Emphasised contour lines every 200 m +major_mask = (df_lines["level"] % 200 == 0) if not df_lines.empty else pd.Series([], dtype=bool) +df_major = df_lines[major_mask].copy() if not df_lines.empty else df_lines + +major_lines = ( + alt.Chart(df_major) + .mark_rule(strokeWidth=2.2, opacity=0.95, color="white") + .encode(x="x1:Q", y="y1:Q", x2="x2:Q", y2="y2:Q") ) -# Save as PNG (scale_factor=3.0 for 4800x2700) -chart.save("plot.png", scale_factor=3.0) +chart = ( + (filled + lines + major_lines) + .properties( + width=1420, + height=785, + title=alt.Title( + "Mountain Terrain · contour-basic · altair · anyplot.ai", fontSize=28, anchor="middle", color=INK + ), + background=PAGE_BG, + ) + .configure_view(fill=PAGE_BG, stroke=None) + .configure_axis( + domainColor=INK_SOFT, + tickColor=INK_SOFT, + gridColor=INK, + gridOpacity=0.10, + labelColor=INK_SOFT, + titleColor=INK, + labelFontSize=18, + titleFontSize=22, + tickSize=8, + ) + .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) +) -# Save interactive HTML -chart.save("plot.html") +chart.save(f"plot-{THEME}.png", scale_factor=3.0) +chart.save(f"plot-{THEME}.html") diff --git a/plots/contour-basic/metadata/python/altair.yaml b/plots/contour-basic/metadata/python/altair.yaml index 5bf50b0b90..d34cfda1fd 100644 --- a/plots/contour-basic/metadata/python/altair.yaml +++ b/plots/contour-basic/metadata/python/altair.yaml @@ -1,165 +1,188 @@ library: altair +language: python specification_id: contour-basic created: '2025-12-23T10:00:33Z' -updated: '2025-12-23T10:06:49Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20457533009 -issue: 0 -python_version: 3.13.11 -library_version: 6.0.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/contour-basic/altair/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/contour-basic/altair/plot.html -quality_score: 98 -impl_tags: - dependencies: [] - techniques: - - colorbar - - layer-composition - - html-export - patterns: - - data-generation - - matrix-construction - - iteration-over-groups - dataprep: [] - styling: [] +updated: '2026-04-24T01:37:35Z' +generated_by: claude-opus +workflow_run: 24867307514 +issue: 855 +python_version: 3.14.4 +library_version: 6.1.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/contour-basic/python/altair/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/contour-basic/python/altair/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/contour-basic/python/altair/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/contour-basic/python/altair/plot-dark.html +quality_score: 86 review: strengths: - - Excellent creative solution for a plot type Altair does not natively support - - Custom marching squares algorithm implementation for generating contour line segments - - Effective use of layered marks (rect for filled background + rule for contour - lines) - - Viridis colormap with white contour lines creates beautiful contrast - - Well-configured legend with appropriate sizing - - Both PNG and HTML outputs generated correctly + - Viridis colormap correctly chosen for sequential continuous elevation data — perceptually + uniform and colorblind-safe + - Major/minor contour line distinction (100m thin 35% opacity vs. 200m thick 95% + opacity) is a deliberate design choice that adds genuine visual hierarchy + - Full theme-adaptive chrome correctly implemented with all five tokens (PAGE_BG, + ELEVATED_BG, INK, INK_SOFT) — both renders pass readability checks + - Realistic topographic scenario with plausible elevation values and two peaks of + different heights demonstrates all core contour features + - Declarative aggregation (mean(elevation):Q with alt.Bin) to create the filled + contour effect is a clever Altair-idiomatic approach weaknesses: - - Axis labels could include units or more context - image_description: The plot displays a contour visualization of two overlapping - Gaussian peaks on a 2D scalar field. The filled background uses the viridis colormap - (purple for low values ~0.1 transitioning through green/cyan to yellow for peak - values ~1.0). White contour lines are clearly overlaid showing isolines at multiple - levels. The primary peak is centered around (1, 1) with bright yellow at its center, - while a secondary smaller peak is visible around (-1, -0.5). The title "contour-basic - · altair · pyplots.ai" appears at the top center. Axis labels show "X Value" (horizontal) - and "Y Value" (vertical) with tick marks from -3.0 to 3.0. A colorbar on the right - displays "Z Value" scale from ~0.1 to 1.0. The overall layout is balanced with - no overlapping elements. + - 'Canvas undersize: width=1420, height=785 produces 4260x2355 px; altair.md guide + specifies width=1600, height=900 to hit 4800x2700 px standard' + - No contour level labels despite spec recommending them when practical + - 'Altair''s distinctive interactive features not utilized: HTML output is generated + but neither .interactive() nor tooltip encoding is added' + - Marching squares implementation adds 45+ lines of dense bit-manipulation code + making the code verbose + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) visible in margins — correct theme surface, not pure white + Chrome: Title "Mountain Terrain · contour-basic · altair · anyplot.ai" in dark ink at top-center; axis labels "Distance East (km)" and "Distance North (km)" in dark ink; decimal tick labels (0.00–9.60) clearly legible; colorbar title "Elevation (m)" readable + Data: Viridis colormap fill (purple ~350m low elevations transitioning through blue-green to yellow ~1250m peak); two mountain peaks visible (upper-right larger, lower-left smaller); white contour lines overlaid — thin semi-transparent minor lines at 100m intervals and thicker near-opaque major lines at 200m intervals; first data color is viridis (continuous data — Okabe-Ito not applicable) + Legibility verdict: PASS — all text readable against light background, no light-on-light failures + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) visible in margins — correct theme surface, not pure black + Chrome: Title and axis labels rendered in light ink (#F0EFE8 / #B8B7B0) — clearly readable against dark background; colorbar labels and title are light-colored and legible; tick labels in light ink — no dark-on-dark failures observed + Data: Viridis colormap colors identical to light render (same purple-to-yellow gradient) — only chrome flipped, data colors unchanged; white contour lines remain clearly visible against dark-themed background + Legibility verdict: PASS — all text readable against dark background, no dark-on-dark failures criteria_checklist: visual_quality: - score: 39 - max: 40 + score: 29 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 10 - max: 10 + score: 8 + max: 8 passed: true - comment: Title, axis labels, and tick marks all clearly readable + comment: 'All font sizes explicitly set: title 28px, axis labels 22px, tick + labels 18px, legend title 22px/labels 18px — readable in both themes' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements anywhere + comment: Tick labels dense on x-axis (13 values) but no actual overlap at + rendered size - id: VQ-03 name: Element Visibility - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: Contour lines clearly visible in white against viridis, filled regions - show smooth gradations + comment: Filled viridis contours and both minor/major white contour lines + clearly distinguishable - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Viridis colormap is colorblind-safe, white contour lines provide - excellent contrast + comment: Viridis is perceptually uniform and colorblind-safe; white contour + lines provide high contrast - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 - passed: true - comment: Good proportions, no cut-off content, proper spacing + name: Layout & Canvas + score: 3 + max: 4 + passed: false + comment: Canvas 1420x785 -> 4260x2355 px output, below 4800x2700 standard; + guide specifies width=1600, height=900 - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 - passed: false - comment: Labels are descriptive ("X Value", "Y Value") but lack units + passed: true + comment: Distance East (km), Distance North (km), colorbar Elevation (m) — + all descriptive with units - id: VQ-07 - name: Grid & Legend + name: Palette Compliance score: 2 max: 2 passed: true - comment: Colorbar well-placed on right, no grid needed for contour plots + comment: 'Viridis correctly applied for sequential continuous data; #FAF8F1/#1A1A17 + backgrounds correct; theme-adaptive chrome correct in both renders' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Thoughtful: viridis fill + major/minor contour distinction with + different opacity/weight; above defaults but not publication-ready' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: stroke=None removes border, grid opacity 0.10 very subtle, theme-adaptive + tick/label/domain colors + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Two peaks immediately apparent via viridis; major/minor contour distinction + guides elevation band reading; visual hierarchy works spec_compliance: - score: 25 - max: 25 + score: 14 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correctly shows isolines (contour lines) of a 2D scalar field - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: X, Y coordinates and Z values correctly assigned - - id: SC-03 + comment: 'Correct: filled contour background (mark_rect + viridis) layered + with isoline segments (mark_rule)' + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 3 + max: 4 + passed: false + comment: Spec says label key contour levels when practical — not implemented; + colorbar, filled+lines, grid resolution all present + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 passed: true - comment: Has both filled contour regions AND contour lines, colorbar present - per spec + comment: x=Distance East km, y=Distance North km, z=Elevation m — all correctly + mapped; axes show full 0-10 km range - id: SC-04 - name: Data Range + name: Title & Legend score: 3 max: 3 passed: true - comment: All data visible within axis ranges - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Colorbar correctly labeled "Z Value" - - id: SC-06 - name: Title Format - score: 2 - max: 2 - passed: true - comment: 'Correct format: "contour-basic · altair · pyplots.ai"' + comment: Mountain Terrain · contour-basic · altair · anyplot.ai format correct; + colorbar titled Elevation (m) correct 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: Two overlapping Gaussian peaks demonstrate gradients, valleys, peaks, - and how contours connect points of equal value + comment: Two peaks at different elevations, saddle valley, linear slope — + demonstrates all core contour features - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Mathematical function z = f(x, y) is explicitly mentioned in spec - as example data + comment: Topographic elevation map of 10km x 10km mountain region; neutral + scientific scenario - id: DQ-03 name: Appropriate Scale - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Values are sensible for mathematical demonstration (-3 to 3 range, - Z values 0-1) + comment: Elevation 350-1250m (realistic), 80x80 grid (within spec range), + contour intervals 100m (standard topographic) code_quality: - score: 10 + score: 9 max: 10 items: - id: CQ-01 @@ -167,41 +190,65 @@ review: score: 3 max: 3 passed: true - comment: 'Linear structure: imports → data → plot → save, no functions/classes' + comment: Imports -> theme tokens -> data -> marching squares -> plot -> save; + no functions or classes - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: Uses np.random.seed(42) for deterministic results + comment: 'Fully deterministic: mathematical functions of x,y with no random + elements' - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports (altair, numpy, pandas) are used + comment: All imported modules (alt, np, pd, os, sys) are used; importlib pattern + justified by path isolation - id: CQ-04 - name: No Deprecated API + name: Code Elegance score: 1 - max: 1 - passed: true - comment: Uses current Altair API + max: 2 + passed: false + comment: Marching squares implementation is 45+ lines of nested loops with + bit manipulation; acceptable given Altair constraints but verbose - 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: 4 - max: 5 + comment: Saves plot-{THEME}.png (scale_factor=3.0) and plot-{THEME}.html correctly + library_mastery: + score: 6 + max: 10 items: - - id: LF-01 - name: Distinctive Features + - id: LM-01 + name: Idiomatic Usage score: 4 max: 5 passed: true - comment: Creative implementation using marching squares algorithm for contour - lines and rect marks for filled background. Altair doesn't have native contour - support, so this demonstrates clever use of available primitives. - verdict: APPROVED + comment: Layer composition (+), declarative aggregation mean(elevation):Q, + alt.Bin, mark_rule with x2/y2, configure_*() chain — idiomatic Altair patterns + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: Layer composition and declarative aggregation+binning are Altair-specific, + but core contour extraction is vanilla Python; no interactivity (tooltips, + zoom/pan) despite HTML output + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - colorbar + - layer-composition + - html-export + patterns: + - data-generation + - matrix-construction + dataprep: [] + styling: + - custom-colormap + - alpha-blending