diff --git a/plots/slope-basic/implementations/python/pygal.py b/plots/slope-basic/implementations/python/pygal.py index 6b24e5ad9d..b40037b1e2 100644 --- a/plots/slope-basic/implementations/python/pygal.py +++ b/plots/slope-basic/implementations/python/pygal.py @@ -1,91 +1,99 @@ -""" pyplots.ai +""" anyplot.ai slope-basic: Basic Slope Chart (Slopegraph) -Library: pygal 3.1.0 | Python 3.13.11 -Quality: 86/100 | Created: 2025-12-23 +Library: pygal 3.1.0 | Python 3.13.13 +Quality: 86/100 | Updated: 2026-04-30 """ -import pygal -from pygal.style import Style +import os +import sys -# Data - Sales figures comparing Q1 vs Q4 for 10 products -products = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] +# Pop script dir so this file (pygal.py) doesn't shadow the installed pygal package +_script_dir = sys.path.pop(0) +import pygal # noqa: E402 +from pygal.style import Style # noqa: E402 + + +sys.path.insert(0, _script_dir) + +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" + +COLOR_INCREASE = "#009E73" # Okabe-Ito position 1 — upward change +COLOR_DECREASE = "#D55E00" # Okabe-Ito position 2 — downward change + +# Realistic product category data — Q1 vs Q4 sales comparison +categories = [ + "Electronics", + "Apparel", + "Furniture", + "Automotive", + "Food & Bev", + "Sporting Goods", + "Home Decor", + "Office Supplies", + "Beauty", + "Toys", +] q1_sales = [85, 72, 95, 45, 68, 52, 78, 62, 88, 40] q4_sales = [92, 58, 102, 75, 65, 71, 82, 48, 95, 55] -# Colorblind-friendly colors for increase vs decrease (spec requirement) -COLOR_INCREASE = "#2166AC" # Blue - value went up -COLOR_DECREASE = "#D6604D" # Orange-red - value went down - -# Separate products by direction for proper color grouping -increasing = [] -decreasing = [] -for i, p in enumerate(products): - if q4_sales[i] >= q1_sales[i]: - increasing.append((p, q1_sales[i], q4_sales[i])) - else: - decreasing.append((p, q1_sales[i], q4_sales[i])) +increasing = [(c, q1_sales[i], q4_sales[i]) for i, c in enumerate(categories) if q4_sales[i] >= q1_sales[i]] +decreasing = [(c, q1_sales[i], q4_sales[i]) for i, c in enumerate(categories) if q4_sales[i] < q1_sales[i]] -# Build color tuple: increasing (blue) then decreasing (orange) series_colors = tuple([COLOR_INCREASE] * len(increasing) + [COLOR_DECREASE] * len(decreasing)) -# Custom style for 4800x2700 px with clean legend (2 grouped entries) custom_style = Style( - background="white", - plot_background="white", - foreground="#333333", - foreground_strong="#333333", - foreground_subtle="#666666", + background=PAGE_BG, + plot_background=PAGE_BG, + foreground=INK, + foreground_strong=INK, + foreground_subtle=INK_MUTED, colors=series_colors, - title_font_size=72, + title_font_size=64, label_font_size=44, - major_label_font_size=48, - legend_font_size=48, - value_font_size=36, - stroke_width=5, - value_label_font_size=32, + major_label_font_size=44, + legend_font_size=38, + value_font_size=32, + value_label_font_size=36, + stroke_width=6, ) -# Create slope chart using Line chart with only 2 x-axis points +# Slope chart: Line chart with only 2 x-axis time points chart = pygal.Line( width=4800, height=2700, - title="slope-basic · pygal · pyplots.ai", + title="slope-basic · pygal · anyplot.ai", x_title="Time Period", y_title="Sales (units)", style=custom_style, show_dots=True, - dots_size=16, - stroke_style={"width": 5}, + dots_size=18, + stroke_style={"width": 6}, show_y_guides=True, show_x_guides=False, show_legend=True, - legend_at_bottom=True, - legend_box_size=32, - truncate_legend=-1, interpolate=None, - margin=120, + margin=140, print_values=False, - print_labels=False, - range=(30, 110), + print_labels=True, + range=(30, 115), + margin_right=340, ) -# X-axis labels (two time points) chart.x_labels = ["Q1 2024", "Q4 2024"] -# Add series - first entry in each group shows in legend, rest use None to hide -for i, (p, start, end) in enumerate(increasing): - chart.add( - "Increasing" if i == 0 else None, - [{"value": start, "label": f"Product {p}: {start}"}, {"value": end, "label": f"Product {p}: {end}"}], - ) - -for i, (p, start, end) in enumerate(decreasing): - chart.add( - "Decreasing" if i == 0 else None, - [{"value": start, "label": f"Product {p}: {start}"}, {"value": end, "label": f"Product {p}: {end}"}], - ) - -# Save as PNG and HTML (HTML provides interactive hover labels) -chart.render_to_png("plot.png") -chart.render_to_file("plot.html") +# Increasing categories — Okabe-Ito green +for c, start, end in increasing: + chart.add(c, [{"value": start, "label": c}, {"value": end, "label": c}]) + +# Decreasing categories — Okabe-Ito vermillion +for c, start, end in decreasing: + chart.add(c, [{"value": start, "label": c}, {"value": end, "label": c}]) + +# Save PNG and interactive HTML +chart.render_to_png(f"plot-{THEME}.png") +with open(f"plot-{THEME}.html", "wb") as f: + f.write(chart.render()) diff --git a/plots/slope-basic/metadata/python/pygal.yaml b/plots/slope-basic/metadata/python/pygal.yaml index c567c73fd9..094bcc5ac6 100644 --- a/plots/slope-basic/metadata/python/pygal.yaml +++ b/plots/slope-basic/metadata/python/pygal.yaml @@ -1,34 +1,238 @@ library: pygal +language: python specification_id: slope-basic created: '2025-12-23T20:44:50Z' -updated: '2025-12-23T21:12:53Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20471153223 -issue: 0 -python_version: 3.13.11 +updated: '2026-04-30T17:06:20Z' +generated_by: claude-sonnet +workflow_run: 25177667873 +issue: 981 +python_version: 3.13.13 library_version: 3.1.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/slope-basic/pygal/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/slope-basic/pygal/plot.html +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/pygal/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/pygal/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/pygal/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/pygal/plot-dark.html quality_score: 86 +review: + strengths: + - Semantic color coding (green=increase, orange=decrease) makes the slope chart + purpose immediately clear + - All font sizes explicitly calibrated for 4800x2700 — readable in both themes + - 'Perfect spec compliance: correct title format, labels at both endpoints, time-labeled + axes' + - Deterministic data with clean KISS code structure and idiomatic pygal usage + - Both HTML and PNG outputs correctly generated for both themes + weaknesses: + - Left-side (Q1 2024) endpoint labels are crowded — Furniture/Beauty/Electronics + cluster in 85-95 range; consider increasing margin_left or reducing entity count + - pygal default legend frame box partially overlaps chart area on the left side + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct theme surface + Chrome: Title "slope-basic · pygal · anyplot.ai" dark ink, clearly readable; Y-axis "Sales (units)" and X-axis "Time Period" visible; tick labels "Q1 2024" / "Q4 2024" readable; full legend in upper-left + Data: 7 green (#009E73) lines for increasing categories, 3 orange (#D55E00) for decreasing; endpoint dots and category-name labels at both endpoints; right-side (Q4) labels well-spaced; left-side (Q1) labels crowded in 85-95 range (Furniture/Beauty/Electronics stacked) + Legibility verdict: PASS (minor label crowding on Q1 side does not prevent readability) + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct theme surface + Chrome: All text flips to light; title, axis labels, tick labels, and endpoint category names all appear in light ink against dark surface; no dark-on-dark failures observed + Data: Colors identical to light render — green (#009E73) and orange (#D55E00) unchanged; same left-side label crowding present but all labels individually readable + Legibility verdict: PASS + criteria_checklist: + visual_quality: + score: 26 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: 'All font sizes explicitly set: title=64, label=44, major_label=44, + legend=38, value_label=36; readable in both themes' + - id: VQ-02 + name: No Overlap + score: 3 + max: 6 + passed: false + comment: Left-side (Q1) endpoint labels crowded; Furniture/Beauty/Electronics + cluster in 85-95 range stacks too tightly + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Stroke width=6 and dot size=18 make all lines and endpoints clearly + visible at full canvas + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Green (#009E73) and vermillion (#D55E00) are CVD-safe Okabe-Ito; + no red-green-only signal + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: Right margin extended for end labels; overall utilisation good; legend + slightly overlaps chart area on left + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: 'Y: ''Sales (units)'' with units; X: ''Time Period'' descriptive' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First series #009E73, second #D55E00; backgrounds #FAF8F1/#1A1A17; + theme chrome fully adaptive in both renders' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: Semantic color coding (green=increase, orange=decrease) shows genuine + design intent; professional and clean but not publication-ready + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Y-only grid is correct; generous margins; pygal default legend frame + and overall styling still visible + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Semantic color encoding makes winners vs losers story immediately + apparent; clear visual hierarchy + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: pygal.Line with 2 x-axis time points correctly implements the slopegraph + format + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Labels at both endpoints, color coding by direction, time-labeled + axes, legend present + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Time period on X, sales values on Y; all 10 entities visible across + full range + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title 'slope-basic · pygal · anyplot.ai' matches format; legend labels + match category names + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: 7 increasing and 3 decreasing categories; bidirectional change; crossing + lines show rank reversals; varied magnitudes + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Q1 vs Q4 product category sales — realistic, neutral business scenario + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Values 40-102 units plausible; changes up to 30 units realistic for + quarterly comparison + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Clean linear flow: imports → constants → data → filtering → style + → chart → add series → save' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Fully deterministic hardcoded arrays + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: Only os, sys, pygal, Style; sys path manipulation necessary to avoid + naming conflict + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: List comprehensions for increasing/decreasing filtering are Pythonic + and clear + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Correct Style usage for all theme tokens, dict-format data for inline + labels, pygal.Line for slope chart + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: HTML export leverages pygal's interactive nature; dict-format data + with 'label' key is a pygal-specific feature + verdict: APPROVED impl_tags: dependencies: [] techniques: - html-export patterns: - - data-generation - iteration-over-groups dataprep: [] styling: [] -review: - strengths: - - Colorblind-friendly color scheme with blue for increasing and orange-red for decreasing - - Clean slope chart visualization with clear start/end points - - Proper title format and axis labels with units - - Good dot and line sizing for visibility - - Produces both PNG and interactive HTML output - weaknesses: - - Legend displays all 10 individual products instead of 2 grouped entries (Increasing/Decreasing) - despite code attempting this - - Right-side endpoint labels are truncated, showing only Product and value without - the letter identifier - - Minor label overlap in crowded middle region (products B, E around 68-72)