From 7e7873582478b4e3bd56a113788306032638a5ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Jun 2026 01:01:48 +0000 Subject: [PATCH 1/4] feat(plotnine): implement heatmap-cohort-retention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regen from quality 90. Addressed: - Canvas: fixed to 6×6 in / dpi=400 → exact 2400×2400 square (was 16×9/dpi=300 → non-standard size) - Save: plot-{THEME}.png (was plot.png, missing theme suffix) - Theme-adaptive chrome: added ANYPLOT_THEME env var, INK/INK_SOFT/INK_MUTED/PAGE_BG/ELEVATED_BG tokens - Colormap: switched from viridis-inspired custom palette to Imprint sequential (blue→green, #4467A3→#009E73) - Font sizes: aligned to library guide (12pt title, 9pt subtitle, 10pt axis, 8pt ticks/legend) - geom_text size: corrected to 2.8 mm (was 13, wrong scale) - Tile borders: theme-adaptive PAGE_BG (was hardcoded #f8f9fa) - Annotation color: theme-adaptive INK_MUTED - Title: added python language segment (was missing) - sys.path fix: remove script dir to prevent self-shadowing plotnine import - Data: stronger cohort improvement trend (trend_bonus i×2.8 vs i×1.5) for clearer storytelling --- .../implementations/python/plotnine.py | 109 ++++++++++-------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/plots/heatmap-cohort-retention/implementations/python/plotnine.py b/plots/heatmap-cohort-retention/implementations/python/plotnine.py index c5c98fd28a..39d95c035d 100644 --- a/plots/heatmap-cohort-retention/implementations/python/plotnine.py +++ b/plots/heatmap-cohort-retention/implementations/python/plotnine.py @@ -1,9 +1,17 @@ -""" pyplots.ai -heatmap-cohort-retention: Cohort Retention Heatmap -Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 90/100 | Created: 2026-03-16 +"""heatmap-cohort-retention — Cohort Retention Heatmap +Library: plotnine | Python +Imprint palette (imprint_seq: blue→green), theme-adaptive chrome """ +# Remove script dir from sys.path first to prevent self-shadowing the 'plotnine' library import +import sys as _sys + + +_sys.path = [p for p in _sys.path if p != __file__.rsplit("/", 1)[0]] +del _sys + +import os + import numpy as np import pandas as pd from plotnine import ( @@ -17,7 +25,7 @@ ggplot, labs, scale_color_identity, - scale_fill_gradientn, + scale_fill_gradient, scale_x_continuous, scale_y_discrete, theme, @@ -25,7 +33,15 @@ ) -# Data +# Theme-adaptive chrome — Imprint palette +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" +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" + +# Data — SaaS monthly cohort retention, Jan–Oct 2024 np.random.seed(42) cohorts = [ "Jan 2024", @@ -49,9 +65,10 @@ if period == 0: retention = 100.0 else: - base_decay = 100 * np.exp(-0.25 * period) - noise = np.random.uniform(-3, 3) - trend_bonus = i * 1.5 + base_decay = 100 * np.exp(-0.28 * period) + noise = np.random.uniform(-2, 2) + # Stronger trend: newer cohorts retain better (product maturity effect) + trend_bonus = i * 2.8 retention = np.clip(base_decay + noise + trend_bonus, 5, 100) rows.append( {"cohort": cohort, "period": period, "retention_rate": round(retention, 1), "cohort_size": cohort_sizes[i]} @@ -59,69 +76,65 @@ df = pd.DataFrame(rows) -# Create y-axis labels with cohort size +# y-axis: cohort labels with cohort size; reversed so Jan 2024 appears at top df["cohort_label"] = df.apply(lambda r: f"{r['cohort']} (n={r['cohort_size']:,})", axis=1) - -# Preserve ordering cohort_labels = [f"{c} (n={s:,})" for c, s in zip(cohorts, cohort_sizes, strict=True)] df["cohort_label"] = pd.Categorical(df["cohort_label"], categories=cohort_labels[::-1], ordered=True) -# Text color: white on dark cells (viridis dark end), dark on light cells -df["text_color"] = df["retention_rate"].apply(lambda v: "#ffffff" if v < 60 else "#1a1a2e") - -# Format retention text +# Cell text: dark ink on bright green tiles (high retention), light on dark blue (low) +# Imprint seq: low → #4467A3 (blue), high → #009E73 (green) +df["text_color"] = df["retention_rate"].apply(lambda v: "#1A1A17" if v >= 60 else "#FFFDF6") df["label"] = df["retention_rate"].apply(lambda v: f"{v:.0f}%") -# Compare earliest vs latest cohort at same period for storytelling +# Storytelling: M4 retention improvement Jan→Jun 2024 (both cohorts have data at period 4) compare_period = 4 -earliest = df[(df["cohort"] == "Jan 2024") & (df["period"] == compare_period)]["retention_rate"].values[0] -latest = df[(df["cohort"] == "Jun 2024") & (df["period"] == compare_period)]["retention_rate"].values[0] -improvement = latest - earliest - -# Perceptually uniform sequential palette (viridis-inspired: dark purple → teal → yellow) -colors = ["#440154", "#31688e", "#35b779", "#fde725"] +jan_m4 = df[(df["cohort"] == "Jan 2024") & (df["period"] == compare_period)]["retention_rate"].values[0] +jun_m4 = df[(df["cohort"] == "Jun 2024") & (df["period"] == compare_period)]["retention_rate"].values[0] +improvement = jun_m4 - jan_m4 -# Plot +# Build plot — square 2400×2400 canvas plot = ( ggplot(df, aes(x="period", y="cohort_label", fill="retention_rate")) - + geom_tile(color="#f8f9fa", size=0.6) - + geom_text(aes(label="label", color="text_color"), size=13, fontweight="bold") - + scale_fill_gradientn(colors=colors, limits=(0, 100), name="Retention %") + + geom_tile(color=PAGE_BG, size=0.5) + + geom_text(aes(label="label", color="text_color"), size=2.8, fontweight="bold") + + scale_fill_gradient(low="#4467A3", high="#009E73", limits=(0, 100), name="Retention %") + scale_color_identity() - + scale_x_continuous(breaks=range(n_cohorts), labels=[f"M{i}" for i in range(n_cohorts)]) - + scale_y_discrete(expand=(0.05, 0)) + + scale_x_continuous(breaks=list(range(n_cohorts)), labels=[f"M{i}" for i in range(n_cohorts)]) + + scale_y_discrete(expand=(0, 0)) + annotate( "text", - x=n_cohorts - 2, + x=7.5, y=3, - label=f"Month {compare_period} retention improved\n+{improvement:.0f}pp from Jan→Jun 2024", - size=11, - color="#2d2d2d", + label=f"M{compare_period}: +{improvement:.0f}pp\nJan→Jun 2024", + size=3.0, + color=INK_MUTED, ha="center", - fontweight="bold", ) + labs( x="Months Since Signup", y="", - title="heatmap-cohort-retention · plotnine · pyplots.ai", - subtitle="Monthly cohort retention — newer cohorts retain significantly better over time", + title="heatmap-cohort-retention · python · plotnine · anyplot.ai", + subtitle="SaaS monthly cohorts — newer signups retain significantly better month over month", ) + theme_minimal() + theme( - figure_size=(16, 9), - plot_title=element_text(size=26, ha="center", weight="bold", color="#0d1b2a"), - plot_subtitle=element_text(size=18, ha="center", color="#555555", style="italic"), - axis_title_x=element_text(size=20, color="#333333"), - axis_text_x=element_text(size=16, color="#444444"), - axis_text_y=element_text(size=16, color="#444444"), - legend_title=element_text(size=16, weight="bold"), - legend_text=element_text(size=14), + figure_size=(6, 6), + plot_title=element_text(size=12, ha="center", weight="bold", color=INK), + plot_subtitle=element_text(size=9, ha="center", color=INK_SOFT, style="italic"), + axis_title_x=element_text(size=10, color=INK), + axis_text_x=element_text(size=8, color=INK_SOFT), + axis_text_y=element_text(size=8, color=INK_SOFT), + legend_title=element_text(size=8, weight="bold", color=INK), + legend_text=element_text(size=8, color=INK_SOFT), panel_grid_major=element_blank(), panel_grid_minor=element_blank(), - plot_background=element_rect(fill="#fafafa", color="none"), - panel_background=element_rect(fill="#fafafa", color="none"), + panel_border=element_blank(), + axis_ticks=element_blank(), + plot_background=element_rect(fill=PAGE_BG, color="none"), + panel_background=element_rect(fill=PAGE_BG, color="none"), + legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), ) ) -# Save -plot.save("plot.png", dpi=300, width=16, height=9) +# Save — square 2400×2400 at dpi=400 (6 in × 400 dpi = 2400 px) +plot.save(f"plot-{THEME}.png", dpi=400, width=6, height=6, units="in") From 825797df7bdea6d66c34e4ad0c6fc7e26a34451a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Jun 2026 01:02:01 +0000 Subject: [PATCH 2/4] chore(plotnine): add metadata for heatmap-cohort-retention --- .../metadata/python/plotnine.yaml | 235 ++---------------- 1 file changed, 16 insertions(+), 219 deletions(-) diff --git a/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml b/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml index a3df8bc2ea..72d0c69338 100644 --- a/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml +++ b/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml @@ -1,224 +1,21 @@ +# Per-library metadata for plotnine implementation of heatmap-cohort-retention +# Auto-generated by impl-generate.yml + library: plotnine +language: python specification_id: heatmap-cohort-retention created: '2026-03-16T20:45:25Z' -updated: '2026-03-16T21:12:49Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 23165008156 +updated: '2026-06-20T01:02:00Z' +generated_by: claude-sonnet +workflow_run: 27855231246 issue: 4570 -python_version: 3.14.3 -library_version: 0.15.3 -preview_url: https://storage.googleapis.com/anyplot-images/plots/heatmap-cohort-retention/plotnine/plot.png -preview_html: null -quality_score: 90 +language_version: 3.13.14 +library_version: 0.15.7 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/heatmap-cohort-retention/python/plotnine/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/heatmap-cohort-retention/python/plotnine/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: null review: - strengths: - - Excellent spec compliance — all required features implemented perfectly (triangular - shape, cell annotations, cohort sizes, colorbar) - - Strong visual design with custom viridis-inspired palette and thoughtful typography - hierarchy - - Good data storytelling with the +8pp improvement annotation and guiding subtitle - - Clean, reproducible code following KISS principles - weaknesses: - - Minor canvas utilization issue due to inherent triangular shape empty space - - Data patterns could show more dramatic variation between cohorts for stronger - storytelling - - Library mastery could leverage more advanced plotnine features - image_description: The plot displays a triangular cohort retention heatmap with - 10 monthly cohorts (Jan–Oct 2024) on the y-axis, each labeled with cohort size - in parentheses (e.g., "Jan 2024 (n=1,200)"). The x-axis shows "Months Since Signup" - labeled M0 through M9. Cells are colored using a viridis-inspired palette progressing - from dark purple (low retention) through teal to bright yellow (100% retention). - Each cell contains bold white or dark text showing the retention percentage. The - triangular shape correctly emerges with Oct 2024 having only one cell and Jan - 2024 having all 10. A colorbar legend on the right shows the 0–100% retention - scale. An annotation in the lower-right empty area reads "Month 4 retention improved - +8pp from Jan→Jun 2024". The title follows the required format with an italic - subtitle. Background is light gray (#fafafa) with no grid lines. - criteria_checklist: - visual_quality: - score: 29 - max: 30 - items: - - id: VQ-01 - name: Text Legibility - score: 8 - max: 8 - passed: true - comment: 'All font sizes explicitly set: title 26pt, subtitle 18pt, axis title - 20pt, axis/tick text 16pt, legend 16/14pt, cell text size=13 bold' - - id: VQ-02 - name: No Overlap - score: 6 - max: 6 - passed: true - comment: No overlapping text. Annotation sits cleanly in empty triangular - area - - id: VQ-03 - name: Element Visibility - score: 6 - max: 6 - passed: true - comment: Tiles well-sized with white borders. Cell text bold and legible against - all color values - - id: VQ-04 - name: Color Accessibility - score: 4 - max: 4 - passed: true - comment: Viridis-inspired palette is perceptually uniform and colorblind-safe. - Text color switches for contrast - - id: VQ-05 - name: Layout & Canvas - score: 3 - max: 4 - passed: true - comment: Good layout but triangular shape leaves noticeable unused canvas - area in lower-right - - id: VQ-06 - name: Axis Labels & Title - score: 2 - max: 2 - passed: true - comment: X-axis reads Months Since Signup. Y-axis labels include cohort name - and size - design_excellence: - score: 15 - max: 20 - items: - - id: DE-01 - name: Aesthetic Sophistication - score: 6 - max: 8 - passed: true - comment: Custom viridis-inspired palette, intentional typography hierarchy, - white tile borders on light background. Clearly above defaults - - id: DE-02 - name: Visual Refinement - score: 5 - max: 6 - passed: true - comment: Grid fully removed, backgrounds customized, tile borders provide - structure. Missing last level of axis/spine polish - - id: DE-03 - name: Data Storytelling - score: 4 - max: 6 - passed: true - comment: Annotation highlights +8pp improvement. Subtitle guides interpretation. - Color gradient creates visual hierarchy - spec_compliance: - score: 15 - max: 15 - items: - - id: SC-01 - name: Plot Type - score: 5 - max: 5 - passed: true - comment: Correct triangular cohort retention heatmap - - id: SC-02 - name: Required Features - score: 4 - max: 4 - passed: true - comment: 'All spec features present: triangular shape, retention % in cells, - cohort sizes, colorbar, period labels' - - id: SC-03 - name: Data Mapping - score: 3 - max: 3 - passed: true - comment: X=period, Y=cohort ordered correctly, fill=retention_rate - - id: SC-04 - name: Title & Legend - score: 3 - max: 3 - passed: true - comment: Title format correct. Legend labeled Retention % - data_quality: - score: 14 - max: 15 - items: - - id: DQ-01 - name: Feature Coverage - score: 5 - max: 6 - passed: true - comment: Shows triangular shape, 100% at period 0, decay, cohort variation, - improvement trend. Could show more dramatic variation - - id: DQ-02 - name: Realistic Context - score: 5 - max: 5 - passed: true - comment: SaaS monthly cohort retention is a perfect real-world neutral scenario - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 4 - passed: true - comment: Realistic exponential decay with noise. Sensible SaaS retention values - code_quality: - score: 10 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Clean linear flow: imports, data, plot, save. No functions or classes' - - id: CQ-02 - name: Reproducibility - score: 2 - max: 2 - passed: true - comment: np.random.seed(42) set at the start - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: All imports are used - - id: CQ-04 - name: Code Elegance - score: 2 - max: 2 - passed: true - comment: Clean, Pythonic code with appropriate complexity - - id: CQ-05 - name: Output & API - score: 1 - max: 1 - passed: true - comment: Saves as plot.png with dpi=300. Uses current plotnine API - library_mastery: - score: 7 - max: 10 - items: - - id: LM-01 - name: Idiomatic Usage - score: 4 - max: 5 - passed: true - comment: Good grammar-of-graphics composition with ggplot + aes + geom layers - + scales + theme. Uses pd.Categorical for ordering - - id: LM-02 - name: Distinctive Features - score: 3 - max: 5 - passed: true - comment: Uses scale_fill_gradientn, scale_color_identity for text color control, - and annotate() within the grammar - verdict: APPROVED -impl_tags: - dependencies: [] - techniques: - - annotations - - layer-composition - patterns: - - data-generation - dataprep: [] - styling: - - custom-colormap - - edge-highlighting + strengths: [] + weaknesses: [] From c1a4df5bd08ba4b2bc429fc59336b33aa7045494 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Jun 2026 01:08:08 +0000 Subject: [PATCH 3/4] chore(plotnine): update quality score 89 and review feedback for heatmap-cohort-retention --- .../implementations/python/plotnine.py | 7 +- .../metadata/python/plotnine.yaml | 250 +++++++++++++++++- 2 files changed, 247 insertions(+), 10 deletions(-) diff --git a/plots/heatmap-cohort-retention/implementations/python/plotnine.py b/plots/heatmap-cohort-retention/implementations/python/plotnine.py index 39d95c035d..cf60ed2793 100644 --- a/plots/heatmap-cohort-retention/implementations/python/plotnine.py +++ b/plots/heatmap-cohort-retention/implementations/python/plotnine.py @@ -1,6 +1,7 @@ -"""heatmap-cohort-retention — Cohort Retention Heatmap -Library: plotnine | Python -Imprint palette (imprint_seq: blue→green), theme-adaptive chrome +""" anyplot.ai +heatmap-cohort-retention: Cohort Retention Heatmap +Library: plotnine 0.15.7 | Python 3.13.14 +Quality: 89/100 | Updated: 2026-06-20 """ # Remove script dir from sys.path first to prevent self-shadowing the 'plotnine' library import diff --git a/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml b/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml index 72d0c69338..c405cf5e81 100644 --- a/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml +++ b/plots/heatmap-cohort-retention/metadata/python/plotnine.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for plotnine implementation of heatmap-cohort-retention -# Auto-generated by impl-generate.yml - library: plotnine language: python specification_id: heatmap-cohort-retention created: '2026-03-16T20:45:25Z' -updated: '2026-06-20T01:02:00Z' +updated: '2026-06-20T01:08:08Z' generated_by: claude-sonnet workflow_run: 27855231246 issue: 4570 @@ -15,7 +12,246 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/heatmap-c preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/heatmap-cohort-retention/python/plotnine/plot-dark.png preview_html_light: null preview_html_dark: null -quality_score: null +quality_score: 89 review: - strengths: [] - weaknesses: [] + strengths: + - Triangular heatmap shape correctly implemented — newer cohorts have fewer columns, + creating the characteristic staircase pattern + - Cell text with adaptive dark/light coloring based on retention value (scale_color_identity + pattern) is elegant and idiomatic plotnine + - Cohort sizes displayed in y-axis labels as required by spec + - SaaS monthly cohort scenario is realistic, business-plausible, and neutral + - Sequential imprint_seq gradient (blue→green) is semantically appropriate — green=high + retention, blue=low retention + - All font sizes explicitly set using theme tokens; chrome is fully theme-adaptive + for both light and dark renders + - Subtitle and M4 annotation provide genuine data storytelling context + - 'Full spec compliance: period 0 = 100% for all cohorts, colorbar legend included' + - Clean KISS structure with np.random.seed(42) for reproducibility + weaknesses: + - 'Annotation text "M4: +14pp Jan→Jun 2024" (size=3.0mm) is somewhat small relative + to the large empty triangular area — bump to 3.5–4.0mm for better legibility' + - 'DE-01: Aesthetic sophistication is good but not exceptional — the legend box + sits outside the plot in a large empty zone; consider repositioning inside the + plot or tightening the right margin to reduce wasted space' + - 'DE-02: Could add a subtle bottom-axis spine or a hairline border around the heatmap + grid to give the triangular shape more visual definition rather than floating + against the warm background' + - 'LM-02: The scale_color_identity technique is good but could be complemented by + using plotnine-native faceting or coord_flip for additional library-distinctive + flavor' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct Imprint light surface. + Chrome: Bold title "heatmap-cohort-retention · python · plotnine · anyplot.ai" in dark ink at top (~80% width, expected for long mandated format). Italic subtitle "SaaS monthly cohorts — newer signups retain significantly better month over month" in dark-soft ink. Y-axis labels "Jan 2024 (n=1,200)" through "Oct 2024 (n=1,020)" in dark-soft ink — all clearly readable. X-axis labels M0–M9 and x-title "Months Since Signup" in dark ink. Colorbar on right side labeled "Retention %" with scale ticks 0/25/50/75/100. Small annotation "M4: +14pp Jan→Jun 2024" in lower-right empty zone — visible but on the small side. + Data: Triangular staircase heatmap — Jan 2024 (top) has all 10 columns (M0–M9), Oct 2024 (bottom) has 1 column (M0 only). High-retention cells (M0–M2, newer cohorts) render in brand green (#009E73); lower-retention cells transition through teal to blue (#4467A3). Cell annotations show percentages as dark text on green tiles and light text on blue tiles. The gradient correctly uses Imprint palette stops only. + Legibility verdict: PASS — all text readable; no light-on-light failures. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17) — correct Imprint dark surface. + Chrome: Title in light (#F0EFE8) ink — fully readable. Subtitle in secondary light text — readable. Y-axis labels in light secondary text — readable. X-axis labels and title in light text — readable. Legend has dark elevated background (#242420) with light text. Small annotation in the lower-right empty zone is rendered in muted light text — visible. + Data: Color gradient (blue→green) identical to light render — Imprint data colors unchanged between themes, only chrome flipped. Cell text adaptive coloring preserved: dark text on bright-green, light text on blue tiles. + Legibility verdict: PASS — no dark-on-dark failures; all chrome properly theme-adaptive. + criteria_checklist: + visual_quality: + score: 29 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 7 + max: 8 + passed: true + comment: All font sizes explicitly set; main text well-proportioned; annotation + size=3.0mm slightly small in the large empty zone + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping elements; annotation placed in empty triangular space + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Tiles and cell annotations clearly visible; gradient contrast is + strong + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Imprint palette is CVD-safe; adaptive cell text (dark/light) ensures + contrast on all tiles + - id: VQ-05 + name: Layout & Canvas + score: 4 + max: 4 + passed: true + comment: Square 2400x2400 canvas; heatmap fills ~60% of canvas; balanced margins + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: X-axis title 'Months Since Signup' descriptive; y-axis labels include + cohort size; colorbar labeled 'Retention %' + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'scale_fill_gradient uses Imprint stops (#4467A3→#009E73); reversed + direction is semantically correct (green=high retention); backgrounds #FAF8F1/#1A1A17 + correct; chrome theme-adaptive in both renders' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Above well-configured default: custom palette, adaptive cell text + coloring, subtitle framing — but legend placement in large empty zone reduces + polish' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Grid removed, border removed, ticks removed, legend has themed border + — good refinement; no spine to anchor the heatmap visually + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Subtitle and M4 annotation together tell the product-maturity story; + triangular shape inherently shows the cohort lifecycle + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct triangular cohort retention heatmap + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Triangular shape, period 0=100%, cell text, cohort sizes in y-labels, + colorbar — all present; M0/M1 labels are acceptable abbreviations for Month + 0/Month 1 + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=periods, Y=cohorts, fill=retention_rate — correct + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title exactly 'heatmap-cohort-retention · python · plotnine · anyplot.ai'; + legend labeled 'Retention %' + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Shows 10 cohorts with varying periods; declining retention curves; + newer cohorts showing improvement trend + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: SaaS monthly cohort retention Jan–Oct 2024; realistic cohort sizes + 980–1450 + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Exponential decay with noise; values 8–100%; product-maturity trend_bonus + plausible + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Flat: imports → chrome tokens → data generation → plot composition + → save' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) set before data generation + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All 14 plotnine imports used; os, numpy, pandas all used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean ggplot grammar; no over-engineering; no fake UI elements + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: plot.save(f'plot-{THEME}.png', dpi=400, width=6, height=6, units='in') + — correct + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Correct ggplot grammar; pd.Categorical with ordered=True for y-axis; + scale_color_identity for adaptive text — very idiomatic + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: scale_color_identity() paired with pre-computed color column is a + distinctively ggplot2/plotnine pattern not easy to replicate elsewhere + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - annotations + - colorbar + - layer-composition + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - custom-colormap + - minimal-chrome From 7e1c0035a9bdc2746639b0cf25ee75a1bfaaf13f Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Sat, 20 Jun 2026 01:16:34 +0000 Subject: [PATCH 4/4] fix(plotnine): address review feedback for heatmap-cohort-retention Attempt 1/3 - fixes based on AI review --- .../implementations/python/plotnine.py | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/plots/heatmap-cohort-retention/implementations/python/plotnine.py b/plots/heatmap-cohort-retention/implementations/python/plotnine.py index cf60ed2793..7faf94eab3 100644 --- a/plots/heatmap-cohort-retention/implementations/python/plotnine.py +++ b/plots/heatmap-cohort-retention/implementations/python/plotnine.py @@ -1,7 +1,6 @@ -""" anyplot.ai +"""anyplot.ai heatmap-cohort-retention: Cohort Retention Heatmap Library: plotnine 0.15.7 | Python 3.13.14 -Quality: 89/100 | Updated: 2026-06-20 """ # Remove script dir from sys.path first to prevent self-shadowing the 'plotnine' library import @@ -93,6 +92,11 @@ jun_m4 = df[(df["cohort"] == "Jun 2024") & (df["period"] == compare_period)]["retention_rate"].values[0] improvement = jun_m4 - jan_m4 +# Discrete y positions: reversed categories → Oct 2024 at pos 1 (bottom), Jan 2024 at pos 10 (top) +# Jun 2024 is index 5 in original → position (n_cohorts - 5) = 5 from bottom +JAN_Y = n_cohorts # 10 +JUN_Y = n_cohorts - cohorts.index("Jun 2024") # 10 - 5 = 5 + # Build plot — square 2400×2400 canvas plot = ( ggplot(df, aes(x="period", y="cohort_label", fill="retention_rate")) @@ -102,12 +106,33 @@ + scale_color_identity() + scale_x_continuous(breaks=list(range(n_cohorts)), labels=[f"M{i}" for i in range(n_cohorts)]) + scale_y_discrete(expand=(0, 0)) + # Highlight the two cells being compared — borders only (fill="none") + + annotate( + "rect", + xmin=compare_period - 0.5, + xmax=compare_period + 0.5, + ymin=JAN_Y - 0.5, + ymax=JAN_Y + 0.5, + fill="none", + color=INK, + size=2.0, + ) + + annotate( + "rect", + xmin=compare_period - 0.5, + xmax=compare_period + 0.5, + ymin=JUN_Y - 0.5, + ymax=JUN_Y + 0.5, + fill="none", + color=INK, + size=2.0, + ) + annotate( "text", - x=7.5, - y=3, + x=7.2, + y=2.3, label=f"M{compare_period}: +{improvement:.0f}pp\nJan→Jun 2024", - size=3.0, + size=3.8, color=INK_MUTED, ha="center", ) @@ -127,9 +152,10 @@ axis_text_y=element_text(size=8, color=INK_SOFT), legend_title=element_text(size=8, weight="bold", color=INK), legend_text=element_text(size=8, color=INK_SOFT), + legend_position=(0.87, 0.58), panel_grid_major=element_blank(), panel_grid_minor=element_blank(), - panel_border=element_blank(), + panel_border=element_rect(color=INK_SOFT, size=0.8), axis_ticks=element_blank(), plot_background=element_rect(fill=PAGE_BG, color="none"), panel_background=element_rect(fill=PAGE_BG, color="none"),