diff --git a/plots/ridgeline-basic/implementations/python/altair.py b/plots/ridgeline-basic/implementations/python/altair.py index e63c15857e..4aac83bf79 100644 --- a/plots/ridgeline-basic/implementations/python/altair.py +++ b/plots/ridgeline-basic/implementations/python/altair.py @@ -1,7 +1,7 @@ """ anyplot.ai ridgeline-basic: Basic Ridgeline Plot -Library: altair 6.1.0 | Python 3.13.13 -Quality: 87/100 | Updated: 2026-04-30 +Library: altair 6.2.2 | Python 3.13.14 +Quality: 93/100 | Updated: 2026-07-25 """ import os @@ -9,6 +9,7 @@ import altair as alt import numpy as np import pandas as pd +from PIL import Image # Theme tokens @@ -18,6 +19,14 @@ INK = "#1A1A17" if THEME == "light" else "#F0EFE8" INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +# Imprint sequential colormap (brand green -> blue) for the 12 ordered ridges +IMPRINT_SEQ = ["#009E73", "#4467A3"] +_c0 = tuple(int(IMPRINT_SEQ[0][i : i + 2], 16) for i in (1, 3, 5)) +_c1 = tuple(int(IMPRINT_SEQ[1][i : i + 2], 16) for i in (1, 3, 5)) +month_colors = [ + "#{:02x}{:02x}{:02x}".format(*(round(_c0[j] + (_c1[j] - _c0[j]) * i / 11) for j in range(3))) for i in range(12) +] + # Data np.random.seed(42) months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] @@ -43,41 +52,39 @@ extent=[-15, 40], bandwidth=2, ) - .mark_area(fillOpacity=0.8, stroke="#306998", strokeWidth=2, interpolate="monotone") + .mark_area(fillOpacity=0.85, stroke=INK_SOFT, strokeWidth=1, interpolate="monotone") .encode( x=alt.X( "temperature:Q", title="Temperature (°C)", - axis=alt.Axis(labelFontSize=18, titleFontSize=22, grid=False), + axis=alt.Axis(labelFontSize=10, titleFontSize=12, grid=False), scale=alt.Scale(domain=[-15, 40]), ), y=alt.Y("density:Q", title=None, axis=None, scale=alt.Scale(domain=[0, 0.15])), - fill=alt.Fill( - "month_order:O", scale=alt.Scale(scheme="blues", domain=list(range(12)), reverse=True), legend=None - ), + fill=alt.Fill("month_order:O", scale=alt.Scale(domain=list(range(12)), range=month_colors), legend=None), row=alt.Row( "month:N", sort=months, header=alt.Header( - labelFontSize=18, labelAngle=0, labelAlign="right", labelPadding=10, title=None, labelColor=INK_SOFT + labelFontSize=12, labelAngle=0, labelAlign="right", labelPadding=8, title=None, labelColor=INK_SOFT ), ), ) .properties( - width=1400, - height=55, + width=650, + height=28, background=PAGE_BG, title=alt.Title( text="Monthly Temperature Distribution · ridgeline-basic · altair · anyplot.ai", - fontSize=28, + fontSize=16, anchor="middle", ), ) - .configure_facet(spacing=-25) + .configure_facet(spacing=-13) .configure_view(fill=PAGE_BG, stroke=None) .configure_axis( - labelFontSize=16, - titleFontSize=20, + labelFontSize=10, + titleFontSize=12, labelColor=INK_SOFT, titleColor=INK, domainColor=INK_SOFT, @@ -88,5 +95,20 @@ ) # Save -chart.save(f"plot-{THEME}.png", scale_factor=3.0) +chart.save(f"plot-{THEME}.png", scale_factor=4.0) + +# PAD-only to exact target canvas — see prompts/library/altair.md "Canvas" +TW, TH = 3200, 1800 +_img = Image.open(f"plot-{THEME}.png").convert("RGB") +_w, _h = _img.size +if _w > TW or _h > TH: + raise SystemExit( + f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " + f"Shrink chart .properties(width=, height=) values and re-render." + ) +if _w < TW or _h < TH: + _canvas = Image.new("RGB", (TW, TH), PAGE_BG) + _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) + _canvas.save(f"plot-{THEME}.png") + chart.save(f"plot-{THEME}.html") diff --git a/plots/ridgeline-basic/metadata/python/altair.yaml b/plots/ridgeline-basic/metadata/python/altair.yaml index e1d53d8ee0..ae4290ac55 100644 --- a/plots/ridgeline-basic/metadata/python/altair.yaml +++ b/plots/ridgeline-basic/metadata/python/altair.yaml @@ -2,51 +2,61 @@ library: altair language: python specification_id: ridgeline-basic created: '2025-12-23T19:42:49Z' -updated: '2026-04-30T03:40:35Z' +updated: '2026-07-25T04:01:44Z' generated_by: claude-sonnet -workflow_run: 25145333048 +workflow_run: 30142991631 issue: 923 -python_version: 3.13.13 -library_version: 6.1.0 +language_version: 3.13.14 +library_version: 6.2.2 preview_url_light: https://storage.googleapis.com/anyplot-images/plots/ridgeline-basic/python/altair/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/ridgeline-basic/python/altair/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/ridgeline-basic/python/altair/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/ridgeline-basic/python/altair/plot-dark.html -quality_score: 87 +quality_score: 93 review: strengths: - - 'Fully idiomatic Altair: transform_density with groupby, configure_facet negative - spacing for ridgeline — uses native mechanisms rather than workarounds' - - Seasonal temperature dataset is realistic and well-scaled with appropriate std - deviations per month - - 'Both light and dark themes render cleanly with correct backgrounds (#FAF8F1 / - #1A1A17) and legible chrome' - - 'Strong data storytelling: blues gradient visually encodes seasonal progression, - reinforcing the narrative without a legend' - - 'Complete code quality: seed, clean imports, correct file output naming' + - 'Idiomatic Altair technique: transform_density (declarative KDE) combined with + alt.Row faceting and negative configure_facet(spacing=-13) is the canonical, distinctive + way to build a ridgeline plot in this library — hard to replicate this concisely + in another library' + - 'Imprint sequential gradient (#009E73 → #4467A3) correctly applied across the + 12 chronologically-ordered ridges, with the first ridge (Jan) exactly #009E73 + as required' + - Canvas gate passes exactly at 3200×1800; verified all four edges (title, tick + labels, month row labels, x-axis title) render with generous margins and zero + clipping + - Theme-adaptive chrome (INK / INK_SOFT / PAGE_BG tokens) is threaded through axis, + row-header, title, and view configuration consistently — both light and dark renders + are fully legible with no dark-on-dark or light-on-light failures + - Realistic monthly temperature data with per-month variation in both mean (2–25°C) + and spread (std 3–5), giving each ridge a distinct silhouette rather than uniform + copies weaknesses: - - 'Canvas slightly undersized: width 4200px (1400x3) vs. 4800px target; increase - width to 1600' - - 'Custom stroke color #306998 is a non-Okabe-Ito hex; replace with #0072B2 (Okabe-Ito - blue) or remove explicit stroke' - - Blues sequential scheme is acceptable for 12-month ordinal data but viridis/cividis - would be more strictly compliant with the altair style guide and provide better - dark-end contrast for Jan visibility in dark mode + - Ridge overlap is roughly 46% (spacing=-13 vs row height=28), slightly below the + spec's 'typically 50-70%' guidance — tightening spacing further (e.g. -16 to -18) + would give a more classic joy-plot silhouette where peaks visibly poke into the + row above + - The green-to-blue color gradient is decorative/redundant with row order (no legend + or caption ties it to a meaning) — a one-line note or subtle color-mapped tick + could reinforce that it encodes month progression + - Design stays fairly minimal/flat overall; a small refinement like a subtle alternating + row background or a mean-value tick per ridge would push Aesthetic Sophistication + further above 'strong design' toward 'publication-ready' image_description: |- Light render (plot-light.png): - Background: Warm off-white #FAF8F1 — correct, not pure white - Chrome: Title "Monthly Temperature Distribution · ridgeline-basic · altair · anyplot.ai" in dark ink, clearly readable. Month labels (Jan-Dec) right-aligned in soft dark ink. X-axis "Temperature (°C)" label and tick labels (-15 to 40) all clearly visible. - Data: 12 ridges using blues colormap reversed — Jan/Feb/Mar in dark navy, mid-year in medium blue, Oct/Nov/Dec in light blue/gray. Semi-transparent fills (0.8 opacity) with #306998 stroke outline. Seasonal shift clearly visible: cold months left-peaked, warm months right-peaked. - Legibility verdict: PASS — all text readable against light background, no light-on-light issues + Background: Warm off-white, matches #FAF8F1 target — not pure white. + Chrome: Title "Monthly Temperature Distribution · ridgeline-basic · altair · anyplot.ai" in bold dark ink, centered, fully visible with margin above and below. Twelve row labels (Jan-Dec) in medium-gray ink, right-aligned to the left of each ridge, all legible. X-axis "Temperature (°C)" title and tick labels (-15 to 40) in dark/soft-dark ink at the bottom, fully visible with generous margin to the canvas edge (verified via edge crops — no clipping on any side). + Data: 12 stacked, partially-overlapping density curves (KDE via transform_density), one per month, ordered chronologically top-to-bottom. Fill color runs as a smooth Imprint-sequential gradient from brand green #009E73 (Jan) to blue #4467A3 (Dec), each ridge outlined with a thin soft-ink stroke for definition. Ridge shapes vary sensibly with the seasonal cycle (coldest/left-shifted in Jan/Dec, warmest/right-shifted in Jul/Aug). + Legibility verdict: PASS — all text reads clearly against the light background, no overlaps, no edge clipping. Dark render (plot-dark.png): - Background: Warm near-black #1A1A17 — correct, not pure black - Chrome: Title and month labels in light cream/white text — clearly readable. X-axis tick labels in light text, all legible. No dark-on-dark failures detected. - Data: Identical blues color scheme as light render. Dark navy fills (Jan/Feb) are slightly close to near-black background but distinguishable via the #306998 stroke. Light blue-gray fills (Oct/Nov/Dec) clearly visible against dark background. - Legibility verdict: PASS — all text readable against dark background, no dark-on-dark failures + Background: Warm near-black, matches #1A1A17 target — not pure black. + Chrome: Same title and axis text, now rendered in light ink (#F0EFE8 primary / #B8B7B0 secondary) — clearly legible against the dark surface, no dark-on-dark failures anywhere (title, row labels, tick labels, and axis title all confirmed readable). + Data: Identical 12-ridge gradient from #009E73 to #4467A3 — data colors are pixel-identical to the light render; only the chrome (background, text, baseline strokes) flipped for the dark theme, as required. + Legibility verdict: PASS — all text and data elements are clearly readable against the dark background, no dark-on-dark or light-on-light issues detected. criteria_checklist: visual_quality: - score: 26 + score: 29 max: 30 items: - id: VQ-01 @@ -54,72 +64,76 @@ review: score: 7 max: 8 passed: true - comment: Font sizes explicitly set (title 28px, axis 22px, ticks 18px); all - text readable in both themes + comment: All font sizes explicitly set (title 16, axis title 12, tick/header + labels 10-12), matches style-guide defaults, legible in both themes, no + overflow - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No problematic overlap; ridgeline overlap is intentional by design + comment: No text or data collisions in either render - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: All ridges visible; Jan/Feb dark fills slightly close to dark background - but stroke provides edge contrast + comment: 200-point density estimate per group with fillOpacity=0.85 and a + defining stroke - clearly visible ridges - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Sequential blues is CVD-safe, no red-green signaling + comment: Smooth CVD-safe green-to-blue gradient, ink stroke around each fill + for contrast, no red-green reliance - id: VQ-05 name: Layout & Canvas - score: 3 + score: 4 max: 4 passed: true - comment: Width 4200px (1400x3) is below 4800px target; height appropriate - for 12-row ridgeline layout + comment: Ridges fill the majority of the canvas with balanced margins; edge + crops confirm no cut-off content - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Temperature (C) with units; descriptive title + comment: '"Temperature (°C)" with units; Y-axis correctly shows group (month) + labels instead of numeric density values, per spec' - id: VQ-07 name: Palette Compliance - score: 1 + score: 2 max: 2 - passed: false - comment: 'Blues sequential acceptable for 12-category ordinal data but custom - #306998 stroke is non-Okabe-Ito; viridis/cividis would be stricter compliance' + passed: true + comment: 'First ridge exactly #009E73, gradient built from the canonical two-stop + imprint_seq range (#009E73 -> #4467A3); backgrounds and chrome theme-correct + in both renders' design_excellence: - score: 12 + score: 15 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 5 + score: 6 max: 8 passed: true - comment: Reversed blues gradient encoding ordinal month order is deliberate; - negative spacing well-calibrated + comment: Thoughtful gradient encoding of month order, clean typography, clearly + above a configured default; not yet at full publication-ready polish - id: DE-02 name: Visual Refinement - score: 3 + score: 5 max: 6 passed: true - comment: Right-aligned month labels, no unnecessary chrome, subtle facet dividers, - no extraneous spines + comment: No grid, no distracting spines, generous whitespace between title + and ridges; per-row baseline stroke is the only minor un-refined touch - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: 'Clear seasonal narrative: dark winter months bracket warm summer - months; temporal flow is intuitive' + comment: Row order plus color gradient jointly make the seasonal cycle immediately + visible without requiring the reader to search for the pattern spec_compliance: score: 15 max: 15 @@ -129,53 +143,56 @@ review: score: 5 max: 5 passed: true - comment: Correct ridgeline plot with row-faceted density areas and partial + comment: Correct ridgeline / joy plot via row-faceted density curves with overlap - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Stacked density curves, 12 groups, overlap via spacing=-25, chronological - ordering, color differentiation + comment: Stacked density curves, partial overlap, group labels on Y-axis, + chronological ordering, color differentiation - all present - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=temperature, Y=density, rows=months ordered Jan-Dec; full -15 to - 40 extent shown + comment: X=temperature, Y=density mapped to row position; all 12 groups' full + distributions are visible - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title includes spec-id and library in required format; no legend - needed + comment: Title follows the {Descriptive Title} · {spec-id} · {library} · anyplot.ai + convention used consistently across this spec's sibling implementations; + no legend needed since row labels already identify each group data_quality: - score: 15 + score: 14 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 6 + score: 5 max: 6 passed: true - comment: 'All ridgeline aspects covered: smooth KDE curves, partial overlap, - temporal ordering, group differentiation' + comment: 12 distributions with varying mean and spread show the main seasonal + pattern; all unimodal (appropriate for temperature per month, no missed + edge case) - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Monthly temperature data for temperate climate is plausible, neutral, - clearly understandable + comment: 'Neutral, comprehensible real-world scenario: monthly climate/temperature + distribution' - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Base temps 2-25C with std 3-5C yields realistic seasonal distributions + comment: Values (2-25°C mean, 3-5°C std) are realistic for a temperate climate's + seasonal cycle code_quality: score: 10 max: 10 @@ -185,33 +202,34 @@ review: score: 3 max: 3 passed: true - comment: No functions, no classes; flat procedural script + comment: Linear imports -> data -> plot -> save, no functions/classes - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: np.random.seed(42) set + comment: np.random.seed(42) - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: Only os, altair, numpy, pandas — all used + comment: os, altair, numpy, pandas, PIL.Image - all used - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Method chaining, appropriate complexity, no fake UI + comment: Appropriately concise, no fake functionality or over-engineering - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves plot-{THEME}.png (scale_factor=3.0) and plot-{THEME}.html correctly + comment: Saves plot-{THEME}.png and plot-{THEME}.html via current chart.save + API library_mastery: - score: 9 + score: 10 max: 10 items: - id: LM-01 @@ -219,25 +237,28 @@ review: score: 5 max: 5 passed: true - comment: transform_density with groupby, configure_facet, Header styling — - canonical Altair patterns + comment: Expert use of the declarative transform_density + Row-facet + negative + configure_facet(spacing=...) pattern, Altair's recommended ridgeline approach - id: LM-02 name: Distinctive Features - score: 4 + score: 5 max: 5 passed: true - comment: transform_density with groupby and configure_facet(spacing=-25) are - distinctive Altair features for ridgeline + comment: transform_density is a Vega-Lite-native declarative KDE transform + unique to Altair's grammar - other libraries need manual scipy/seaborn KDE + computation to achieve the same result verdict: APPROVED impl_tags: - dependencies: [] + dependencies: + - pillow techniques: - faceting - html-export patterns: - data-generation + - iteration-over-groups dataprep: - kde styling: - alpha-blending - - custom-colormap + - gradient-fill