diff --git a/plots/scatter-connected-temporal/implementations/python/pygal.py b/plots/scatter-connected-temporal/implementations/python/pygal.py index 88807f009a..7e5f9b2c47 100644 --- a/plots/scatter-connected-temporal/implementations/python/pygal.py +++ b/plots/scatter-connected-temporal/implementations/python/pygal.py @@ -1,72 +1,89 @@ -""" pyplots.ai +""" anyplot.ai scatter-connected-temporal: Connected Scatter Plot with Temporal Path -Library: pygal 3.1.0 | Python 3.14.3 -Quality: 87/100 | Created: 2026-03-13 +Library: pygal 3.1.0 | Python 3.13.13 +Quality: 84/100 | Updated: 2026-06-09 """ +import os +import re +import sys + + +# Remove script's own directory from sys.path so the real pygal package is found first +_here = os.path.dirname(os.path.abspath(__file__)) +if _here in sys.path: + sys.path.remove(_here) + import cairosvg import numpy as np import pygal from pygal.style import Style -# Data — Life expectancy vs GDP per capita for a fictional country (1990-2023) +# Theme tokens +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" + +# Imprint palette — amber semantic anchor for key events +ANYPLOT_AMBER = "#DDCC77" + +# Data — Life expectancy vs GDP per capita for a developing country (1990–2023) np.random.seed(42) years = list(range(1990, 2024)) n_years = len(years) -# GDP per capita starts ~8000, grows with occasional dips (recessions) gdp_base = 8000 gdp_growth = np.cumsum(np.random.normal(450, 300, n_years)) -gdp_growth[8:10] -= 1500 # 1998-1999 recession dip -gdp_growth[18:20] -= 2000 # 2008-2009 financial crisis -gdp_growth[30:32] -= 800 # 2020-2021 pandemic dip +gdp_growth[8:10] -= 1500 # 1998–1999 recession +gdp_growth[18:20] -= 2000 # 2008–2009 financial crisis +gdp_growth[30:32] -= 800 # 2020–2021 pandemic gdp_per_capita = gdp_base + gdp_growth gdp_per_capita = np.maximum(gdp_per_capita, 5000) -# Life expectancy starts ~68, rises gradually with dips during crises le_base = 68.0 le_growth = np.cumsum(np.random.normal(0.25, 0.12, n_years)) -le_growth[18:20] -= 0.4 # Financial crisis stress -le_growth[30:32] -= 1.2 # Pandemic drop +le_growth[18:20] -= 0.4 +le_growth[30:32] -= 1.2 life_expectancy = le_base + le_growth life_expectancy = np.clip(life_expectancy, 64, 82) -# Define temporal eras for color gradient segments (light → dark blue) -# Darkened lightest shades for better contrast against light background +# Imprint imprint_seq gradient (#009E73 → #4467A3) for temporal progression eras = [ - ("1990-1997", 0, 8, "#4da6c9"), - ("1998-2003", 8, 14, "#3d8fb5"), - ("2004-2009", 14, 20, "#2e78a0"), - ("2010-2015", 20, 26, "#22618a"), - ("2016-2019", 26, 30, "#1a4d70"), - ("2020-2023", 30, 34, "#0e2f44"), + ("1990–1997", 0, 8, "#009E73"), + ("1998–2003", 8, 14, "#0E937D"), + ("2004–2009", 14, 20, "#1B8886"), + ("2010–2015", 20, 26, "#297D90"), + ("2016–2019", 26, 30, "#367299"), + ("2020–2023", 30, 34, "#4467A3"), ] -# Select key years to annotate annotate_years = {1998, 2005, 2008, 2015, 2020} -# Style — refined with subtle grid and cohesive blue palette +# Title scaled for 81-char length: round(66 × 67/81) = 55 +title = "Life Expectancy vs GDP · scatter-connected-temporal · python · pygal · anyplot.ai" +title_font_size = max(44, round(66 * 67 / len(title))) + font = "DejaVu Sans, Helvetica, Arial, sans-serif" custom_style = Style( - background="white", - plot_background="#f4f7fa", - foreground="#3a3a3a", - foreground_strong="#2a2a2a", - foreground_subtle="#d8d8d8", - guide_stroke_color="#e0e0e0", + background=PAGE_BG, + plot_background=PAGE_BG, + foreground=INK, + foreground_strong=INK, + foreground_subtle=INK_MUTED, + guide_stroke_color=INK_MUTED, guide_stroke_dasharray="3,5", - colors=("#4da6c9", "#3d8fb5", "#2e78a0", "#22618a", "#1a4d70", "#0e2f44", "#e8a838", "#c0392b", "#1a3a5c"), + colors=("#009E73", "#0E937D", "#1B8886", "#297D90", "#367299", "#4467A3", ANYPLOT_AMBER, "#AE3030", "#4467A3"), font_family=font, title_font_family=font, - title_font_size=52, - label_font_size=40, - major_label_font_size=36, - legend_font_size=34, - legend_font_family=font, - value_font_size=32, - value_label_font_size=36, - tooltip_font_size=26, + title_font_size=title_font_size, + label_font_size=56, + major_label_font_size=44, + legend_font_size=44, + value_font_size=36, + value_label_font_size=44, + tooltip_font_size=36, tooltip_font_family=font, opacity=0.92, opacity_hover=1.0, @@ -74,36 +91,34 @@ stroke_opacity_hover=1.0, ) -# Axis ranges x_min = float(np.floor(gdp_per_capita.min() / 1000) * 1000) x_max = float(np.ceil(gdp_per_capita.max() / 1000) * 1000) y_min = float(np.floor(life_expectancy.min())) y_max = float(np.ceil(life_expectancy.max()) + 1) -# Create XY chart with native print_labels for year annotations chart = pygal.XY( - width=4800, - height=2700, + width=3200, + height=1800, style=custom_style, - title="Life Expectancy vs GDP · scatter-connected-temporal · pygal · pyplots.ai", + title=title, x_title="GDP per Capita (USD)", y_title="Life Expectancy (years)", show_legend=True, legend_at_bottom=True, legend_at_bottom_columns=3, - legend_box_size=24, + legend_box_size=28, stroke=True, - dots_size=10, + dots_size=12, show_x_guides=True, show_y_guides=True, - x_value_formatter=lambda x: f"${x:,.0f}", + x_value_formatter=lambda x: f"${x / 1000:.0f}k", value_formatter=lambda y: f"{y:.1f} yrs", print_labels=True, print_values=False, - margin_bottom=120, - margin_left=70, - margin_right=70, - margin_top=55, + margin_bottom=130, + margin_left=80, + margin_right=80, + margin_top=60, range=(y_min, y_max), xrange=(x_min, x_max), x_labels_major_count=7, @@ -113,8 +128,7 @@ show_y_labels=True, ) -# Add temporal path as gradient-colored era segments -# Each segment overlaps by 1 point for visual continuity +# Temporal path as Imprint imprint_seq gradient era segments for era_name, start, end, color in eras: end_idx = min(end + 1, n_years) segment_points = [ @@ -125,34 +139,53 @@ segment_points, stroke=True, show_dots=True, - dots_size=10, - stroke_style={"width": 6, "linecap": "round", "linejoin": "round"}, + dots_size=12, + stroke_style={"width": 5, "linecap": "round", "linejoin": "round"}, ) -# Highlight annotated key years with larger amber dots and native year labels +# Key years highlighted with amber dots and year labels annotated_points = [] for yr in sorted(annotate_years): i = yr - 1990 annotated_points.append( - {"value": (float(gdp_per_capita[i]), float(life_expectancy[i])), "label": str(yr), "color": "#e8a838"} + {"value": (float(gdp_per_capita[i]), float(life_expectancy[i])), "label": str(yr), "color": ANYPLOT_AMBER} ) -chart.add("Key years", annotated_points, stroke=False, dots_size=16) +chart.add("Key years", annotated_points, stroke=False, dots_size=20) -# Highlight start and end with distinct large markers +# Start and end markers chart.add( f"Start ({years[0]})", - [{"value": (float(gdp_per_capita[0]), float(life_expectancy[0])), "label": "\u25b6 1990", "color": "#c0392b"}], + [{"value": (float(gdp_per_capita[0]), float(life_expectancy[0])), "label": "▶ 1990", "color": "#AE3030"}], stroke=False, - dots_size=22, + dots_size=26, ) chart.add( f"End ({years[-1]})", - [{"value": (float(gdp_per_capita[-1]), float(life_expectancy[-1])), "label": "\u25cf 2023", "color": "#1a3a5c"}], + [{"value": (float(gdp_per_capita[-1]), float(life_expectancy[-1])), "label": "● 2023", "color": "#4467A3"}], stroke=False, - dots_size=22, + dots_size=26, ) -# Save PNG via cairosvg and HTML +# Patch label text colors for dark-theme legibility before PNG conversion +# pygal's print_labels text color does not adapt to the dark background via the foreground Style token +_label_texts = {str(yr) for yr in sorted(annotate_years)} | {"▶ 1990", "● 2023"} + + +def _patch_label_colors(svg_str, labels, fill_color): + def _fix(m): + tag_attrs, content = m.group(1), m.group(2) + if not any(lbl in content for lbl in labels): + return m.group(0) + if "fill=" in tag_attrs: + tag_attrs = re.sub(r'\bfill="[^"]*"', f'fill="{fill_color}"', tag_attrs) + else: + tag_attrs += f' fill="{fill_color}"' + return f"{content}" + + return re.sub(r"]*)>(.*?)", _fix, svg_str, flags=re.DOTALL) + + svg_data = chart.render() -cairosvg.svg2png(bytestring=svg_data, write_to="plot.png") -chart.render_to_file("plot.html") +svg_str = _patch_label_colors(svg_data.decode("utf-8"), _label_texts, INK) +cairosvg.svg2png(bytestring=svg_str.encode("utf-8"), write_to=f"plot-{THEME}.png") +chart.render_to_file(f"plot-{THEME}.html") diff --git a/plots/scatter-connected-temporal/metadata/python/pygal.yaml b/plots/scatter-connected-temporal/metadata/python/pygal.yaml index e90bc7fcc4..7e8dd1a267 100644 --- a/plots/scatter-connected-temporal/metadata/python/pygal.yaml +++ b/plots/scatter-connected-temporal/metadata/python/pygal.yaml @@ -1,43 +1,62 @@ library: pygal +language: python specification_id: scatter-connected-temporal created: '2026-03-13T15:25:29Z' -updated: '2026-03-13T16:10:33Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 23057666006 +updated: '2026-06-09T23:46:48Z' +generated_by: claude-sonnet +workflow_run: 27242045143 issue: 4675 -python_version: 3.14.3 +language_version: 3.13.13 library_version: 3.1.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/pygal/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/pygal/plot.html -quality_score: 87 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/pygal/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/pygal/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/pygal/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/pygal/plot-dark.html +quality_score: 84 review: strengths: - - Excellent data storytelling with temporal color gradient, annotated key years, - and distinct start/end markers creating a clear narrative arc - - Clean, cohesive blue palette with thoughtful accent colors (amber, red) that serve - functional purposes - - 'Full spec compliance: all required features (connected path, annotations, color - gradient, visible markers) implemented correctly' - - Realistic and engaging data scenario with meaningful crisis events visible in - the path - - Perfect code quality with clean KISS structure and elegant era-based loop + - 'Excellent temporal narrative: imprint_seq gradient (green→blue across 6 eras) + encodes time direction visually and the ▶ 1990 / ● 2023 bookend markers frame + the story clearly' + - Three-tier marker hierarchy (dots_size 12→20→26) creates intentional visual prominence + for era points, key events, and start/end anchors + - 'Correct semantic anchor usage: amber (#DDCC77) for key years and #AE3030 for + the start marker match the style-guide roles precisely' + - SVG label-color patching correctly adapts year annotation text to INK token in + dark theme, avoiding dark-on-dark failure + - 'Full spec compliance: connected path, chronological connection, color gradient, + key-year annotations, temporal direction arrow — all present' + - Realistic and rich dataset (developing country GDP vs life expectancy 1990–2023) + with correctly calibrated economic shocks (1998, 2008, 2020) + - Interactive HTML output produced alongside the PNG — pygal's distinctive interactive + feature used correctly weaknesses: - - Lightest blue era (#4da6c9) could have slightly more contrast against the light - plot background - - Minor visual density in the upper-right area where 2020-2023 points cluster - - Legend with 9 entries takes notable vertical space - image_description: 'The plot displays a connected scatter plot tracking Life Expectancy - (y-axis, ~68-78 yrs) against GDP per Capita (x-axis, ~$8,000-$22,000) from 1990 - to 2023. The temporal path progresses from bottom-left to upper-right, encoded - through a blue color gradient: light blue for 1990-1997 era, darkening progressively - through six era segments to dark navy for 2020-2023. Key years (1998, 2005, 2008, - 2015, 2020) are highlighted with amber/orange dots and year labels. The start - point (1990) features a large red dot with a triangle-right 1990 label, and the - end point (2023) has a large dark navy dot with a circle 2023 label. The plot - background is a subtle off-white (#f4f7fa) with dashed grid lines. The legend - at the bottom is organized in 3 columns showing all era segments plus key years, - start, and end markers. The path shows visible dips during the 1998 recession, - 2008 financial crisis, and 2020 pandemic.' + - 'Label crowding in the $13k–$16k GDP band: the ''2005'' and ''2008'' year labels + plus their amber dots cluster tightly with the era connection line — reduce label + font size slightly or offset the labels outward to reduce visual noise' + - Right margin is tight for the '● 2023' end-label — increase margin_right from + 80 to ~120 to give the label comfortable breathing room and prevent edge proximity + on the dark render + - Pygal's frame renders with all four spines (no spine removal support) — reduces + visual refinement compared to the L-shaped or no-spine ideal; this is a library + constraint but worth noting as a polish gap + - The '2015' and '2020' labels in the upper-right zone also cluster near the data + path — slightly larger x-offset for those label positions would improve readability + at the dense end of the trajectory + - Top margin (margin_top=60) is slightly small for the title; raising to ~90 would + give the title line more separation from the top of the chart area + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct theme surface. + Chrome: Title "Life Expectancy vs GDP · scatter-connected-temporal · python · pygal · anyplot.ai" in dark ink at top, clearly readable. X-axis label "GDP per Capita (USD)" and Y-axis label "Life Expectancy (years)" are dark and readable. Tick labels ($8k–$22k, 68.0–78.0 yrs) in muted dark tone, readable. Subtle dashed grid lines visible without competing with data. Legend at bottom with 7 entries in 3 columns — all text readable. + Data: Six era segments form a connected scatter path from lower-left (1990, ~$8k / 68 yrs) to upper-right (2023, ~$22k / 76.5 yrs). Colors progress from #009E73 (brand green, 1990–1997) through teal-to-blue interpolations to #4467A3 (2020–2023), correctly implementing imprint_seq. Amber (#DDCC77) dots mark key years (1998, 2005, 2008, 2015, 2020) with year labels in dark text. Red (#AE3030) large dot at 1990 start with "▶ 1990" label; dark-blue large dot at 2023 end with "● 2023" label. Dots clearly visible at 12/20/26px sizes. Some label crowding around $13k–$16k zone. + Legibility verdict: PASS — all text readable against warm off-white background. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct dark surface. + Chrome: Title text light-colored and readable against dark background. Axis labels and tick labels render in light ink, clearly legible. Grid lines subtle. Legend text readable. No dark-on-dark failures detected — the SVG label-color patch correctly changed year annotation text (1998, 2005, 2008, 2015, 2020, "▶ 1990", "● 2023") to INK (#F0EFE8), so they read as light text on the dark background. + Data: Colors are identical to the light render — brand green (#009E73) first era, sequential gradient to blue (#4467A3) final era. Amber key-year markers and red/blue start/end markers retain their correct colors. Data colors unchanged between themes as required. + Legibility verdict: PASS — all text readable against warm near-black background; no dark-on-dark failures. criteria_checklist: visual_quality: score: 26 @@ -48,68 +67,83 @@ review: score: 7 max: 8 passed: true - comment: All font sizes explicitly set (title=52, labels=40, ticks=36, legend=34). - Year annotation labels slightly small but legible. + comment: All font sizes explicitly set; title, axis labels, ticks, year labels + readable in both themes. Some year labels in the $13k–$16k zone are small + relative to the dense path. - id: VQ-02 name: No Overlap - score: 5 + score: 4 max: 6 passed: true - comment: Most labels well-placed. Minor density in upper-right cluster where - 2020-2023 points converge. + comment: 'Label crowding in middle zone ($13k–$16k): ''2005'' and ''2008'' + labels cluster with era path. ''2015''/''2020'' labels tight in upper-right. + Readable but noisy.' - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Dots well-sized (10/16/22). Lightest era color has adequate but not - optimal contrast against light background. + comment: 'Marker sizes well-adapted to 34 data points: era dots 12px, key-year + 20px, start/end 26px. Line width 5px. Excellent density adaptation.' - id: VQ-04 name: Color Accessibility - score: 4 - max: 4 + score: 2 + max: 2 passed: true - comment: Blue monochromatic gradient is colorblind-safe. Amber and red accents - provide strong contrast. + comment: Imprint seq gradient is CVD-safe; amber and red markers are clearly + distinct; no red-green-only encoding. - id: VQ-05 name: Layout & Canvas score: 3 max: 4 passed: true - comment: Good plot area utilization. Legend with 9 entries in 3 columns takes - notable space. + comment: 3200x1800 canvas, good margin settings. Right margin (80px) is tight + for '● 2023' label. Top margin (60px) slightly small for title breathing + room. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: GDP per Capita (USD) with $ formatting, Life Expectancy (years) with - yrs suffix. + comment: '''GDP per Capita (USD)'' and ''Life Expectancy (years)'' — both + descriptive with units.' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'First era = #009E73 (brand green). Sequential gradient #009E73→#4467A3 + = imprint_seq. Amber anchor for key events. Backgrounds #FAF8F1/#1A1A17. + Data colors identical across themes; chrome correctly flips.' design_excellence: - score: 15 + score: 12 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 6 + score: 5 max: 8 passed: true - comment: Strong design with cohesive blue gradient palette, intentional color - hierarchy, custom background, rounded line styling. + comment: 'Above defaults: temporal gradient across 6 era colors, three-tier + marker hierarchy, arrow-character start marker, SVG patching for dark theme. + Thoughtful and intentional, not publication-ready but clearly above a configured + default.' - id: DE-02 name: Visual Refinement - score: 4 + score: 3 max: 6 passed: true - comment: Dashed grid lines, customized plot background, tuned margins. Pygal - limits spine removal. + comment: Dashed grid guides, generous margins, round linecap/linejoin on stroke, + bottom legend organized in columns. Pygal's forced four-spine frame limits + further refinement. - id: DE-03 name: Data Storytelling - score: 5 + score: 4 max: 6 passed: true - comment: Temporal color gradient guides viewer through 34 years. Key years - mark crises. Start/end markers frame the narrative. + comment: 'Clear temporal narrative: green start → blue end, economic shocks + visible as path reversals, key-event amber labels contextualize the story. + Visual hierarchy guides the viewer.' spec_compliance: score: 15 max: 15 @@ -119,62 +153,65 @@ review: score: 5 max: 5 passed: true - comment: Correct connected scatter plot with temporal path using pygal.XY. + comment: Connected scatter with temporal path — XY chart with stroke. Correct + chart type fully implemented. - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All features present: connected path, annotations, color gradient, - visible markers, start/end highlights.' + comment: Chronological connection, key-year annotations, color gradient for + temporal direction, visible markers, directional arrow — all present. - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=GDP, Y=Life expectancy, temporal ordering correct. + comment: X=GDP per capita, Y=life expectancy, time encoded as 2D path trajectory. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title follows format. Legend clearly describes era segments and special - markers. + comment: 'Title: ''Life Expectancy vs GDP · scatter-connected-temporal · python + · pygal · anyplot.ai'' — correct format. Legend with 7 meaningful entries.' data_quality: - score: 14 + score: 15 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 5 + score: 6 max: 6 passed: true - comment: Shows co-evolution, recession dips, pandemic impact, growth trend. - Could show more cyclical variation. + comment: 'All aspects present: temporal path, era coloring, key events, start/end + markers, path direction indicator.' - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Life expectancy vs GDP is a classic development economics scenario. - Neutral and educational. + comment: Developing country GDP vs life expectancy 1990–2023. Realistic economic + development narrative, neutral topic. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: GDP $8K-$22K and life expectancy 68-76 yrs realistic for developing-to-middle-income - country. + comment: $8k–$22k GDP per capita and 68–77 year life expectancy are plausible + for a transitioning developing economy. Economic shocks calibrated correctly. code_quality: - score: 10 + score: 9 max: 10 items: - id: CQ-01 name: KISS Structure - score: 3 + score: 2 max: 3 passed: true - comment: Clean imports-data-style-chart-save flow. No functions or classes. + comment: One helper function (_patch_label_colors) is present but justified + — workaround for pygal's label color limitation in dark theme. Code otherwise + linear and clean. - id: CQ-02 name: Reproducibility score: 2 @@ -186,20 +223,22 @@ review: score: 2 max: 2 passed: true - comment: 'All imports used: cairosvg, numpy, pygal, Style.' + comment: 'All imports used: os (theme env), re (SVG patching), sys (path fix), + cairosvg (PNG), numpy (data), pygal, pygal.style.Style.' - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Elegant era-based loop, clean per-point dictionary styling. No fake - functionality. + comment: Clean, idiomatic. Era loop is elegant. SVG patch is a smart technical + solution to a real library limitation. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves plot.png via cairosvg and plot.html. Current pygal API. + comment: plot-{THEME}.png via cairosvg, plot-{THEME}.html via render_to_file. + Both outputs correct. library_mastery: score: 7 max: 10 @@ -209,26 +248,28 @@ review: score: 4 max: 5 passed: true - comment: Good pygal.XY with Style, per-point dictionaries, print_labels, value - formatters, stroke_style. + comment: pygal.XY with dict-format data points (value, label, color), stroke/dots_size + params, stroke_style dict, print_labels, legend_at_bottom — all idiomatic + pygal patterns. - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: Per-point dictionary styling, print_labels, guide_stroke_dasharray, - JS=[] for static output. - verdict: REJECTED + comment: Per-point coloring via 'color' key in data dicts (pygal-specific), + stroke_style with linecap/linejoin, interactive HTML output, print_labels + with per-point label text — three pygal-distinctive features leveraged. + verdict: APPROVED impl_tags: dependencies: - cairosvg techniques: + - html-export - annotations - - custom-legend patterns: - data-generation - iteration-over-groups dataprep: - cumulative-sum styling: - - grid-styling + - alpha-blending