diff --git a/plots/scatter-connected-temporal/implementations/python/letsplot.py b/plots/scatter-connected-temporal/implementations/python/letsplot.py index 423881f450..99a0860045 100644 --- a/plots/scatter-connected-temporal/implementations/python/letsplot.py +++ b/plots/scatter-connected-temporal/implementations/python/letsplot.py @@ -1,23 +1,58 @@ -""" pyplots.ai +""" anyplot.ai scatter-connected-temporal: Connected Scatter Plot with Temporal Path -Library: letsplot 4.9.0 | Python 3.14.3 -Quality: 90/100 | Created: 2026-03-13 +Library: letsplot 4.10.1 | Python 3.13.13 +Quality: 88/100 | Updated: 2026-06-09 """ +import os + import numpy as np import pandas as pd -from lets_plot import * # noqa: F403 -from lets_plot.export import ggsave as export_ggsave +from lets_plot import ( + LetsPlot, + aes, + arrow, + element_blank, + element_line, + element_rect, + element_text, + geom_path, + geom_point, + geom_segment, + geom_text, + ggplot, + ggsave, + ggsize, + labs, + layer_tooltips, + scale_color_gradient, + scale_fill_gradient, + scale_x_continuous, + scale_y_continuous, + theme, + theme_minimal, +) + +LetsPlot.setup_html() -LetsPlot.setup_html() # noqa: F405 +# Theme tokens — Imprint palette, theme-adaptive chrome +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +GRID = "rgba(26,26,23,0.12)" if THEME == "light" else "rgba(240,239,232,0.12)" -# Data — Unemployment rate vs inflation rate (Phillips curve), 1990-2023 +# Imprint sequential colormap: brand green (early) → blue (recent) +SEQ_LOW = "#009E73" # Imprint position 1 — start of temporal path +SEQ_HIGH = "#4467A3" # Imprint position 3 — end of temporal path + +# Data — Phillips curve dynamics, unemployment vs inflation 1990–2023 np.random.seed(42) years = np.arange(1990, 2024) n = len(years) -# Simulate realistic Phillips curve dynamics with regime shifts unemployment = np.concatenate( [ np.linspace(5.6, 4.0, 10) + np.random.randn(10) * 0.3, # 1990s decline @@ -47,14 +82,10 @@ } ) -# Color gradient: map time index to a normalized value for color encoding -df["time_norm"] = df["time_idx"] / (n - 1) - -# Label only key years — reduced set to avoid crowding in dense areas +# Annotate key economic turning points key_years = {1990, 2000, 2007, 2009, 2020, 2023} df_labels = df[df["year"].isin(key_years)].copy() -# Per-label offset to prevent overlap and clipping nudge_map = { 1990: (0.35, 0.7), 2000: (0.35, -0.7), @@ -64,96 +95,90 @@ 2023: (0.35, 0.7), } -# Highlight start and end points with larger markers df_endpoints = df[df["year"].isin([1990, 2023])].copy() df_labels["label_x"] = df_labels.apply(lambda r: r["unemployment"] + nudge_map.get(r["year"], (0, 0))[0], axis=1) df_labels["label_y"] = df_labels.apply(lambda r: r["inflation"] + nudge_map.get(r["year"], (0, 0))[1], axis=1) -# Arrow segment at end of path to show time direction +# Direction arrow at terminal segment of the path last = df.iloc[-1] prev = df.iloc[-2] - arrow_df = pd.DataFrame( {"x": [prev["unemployment"]], "y": [prev["inflation"]], "xend": [last["unemployment"]], "yend": [last["inflation"]]} ) +title = "scatter-connected-temporal · python · letsplot · anyplot.ai" + # Plot plot = ( - ggplot(df, aes(x="unemployment", y="inflation")) # noqa: F405 - + geom_path( # noqa: F405 - aes(color="time_idx"), # noqa: F405 - size=1.8, - alpha=0.7, - tooltips="none", - ) - + geom_segment( # noqa: F405 + ggplot(df, aes(x="unemployment", y="inflation")) + + geom_path(aes(color="time_idx"), size=1.5, alpha=0.75, tooltips="none") + + geom_segment( data=arrow_df, - mapping=aes(x="x", y="y", xend="xend", yend="yend"), # noqa: F405 - color="#1a3a5c", - size=2.5, - arrow=arrow(angle=25, length=12, type="closed"), # noqa: F405 + mapping=aes(x="x", y="y", xend="xend", yend="yend"), + color=SEQ_HIGH, + size=2.2, + arrow=arrow(angle=25, length=10, type="closed"), ) - + geom_point( # noqa: F405 - aes(fill="time_idx"), # noqa: F405 - color="white", - size=7, - stroke=1.2, + + geom_point( + aes(fill="time_idx"), + color=PAGE_BG, + size=3.5, + stroke=1.0, shape=21, - alpha=0.85, - tooltips=layer_tooltips() # noqa: F405 + alpha=0.9, + tooltips=layer_tooltips() .line("Year|@year") .line("Unemployment|@{unemployment}{.1f}%") .line("Inflation|@{inflation}{.1f}%"), ) - + geom_point( # noqa: F405 + + geom_point( data=df_endpoints, - mapping=aes(x="unemployment", y="inflation", fill="time_idx"), # noqa: F405 - color="#1a1a1a", - size=11, - stroke=2.0, + mapping=aes(x="unemployment", y="inflation", fill="time_idx"), + color=INK, + size=6.0, + stroke=1.8, shape=21, alpha=1.0, ) - + geom_text( # noqa: F405 + + geom_text( data=df_labels, - mapping=aes(x="label_x", y="label_y", label="year_label"), # noqa: F405 - size=13, - color="#222222", + mapping=aes(x="label_x", y="label_y", label="year_label"), + size=6, + color=INK, family="monospace", fontface="bold", ) - + scale_color_gradient( # noqa: F405 - low="#a8d5e2", high="#1a3a5c", name="Year", breaks=[0, (n - 1) / 2, n - 1], labels=["1990", "2006", "2023"] + + scale_color_gradient( + low=SEQ_LOW, high=SEQ_HIGH, name="Year", breaks=[0, (n - 1) / 2, n - 1], labels=["1990", "2006", "2023"] ) - + scale_fill_gradient( # noqa: F405 - low="#a8d5e2", high="#1a3a5c", guide="none" - ) - + scale_x_continuous(expand=[0.06, 0]) # noqa: F405 - + scale_y_continuous(expand=[0.08, 0]) # noqa: F405 - + labs( # noqa: F405 + + scale_fill_gradient(low=SEQ_LOW, high=SEQ_HIGH, guide="none") + + scale_x_continuous(expand=[0.06, 0]) + + scale_y_continuous(expand=[0.08, 0]) + + labs( x="Unemployment Rate (%)", y="Inflation Rate (%)", - title="scatter-connected-temporal · letsplot · pyplots.ai", - subtitle="Phillips Curve Dynamics — US-style unemployment vs inflation, 1990-2023", + title=title, + subtitle="Phillips Curve: unemployment vs inflation, 1990–2023", ) - + ggsize(1600, 900) # noqa: F405 - + theme_minimal() # noqa: F405 - + theme( # noqa: F405 - axis_text=element_text(size=16, color="#555555"), # noqa: F405 - axis_title=element_text(size=20, color="#333333"), # noqa: F405 - plot_title=element_text(size=24, color="#1a1a1a", face="bold"), # noqa: F405 - plot_subtitle=element_text(size=16, color="#555555"), # noqa: F405 - legend_text=element_text(size=14), # noqa: F405 - legend_title=element_text(size=16, face="bold"), # noqa: F405 - panel_grid_major=element_line(color="#E0E0E0", size=0.3), # noqa: F405 - panel_grid_minor=element_blank(), # noqa: F405 - plot_background=element_rect(fill="#FAFBFC"), # noqa: F405 - plot_margin=[60, 50, 20, 20], + + ggsize(800, 450) + + theme_minimal() + + theme( + axis_text=element_text(size=10, color=INK_SOFT), + axis_title=element_text(size=12, color=INK), + plot_title=element_text(size=16, color=INK), + plot_subtitle=element_text(size=10, color=INK_SOFT), + legend_text=element_text(size=10, color=INK_SOFT), + legend_title=element_text(size=12, color=INK), + panel_grid_major=element_line(color=GRID, size=0.3), + panel_grid_minor=element_blank(), + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + panel_background=element_rect(fill=PAGE_BG), + legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), + panel_border=element_blank(), + plot_margin=[20, 20, 10, 10], ) ) -# Save PNG (scale 3x to get 4800 x 2700 px) -export_ggsave(plot, filename="plot.png", path=".", scale=3) - -# Save HTML for interactive version -export_ggsave(plot, filename="plot.html", path=".") +# Save — PNG at 3200×1800 (800×450 × scale=4), plus interactive HTML +ggsave(plot, f"plot-{THEME}.png", path=".", scale=4) +ggsave(plot, f"plot-{THEME}.html", path=".") diff --git a/plots/scatter-connected-temporal/metadata/python/letsplot.yaml b/plots/scatter-connected-temporal/metadata/python/letsplot.yaml index db329f531b..f7bbf67305 100644 --- a/plots/scatter-connected-temporal/metadata/python/letsplot.yaml +++ b/plots/scatter-connected-temporal/metadata/python/letsplot.yaml @@ -1,41 +1,53 @@ library: letsplot +language: python specification_id: scatter-connected-temporal created: '2026-03-13T15:24:50Z' -updated: '2026-03-13T15:45:39Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 23057666052 +updated: '2026-06-09T23:41:45Z' +generated_by: claude-sonnet +workflow_run: 27242210557 issue: 4675 -python_version: 3.14.3 -library_version: 4.9.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/letsplot/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/letsplot/plot.html -quality_score: 90 +language_version: 3.13.13 +library_version: 4.10.1 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/letsplot/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/letsplot/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/letsplot/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/scatter-connected-temporal/python/letsplot/plot-dark.html +quality_score: 88 review: strengths: - - Excellent data storytelling with regime-labeled key years and endpoint emphasis - creating a clear economic narrative - - Cohesive monochromatic blue gradient with professional visual hierarchy (point - sizing, arrow, borders) - - 'All spec features fully implemented: temporal color gradient, annotations, directional - arrow, visible markers' - - Realistic Phillips curve data with diverse regime shifts covering 34 years - - Clean idiomatic lets-plot code with interactive tooltips and HTML export + - Full spec compliance — temporal path, directional arrow, color gradient, endpoint + highlighting, and key-year annotations all implemented as required + - 'Correct Imprint sequential gradient (#009E73 → #4467A3) used for continuous temporal + encoding, matching imprint_seq exactly' + - Complete theme adaptation with correct token values (PAGE_BG, INK, INK_SOFT, ELEVATED_BG) + applied to all chrome elements — both renders are fully legible + - letsplot-native interactive tooltips via layer_tooltips() with formatted year/unemployment/inflation + data + - 'Compelling data storytelling: Phillips curve economic context, economic turning-point + annotations, temporal arrow — the narrative reads naturally' + - Canvas correctly 800×450 @ scale=4 → 3200×1800; HTML export also included weaknesses: - - Mid-period points (light gray-blue) could have slightly more contrast against - the light background - - Annotation font size (13) is functional but on the smaller side relative to other - text elements - image_description: The plot displays a connected scatter plot showing Phillips Curve - dynamics (unemployment rate vs inflation rate) from 1990 to 2023. Points are connected - by line segments in chronological order, with a continuous color gradient from - light blue (1990) to dark navy blue (2023). Six key years (1990, 2000, 2007, 2009, - 2020, 2023) are annotated with bold monospace labels, each manually nudged to - avoid overlap. The start (1990) and end (2023) points are emphasized with larger - markers and darker borders. A directional arrow is drawn at the terminal segment - pointing toward the 2023 endpoint. The background is off-white (#FAFBFC) with - subtle light gray major grid lines and no minor grid. Axes are labeled "Unemployment - Rate (%)" and "Inflation Rate (%)" with a subtitle explaining the context. A vertical - color bar legend on the right maps the gradient to years 1990, 2006, 2023. + - Year annotation text (geom_text size=6 mm ≈ 17pt at source resolution) is slightly + large relative to tick labels (10pt); reducing to size=4–5 mm would improve proportional + balance between annotation and tick label sizes + - Dense mid-plot cluster (unemployment 4–6%, 1990s–2000s regime) makes the temporal + path hard to follow locally — fading or thinning earlier path segments (e.g., + alpha ramping with time_idx) would improve readability of the historical path + - 'DE-01 headroom: standard horizontal gradient colorbar legend; a custom legend + approach (e.g., inline year markers at 1990/2006/2023 along the gradient bar with + small point icons) would lift aesthetic sophistication' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct, no pure white. + Chrome: Title "scatter-connected-temporal · python · letsplot · anyplot.ai" in dark ink at top-left — clearly readable. Subtitle "Phillips Curve: unemployment vs inflation, 1990–2023" in smaller INK_SOFT — readable. X-axis label "Unemployment Rate (%)" and Y-axis label "Inflation Rate (%)" in dark ink — both readable. Tick labels (3–10% on x, −1–8% on y) in INK_SOFT — all readable. Year annotations 1990/2000/2007/2009/2020/2023 in bold monospace dark ink — readable. Legend "Year" with 1990/2006/2023 labels on elevated (#FFFDF6) background — readable. + Data: Gradient path from green (#009E73, 1990) to blue (#4467A3, 2023). Small circular point markers along the path; larger endpoint markers (size=6) with INK border highlighting 1990 (green) and 2023 (blue). Arrow at terminal segment pointing toward 2023 in blue. Subtle grid visible behind data. + Legibility verdict: PASS — all text is clearly readable against the warm off-white background. No light-on-light issues. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct, no pure black. + Chrome: Title, subtitle, axis labels, tick labels, year annotations all rendered in light (#F0EFE8 / #B8B7B0) — clearly readable against the dark surface. Legend text light on elevated dark (#242420) background — readable. No dark-on-dark failures observed. + Data: Data colors (green-to-blue gradient) are identical to the light render — only chrome elements (text, background, grid, legend box) have flipped. Arrow and endpoint highlights use the same gradient values. + Legibility verdict: PASS — all text is clearly readable against the warm near-black background. No dark-on-dark issues. criteria_checklist: visual_quality: score: 28 @@ -46,66 +58,78 @@ review: score: 7 max: 8 passed: true - comment: All font sizes explicitly set (title 24, axis title 20, axis text - 16, legend 14-16). Annotation labels at size 13 monospace bold readable - but slightly small. + comment: All text readable in both renders. Year annotations (geom_text size=6mm + ~17pt) are slightly large relative to tick labels but not problematic. - id: VQ-02 name: No Overlap - score: 6 + score: 5 max: 6 passed: true - comment: Manual nudge offsets per label prevent all text collisions. + comment: nudge_map prevents point-label collision; minor visual crowding in + dense 4–6% unemployment cluster but no text overlap. - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Points clearly visible with white stroke borders. Mid-period light - gray-blue points blend slightly with grid. + comment: Path (size=1.5, alpha=0.75), points (size=3.5), endpoint highlights + (size=6), arrow all clearly visible against background in both renders. - id: VQ-04 name: Color Accessibility - score: 4 - max: 4 + score: 2 + max: 2 passed: true - comment: Single-hue sequential blue gradient is fully colorblind-safe. + comment: Imprint sequential gradient (green→blue) is CVD-safe. Good contrast + in both themes. - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: Plot fills well over 50% of canvas with balanced margins. + comment: Canvas gate passed (3200×1800). Good proportions, legend fits within + bounds, no clipping or overflow. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: 'Descriptive labels with units: Unemployment Rate (%), Inflation - Rate (%).' + comment: Both axis labels include units (%). Title format correct. Subtitle + provides context. + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'imprint_seq gradient (#009E73 → #4467A3). Backgrounds #FAF8F1/#1A1A17 + correct. Data colors identical across both renders; only chrome flips.' design_excellence: - score: 15 + score: 13 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 6 + score: 5 max: 8 passed: true - comment: Cohesive monochromatic blue palette, white-bordered points, intentional - size hierarchy, directional arrow, off-white background. + comment: 'Above default: temporal gradient encoding, endpoint emphasis, directional + arrow, monospace bold annotations, theme adaptation. Standard gradient colorbar + limits ceiling.' - id: DE-02 name: Visual Refinement score: 4 max: 6 passed: true - comment: theme_minimal removes spines, minor grid suppressed, major grid subtle, - generous whitespace. + comment: 'Above default: panel_border removed, minor grid hidden, subtle grid + opacity (rgba 0.12), generous margins. Theme-minimal base with deliberate + overrides.' - id: DE-03 name: Data Storytelling - score: 5 + score: 4 max: 6 passed: true - comment: Strong narrative through regime-labeled key years, emphasized endpoints, - and temporal arrow. + comment: 'Above default: Phillips curve narrative is clear, arrow shows direction + of time, gradient reinforces temporal flow, turning-point annotations provide + economic context.' spec_compliance: score: 15 max: 15 @@ -115,26 +139,28 @@ review: score: 5 max: 5 passed: true - comment: Connected scatter plot with temporal path, exactly as specified. + comment: Correct connected scatter plot with temporal path. - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All features present: chronological connections, annotations, arrow, - markers, color gradient.' + comment: Sequential connections, key-year annotations, directional arrow, + visible point markers, temporal gradient — all present. - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=unemployment, Y=inflation, temporal ordering correct. + comment: X=unemployment, Y=inflation, time_idx drives color gradient and path + ordering. 34 data points (1990–2023) within spec range. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct, legend labeled Year with correct breakpoints. + comment: Title 'scatter-connected-temporal · python · letsplot · anyplot.ai' + is correct. Year gradient legend with 1990/2006/2023 labels present. data_quality: score: 15 max: 15 @@ -144,21 +170,22 @@ review: score: 6 max: 6 passed: true - comment: 'Multiple regime shifts: 1990s decline, 2001 recession, mid-2000s - recovery, 2008 crisis, post-COVID spike.' + comment: 'Full feature set: temporal path, color gradient, arrows, endpoint + highlights, interactive tooltips, key-year annotations.' - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: US-style Phillips curve data with plausible economic dynamics. Neutral - topic. + comment: Phillips curve is a well-established economic concept. Unemployment + 3.5–10% and inflation −0.4–6.5% are realistic. Economic turning points historically + grounded. Neutral, factual data. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Unemployment 3-10%, inflation -1 to 8% — realistic US ranges. + comment: 34 data points within spec's 10–100 range. Value ranges are domain-appropriate. code_quality: score: 10 max: 10 @@ -168,7 +195,7 @@ review: score: 3 max: 3 passed: true - comment: Linear flow with no functions or classes. + comment: Flat script, no functions or classes, linear flow. - id: CQ-02 name: Reproducibility score: 2 @@ -180,19 +207,20 @@ review: score: 2 max: 2 passed: true - comment: All imports used. + comment: All lets_plot imports are used. os, numpy, pandas as expected. - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Clean and well-organized with practical nudge map approach. + comment: Appropriate complexity. layer_tooltips() is real interactive feature, + not fake UI. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png via export_ggsave with scale=3. + comment: Saves plot-{THEME}.png and plot-{THEME}.html. Current API usage. library_mastery: score: 7 max: 10 @@ -202,23 +230,24 @@ review: score: 4 max: 5 passed: true - comment: Expert grammar-of-graphics composition with properly layered geoms - and paired scales. + comment: 'Idiomatic ggplot grammar: geom layering, aes() mappings, scale_*_gradient, + theme_minimal() + theme() overrides, ggsize(), ggsave(scale=4).' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: Uses layer_tooltips() with formatted hover info, arrow() in geom_segment, - and HTML export. + comment: Uses layer_tooltips() with custom line formatting (a distinctive + letsplot feature), arrow() function, geom_path() for connected ordering, + shape=21 fill/stroke split. Above default. verdict: APPROVED impl_tags: dependencies: [] techniques: - - annotations - layer-composition - hover-tooltips - html-export + - annotations patterns: - data-generation dataprep: [] @@ -226,4 +255,3 @@ impl_tags: - custom-colormap - alpha-blending - edge-highlighting - - grid-styling