diff --git a/plots/dumbbell-basic/implementations/python/letsplot.py b/plots/dumbbell-basic/implementations/python/letsplot.py index 437721e819..b013c608bc 100644 --- a/plots/dumbbell-basic/implementations/python/letsplot.py +++ b/plots/dumbbell-basic/implementations/python/letsplot.py @@ -1,7 +1,7 @@ """ anyplot.ai dumbbell-basic: Basic Dumbbell Chart -Library: letsplot 4.9.0 | Python 3.14.4 -Quality: 86/100 | Updated: 2026-04-26 +Library: letsplot 4.11.0 | Python 3.13.14 +Quality: 91/100 | Updated: 2026-06-30 """ import os @@ -19,6 +19,7 @@ ggplot, ggsize, labs, + layer_tooltips, scale_color_manual, scale_x_continuous, scale_y_continuous, @@ -36,15 +37,14 @@ ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" INK = "#1A1A17" if THEME == "light" else "#F0EFE8" INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" -GRID = "#1A1A17" if THEME == "light" else "#F0EFE8" +RULE = "rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)" -# Okabe-Ito palette — "After" comes first alphabetically → brand green -BRAND = "#009E73" -ACCENT = "#C475FD" -SEGMENT = INK_SOFT +# Imprint palette — position 1 = After (hero), position 2 = Before, position 5 = semantic decline +BRAND = "#009E73" # position 1 — After scores / positive change direction +LAVENDER = "#C475FD" # position 2 — Before scores +DECLINE = "#AE3030" # position 5 — semantic red for regressions -# Data — Employee satisfaction scores before and after policy changes. -# Mix of strong gains, modest shifts, and a regression to show full plot capability. +# Data — Employee satisfaction scores before and after policy changes categories = [ "Engineering", "Marketing", @@ -65,46 +65,86 @@ df = df.sort_values("diff", ascending=True).reset_index(drop=True) df["y_pos"] = range(len(df)) +df_improved = df[df["diff"] > 0] +df_declined = df[df["diff"] <= 0] + df_points = pd.concat( [ - pd.DataFrame({"y_pos": df["y_pos"], "value": df["before"], "period": "Before"}), - pd.DataFrame({"y_pos": df["y_pos"], "value": df["after"], "period": "After"}), + pd.DataFrame( + { + "y_pos": df["y_pos"], + "value": df["before"], + "period": "Before", + "category": df["category"].values, + "diff": df["diff"].values, + } + ), + pd.DataFrame( + { + "y_pos": df["y_pos"], + "value": df["after"], + "period": "After", + "category": df["category"].values, + "diff": df["diff"].values, + } + ), ] ) -# Plot — horizontal dumbbell +# Scale title fontsize for longer-than-baseline title (floor: 11px) +title = "Employee Satisfaction · dumbbell-basic · python · letsplot · anyplot.ai" +n = len(title) +title_size = max(11, round(16 * 67 / n)) if n > 67 else 16 + +# Plot — horizontal dumbbell; segments color-coded by change direction plot = ( ggplot() - + geom_segment(data=df, mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"), size=1.5, color=SEGMENT) - + geom_point(data=df_points, mapping=aes(x="value", y="y_pos", color="period"), size=8) - + scale_color_manual(values=[BRAND, ACCENT], name="Period") + + geom_segment( + data=df_improved, + mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"), + color=BRAND, + size=1.2, + alpha=0.65, + ) + + geom_segment( + data=df_declined, + mapping=aes(x="before", xend="after", y="y_pos", yend="y_pos"), + color=DECLINE, + size=1.2, + alpha=0.65, + ) + + geom_point( + data=df_points, + mapping=aes(x="value", y="y_pos", color="period"), + size=5, + tooltips=layer_tooltips().line("@category").line("@period: @value").line("Change: @diff"), + ) + + scale_color_manual(values=[BRAND, LAVENDER], name="Period") + scale_x_continuous(limits=[50, 95]) + scale_y_continuous(breaks=list(range(len(df))), labels=df["category"].tolist()) - + labs( - x="Satisfaction Score", y="Department", title="Employee Satisfaction · dumbbell-basic · letsplot · anyplot.ai" - ) - + ggsize(1600, 900) + + labs(x="Satisfaction Score", y="Department", title=title) + + ggsize(800, 450) + theme_minimal() + theme( plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG), - legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), - panel_grid_major_x=element_line(color=GRID, size=0.3), - panel_grid_minor_x=element_line(color=GRID, size=0.2), + legend_background=element_rect(fill=ELEVATED_BG, color="transparent"), + panel_grid_major_x=element_line(color=RULE, size=0.3), + panel_grid_minor_x=element_blank(), panel_grid_major_y=element_blank(), panel_grid_minor_y=element_blank(), axis_line=element_line(color=INK_SOFT), axis_ticks=element_blank(), - axis_title=element_text(size=20, color=INK), - axis_text=element_text(size=16, color=INK_SOFT), - plot_title=element_text(size=24, color=INK, hjust=0.5), - legend_title=element_text(size=18, color=INK), - legend_text=element_text(size=16, color=INK_SOFT), - legend_position=[0.88, 0.18], + axis_title=element_text(size=12, color=INK), + axis_text=element_text(size=10, color=INK_SOFT), + plot_title=element_text(size=title_size, color=INK, hjust=0.5), + legend_title=element_text(size=10, color=INK), + legend_text=element_text(size=10, color=INK_SOFT), + legend_position=[0.92, 0.2], legend_justification=[1, 0], ) ) # Save -ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=3) +ggsave(plot, filename=f"plot-{THEME}.png", path=".", scale=4) ggsave(plot, filename=f"plot-{THEME}.html", path=".") diff --git a/plots/dumbbell-basic/metadata/python/letsplot.yaml b/plots/dumbbell-basic/metadata/python/letsplot.yaml index 02e05ecdc7..1d8742b17a 100644 --- a/plots/dumbbell-basic/metadata/python/letsplot.yaml +++ b/plots/dumbbell-basic/metadata/python/letsplot.yaml @@ -2,57 +2,54 @@ library: letsplot language: python specification_id: dumbbell-basic created: '2025-12-23T13:02:17Z' -updated: '2026-04-26T01:53:35Z' -generated_by: claude-opus -workflow_run: 24945502773 +updated: '2026-06-30T23:13:33Z' +generated_by: claude-sonnet +workflow_run: 28480996002 issue: 945 -python_version: 3.14.4 -library_version: 4.9.0 +language_version: 3.13.14 +library_version: 4.11.0 preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/python/letsplot/plot-light.png preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/python/letsplot/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/python/letsplot/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/python/letsplot/plot-dark.html -quality_score: 86 +quality_score: 91 review: strengths: - - 'Perfect spec compliance: correct horizontal dumbbell with geom_segment + geom_point - layers, horizontal orientation, distinct Okabe-Ito colors for before/after dots, - sorted by difference to reveal patterns' - - 'Excellent visual quality: all font sizes explicitly set (title 24pt, axis 20pt, - ticks 16pt, legend 16pt), no overlap, elements clearly visible at export scale' - - 'Complete theme-adaptive chrome: PAGE_BG, ELEVATED_BG, INK, INK_SOFT tokens correctly - applied to all chrome elements in both light and dark renders' - - 'Strong data quality: realistic employee satisfaction scenario with 10 departments, - includes a regression case (Operations: before > after), neutral business context, - plausible 55-90 score range' - - 'Clean code: flat KISS structure, deterministic data, all imports used, saves - plot-{THEME}.png and plot-{THEME}.html' + - Correct horizontal dumbbell layout with departments on Y-axis and satisfaction + scores on X-axis + - 'Semantic color coding: green segments for improvement, red (#AE3030 semantic + anchor) for Operations decline — immediately communicates direction' + - Data sorted ascending by diff so viewer reads most-to-least improved top-to-bottom, + strong visual hierarchy + - 'Full theme-adaptive chrome: both renders pass legibility checks with warm off-white + / near-black backgrounds' + - layer_tooltips() leverages letsplot interactive HTML output — shows category, + period, value, and delta + - 'All Imprint palette positions used correctly: Before=#009E73 (pos 1), After=#C475FD + (pos 2), decline segment=#AE3030 (semantic anchor)' + - Title includes descriptive prefix Employee Satisfaction with proper mandated format + - Deterministic hardcoded data covers both improvement and decline scenarios weaknesses: - - 'Grid color uses full-opacity ink (#1A1A17 in light, #F0EFE8 in dark) instead - of the recommended low-opacity RULE token (rgba(26,26,23,0.10) / rgba(240,239,232,0.10)) - — x-axis grid lines appear prominently dark/bright instead of the intended 10-15% - opacity subtle guide lines' - - 'No visual storytelling: the direction of change (improvement vs. regression) - is not encoded — Operations (negative diff) looks the same as Customer Support - (largest gain). Consider color-coding segments by direction (green for positive, - red-orange for negative) or adding difference labels to create a clear focal point' - - 'Aesthetic sophistication stays at well-configured defaults: no focal point, no - size variation, no typography hierarchy beyond standard sizes — design lacks the - polish needed to exceed 90 points' + - Two separate geom_segment calls (df_improved / df_declined) rather than a single + geom_segment with a direction color aesthetic — the more idiomatic ggplot/letsplot + approach would add a direction column and map color=aes(direction) inside one + layer + - DE-02 refinement is solid but legend border color is transparent rather than INK_SOFT + — minor polish gap + - LM-02 could be elevated by using geom_lollipop or a custom stat if available, + or annotating the delta value directly on each segment for richer storytelling image_description: |- Light render (plot-light.png): - Background: Warm off-white (#FAF8F1) — correct theme surface, not pure white. - Chrome: Title "Employee Satisfaction · dumbbell-basic · letsplot · anyplot.ai" in dark ink, clearly centered. Y-axis label "Department" and x-axis label "Satisfaction Score" in dark text. Department category tick labels on left, numeric score labels (50–95) on bottom — all clearly readable against the light background. Legend "Period" box in the bottom-right with ELEVATED_BG fill. - Data: 10 horizontal dumbbells sorted by difference ascending (lowest diff at bottom, highest at top). Green (#009E73) dots = Before scores, orange (#D55E00) dots = After scores, connected by dark-gray segments (INK_SOFT=#4A4A44). Vertical x-axis grid lines are moderately prominent dark lines — more visible than the recommended 10-15% opacity guides. - Data colors: First series (Before) is #009E73 ✅, second (After) is #D55E00 ✅. - Legibility verdict: PASS — all text clearly readable against #FAF8F1 background. + Background: Warm off-white #FAF8F1 — correct, not pure white + Chrome: Title "Employee Satisfaction · dumbbell-basic · python · letsplot · anyplot.ai" centered in dark ink (#1A1A17), font scaled to ~15pt for long string, fully visible. Y-axis label "Department" in dark ink, X-axis label "Satisfaction Score" in dark ink — both explicitly sized at 12pt. Y-axis category tick labels (department names) in muted #4A4A44 at 10pt, readable. X-axis numeric tick labels (50–95) in same muted tone, readable. No text-on-text overlap anywhere. + Data: 10 horizontal dumbbells. Green (#009E73, pos 1) filled circles = Before scores. Lavender (#C475FD, pos 2) filled circles = After scores. Connecting segments green for 9 improved departments, red (#AE3030, semantic anchor) for Operations (declined). Segment alpha=0.65, markers size=5 — both clearly visible. Sorted ascending by diff so Operations (−7) is at bottom, Customer Support (+25) at top. Legend "Period" with Before/After entries positioned bottom-right, does not overlap data. + Legibility verdict: PASS — all elements fully legible against warm off-white background. Dark render (plot-dark.png): - Background: Warm near-black (#1A1A17) — correct dark theme surface. - Chrome: Title, axis labels, and tick labels all render in light/warm-gray tones (INK=#F0EFE8 for primary text, INK_SOFT=#B8B7B0 for secondary). All text clearly readable against the dark background. Legend box uses #242420 elevated fill with INK_SOFT border. - Data: Identical layout — same 10 horizontal dumbbells sorted the same way. Green (#009E73) and orange (#D55E00) dots visible and bright against the dark background. Connecting segments appear as light-gray (INK_SOFT=#B8B7B0 in dark mode). Vertical grid lines are near-white (#F0EFE8) at full opacity — more prominent than ideal. - Data colors: Identical to light render — #009E73 and #D55E00 unchanged ✅. - Legibility verdict: PASS — no dark-on-dark failures. All text uses light chrome tokens correctly. + Background: Near-black #1A1A17 — correct, warm near-black + Chrome: Title in light #F0EFE8 — clearly readable. "Department" and "Satisfaction Score" axis labels in light ink. Y-axis category labels and X-axis tick labels in #B8B7B0 muted light — all readable. Legend box has elevated #242420 dark background with light text. No dark-on-dark failures observed. + Data: Data colors are identical to light render — Before dots remain #009E73, After dots remain #C475FD, improved segments green, Operations segment red. All markers and segments clearly distinguishable against the near-black background. + Legibility verdict: PASS — both theme-adaptive chrome and data elements fully legible. criteria_checklist: visual_quality: score: 30 @@ -63,78 +60,81 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title=24, axis_title=20, axis_text=16, - legend=16/18. Both renders fully readable.' + comment: 'All font sizes explicitly set: title scaled by formula (16*67/71=15pt), + axis titles 12pt, tick labels 10pt. Both renders fully legible at canvas + size and when scaled to mobile width.' - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: 10 department labels well-spaced. No dot or text collisions. + comment: No overlapping elements. Department labels well-spaced on Y-axis. + Legend positioned clear of data area. - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: size=8 dots clearly visible at export scale. Segments at size=1.5 - clearly connect the points. + comment: 10 data points (sparse), size=5 in letsplot units — dots prominent + and clearly distinguishable. Segments at alpha=0.65 visible without dominating. - id: VQ-04 name: Color Accessibility score: 2 max: 2 passed: true - comment: Okabe-Ito green+orange are CVD-safe and have sufficient luminance - difference. + comment: Green (#009E73) and lavender (#C475FD) are perceptually distinct + under CVD. Red segment (#AE3030) for Operations uses position redundancy + (always at bottom). Not relying on red-green as sole signal. - id: VQ-05 name: Layout & Canvas score: 4 max: 4 passed: true - comment: 1600x900 at scale=3 = 4800x2700. Plot fills canvas well, margins - balanced, legend inside plot area. + comment: 3200x1800 canvas (ggsize 800x450, scale=4). Plot fills canvas well, + balanced margins. Legend well-integrated. No canvas gate failure. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: x='Satisfaction Score', y='Department', title includes spec-id and - library. + comment: 'X: ''Satisfaction Score'' (descriptive). Y: ''Department'' (descriptive). + Title: ''Employee Satisfaction · dumbbell-basic · python · letsplot · anyplot.ai'' + — correct format with descriptive prefix.' - id: VQ-07 name: Palette Compliance score: 2 max: 2 passed: true - comment: 'First series #009E73, second #D55E00 (Okabe-Ito positions 1-2). - Background #FAF8F1 light / #1A1A17 dark. All chrome theme-adaptive.' + comment: 'Before=#009E73 (position 1, first series). After=#C475FD (position + 2). Decline segment uses #AE3030 semantic anchor correctly. Backgrounds + #FAF8F1 light / #1A1A17 dark. Chrome is theme-correct in both renders.' design_excellence: - score: 10 + score: 14 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 4 + score: 6 max: 8 - passed: false - comment: 'Well-configured defaults: correct palette, theme_minimal base, sorted - data. Not publication-ready — no focal point, no typographic hierarchy beyond - size, no design details beyond standard.' + passed: true + comment: Thoughtful semantic color coding (green improvement vs red decline), + descriptive title prefix, sorted data revealing pattern. Clearly above defaults + but not FiveThirtyEight-level polish. - id: DE-02 name: Visual Refinement - score: 3 + score: 4 max: 6 - passed: false - comment: 'Removes y-axis grid (element_blank), removes tick marks, positions - legend inside. But x-grid uses full-opacity #1A1A17/#F0EFE8 instead of ~10% - opacity — grid lines appear prominently dark/bright rather than subtle.' + passed: true + comment: Y-axis grid removed, subtle X-axis grid only, ticks removed (element_blank), + theme_minimal base, strategic legend placement. Good refinement above defaults. - id: DE-03 name: Data Storytelling - score: 3 + score: 4 max: 6 - passed: false - comment: Sorted by difference which creates natural rank order and reveals - Operations regression vs. Customer Support gain. Above default, but no visual - emphasis on direction — improving and regressing departments look identical - in style. + passed: true + comment: Sort by diff magnitude with red accent on Operations creates immediate + visual hierarchy — viewer sees most-improved vs outlier decline without + annotations. spec_compliance: score: 15 max: 15 @@ -144,29 +144,29 @@ review: score: 5 max: 5 passed: true - comment: 'Correct horizontal dumbbell chart: geom_segment for connections, - geom_point for endpoints.' + comment: Correct horizontal dumbbell chart with dots connected by segments. - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: Two distinct colored dots per category, thin connecting line, horizontal - orientation, categories on y-axis, values on x-axis, sorted by difference. + comment: Two dots per category (before/after), connecting segment, distinct + colors for each endpoint, horizontal orientation, sorted by difference. - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Satisfaction Score on x-axis, Department on y-axis. All 10 categories - visible. Correct before/after mapping. + comment: X=satisfaction score, Y=department. All 10 departments visible. X-range + 50–95 shows full data. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: 'Title: ''Employee Satisfaction · dumbbell-basic · letsplot · anyplot.ai''. - Legend shows ''Period'' with Before/After labels matching dot colors.' + comment: 'Title: ''Employee Satisfaction · dumbbell-basic · python · letsplot + · anyplot.ai''. Legend: ''Period'' with ''Before'' / ''After'' labels — + correct.' data_quality: score: 15 max: 15 @@ -176,23 +176,23 @@ review: score: 6 max: 6 passed: true - comment: 'Shows all dumbbell aspects: large differences (Customer Support - +25), small differences (Legal +1), and a regression (Operations -7). Full - range of the plot type demonstrated.' + comment: Shows improvements of varying magnitude AND one decline (Operations). + Range from +1 (Legal) to +25 (Customer Support) demonstrates all aspects + of the dumbbell chart including directionality. - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Employee satisfaction scores 0-100 for named business departments - before/after policy changes. Neutral, plausible, real-world scenario. + comment: Employee satisfaction by department before/after policy changes — + real, neutral, business scenario. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Scores in 55-90 range, realistic for satisfaction surveys. Axis 50-95 - shows full data context. + comment: Satisfaction scores 55–90 realistic on a 0–100 scale. Differences + (1–25 points) plausible for a policy change impact. code_quality: score: 10 max: 10 @@ -202,37 +202,35 @@ review: score: 3 max: 3 passed: true - comment: 'Flat script: imports → theme tokens → data → plot → save. No functions - or classes.' + comment: Clean Imports → Tokens → Data → Plot → Save structure. No functions + or classes. - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: All data is hardcoded static lists — fully deterministic, no randomness. + comment: All data hardcoded, fully deterministic — no random elements. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imported symbols (element_blank, element_line, element_rect, - element_text, etc.) are used in the theme configuration. + comment: All imported symbols are used. No unused imports. - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Clean and Pythonic. Wide-to-long reshape via pd.concat is the correct - approach for multi-series color mapping in letsplot. + comment: Clean, appropriate complexity. Two-segment-group approach for semantic + coloring is a valid pattern, not over-engineering. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves plot-{THEME}.png (scale=3) and plot-{THEME}.html. Current letsplot - API used. + comment: Saves plot-{THEME}.png and plot-{THEME}.html. No deprecated API usage. library_mastery: - score: 6 + score: 7 max: 10 items: - id: LM-01 @@ -240,25 +238,28 @@ review: score: 4 max: 5 passed: true - comment: 'Proper ggplot grammar: layered geom_segment + geom_point with aes() - mappings, scale_color_manual, scale_*_continuous with limits/breaks/labels, - theme() customization on top of theme_minimal(). Good idiomatic approach.' + comment: 'Correct ggplot grammar, ggsize(), ggsave() with scale, scale_color_manual, + scale_y_continuous with explicit breaks/labels. Minor deduction: splitting + into df_improved/df_declined rather than using a direction aesthetic column + is slightly less idiomatic than a single geom_segment with color=aes(''direction'').' - id: LM-02 name: Distinctive Features - score: 2 + score: 3 max: 5 - passed: false - comment: Uses letsplot's HTML export capability and ggplot grammar-of-graphics - layer composition. geom_segment for dumbbell connections is the correct - letsplot idiom. Not exceptional but above generic usage. - verdict: REJECTED + passed: true + comment: layer_tooltips() with multi-field display (category, period, value, + change delta) is distinctively letsplot — this feature does not exist in + matplotlib or plotnine. HTML export via ggsave is also letsplot-specific. + verdict: APPROVED impl_tags: dependencies: [] techniques: - - layer-composition + - hover-tooltips - html-export + - manual-ticks + - layer-composition patterns: - - data-generation - wide-to-long dataprep: [] - styling: [] + styling: + - alpha-blending