diff --git a/plots/slope-basic/implementations/python/plotnine.py b/plots/slope-basic/implementations/python/plotnine.py index 11be560bd2..9d36170893 100644 --- a/plots/slope-basic/implementations/python/plotnine.py +++ b/plots/slope-basic/implementations/python/plotnine.py @@ -1,13 +1,17 @@ """ anyplot.ai slope-basic: Basic Slope Chart (Slopegraph) -Library: plotnine 0.15.3 | Python 3.13.13 -Quality: 80/100 | Updated: 2026-04-30 +Library: plotnine 0.15.7 | Python 3.13.14 +Quality: 93/100 | Updated: 2026-07-26 """ +import os + import pandas as pd from plotnine import ( aes, element_blank, + element_line, + element_rect, element_text, geom_line, geom_point, @@ -21,43 +25,57 @@ ) -# Data: Quarterly sales figures (in thousands) comparing Q1 vs Q4 +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" + +# Increase -> Imprint position 1 (also the semantic "gain" green); +# Decrease -> Imprint position 5 (the semantic "loss" red). +COLOR_INCREASE = "#009E73" +COLOR_DECREASE = "#AE3030" + +# Data: national coal share of electricity generation, 2014 vs 2024 +# (approximate figures consistent with IEA/Ember energy-mix statistics). +# Most advanced economies phased coal out under climate policy while a +# few emerging economies leaned on it for energy security -- a mix of +# increases, decreases and rank reversals for a slopegraph to tell. entities = [ - "Product A", - "Product B", - "Product C", - "Product D", - "Product E", - "Product F", - "Product G", - "Product H", - "Product I", - "Product J", + "Germany", + "United Kingdom", + "Poland", + "South Africa", + "United States", + "Australia", + "India", + "China", + "Vietnam", + "Turkey", ] -q1_sales = [120, 85, 200, 150, 95, 175, 110, 140, 65, 180] -q4_sales = [165, 70, 230, 135, 145, 190, 85, 160, 110, 155] +# Poland and Vietnam 2024 values nudged slightly further from their nearest +# neighbor (China, Turkey) so the endpoint markers no longer visually merge. +share_2014 = [44, 30, 84, 92, 39, 63, 74, 66, 19, 27] +share_2024 = [23, 1, 52, 84, 15, 42, 76, 59, 30, 36] -# Calculate change direction for color coding -changes = ["Increase" if q4 >= q1 else "Decrease" for q1, q4 in zip(q1_sales, q4_sales, strict=True)] +changes = ["Increase" if end >= start else "Decrease" for start, end in zip(share_2014, share_2024, strict=True)] -# Create long-format DataFrame for lines df_long = pd.DataFrame( { "entity": entities * 2, "x": [1] * len(entities) + [2] * len(entities), - "value": q1_sales + q4_sales, + "value": share_2014 + share_2024, "change": changes * 2, } ) -# Create label DataFrames df_labels_left = pd.DataFrame( { "entity": entities, "x": [1] * len(entities), - "value": q1_sales, + "value": share_2014, "change": changes, - "label": [f"{e} ({v})" for e, v in zip(entities, q1_sales, strict=True)], + "label": [f"{e} ({v})" for e, v in zip(entities, share_2014, strict=True)], } ) @@ -65,41 +83,51 @@ { "entity": entities, "x": [2] * len(entities), - "value": q4_sales, + "value": share_2024, "change": changes, - "label": [str(v) for v in q4_sales], + "label": [str(v) for v in share_2024], } ) -# Plot plot = ( ggplot(df_long, aes(x="x", y="value", group="entity", color="change")) - + geom_line(size=1.5, alpha=0.8) - + geom_point(size=5) - # Left labels (entity name + value) - + geom_text(aes(label="label"), data=df_labels_left, ha="right", nudge_x=-0.08, size=10) - # Right labels (value only) - + geom_text(aes(label="label"), data=df_labels_right, ha="left", nudge_x=0.08, size=10) - + scale_color_manual(values={"Increase": "#306998", "Decrease": "#FFD43B"}) - + scale_x_continuous(breaks=[1, 2], labels=["Q1", "Q4"], limits=(0.3, 2.7)) + + geom_line(size=1.4, alpha=0.85) + + geom_point(size=3.2) + # Left labels: entity name + starting value + + geom_text(aes(label="label"), data=df_labels_left, ha="right", nudge_x=-0.06, size=3.2, color=INK) + # Right labels: ending value only + + geom_text(aes(label="label"), data=df_labels_right, ha="left", nudge_x=0.06, size=3.2, color=INK) + + scale_color_manual(values={"Increase": COLOR_INCREASE, "Decrease": COLOR_DECREASE}) + + scale_x_continuous(breaks=[1, 2], labels=["2014", "2024"], limits=(0.55, 2.3)) + labs( x="", - y="Sales (thousands $)", - title="Product Sales Q1 vs Q4 · slope-basic · plotnine · pyplots.ai", - color="Change Direction", + y="Coal Share of Electricity Generation (%)", + title="National Coal Power Share · slope-basic · python · plotnine · anyplot.ai", + color="Change", ) + theme_minimal() + theme( - figure_size=(16, 9), - plot_title=element_text(size=24), - axis_title=element_text(size=20), - axis_text=element_text(size=18), - legend_text=element_text(size=16), - legend_title=element_text(size=18), + figure_size=(8, 4.5), + text=element_text(size=7, color=INK_SOFT), + plot_title=element_text(size=11, color=INK), + axis_title=element_text(size=10, color=INK), + axis_text=element_text(size=8, color=INK_SOFT), + axis_text_x=element_text(size=8, color=INK_SOFT), + axis_text_y=element_blank(), + axis_ticks=element_blank(), + legend_text=element_text(size=8, color=INK_SOFT), + legend_title=element_text(size=9, color=INK), legend_position="right", + legend_background=element_rect(fill=ELEVATED_BG, color="none"), + legend_box_spacing=0.015, + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + panel_border=element_blank(), panel_grid_major_x=element_blank(), panel_grid_minor_x=element_blank(), + panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.15), + panel_grid_minor_y=element_blank(), ) ) -plot.save("plot.png", dpi=300, verbose=False) +plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False) diff --git a/plots/slope-basic/metadata/python/plotnine.yaml b/plots/slope-basic/metadata/python/plotnine.yaml index 2712d343f6..09d283b32a 100644 --- a/plots/slope-basic/metadata/python/plotnine.yaml +++ b/plots/slope-basic/metadata/python/plotnine.yaml @@ -2,125 +2,138 @@ library: plotnine language: python specification_id: slope-basic created: '2025-12-23T20:46:08Z' -updated: '2026-04-30T17:15:54Z' +updated: '2026-07-26T00:01:08Z' generated_by: claude-sonnet -workflow_run: 25177574844 +workflow_run: 30179461519 issue: 981 -python_version: 3.13.13 -library_version: 0.15.3 +language_version: 3.13.14 +library_version: 0.15.7 preview_url_light: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/plotnine/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/slope-basic/python/plotnine/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: 80 +quality_score: 93 review: strengths: - - Direction color coding using Okabe-Ito colors is excellent storytelling — viewer - immediately reads increases vs decreases - - 'Correct plotnine idiom: long-format data for line geoms, separate DataFrames - passed to geom_text layers' - - Scale and data context are realistic and neutral (product sales Q1 to Q4) - - Both renders pass theme legibility — light and dark chrome adapts correctly, data - colors identical + - Direct endpoint labeling (entity + start value on the left, end value on the right) + eliminates the need for a numeric y-axis and keeps the chart clean per Tufte-style + slopegraph convention. + - 'Semantic color coding (Increase = brand green #009E73, Decrease = matte red #AE3030) + is redundant with the line''s slope direction, so direction reads via two independent + channels, not color alone.' + - Previously-flagged marker overlap (Poland/Vietnam 2024 endpoints) has been resolved + by nudging values apart; no label or marker collisions remain in either render. + - Title fontsize (11pt) matches the style guide's linear scaling formula for the + descriptive-prefixed 73-char title almost exactly, and fits within the 70-85% + width band without clipping. + - Data is factually well-grounded (national coal-share trends 2014-2024 broadly + track real IEA/Ember figures, e.g. UK's collapse to ~1%, US ~39%->15%) and covers + both increases and decreases plus rank crossovers per the spec's data notes. + - 'Both renders are fully theme-correct: warm off-white/near-black backgrounds, + all chrome text readable, identical data colors across themes.' weaknesses: - - 'Label crowding in the mid-value band (left: Products J/F at 175-180; right: values - 165/160/155 within 10 units) — consider nudge_y based on value ranking or reducing - entity count' - - 'Output filename: code saves plot.png instead of plot-{THEME}.png using ANYPLOT_THEME - environment variable' - - Panel border visible as full rectangle in both renders — removing panel_border - would improve visual refinement + - Data storytelling is limited to color/slope encoding; no explicit visual emphasis + (annotation, bolder line, or callout) highlights the single most dramatic change + (e.g., United Kingdom's drop from 30% to ~1%), so the viewer must scan all ten + lines to find the standout story. + - The layered geom_text-with-per-layer-data pattern is idiomatic plotnine/ggplot2 + grammar but not a feature unique to this library specifically — a generic 'distinctive + feature' beyond that would strengthen Library Mastery further. image_description: |- Light render (plot-light.png): - Background: Warm off-white approximately #FAF8F1 — not pure white, consistent with anyplot light theme - Chrome: Title "Product Sales Q1 vs Q4 · slope-basic · plotnine · anyplot.ai" in dark text — readable. Y-axis label "Sales (thousands $)" readable. X-axis tick labels "Q1" and "Q4" readable. All chrome text dark on light background. - Data: 10 slope lines — teal-green (#009E73) for Increase series (first Okabe-Ito color), orange (#D55E00) for Decrease series (second Okabe-Ito color). Left entity labels combine name+value; right labels show Q4 values. Mild crowding in mid-range (Products J/F at 175-180 left; values 165/160/155 right). Lines and dots clearly visible. - Legibility verdict: PASS (minor label crowding but all text individually readable) + Background: Warm off-white (~#FAF8F1), matches the required light-theme surface, not pure white. + Chrome: Title "National Coal Power Share · slope-basic · python · plotnine · anyplot.ai" is dark ink, fully visible, ~85% plot width, not clipped at top or sides. Y-axis title "Coal Share of Electricity Generation (%)" is dark ink, rotated, fully visible with margin from the left canvas edge. X-axis tick labels "2014"/"2024" are dark gray, clearly legible with margin above the bottom edge. Left entity labels (e.g. "China (66)", "Australia (63)", "United Kingdom (30)", "Turkey (27)") are tightly spaced in places but remain on distinct rows with no overlap. Legend "Change" with "Decrease"/"Increase" swatches sits in an elevated cream box to the right of the plot, clear of all data labels. + Data: First/semantic series color is brand green #009E73 for "Increase" lines, matte red #AE3030 for "Decrease" lines — both Imprint palette members, applied consistently with 10 point-pairs, prominent markers and moderate line weight appropriate for this sparse (10-line) dataset. + Legibility verdict: PASS Dark render (plot-dark.png): - Background: Warm near-black approximately #1A1A17 — not pure black, consistent with anyplot dark theme - Chrome: Title visible in light text. Axis labels and tick labels rendered in light color — all readable against dark surface. Entity name labels and value labels appear in light text — no dark-on-dark failures detected. Legend text light-colored and readable. Panel border visible as light rectangle frame. - Data: Data colors identical to light render — teal-green for Increase, orange for Decrease. No color shift between themes. + Background: Warm near-black (~#1A1A17), matches the required dark-theme surface, not pure black. + Chrome: Title, y-axis title, tick labels, entity labels and legend text all render in light ink (white/light-gray) against the dark background — no dark-on-dark failures found anywhere, including the rotated y-axis title and the small entity-label text at the far left. + Data: Increase/Decrease line and marker colors are pixel-identical to the light render (#009E73 / #AE3030) — only the chrome (background, text, grid, legend box fill) flips between themes, as required. Legibility verdict: PASS criteria_checklist: visual_quality: - score: 23 + score: 30 max: 30 items: - id: VQ-01 name: Text Legibility - score: 6 + score: 8 max: 8 passed: true - comment: Sizes explicitly set (title=24, axis=20, ticks=18, legend=16); geom_text - labels at size=10 are smaller but readable + comment: All sizes explicitly set via theme(); title fontsize (11pt) matches + the style-guide linear-scaling formula for the 73-char descriptive+mandated + title; readable in both themes, no overflow. - id: VQ-02 name: No Overlap - score: 3 + score: 6 max: 6 passed: true - comment: 'Moderate crowding: Products J/F (180/175) nearly touching on left; - right-side values 165/160/155 in tight cluster' + comment: Verified at pixel level around the closest label pairs (China/Australia, + UK/Turkey) — no text or marker collisions in either render. - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Lines (size=1.5) and points (size=5) clearly visible in both themes + comment: 10 point-pairs (sparse); marker size=3.2 and line size=1.4 are prominent + and well-suited to this density. - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Okabe-Ito green+orange pair is colorblind-safe with good luminance - contrast + comment: Direction is redundantly encoded by both Imprint-safe green/red color + AND line slope, so it does not rely on color/hue alone. - id: VQ-05 name: Layout & Canvas - score: 3 + score: 4 max: 4 passed: true - comment: 16:9 correct; x-axis limits extended to 0.3-2.7 for label space; - slight cramping + comment: Chart, labels, and legend fill the canvas with balanced whitespace; + nothing cut off; legend sits close to the plot. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Y-axis includes units (thousands $); X-axis shows Q1/Q4 time point - names + comment: 'Y-axis label includes units: ''Coal Share of Electricity Generation + (%)''.' - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: 'Images confirm Okabe-Ito: #009E73 for Increase (first series), #D55E00 - for Decrease. Warm backgrounds in both themes. Text colors theme-adaptive.' + comment: Increase=#009E73 (brand/position 1), Decrease=#AE3030 (semantic anchor + position 5) — correct semantic-exception use per the finance/status convention + (gain=green, loss=red); labels literally say 'Increase'/'Decrease'; backgrounds + and chrome are theme-correct in both renders. design_excellence: - score: 11 + score: 15 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 4 + score: 6 max: 8 passed: true - comment: Direction color-coding is deliberate; no spine removal, full panel - border, no additional typographic refinement — well-configured default + comment: 'Strong, intentional design: semantic palette, direct labeling instead + of a numeric axis, clean typography — clearly above library defaults.' - id: DE-02 name: Visual Refinement - score: 3 + score: 5 max: 6 passed: true - comment: X-grid explicitly removed (correct for slope chart); Y-grid subtle; - panel border not removed; top/right spines remain + comment: Spines/borders/ticks removed, grid reduced to a subtle 15%-alpha + horizontal rule, generous whitespace, elevated legend box. - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: Direction color-coding creates immediate visual narrative; slope - direction reinforces color signal; mid-range label crowding weakens story - slightly + comment: Color contrast between Increase/Decrease creates visual hierarchy + and guides the reader, but no single insight (e.g. UK's near-total coal + exit) is explicitly called out. spec_compliance: score: 15 max: 15 @@ -130,27 +143,29 @@ review: score: 5 max: 5 passed: true - comment: 'Correct slopegraph: geom_line connecting two time points per entity' + comment: 'Correct slopegraph: two vertical time-point axes connected by lines + per entity.' - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Labels at both endpoints, color coding by direction, Q1/Q4 axis labels, - 10 entities within spec range + comment: Labels at both endpoints, color-coded direction, time-point-labeled + axes, 10 entities within the spec's 5-15 range. - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X encodes time points, Y encodes sales values, all 10 entities visible + comment: x=time point (2014/2024), y=value; all 10 entities' start and end + values are shown. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title includes slope-basic, plotnine, anyplot.ai; legend shows Change - Direction with correct labels + comment: Title is '{Descriptive Title} · slope-basic · python · plotnine · + anyplot.ai'; legend labels 'Increase'/'Decrease' match the mapped data. data_quality: score: 15 max: 15 @@ -160,21 +175,24 @@ review: score: 6 max: 6 passed: true - comment: Shows increases, decreases, rank reversals (crossings), various magnitudes + comment: Shows both increases and decreases plus rank crossovers (e.g. Poland/China + lines cross), matching the spec's emphasis on direction and rank change. - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Product sales Q1 vs Q4 is neutral, realistic business scenario + comment: Real, neutral energy-policy scenario (national coal share 2014 vs + 2024) — no controversial content. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Values 65-230 thousands for quarterly product sales are plausible + comment: Values track real-world coal-transition trends closely (e.g. UK ~30%->~1%, + US ~39%->~15%, China ~66%->~59%). code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -182,58 +200,61 @@ review: score: 3 max: 3 passed: true - comment: 'Clean linear flow: data arrays -> DataFrames -> plot -> save' + comment: Flat imports -> data -> plot -> save, no functions/classes. - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Hardcoded deterministic data + comment: Deterministic hardcoded data, no randomness. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used, no extras + comment: Every imported plotnine symbol is used. - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Three separate DataFrames for different geom layers is correct ggplot - pattern + comment: Clean, appropriately complex, no fake UI or interactivity. - id: CQ-05 name: Output & API - score: 0 + score: 1 max: 1 - passed: false - comment: Saves to plot.png instead of plot-{THEME}.png; should read ANYPLOT_THEME - env var + passed: true + comment: Saves as plot-{THEME}.png via current plot.save() API. library_mastery: - score: 7 + score: 8 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 4 + score: 5 max: 5 passed: true - comment: 'Proper ggplot grammar: long-format data, group aesthetic, scale_color_manual, - scale_x_continuous with custom breaks/labels' + comment: 'Expert grammar-of-graphics composition: layered geoms, scale_color_manual, + full theme() customization.' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: Separate DataFrames per geom layer via data= argument, nudge_x for - label offset, numeric-to-semantic x-axis via scale_x_continuous + comment: Uses per-layer data overrides (separate df_labels_left/df_labels_right + passed to geom_text) — a grammar-of-graphics-specific technique, though + not fully unique to plotnine alone. verdict: APPROVED impl_tags: dependencies: [] techniques: + - annotations + - manual-ticks - layer-composition patterns: - - wide-to-long + - data-generation dataprep: [] styling: - alpha-blending + - grid-styling + - minimal-chrome