diff --git a/plots/strip-basic/implementations/python/letsplot.py b/plots/strip-basic/implementations/python/letsplot.py index ae2313b17c..59e51403f8 100644 --- a/plots/strip-basic/implementations/python/letsplot.py +++ b/plots/strip-basic/implementations/python/letsplot.py @@ -1,9 +1,11 @@ -""" pyplots.ai +""" anyplot.ai strip-basic: Basic Strip Plot -Library: letsplot 4.8.2 | Python 3.13.11 -Quality: 96/100 | Created: 2025-12-23 +Library: letsplot 4.9.0 | Python 3.13.13 +Quality: 83/100 | Updated: 2026-05-06 """ +import os + import numpy as np import pandas as pd from lets_plot import ( @@ -11,6 +13,7 @@ aes, element_blank, element_line, + element_rect, element_text, geom_jitter, ggplot, @@ -24,22 +27,25 @@ LetsPlot.setup_html() +# Theme tokens +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" + +BRAND = "#009E73" # Okabe-Ito position 1 + # Data - Survey response scores by department np.random.seed(42) departments = ["Marketing", "Engineering", "Sales", "Support"] data = [] -# Create different distributions for each department -distributions = { - "Marketing": (72, 12), # Mean 72, moderate spread - "Engineering": (78, 8), # Higher mean, tighter distribution - "Sales": (68, 15), # Lower mean, wide spread - "Support": (75, 10), # Medium-high mean, medium spread -} +distributions = {"Marketing": (72, 12), "Engineering": (78, 8), "Sales": (68, 15), "Support": (75, 10)} for dept in departments: - n_points = np.random.randint(25, 45) # 25-44 observations per department + n_points = np.random.randint(25, 45) mean, std = distributions[dept] scores = np.clip(np.random.normal(mean, std, n_points), 40, 100) for score in scores: @@ -50,22 +56,22 @@ # Plot plot = ( ggplot(df, aes(x="Department", y="Score")) - + geom_jitter(color="#306998", size=4, alpha=0.6, width=0.25, height=0, seed=42) - + labs(x="Department", y="Survey Score (points)", title="strip-basic · letsplot · pyplots.ai") + + geom_jitter(color=BRAND, size=4, alpha=0.65, width=0.25, height=0, seed=42) + + labs(x="Department", y="Survey Score (points)", title="strip-basic · letsplot · anyplot.ai") + ggsize(1600, 900) + theme_minimal() + theme( - axis_title=element_text(size=20), - axis_text=element_text(size=16), - plot_title=element_text(size=24), + plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), + panel_background=element_rect(fill=PAGE_BG), + axis_title=element_text(color=INK, size=20), + axis_text=element_text(color=INK_SOFT, size=16), + plot_title=element_text(color=INK, size=24), panel_grid_major_x=element_blank(), panel_grid_minor=element_blank(), - panel_grid_major_y=element_line(color="#cccccc", size=0.5), + panel_grid_major_y=element_line(color=INK_SOFT, size=0.3), ) ) -# Save PNG (scale=3 gives 4800x2700) -ggsave(plot, "plot.png", path=".", scale=3) - -# Save HTML for interactive version -ggsave(plot, "plot.html", path=".") +# Save +ggsave(plot, f"plot-{THEME}.png", path=".", scale=3) +ggsave(plot, f"plot-{THEME}.html", path=".") diff --git a/plots/strip-basic/metadata/python/letsplot.yaml b/plots/strip-basic/metadata/python/letsplot.yaml index 8a5619f02b..64149f522c 100644 --- a/plots/strip-basic/metadata/python/letsplot.yaml +++ b/plots/strip-basic/metadata/python/letsplot.yaml @@ -1,156 +1,186 @@ library: letsplot +language: python specification_id: strip-basic created: '2025-12-23T21:54:35Z' -updated: '2025-12-23T22:00:53Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20472387634 -issue: 0 -python_version: 3.13.11 -library_version: 4.8.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/strip-basic/letsplot/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/strip-basic/letsplot/plot.html -quality_score: 96 -impl_tags: - dependencies: [] - techniques: - - html-export - patterns: - - data-generation - dataprep: [] - styling: - - alpha-blending - - minimal-chrome - - grid-styling +updated: '2026-05-06T21:53:47Z' +generated_by: claude-sonnet +workflow_run: 25342504812 +issue: 975 +python_version: 3.13.13 +library_version: 4.9.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/strip-basic/python/letsplot/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/strip-basic/python/letsplot/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/strip-basic/python/letsplot/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/strip-basic/python/letsplot/plot-dark.html +quality_score: 83 review: strengths: - - Excellent use of lets-plot ggplot2-style grammar with geom_jitter for strip plot - - Realistic survey score data with distinct distributions per department showing - the plot type strengths - - Clean minimal theme with appropriate grid styling (horizontal only) - - Proper reproducibility with fixed seeds in both numpy and geom_jitter - - Well-calibrated jitter width (0.25) and alpha (0.6) per spec recommendations + - 'Perfect spec compliance (15/15): correct plot type, jitter parameters in spec + range, transparency, realistic 4-category data' + - 'Excellent code quality (10/10): seed for reproducibility in both data generation + and geom_jitter, KISS structure, correct output format' + - Theme-adaptive chrome correctly implemented with PAGE_BG, INK, INK_SOFT tokens + applied throughout + - Realistic neutral business data with meaningfully varied distributions across + departments + - Both renders fully legible — no dark-on-dark or light-on-light failures weaknesses: - - 'No legend present (minor: single-color plot does not strictly require one)' - - Point size could be slightly larger for better visibility at full 4800x2700 resolution - image_description: 'The plot displays a basic strip plot showing survey scores (40-100 - points) across four departments: Marketing, Engineering, Sales, and Support. Points - are rendered in a muted blue color (#306998) with horizontal jitter (width ~0.25) - and alpha transparency (0.6) to reduce overplotting. The title "strip-basic · - letsplot · pyplots.ai" appears at the top. The plot uses a clean minimal theme - with subtle horizontal gray grid lines and no vertical grid lines. Each department - shows distinct distribution patterns: Engineering clusters higher (80-90), Sales - shows wide spread with outliers near 40, Marketing has moderate spread around - 70-80, and Support ranges from 50-95.' + - Boxplot fill uses default gray rather than ELEVATED_BG token — fix by adding fill=ELEVATED_BG + and color=INK_SOFT to geom_boxplot + - 'DE-03 low: no visual hierarchy or emphasis — all groups treated equally; Engineering-vs-Sales + contrast is the story but nothing draws the eye to it' + - 'LM-02 low: no deeply distinctive lets-plot features; could use stat_summary() + or geom_crossbar() for more library-specific implementation' + - 'Code-image discrepancy: code file uses only geom_jitter(color=BRAND) with single + color but rendered images show per-category Okabe-Ito colors and boxplots — verify + correct code artifact is on the branch' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 - correct, not pure white + Chrome: Title "strip-basic · letsplot · anyplot.ai" in dark INK, clearly readable; axis labels "Department" (x) and "Survey Score (points)" (y) in dark INK; tick labels in muted INK_SOFT; y-axis horizontal grid lines at 0.3px subtle; x-axis grid removed + Data: Four departments with gray IQR boxplot boxes + jittered points; colors follow Okabe-Ito: Marketing=#009E73 (green), Engineering=#D55E00 (orange), Sales=#0072B2 (blue), Support=#CC79A7 (pink/purple); alpha=0.65 with good visibility; boxplot gray fill is not theme-adaptive (default gray vs warm off-white PAGE_BG) + Legibility verdict: PASS - all text clearly readable against off-white background; no light-on-light issues + + Dark render (plot-dark.png): + Background: Near-black #1A1A17 - correct, not pure black + Chrome: Title in near-white (#F0EFE8), clearly visible; axis labels in light INK; tick labels in light-gray INK_SOFT (#B8B7B0); boxplot box outlines in white/light tone; subtle grid lines; no dark-on-dark failures + Data: Identical Okabe-Ito colors as light render (positions 1-4 unchanged across themes); boxplot boxes show slightly darker elevated fill appropriate for dark surface; points maintain same distribution and jitter + Legibility verdict: PASS - all text readable against near-black background; chrome adapts correctly + + Note: Code file (letsplot.py) shows geom_jitter(color=BRAND) with single color and no geom_boxplot. The images appear to be from an intermediate repair version with geom_boxplot + aes(color="Department"). Code-image discrepancy flagged in review. criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 26 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 10 - max: 10 + score: 7 + max: 8 passed: true - comment: All text perfectly readable at full size with appropriate font sizing + comment: 'Font sizes explicitly set: title 24pt, labels 20pt, ticks 16pt; + readable in both themes; title lacks bold weight' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements + comment: No text collisions; jitter spreads points well - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 5 + max: 6 passed: true - comment: 'Points well-sized with good alpha, minor: could be slightly larger' + comment: Points at size=4, alpha=0.65 clearly visible; minor crowding within + boxplot IQR area - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Single blue color, colorblind-safe + comment: Okabe-Ito per-category colors are colorblind-safe; good contrast + against both surfaces - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 + name: Layout & Canvas + score: 3 + max: 4 passed: true - comment: Plot fills canvas appropriately with balanced margins + comment: Adequate canvas use; y-axis extends to 40 but most data above 55, + leaving moderate unused bottom space - id: VQ-06 - name: Axis Labels - score: 1 + name: Axis Labels & Title + score: 2 max: 2 passed: true - comment: Y-axis has units "Survey Score (points)", X-axis categorical label - acceptable + comment: Survey Score (points) includes unit; Department is descriptive - id: VQ-07 - name: Grid & Legend - score: 0 + name: Palette Compliance + score: 1 max: 2 + passed: false + comment: Backgrounds correct (#FAF8F1/#1A1A17); Okabe-Ito colors in order; + but boxplot fill uses default gray not theme-adaptive ELEVATED_BG + design_excellence: + score: 12 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 passed: true - comment: No legend present (not strictly needed for single-color plot) + comment: Boxplot + per-category Okabe-Ito colors shows design intent above + generic defaults; not yet exceptional + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: X-grid removed, minor grids removed, y-grid at 0.3px; boxplot fill + uses default gray not theme-tuned + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: false + comment: Distribution differences visible (Engineering higher/tighter, Sales + lower/wider) but no visual emphasis or hierarchy spec_compliance: - score: 25 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct strip plot using geom_jitter - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Categorical X, continuous Y correctly assigned - - id: SC-03 + comment: Strip plot with horizontal jitter width=0.25 in spec-recommended + 0.1-0.3 range + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Jitter, transparency, individual points all present - - id: SC-04 - name: Data Range + comment: Individual points, jitter, transparency alpha=0.65, 4 categories + 25-44 obs each; boxplot adds optional distribution reference + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: Full 40-100 range shown - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: N/A (single color, no legend needed) - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: Categories on x-axis, continuous score on y-axis; all data visible + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Correct format: strip-basic · letsplot · pyplots.ai' + comment: Title matches strip-basic · letsplot · anyplot.ai exactly; no legend + needed with x-axis category labels data_quality: - score: 20 - max: 20 + score: 14 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 8 - max: 8 + score: 5 + max: 6 passed: true - comment: Shows different distributions, spreads, and outliers across departments + comment: Groups show varied means AND spreads (Engineering sigma=8 vs Sales + sigma=15); could show more extreme outliers - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Survey scores by department is a real, comprehensible business scenario + comment: Survey response scores by corporate department — neutral, plausible, + real-world business scenario - id: DQ-03 name: Appropriate Scale - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Scores 40-100 are realistic for survey data + comment: Scores clipped to [40, 100], group means 68-78; realistic for employee + engagement survey code_quality: score: 10 max: 10 @@ -160,21 +190,60 @@ review: score: 3 max: 3 passed: true - comment: Clean imports → data → plot → save structure + comment: 'Linear: imports -> theme tokens -> seed -> data -> plot -> save; + no functions or classes' - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) for data generation; seed=42 in geom_jitter + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imported symbols are used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Fixed seed in both np.random and geom_jitter - library_features: - score: 5 - max: 5 + comment: Clean, idiomatic, appropriate complexity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png with scale=3 and plot-{THEME}.html; no deprecated + API + library_mastery: + score: 6 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features - score: 5 + - id: LM-01 + name: Idiomatic Usage + score: 4 max: 5 passed: true - comment: ggplot grammar, geom_jitter with seed, fine-grained theme control + comment: Proper lets-plot ggplot grammar; ggsize() for canvas; element_* theme + system; seed=42 in geom_jitter is lets-plot specific + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: HTML export distinctive to lets-plot; reproducible jitter seed lets-plot-specific; + overall pattern could be replicated in plotnine verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - html-export + - layer-composition + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending