From 266ab443644fb8a2814d7101a6d05b2667b48cff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 23:31:03 +0000 Subject: [PATCH 1/5] feat(altair): implement slope-basic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regen from quality 86. Addressed: - label crowding on left side (390-440 cluster) — redesigned Q1/Q4 data with a strict ~90-unit minimum gap between adjacent values on both sides, plus smaller entity-label fontSize (16 -> 11) matching style-guide tick sizing - canvas dims 1400x850 below target — moved to canonical altair inner-view (width=620, height=320, scale_factor=4.0) with PIL pad-to-3200x1800 per the library's hard canvas rule - full rectangular border instead of L-shaped frame — configure_view strokeWidth=0, keeping only the axis domain lines (bottom + left) - title missing the mandated {language} token — now "slope-basic · python · altair · anyplot.ai" - font sizes across axis/legend/title brought in line with the current scale-based Visual Sizing Defaults table Kept: semantic directional color encoding (green/red), layer composition structure, detail encoding, Okabe-Ito-derived Imprint palette usage. --- .../implementations/python/altair.py | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/plots/slope-basic/implementations/python/altair.py b/plots/slope-basic/implementations/python/altair.py index ed16a62e74..d0e86e9907 100644 --- a/plots/slope-basic/implementations/python/altair.py +++ b/plots/slope-basic/implementations/python/altair.py @@ -1,20 +1,14 @@ -""" anyplot.ai +"""anyplot.ai slope-basic: Basic Slope Chart (Slopegraph) -Library: altair 6.1.0 | Python 3.13.13 -Quality: 86/100 | Created: 2026-04-30 +Library: altair 6.2.2 | Python 3.13.12 +Quality: 86/100 | Updated: 2026-07-25 """ import os -import sys +import altair as alt import pandas as pd - - -_script_dir = os.path.dirname(os.path.abspath(__file__)) -if _script_dir in sys.path: - sys.path.remove(_script_dir) - -import altair as alt # noqa: E402 +from PIL import Image # Theme tokens @@ -24,27 +18,28 @@ INK = "#1A1A17" if THEME == "light" else "#F0EFE8" INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -# Okabe-Ito: position 1 = Increase, position 2 = Decrease +# Imprint palette: position 1 = Increase, position 5 (deferred semantic-red anchor) = Decrease COLOR_INCREASE = "#009E73" -COLOR_DECREASE = "#AE3030" # imprint red — decrease +COLOR_DECREASE = "#AE3030" -# Data +# Data — Q1 vs Q4 product sales, deliberately spaced (min gap ~90 units within each +# period) so entity labels never crowd, with a full rank shuffle for a rich story data = pd.DataFrame( { "Product": [ - "Laptop", - "Phone", - "Tablet", - "Monitor", - "Keyboard", - "Mouse", - "Headphones", "Webcam", "Speaker", "Charger", + "Monitor", + "Headphones", + "Keyboard", + "Tablet", + "Mouse", + "Laptop", + "Phone", ], - "Q1 Sales": [850, 1200, 420, 310, 580, 720, 390, 180, 260, 440], - "Q4 Sales": [920, 980, 650, 410, 520, 810, 620, 350, 240, 380], + "Q1 Sales": [190, 310, 400, 490, 580, 680, 780, 890, 1010, 1150], + "Q4 Sales": [400, 190, 310, 580, 780, 490, 890, 680, 1150, 1010], } ) @@ -54,19 +49,23 @@ color_scale = alt.Scale(domain=["Increase", "Decrease"], range=[COLOR_INCREASE, COLOR_DECREASE]) +# Title (42 chars < 67 baseline — no fontsize reduction needed) +title = "slope-basic · python · altair · anyplot.ai" +title_fontsize = 16 + # Plot lines = ( alt.Chart(df_long) .mark_line(strokeWidth=3, opacity=0.8) .encode( - x=alt.X("Period:N", axis=alt.Axis(labelFontSize=20, title=None, labelAngle=0)), + x=alt.X("Period:N", axis=alt.Axis(labelFontSize=14, title=None, labelAngle=0)), y=alt.Y( "Sales:Q", - axis=alt.Axis(labelFontSize=18, titleFontSize=22, title="Sales (units)"), + axis=alt.Axis(labelFontSize=10, titleFontSize=12, title="Sales (units)"), scale=alt.Scale(zero=False), ), color=alt.Color( - "Direction:N", scale=color_scale, legend=alt.Legend(titleFontSize=20, labelFontSize=18, orient="top-right") + "Direction:N", scale=color_scale, legend=alt.Legend(titleFontSize=12, labelFontSize=10, orient="top-right") ), detail="Product:N", ) @@ -80,35 +79,54 @@ labels_left = ( alt.Chart(df_long[df_long["Period"] == "Q1 Sales"]) - .mark_text(align="right", dx=-15, fontSize=16) + .mark_text(align="right", dx=-12, fontSize=11) .encode(x="Period:N", y="Sales:Q", text="Product:N", color=alt.Color("Direction:N", scale=color_scale, legend=None)) ) labels_right = ( alt.Chart(df_long[df_long["Period"] == "Q4 Sales"]) - .mark_text(align="left", dx=15, fontSize=16) + .mark_text(align="left", dx=12, fontSize=11) .encode(x="Period:N", y="Sales:Q", text="Product:N", color=alt.Color("Direction:N", scale=color_scale, legend=None)) ) -# Style +# Style — L-shaped frame: no view stroke, axis domain lines (bottom + left) stay visible chart = ( (lines + points + labels_left + labels_right) - .properties(width=1400, height=850, background=PAGE_BG, title="slope-basic · altair · anyplot.ai") - .configure_title(color=INK, fontSize=28, anchor="middle") + .properties( + width=620, + height=320, + background=PAGE_BG, + title=alt.Title(title, fontSize=title_fontsize, color=INK, anchor="middle"), + ) + .configure_view(fill=PAGE_BG, strokeWidth=0) .configure_axis( domainColor=INK_SOFT, tickColor=INK_SOFT, grid=True, gridColor=INK, - gridOpacity=0.10, + gridOpacity=0.12, gridDash=[4, 4], labelColor=INK_SOFT, titleColor=INK, ) - .configure_view(fill=PAGE_BG, stroke=INK_SOFT) .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) ) -# Save -chart.save(f"plot-{THEME}.png", scale_factor=3.0) +# Save PNG then pad to exact 3200×1800 target +chart.save(f"plot-{THEME}.png", scale_factor=4.0) + +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") + +# Save interactive HTML chart.save(f"plot-{THEME}.html") From 59db341dc95356cded21c876b0b306bcd2470ad1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 23:31:09 +0000 Subject: [PATCH 2/5] chore(altair): add metadata for slope-basic --- plots/slope-basic/metadata/python/altair.yaml | 229 +----------------- 1 file changed, 10 insertions(+), 219 deletions(-) diff --git a/plots/slope-basic/metadata/python/altair.yaml b/plots/slope-basic/metadata/python/altair.yaml index 5b4ad12c43..bf51c25a16 100644 --- a/plots/slope-basic/metadata/python/altair.yaml +++ b/plots/slope-basic/metadata/python/altair.yaml @@ -1,230 +1,21 @@ +# Per-library metadata for altair implementation of slope-basic +# Auto-generated by impl-generate.yml + library: altair language: python specification_id: slope-basic created: 2025-12-17 02:32:26+00:00 -updated: '2026-04-30T17:06:43Z' +updated: '2026-07-25T23:31:08Z' generated_by: claude-sonnet -workflow_run: 25177483606 +workflow_run: 30179400333 issue: 981 -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/slope-basic/python/altair/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-dark.html -quality_score: 86 +quality_score: null review: - strengths: - - Semantic directional color encoding (green=increase, orange=decrease) creates - immediate visual clarity and meaningful hierarchy - - Full spec compliance — all required features present and correctly implemented - - Excellent data quality with realistic business context and visible rank changes - - Clean, idiomatic Altair code using layer composition and detail encoding correctly - - Correct Okabe-Ito palette with proper theme adaptation in both light and dark - renders - weaknesses: - - Label crowding on left side around the 390-440 sales range (Charger/Tablet/Headphones - too close together); consider adjusting dx offsets or filtering to reduce cluster - density - - Canvas dimensions 1400x850 are below the recommended 1600x900 target - - Full rectangular view border instead of recommended L-shaped frame (remove top/right - spines via configure_view stroke removal or configure_axis settings) - image_description: |- - Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct theme surface, not pure white - Chrome: Title "slope-basic · altair · anyplot.ai" in dark ink, clearly readable. Y-axis label "Sales (units)" in dark ink, readable. Tick labels in soft dark (#4A4A44), readable. X-axis period labels "Q1 Sales" / "Q4 Sales" in soft dark, readable. Legend box with elevated background (#FFFDF6) and soft border. - Data: Ten slope lines — green (#009E73) for increases, orange (#D55E00) for decreases. Circle markers at endpoints. Entity labels on both sides in matching directional colors. Left-side cluster around 390-440 (Charger/Tablet/Headphones) shows some crowding. - Legibility verdict: PASS (minor label crowding but no unreadable text) - - Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark theme surface, not pure black - Chrome: Title in light cream (#F0EFE8), clearly readable against dark background. Axis labels and tick labels in light grey (#B8B7B0), all readable. No dark-on-dark failures detected. Legend box uses elevated dark (#242420). - Data: Colors are identical to light render — same green (#009E73) for increases, same orange (#D55E00) for decreases. Data identity maintained across themes. - Legibility verdict: PASS - criteria_checklist: - visual_quality: - score: 25 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 7 - max: 8 - passed: true - comment: Font sizes explicitly set throughout; data labels at fontSize=16 - slightly small - - id: VQ-02 - name: No Overlap - score: 3 - max: 6 - passed: false - comment: Label crowding on left side at 390-440 range (Charger/Tablet/Headphones) - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Lines strokeWidth=3 and markers size=200 well-sized - - id: VQ-04 - name: Color Accessibility - score: 2 - max: 2 - passed: true - comment: Green vs orange CVD-safe contrast - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Canvas 1400x850 slightly undersized vs recommended 1600x900 - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: Y-axis Sales (units) with units; period names descriptive - - id: VQ-07 - name: Palette Compliance - score: 2 - max: 2 - passed: true - comment: 'Correct Okabe-Ito #009E73 first, #D55E00 second; correct backgrounds - both themes' - design_excellence: - score: 13 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 5 - max: 8 - passed: true - comment: Semantic directional coloring above defaults; thoughtful design not - publication-ready - - id: DE-02 - name: Visual Refinement - score: 4 - max: 6 - passed: true - comment: Subtle dashed grid at 10% opacity; full view border instead of L-frame - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Directional colors create immediate visual hierarchy; viewer instantly - sees decliners vs growers - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct slopegraph - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: All spec features present - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: Correct X/Y/color/detail mapping - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct; legend labels match - data_quality: - score: 15 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 6 - max: 6 - passed: true - comment: Both increases and decreases with rank changes visible - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: Tech product sales Q1 vs Q4 — neutral business scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Values 180-1200 units with factually plausible variation - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: Clean Imports->Data->Plot->Save, no functions or classes - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: Hardcoded deterministic data - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only os, sys, pandas, altair — all used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean Altair layer composition with pd.melt for reshaping - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves plot-{THEME}.png and .html with current API - library_mastery: - score: 8 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 5 - max: 5 - passed: true - comment: Expert use of layer composition, detail encoding, alt.Scale, configure_* - system - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: detail encoding for per-product lines grouped by color is distinctly - Altair; layer composition strength used well - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - layer-composition - - html-export - patterns: - - wide-to-long - dataprep: [] - styling: - - alpha-blending - - grid-styling + strengths: [] + weaknesses: [] From a4d429967e857caad6049e55709666a3e7d720c7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 23:38:39 +0000 Subject: [PATCH 3/5] chore(altair): update quality score 89 and review feedback for slope-basic --- .../implementations/python/altair.py | 6 +- plots/slope-basic/metadata/python/altair.yaml | 254 +++++++++++++++++- 2 files changed, 250 insertions(+), 10 deletions(-) diff --git a/plots/slope-basic/implementations/python/altair.py b/plots/slope-basic/implementations/python/altair.py index d0e86e9907..bda223b5cc 100644 --- a/plots/slope-basic/implementations/python/altair.py +++ b/plots/slope-basic/implementations/python/altair.py @@ -1,7 +1,7 @@ -"""anyplot.ai +""" anyplot.ai slope-basic: Basic Slope Chart (Slopegraph) -Library: altair 6.2.2 | Python 3.13.12 -Quality: 86/100 | Updated: 2026-07-25 +Library: altair 6.2.2 | Python 3.13.14 +Quality: 89/100 | Created: 2026-07-25 """ import os diff --git a/plots/slope-basic/metadata/python/altair.yaml b/plots/slope-basic/metadata/python/altair.yaml index bf51c25a16..b2814461e7 100644 --- a/plots/slope-basic/metadata/python/altair.yaml +++ b/plots/slope-basic/metadata/python/altair.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for altair implementation of slope-basic -# Auto-generated by impl-generate.yml - library: altair language: python specification_id: slope-basic created: 2025-12-17 02:32:26+00:00 -updated: '2026-07-25T23:31:08Z' +updated: '2026-07-25T23:38:39Z' generated_by: claude-sonnet workflow_run: 30179400333 issue: 981 @@ -15,7 +12,250 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/slope-bas preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-dark.html -quality_score: null +quality_score: 89 review: - strengths: [] - weaknesses: [] + strengths: + - Correct, idiomatic Altair slopegraph built from four layered marks (line + circle + + two direct-label text layers) with a full rank-shuffle across 10 entities, making + the increase/decrease story immediately legible. + - 'Exact theme-token compliance: measured background pixels are precisely #FAF8F1 + (light) and #1A1A17 (dark); first series is exactly #009E73; ''Decrease'' correctly + uses the semantic red anchor #AE3030 (loss/decline meaning matches the legend + label).' + - Entities are deliberately spaced (~90-unit minimum gap per comment) so all 20 + direct end-labels stay collision-free in both renders despite 10 overlapping entities. + - Canvas padding logic (raise on overshoot, center-pad on undershoot) lands both + PNGs exactly at 3200x1800 with no drift. + weaknesses: + - 'Red ''Decrease'' elements (line, markers, product labels) measure ~2.7:1 contrast + against the #1A1A17 dark background -- below the 3:1 AA large-text floor, and + exactly the case the style guide flags as ''matte-red marginally below on dark + bg.'' Since Decrease is meaningful status information (not decorative), add the + style guide''s recommended 1px ink-color stroke to the red line/markers/labels + in dark mode.' + - X-axis tick labels ('Q1 Sales' / 'Q4 Sales', labelFontSize=14) render visibly + larger than the Y-axis tick labels (labelFontSize=10) -- measured glyph heights + ~42px vs ~26px, a ~1.6x mismatch with no semantic reason. Bring the x tick fontsize + down to ~11-12 for balance with the y ticks. + - Grid lines use gridDash=[4,4] on both axes; the style guide's Grid Guidelines + call for solid thin lines at low opacity, not dashed, for the recurring data grid + -- reserve dashing for genuine reference lines only. + - Library Mastery is generic layering (lines+points+text) that most declarative-grammar + libraries could replicate; a distinctive Altair touch (e.g. alt.condition to highlight + a specific entity, or an interactive selection in the HTML view) would lift LM-02 + beyond 'some library-specific features.' + image_description: |- + Light render (plot-light.png): + Background: Measured exactly #FAF8F1 -- warm off-white, matches spec. + Chrome: Title "slope-basic · python · altair · anyplot.ai" bold dark ink, clearly visible. Y-axis title "Sales (units)" dark ink with units. Y tick numbers (100-1,200) in soft dark ink, readable. X-axis has two categorical positions "Q1 Sales" / "Q4 Sales" in bold dark ink -- readable but noticeably larger than the y tick numbers (measured glyph height ~42px vs ~26px). Grid: dashed horizontal lines plus two dashed vertical reference lines at the Q1/Q4 columns, subtle opacity (~0.12). + Data: 10 sloped lines colored either brand green #009E73 (Increase) or matte red #AE3030 (Decrease), with circular endpoint markers and direct product-name labels colored to match their line's direction at both the Q1 and Q4 ends. Legend top-right in a bordered cream box, dark text, "Direction" title with green/red swatches. + Legibility verdict: PASS -- all text is clearly readable against the light background; no light-on-light issues. + + Dark render (plot-dark.png): + Background: Measured exactly #1A1A17 -- warm near-black, matches spec. + Chrome: Title renders in light/white bold ink, clearly visible. Axis title and tick labels use theme-adaptive soft-light ink, readable. Grid dashed at the same subtle opacity. Legend box correctly flips to a dark-elevated fill (#242420-ish) with light border and light text -- theme-adaptive chrome confirmed correct, no dark-on-dark failure there. + Data: Confirmed pixel-identical hex values to the light render (#009E73 green / #AE3030 red) -- only chrome flipped, as required. + Legibility verdict: PASS overall (nothing is fully unreadable), but flagged: the red "Decrease" line/markers/labels measure ~2.7:1 contrast against the #1A1A17 background (below the 3:1 AA large-text floor) and read visibly duller than their green counterparts when directly compared (e.g. "Speaker" label crop vs "Webcam" label crop) -- not a hard dark-on-dark failure, but a real accessibility dip worth a stroke/outline fix. + criteria_checklist: + visual_quality: + score: 27 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All font sizes explicitly set, readable in both themes; deducted + for x/y tick-label size imbalance (see VQ-05). + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: Deliberate ~90-unit entity spacing keeps all 20 direct labels collision-free + in both renders. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Marker size=200 and strokeWidth=3 are well-suited to 10 sparse entities. + - id: VQ-04 + name: Color Accessibility + score: 1 + max: 2 + passed: true + comment: Red 'Decrease' elements measure ~2.7:1 contrast on the dark background + (below 3:1 AA large-text floor) -- matches the style guide's own flagged + 'matte-red marginally below on dark bg' case. Green on light measures ~3.2:1, + acceptable. + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: Content spans ~83% width / ~81% height of canvas, well-balanced margins, + legend sits near the plot. Deducted for x-tick (14px) vs y-tick (10px) label + size mismatch -- measured glyph heights ~42px vs ~26px. + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Y-axis labeled 'Sales (units)' with units; x categorical ticks ('Q1 + Sales'/'Q4 Sales') are self-descriptive. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Backgrounds measured exactly #FAF8F1/#1A1A17; first series exactly + #009E73; Decrease correctly uses semantic red anchor #AE3030; data colors + identical across themes.' + design_excellence: + score: 15 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Semantic color coding (green=increase, red=decrease) plus direct + end-labels goes clearly above a configured default. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: L-shaped frame, subtle grid opacity, generous whitespace; deducted + because gridDash=[4,4] contradicts the style guide's 'solid thin lines (not + dashed)' grid guideline. + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: true + comment: Direction color-coding + direct labeling gives an immediate, clear + focal point on which entities rose vs fell and by how much. + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: 'Correct slopegraph construction: two categorical x-positions connected + by lines per entity.' + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Labels at both endpoints, direction color-coding, and time-point-labeled + axis positions all present per spec Notes. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=Period, Y=Sales; all 10 entities x 2 periods visible. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title exactly matches 'slope-basic · python · altair · anyplot.ai'; + legend labels 'Increase'/'Decrease' match the Direction field. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Full rank shuffle shows both increases and decreases with varied + magnitudes, not a one-sided trend. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Q1 vs Q4 product sales -- realistic, neutral business scenario. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Sales values (190-1150 units) are plausible and internally consistent. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Flat imports -> data -> plot -> save, no functions/classes. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Fully deterministic hardcoded data, no randomness. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: os, altair, pandas, PIL.Image all used. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Appropriately complex for a 4-layer slope chart, no over-engineering. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png and plot-{THEME}.html via current chart.save + API. + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Declarative encode() chains and layer composition are idiomatic Altair. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: Layer composition of line+point+dual-text marks is Altair-flavored, + but the pattern is generic enough that other grammar-of-graphics libraries + could replicate it closely. + verdict: REJECTED +impl_tags: + dependencies: + - pillow + techniques: + - layer-composition + - annotations + patterns: + - wide-to-long + dataprep: [] + styling: + - alpha-blending + - grid-styling From 6b28d7db0511d7463706c519a195b4daa44dccf2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 23:42:59 +0000 Subject: [PATCH 4/5] fix(altair): address review feedback for slope-basic Attempt 1/4 - fixes based on AI review --- .../implementations/python/altair.py | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/plots/slope-basic/implementations/python/altair.py b/plots/slope-basic/implementations/python/altair.py index bda223b5cc..4236b06523 100644 --- a/plots/slope-basic/implementations/python/altair.py +++ b/plots/slope-basic/implementations/python/altair.py @@ -1,4 +1,4 @@ -""" anyplot.ai +"""anyplot.ai slope-basic: Basic Slope Chart (Slopegraph) Library: altair 6.2.2 | Python 3.13.14 Quality: 89/100 | Created: 2026-07-25 @@ -53,12 +53,19 @@ title = "slope-basic · python · altair · anyplot.ai" title_fontsize = 16 +# Matte-red "Decrease" measures ~2.7:1 against #1A1A17 (below the 3:1 AA floor for +# meaningful status info); per the style guide's stroke recommendation, outline +# Decrease elements with a 1px ink halo in dark mode only (fine on the light bg). +decrease_stroke_width = alt.condition( + alt.datum.Direction == "Decrease", alt.value(1 if THEME == "dark" else 0), alt.value(0) +) + # Plot lines = ( alt.Chart(df_long) .mark_line(strokeWidth=3, opacity=0.8) .encode( - x=alt.X("Period:N", axis=alt.Axis(labelFontSize=14, title=None, labelAngle=0)), + x=alt.X("Period:N", axis=alt.Axis(labelFontSize=11, title=None, labelAngle=0)), y=alt.Y( "Sales:Q", axis=alt.Axis(labelFontSize=10, titleFontSize=12, title="Sales (units)"), @@ -71,27 +78,57 @@ ) ) +lines_decrease_halo = ( + alt.Chart(df_long[df_long["Direction"] == "Decrease"]) + .mark_line(strokeWidth=5, opacity=0.9, color=INK) + .encode(x="Period:N", y="Sales:Q", detail="Product:N") +) + points = ( alt.Chart(df_long) .mark_circle(size=200, opacity=0.9) - .encode(x="Period:N", y="Sales:Q", color=alt.Color("Direction:N", scale=color_scale, legend=None)) + .encode( + x="Period:N", + y="Sales:Q", + color=alt.Color("Direction:N", scale=color_scale, legend=None), + stroke=alt.value(INK), + strokeWidth=decrease_stroke_width, + ) ) labels_left = ( alt.Chart(df_long[df_long["Period"] == "Q1 Sales"]) .mark_text(align="right", dx=-12, fontSize=11) - .encode(x="Period:N", y="Sales:Q", text="Product:N", color=alt.Color("Direction:N", scale=color_scale, legend=None)) + .encode( + x="Period:N", + y="Sales:Q", + text="Product:N", + color=alt.Color("Direction:N", scale=color_scale, legend=None), + stroke=alt.value(INK), + strokeWidth=decrease_stroke_width, + ) ) labels_right = ( alt.Chart(df_long[df_long["Period"] == "Q4 Sales"]) .mark_text(align="left", dx=12, fontSize=11) - .encode(x="Period:N", y="Sales:Q", text="Product:N", color=alt.Color("Direction:N", scale=color_scale, legend=None)) + .encode( + x="Period:N", + y="Sales:Q", + text="Product:N", + color=alt.Color("Direction:N", scale=color_scale, legend=None), + stroke=alt.value(INK), + strokeWidth=decrease_stroke_width, + ) ) +# Layer order: dark-mode Decrease halo sits beneath everything else; empty in light mode. +layers = [lines_decrease_halo] if THEME == "dark" else [] +layers += [lines, points, labels_left, labels_right] + # Style — L-shaped frame: no view stroke, axis domain lines (bottom + left) stay visible chart = ( - (lines + points + labels_left + labels_right) + alt.layer(*layers) .properties( width=620, height=320, @@ -105,7 +142,6 @@ grid=True, gridColor=INK, gridOpacity=0.12, - gridDash=[4, 4], labelColor=INK_SOFT, titleColor=INK, ) From fac275f754d3eb257eb417b3c5d8a0c6f13ce76f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 23:51:59 +0000 Subject: [PATCH 5/5] chore(altair): update quality score 87 and review feedback for slope-basic --- .../implementations/python/altair.py | 4 +- plots/slope-basic/metadata/python/altair.yaml | 204 ++++++++---------- 2 files changed, 97 insertions(+), 111 deletions(-) diff --git a/plots/slope-basic/implementations/python/altair.py b/plots/slope-basic/implementations/python/altair.py index 4236b06523..47791cd638 100644 --- a/plots/slope-basic/implementations/python/altair.py +++ b/plots/slope-basic/implementations/python/altair.py @@ -1,7 +1,7 @@ -"""anyplot.ai +""" anyplot.ai slope-basic: Basic Slope Chart (Slopegraph) Library: altair 6.2.2 | Python 3.13.14 -Quality: 89/100 | Created: 2026-07-25 +Quality: 87/100 | Created: 2026-07-25 """ import os diff --git a/plots/slope-basic/metadata/python/altair.yaml b/plots/slope-basic/metadata/python/altair.yaml index b2814461e7..1076bb1583 100644 --- a/plots/slope-basic/metadata/python/altair.yaml +++ b/plots/slope-basic/metadata/python/altair.yaml @@ -2,7 +2,7 @@ library: altair language: python specification_id: slope-basic created: 2025-12-17 02:32:26+00:00 -updated: '2026-07-25T23:38:39Z' +updated: '2026-07-25T23:51:59Z' generated_by: claude-sonnet workflow_run: 30179400333 issue: 981 @@ -12,50 +12,57 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/slope-bas preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/altair/plot-dark.html -quality_score: 89 +quality_score: 87 review: strengths: - - Correct, idiomatic Altair slopegraph built from four layered marks (line + circle - + two direct-label text layers) with a full rank-shuffle across 10 entities, making - the increase/decrease story immediately legible. - - 'Exact theme-token compliance: measured background pixels are precisely #FAF8F1 - (light) and #1A1A17 (dark); first series is exactly #009E73; ''Decrease'' correctly - uses the semantic red anchor #AE3030 (loss/decline meaning matches the legend - label).' - - Entities are deliberately spaced (~90-unit minimum gap per comment) so all 20 - direct end-labels stay collision-free in both renders despite 10 overlapping entities. - - Canvas padding logic (raise on overshoot, center-pad on undershoot) lands both - PNGs exactly at 3200x1800 with no drift. + - 'Correctly addressed all three attempt-1 weaknesses: grid is now solid (no more + gridDash), x-axis tick fontSize brought down to 11 (near-balanced with the y-axis''s + 10), and a dark-mode halo/stroke technique was added to lift the matte-red ''Decrease'' + contrast on the near-black background.' + - Idiomatic layered Altair slopegraph (line + circle + two direct-label text layers) + with a full rank-shuffle across 10 entities and deliberately spaced values (~90-unit + minimum gap) so all 20 endpoint labels stay collision-free in both renders. + - 'Exact theme-token compliance for backgrounds, axis chrome, and legend box; first + series is exactly #009E73 and the legend swatches correctly show green/red for + Increase/Decrease in both themes.' + - Canvas padding logic lands both PNGs exactly at 3200x1800 with no drift; spec + data (entity + two time points), realistic product-sales scenario, and clean KISS + code structure are all still excellent. weaknesses: - - 'Red ''Decrease'' elements (line, markers, product labels) measure ~2.7:1 contrast - against the #1A1A17 dark background -- below the 3:1 AA large-text floor, and - exactly the case the style guide flags as ''matte-red marginally below on dark - bg.'' Since Decrease is meaningful status information (not decorative), add the - style guide''s recommended 1px ink-color stroke to the red line/markers/labels - in dark mode.' - - X-axis tick labels ('Q1 Sales' / 'Q4 Sales', labelFontSize=14) render visibly - larger than the Y-axis tick labels (labelFontSize=10) -- measured glyph heights - ~42px vs ~26px, a ~1.6x mismatch with no semantic reason. Bring the x tick fontsize - down to ~11-12 for balance with the y ticks. - - Grid lines use gridDash=[4,4] on both axes; the style guide's Grid Guidelines - call for solid thin lines at low opacity, not dashed, for the recurring data grid - -- reserve dashing for genuine reference lines only. - - Library Mastery is generic layering (lines+points+text) that most declarative-grammar - libraries could replicate; a distinctive Altair touch (e.g. alt.condition to highlight - a specific entity, or an interactive selection in the HTML view) would lift LM-02 - beyond 'some library-specific features.' + - 'Regression from the attempt-1 fix: applying `stroke=alt.value(INK)` with `strokeWidth=1` + directly to the `mark_text` layers (labels_left/labels_right) for the ''Decrease'' + direction in dark mode does not just add a thin outline -- at fontSize=11 the + 1px ink stroke fully overwhelms the thin matte-red fill, so ''Phone'', ''Mouse'', + ''Keyboard'', ''Charger'', and ''Speaker'' render as solid ink-white/bold text + in plot-dark.png with essentially zero red visible (pixel-sampled: dominant colors + are exactly (26,26,23) background and (240,239,232) INK, no AE3030 present). The + circle markers and connecting lines are unaffected (they keep a correctly-colored + red fill with a thin visible ink ring) because they use a separate wider ink halo + layer underneath rather than a stroke on the mark itself -- only the text marks + have this problem.' + - Because of the above, the legend swatch correctly shows red for 'Decrease' but + half of the direct-label text in dark mode no longer matches that legend color, + and the two direction categories end up with visibly different font weights in + dark mode (thin green 'Increase' labels vs. bold-looking white 'Decrease' labels) + even though both use the same fontSize=11 -- an unintended typographic inconsistency, + not a deliberate hierarchy choice. + - 'Fix: do not apply a stroke to the text marks. Either drop `stroke`/`strokeWidth` + entirely from labels_left/labels_right (contrast for red-on-dark can be handled + by using a wider outline **halo** approach like the one already used for lines_decrease_halo, + or a much thinner stroke e.g. 0.3-0.4px, or by making the label font slightly + heavier without an outline).' image_description: |- Light render (plot-light.png): - Background: Measured exactly #FAF8F1 -- warm off-white, matches spec. - Chrome: Title "slope-basic · python · altair · anyplot.ai" bold dark ink, clearly visible. Y-axis title "Sales (units)" dark ink with units. Y tick numbers (100-1,200) in soft dark ink, readable. X-axis has two categorical positions "Q1 Sales" / "Q4 Sales" in bold dark ink -- readable but noticeably larger than the y tick numbers (measured glyph height ~42px vs ~26px). Grid: dashed horizontal lines plus two dashed vertical reference lines at the Q1/Q4 columns, subtle opacity (~0.12). - Data: 10 sloped lines colored either brand green #009E73 (Increase) or matte red #AE3030 (Decrease), with circular endpoint markers and direct product-name labels colored to match their line's direction at both the Q1 and Q4 ends. Legend top-right in a bordered cream box, dark text, "Direction" title with green/red swatches. - Legibility verdict: PASS -- all text is clearly readable against the light background; no light-on-light issues. + Background: Warm off-white, matches #FAF8F1 -- not pure white, correct. + Chrome: Title "slope-basic · python · altair · anyplot.ai" bold dark ink, centered, clearly visible. Y-axis title "Sales (units)" and tick labels (100-1,200) dark/soft-dark ink, readable. X-axis categorical ticks "Q1 Sales"/"Q4 Sales" now close in size to the y ticks (fontSize 11 vs 10), a big improvement over attempt 1's 14 vs 10 mismatch. Grid is now solid, thin, subtle (attempt-1's dashed grid was fixed). Legend "Direction" sits top-right in a bordered cream box with no overlap against the "Laptop" data point beside it. + Data: 10 sloped lines/markers colored brand green #009E73 (Increase) or matte red #AE3030 (Decrease), with circular endpoint markers (white ring) and direct product-name labels colored to match direction at both ends. All labels are cleanly spaced with no collisions. + Legibility verdict: PASS -- all text is dark-on-light and fully readable, no light-on-light issues. Dark render (plot-dark.png): - Background: Measured exactly #1A1A17 -- warm near-black, matches spec. - Chrome: Title renders in light/white bold ink, clearly visible. Axis title and tick labels use theme-adaptive soft-light ink, readable. Grid dashed at the same subtle opacity. Legend box correctly flips to a dark-elevated fill (#242420-ish) with light border and light text -- theme-adaptive chrome confirmed correct, no dark-on-dark failure there. - Data: Confirmed pixel-identical hex values to the light render (#009E73 green / #AE3030 red) -- only chrome flipped, as required. - Legibility verdict: PASS overall (nothing is fully unreadable), but flagged: the red "Decrease" line/markers/labels measure ~2.7:1 contrast against the #1A1A17 background (below the 3:1 AA large-text floor) and read visibly duller than their green counterparts when directly compared (e.g. "Speaker" label crop vs "Webcam" label crop) -- not a hard dark-on-dark failure, but a real accessibility dip worth a stroke/outline fix. + Background: Warm near-black, matches #1A1A17 -- not pure black, correct. + Chrome: Title, axis title, and tick labels correctly use light ink and are all clearly readable; grid is solid and subtle; legend box correctly flips to a dark-elevated fill with light border/text and does not overlap data. + Data: Circle markers and connecting lines are pixel-identical in hue to the light render (#009E73 / #AE3030) -- confirmed by pixel sampling, the matte-red circle/line for "Phone" is still (174,48,48). However, the direct-label TEXT for every 'Decrease' entity ("Phone", "Mouse", "Keyboard", "Charger", "Speaker") renders as solid ink-white/bold instead of red -- pixel sampling over the "Phone" label found only background (26,26,23) and INK (240,239,232), zero red pixels. This is caused by the new `stroke=INK, strokeWidth=1` fix applied directly to the text marks: at fontSize=11 the ink stroke completely overwhelms the thin red glyph fill. The legend swatch still correctly shows red for "Decrease", so there is a real mismatch between the legend's red indicator and the white-rendered direct labels for that category. + Legibility verdict: PASS for reading the words themselves (high contrast, no dark-on-dark), but FAIL for color-identity: the 'Decrease' category's text color is not preserved between themes, unlike every other data-encoded element in the chart. criteria_checklist: visual_quality: score: 27 @@ -66,79 +73,75 @@ review: score: 7 max: 8 passed: true - comment: All font sizes explicitly set, readable in both themes; deducted - for x/y tick-label size imbalance (see VQ-05). + comment: Explicit font sizes throughout; x-tick vs y-tick balance fixed (11 + vs 10, was 14 vs 10). All text reads clearly in both themes. - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: Deliberate ~90-unit entity spacing keeps all 20 direct labels collision-free - in both renders. + comment: All 20 direct end-labels stay collision-free in both renders; legend + does not overlap data. - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Marker size=200 and strokeWidth=3 are well-suited to 10 sparse entities. + comment: Line width 3, circle size 200, well-adapted for 10 sparse entities. - id: VQ-04 name: Color Accessibility score: 1 max: 2 - passed: true - comment: Red 'Decrease' elements measure ~2.7:1 contrast on the dark background - (below 3:1 AA large-text floor) -- matches the style guide's own flagged - 'matte-red marginally below on dark bg' case. Green on light measures ~3.2:1, - acceptable. + passed: false + comment: The dark-mode ink stroke fix for red-on-dark contrast overcorrects + on text marks, fully erasing the red fill instead of tastefully outlining + it. - id: VQ-05 name: Layout & Canvas - score: 3 + score: 4 max: 4 passed: true - comment: Content spans ~83% width / ~81% height of canvas, well-balanced margins, - legend sits near the plot. Deducted for x-tick (14px) vs y-tick (10px) label - size mismatch -- measured glyph heights ~42px vs ~26px. + comment: Balanced margins, no cut-off content, canvas lands exactly on 3200x1800 + target. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Y-axis labeled 'Sales (units)' with units; x categorical ticks ('Q1 - Sales'/'Q4 Sales') are self-descriptive. + comment: Y-axis labeled 'Sales (units)' with units. - id: VQ-07 name: Palette Compliance - score: 2 + score: 1 max: 2 - passed: true - comment: 'Backgrounds measured exactly #FAF8F1/#1A1A17; first series exactly - #009E73; Decrease correctly uses semantic red anchor #AE3030; data colors - identical across themes.' + passed: false + comment: 'Line/marker/legend colors are pixel-identical across themes as required, + but the ''Decrease'' direct-label text renders ink-white instead of #AE3030 + in dark mode -- a data color that is not identical between themes.' design_excellence: - score: 15 + score: 13 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 6 + score: 5 max: 8 - passed: true - comment: Semantic color coding (green=increase, red=decrease) plus direct - end-labels goes clearly above a configured default. + comment: Semantic direction coloring, direct end-labels, and a deliberate + halo technique show real design intent, but the dark-mode text bug is a + visible flaw that keeps this below 'strong design' tier. - id: DE-02 name: Visual Refinement score: 4 max: 6 - passed: true - comment: L-shaped frame, subtle grid opacity, generous whitespace; deducted - because gridDash=[4,4] contradicts the style guide's 'solid thin lines (not - dashed)' grid guideline. + comment: Grid is now solid and subtle (attempt-1's dashed grid fixed), spines + L-shaped, generous whitespace -- offset by the unintended font-weight inconsistency + between direction categories in dark mode. - id: DE-03 name: Data Storytelling - score: 5 + score: 4 max: 6 - passed: true - comment: Direction color-coding + direct labeling gives an immediate, clear - focal point on which entities rose vs fell and by how much. + comment: Color + direct labels give an immediate focal point in light mode + and via lines/legend in dark mode, but the broken text-color identity for + 'Decrease' weakens the at-a-glance read in dark mode specifically. spec_compliance: score: 15 max: 15 @@ -147,29 +150,24 @@ review: name: Plot Type score: 5 max: 5 - passed: true - comment: 'Correct slopegraph construction: two categorical x-positions connected - by lines per entity.' + comment: Correct slopegraph with two time-point vertical axes. - id: SC-02 name: Required Features score: 4 max: 4 - passed: true - comment: Labels at both endpoints, direction color-coding, and time-point-labeled - axis positions all present per spec Notes. + comment: Labels at both endpoints, color-coded lines by direction, time-point + axis labels -- all present. - id: SC-03 name: Data Mapping score: 3 max: 3 - passed: true - comment: X=Period, Y=Sales; all 10 entities x 2 periods visible. + comment: X = period, Y = sales, all 10 entities visible. - id: SC-04 name: Title & Legend score: 3 max: 3 - passed: true - comment: Title exactly matches 'slope-basic · python · altair · anyplot.ai'; - legend labels 'Increase'/'Decrease' match the Direction field. + comment: Title format exact; legend labels 'Increase'/'Decrease' match the + data. data_quality: score: 15 max: 15 @@ -178,20 +176,16 @@ review: name: Feature Coverage score: 6 max: 6 - passed: true - comment: Full rank shuffle shows both increases and decreases with varied - magnitudes, not a one-sided trend. + comment: Full rank shuffle with both increases and decreases across 10 entities. - id: DQ-02 name: Realistic Context score: 5 max: 5 - passed: true - comment: Q1 vs Q4 product sales -- realistic, neutral business scenario. + comment: Neutral, comprehensible product-sales scenario (Q1 vs Q4). - id: DQ-03 name: Appropriate Scale score: 4 max: 4 - passed: true comment: Sales values (190-1150 units) are plausible and internally consistent. code_quality: score: 10 @@ -201,33 +195,28 @@ review: name: KISS Structure score: 3 max: 3 - passed: true - comment: Flat imports -> data -> plot -> save, no functions/classes. + comment: Linear imports -> data -> plot -> save, no functions/classes. - id: CQ-02 name: Reproducibility score: 2 max: 2 - passed: true - comment: Fully deterministic hardcoded data, no randomness. + comment: Fully deterministic hard-coded data. - id: CQ-03 name: Clean Imports score: 2 max: 2 - passed: true - comment: os, altair, pandas, PIL.Image all used. + comment: Only altair, pandas, PIL (for the mandatory pad-to-canvas step), + os. - id: CQ-04 name: Code Elegance score: 2 max: 2 - passed: true - comment: Appropriately complex for a 4-layer slope chart, no over-engineering. + comment: Clean, appropriately complex for a layered slopegraph. - id: CQ-05 name: Output & API score: 1 max: 1 - passed: true - comment: Saves plot-{THEME}.png and plot-{THEME}.html via current chart.save - API. + comment: Saves plot-{THEME}.png and .html via current API. library_mastery: score: 7 max: 10 @@ -236,26 +225,23 @@ review: name: Idiomatic Usage score: 4 max: 5 - passed: true - comment: Declarative encode() chains and layer composition are idiomatic Altair. + comment: Correct declarative encoding, alt.condition, alt.layer usage. - id: LM-02 name: Distinctive Features score: 3 max: 5 - passed: true - comment: Layer composition of line+point+dual-text marks is Altair-flavored, - but the pattern is generic enough that other grammar-of-graphics libraries - could replicate it closely. - verdict: REJECTED + comment: Layered mark composition with conditional per-datum styling is Altair-flavored, + though a generic-enough pattern that other declarative-grammar libraries + could replicate. + verdict: APPROVED impl_tags: dependencies: - pillow techniques: - layer-composition - - annotations patterns: - wide-to-long dataprep: [] styling: - alpha-blending - - grid-styling + - edge-highlighting