Skip to content

Commit 666e025

Browse files
feat(matplotlib): implement dumbbell-basic (#5415)
## Implementation: `dumbbell-basic` - python/matplotlib Implements the **python/matplotlib** version of `dumbbell-basic`. **File:** `plots/dumbbell-basic/implementations/python/matplotlib.py` **Parent Issue:** #945 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24944791960)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 5df334c commit 666e025

2 files changed

Lines changed: 290 additions & 47 deletions

File tree

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,78 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
dumbbell-basic: Basic Dumbbell Chart
3-
Library: matplotlib 3.10.8 | Python 3.13.11
4-
Quality: 95/100 | Created: 2025-12-15
3+
Library: matplotlib 3.10.9 | Python 3.14.4
4+
Quality: 85/100 | Created: 2026-04-26
55
"""
66

7+
import os
8+
79
import matplotlib.pyplot as plt
810
import numpy as np
911

1012

13+
# Theme tokens
14+
THEME = os.getenv("ANYPLOT_THEME", "light")
15+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
16+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
17+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
18+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
19+
RULE = (0.10, 0.10, 0.09, 0.18) if THEME == "light" else (0.94, 0.94, 0.91, 0.22)
20+
21+
BEFORE_COLOR = "#009E73" # Okabe-Ito 1 — first series
22+
AFTER_COLOR = "#D55E00" # Okabe-Ito 2
23+
1124
# Data: Employee satisfaction scores before and after workplace policy changes
1225
categories = ["Engineering", "Marketing", "Sales", "HR", "Finance", "Operations", "Customer Support", "Product"]
1326
before_scores = [65, 58, 72, 45, 68, 52, 40, 75]
1427
after_scores = [82, 71, 78, 73, 75, 68, 62, 88]
1528

16-
# Sort by difference (largest improvement first)
29+
# Sort by improvement (largest at top)
1730
differences = [a - b for a, b in zip(after_scores, before_scores, strict=True)]
18-
sorted_indices = np.argsort(differences)[::-1]
31+
sorted_indices = np.argsort(differences)
1932
categories = [categories[i] for i in sorted_indices]
2033
before_scores = [before_scores[i] for i in sorted_indices]
2134
after_scores = [after_scores[i] for i in sorted_indices]
2235

2336
# Plot
24-
fig, ax = plt.subplots(figsize=(16, 9))
37+
fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)
38+
ax.set_facecolor(PAGE_BG)
2539

2640
y_positions = np.arange(len(categories))
2741

28-
# Draw connecting lines (thin and subtle)
42+
# Connecting lines
2943
for i, (start, end) in enumerate(zip(before_scores, after_scores, strict=True)):
30-
ax.plot([start, end], [i, i], color="#888888", linewidth=2, zorder=1)
44+
ax.plot([start, end], [i, i], color=INK_SOFT, linewidth=2.5, alpha=0.55, zorder=1)
3145

32-
# Draw dots with distinct colors
46+
# Dots
47+
ax.scatter(
48+
before_scores, y_positions, s=320, color=BEFORE_COLOR, label="Before", zorder=2, edgecolors=PAGE_BG, linewidths=2
49+
)
3350
ax.scatter(
34-
before_scores, y_positions, s=300, color="#306998", label="Before", zorder=2, edgecolors="white", linewidths=2
51+
after_scores, y_positions, s=320, color=AFTER_COLOR, label="After", zorder=2, edgecolors=PAGE_BG, linewidths=2
3552
)
36-
ax.scatter(after_scores, y_positions, s=300, color="#FFD43B", label="After", zorder=2, edgecolors="white", linewidths=2)
3753

38-
# Labels and styling
54+
# Style
3955
ax.set_yticks(y_positions)
40-
ax.set_yticklabels(categories)
41-
ax.set_xlabel("Satisfaction Score", fontsize=20)
42-
ax.set_ylabel("Department", fontsize=20)
43-
ax.set_title("dumbbell-basic · matplotlib · pyplots.ai", fontsize=24)
44-
ax.tick_params(axis="both", labelsize=16)
56+
ax.set_yticklabels(categories, color=INK_SOFT)
57+
ax.set_xlabel("Satisfaction Score", fontsize=20, color=INK)
58+
ax.set_ylabel("Department", fontsize=20, color=INK)
59+
ax.set_title("dumbbell-basic · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK)
60+
ax.tick_params(axis="both", labelsize=16, colors=INK_SOFT)
4561
ax.set_xlim(30, 100)
46-
ax.grid(True, axis="x", alpha=0.3, linestyle="--")
47-
ax.legend(fontsize=16, loc="lower right")
62+
63+
ax.spines["top"].set_visible(False)
64+
ax.spines["right"].set_visible(False)
65+
for s in ("left", "bottom"):
66+
ax.spines[s].set_color(INK_SOFT)
67+
68+
ax.xaxis.grid(True, color=RULE, linewidth=0.8)
69+
ax.set_axisbelow(True)
70+
71+
leg = ax.legend(fontsize=16, loc="lower right", frameon=True)
72+
leg.get_frame().set_facecolor(ELEVATED_BG)
73+
leg.get_frame().set_edgecolor(INK_SOFT)
74+
leg.get_frame().set_linewidth(0.8)
75+
plt.setp(leg.get_texts(), color=INK_SOFT)
4876

4977
plt.tight_layout()
50-
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
78+
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)
Lines changed: 242 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,252 @@
11
library: matplotlib
2+
language: python
23
specification_id: dumbbell-basic
34
created: 2025-12-15 16:43:15+00:00
4-
updated: 2025-12-15 16:43:15+00:00
5-
generated_by: claude-opus-4-5-20251101
6-
workflow_run: 20240004346
5+
updated: '2026-04-26T01:13:04Z'
6+
generated_by: claude-opus
7+
workflow_run: 24944791960
78
issue: 945
8-
python_version: 3.13.11
9-
library_version: 3.10.8
10-
preview_url: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/matplotlib/plot.png
11-
preview_html: null
12-
quality_score: 95
9+
python_version: 3.14.4
10+
library_version: 3.10.9
11+
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/python/matplotlib/plot-light.png
12+
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/dumbbell-basic/python/matplotlib/plot-dark.png
13+
preview_html_light: null
14+
preview_html_dark: null
15+
quality_score: 85
16+
review:
17+
strengths:
18+
- 'Full theme-adaptation: backgrounds, text, connecting lines, legend frame, and
19+
dot halos (edgecolors=PAGE_BG) all flip correctly between light and dark renders'
20+
- Correct Okabe-Ito palette with Before=#009E73 as first series and After=#D55E00
21+
as second
22+
- Data sorting by improvement size (largest at top) creates a natural visual narrative
23+
- Connecting lines intentionally muted (alpha=0.55) so they do not overpower the
24+
dots, per spec recommendation
25+
- Deterministic, realistic, neutral example data (employee satisfaction across 8
26+
departments, before/after policy changes)
27+
- 'Perfect spec compliance: horizontal orientation, distinct colors, sorted by difference,
28+
subtle connecting line, correct title format'
29+
weaknesses:
30+
- X-axis label 'Satisfaction Score' lacks a scale indicator — should be 'Satisfaction
31+
Score (0-100)' to satisfy the units rubric
32+
- No visual emphasis on notable data points — the highest-improvement department
33+
(HR, +28 pts) and lowest (Sales, +6 pts) receive identical visual treatment; color
34+
intensity variation, line thickness by improvement, or a value difference label
35+
on the widest gap would elevate storytelling
36+
- Library Mastery could be stronger — auto-generated legend from scatter labels
37+
rather than custom handles (plt.scatter([], [], ...) or mlines.Line2D) misses
38+
a matplotlib-specific opportunity for more control
39+
- Data storytelling is limited to sorting; consider a subtle annotation or dot-size
40+
variation to create a focal point and guide the viewer to the key insight
41+
image_description: |-
42+
Light render (plot-light.png):
43+
Background: Warm off-white #FAF8F1 — confirmed, not pure white
44+
Chrome: Title "dumbbell-basic · matplotlib · anyplot.ai" in dark ink, clearly readable. X-axis label "Satisfaction Score" and Y-axis label "Department" in dark ink, readable. Y-axis tick labels (HR, Customer Support, Engineering, Operations, Marketing, Product, Finance, Sales) and X-axis tick labels (30-100 range) all readable against the light background. Legend in lower right with warm elevated-BG frame and dark text.
45+
Data: Before dots in #009E73 (Okabe-Ito 1), After dots in #D55E00 (Okabe-Ito 2). Connecting lines in muted gray with alpha=0.55. Dots have white/PAGE_BG halos. Data sorted with HR (largest improvement) at top, Sales (smallest improvement) at bottom.
46+
Legibility verdict: PASS — all text readable, no light-on-light issues
47+
48+
Dark render (plot-dark.png):
49+
Background: Warm near-black #1A1A17 — confirmed, not pure black
50+
Chrome: Title in light off-white, clearly readable against dark background. Axis labels and tick labels all rendered in light colors, fully readable. Legend uses elevated dark background (#242420) with light edge and light text. No dark-on-dark failures detected.
51+
Data: Before dots still #009E73 (identical to light render), After dots still #D55E00 (identical). Connecting lines appear as medium gray against dark surface. Dot halos use PAGE_BG (#1A1A17) so halos blend into background — intentional adaptive behavior.
52+
Legibility verdict: PASS — all text readable, no dark-on-dark issues detected
53+
criteria_checklist:
54+
visual_quality:
55+
score: 28
56+
max: 30
57+
items:
58+
- id: VQ-01
59+
name: Text Legibility
60+
score: 7
61+
max: 8
62+
passed: true
63+
comment: Font sizes explicitly set (title=24, labels=20, ticks=16), all text
64+
readable in both themes
65+
- id: VQ-02
66+
name: No Overlap
67+
score: 6
68+
max: 6
69+
passed: true
70+
comment: All elements well-spaced, no collisions with 8 categories
71+
- id: VQ-03
72+
name: Element Visibility
73+
score: 6
74+
max: 6
75+
passed: true
76+
comment: Dot size s=320 optimal for 8 data points; connecting lines at linewidth=2.5
77+
alpha=0.55 visible but appropriately subtle
78+
- id: VQ-04
79+
name: Color Accessibility
80+
score: 2
81+
max: 2
82+
passed: true
83+
comment: Okabe-Ito palette CVD-safe; green vs vermillion distinguishable by
84+
luminance
85+
- id: VQ-05
86+
name: Layout & Canvas
87+
score: 4
88+
max: 4
89+
passed: true
90+
comment: 16:9 canvas well-utilized, tight_layout applied, legend near data,
91+
nothing cut off
92+
- id: VQ-06
93+
name: Axis Labels & Title
94+
score: 1
95+
max: 2
96+
passed: false
97+
comment: Satisfaction Score is descriptive but lacks units/scale indicator
98+
(0-100)
99+
- id: VQ-07
100+
name: Palette Compliance
101+
score: 2
102+
max: 2
103+
passed: true
104+
comment: 'First series #009E73, second #D55E00; backgrounds #FAF8F1/#1A1A17
105+
correct; full theme-adaptive chrome'
106+
design_excellence:
107+
score: 12
108+
max: 20
109+
items:
110+
- id: DE-01
111+
name: Aesthetic Sophistication
112+
score: 5
113+
max: 8
114+
passed: false
115+
comment: 'Above default: correct palette, theme-adaptive dot halos, data sorting,
116+
clean spine removal. Not yet publication-ready.'
117+
- id: DE-02
118+
name: Visual Refinement
119+
score: 4
120+
max: 6
121+
passed: false
122+
comment: Top/right spines removed, subtle x-axis grid (alpha~0.18), connecting
123+
lines muted, legend frame styled
124+
- id: DE-03
125+
name: Data Storytelling
126+
score: 3
127+
max: 6
128+
passed: false
129+
comment: Sorting by improvement reveals pattern but no visual emphasis on
130+
top/bottom performer; viewer must identify insights themselves
131+
spec_compliance:
132+
score: 15
133+
max: 15
134+
items:
135+
- id: SC-01
136+
name: Plot Type
137+
score: 5
138+
max: 5
139+
passed: true
140+
comment: Correct dumbbell/connected dot chart
141+
- id: SC-02
142+
name: Required Features
143+
score: 4
144+
max: 4
145+
passed: true
146+
comment: Horizontal orientation, distinct Before/After colors, sorted by difference,
147+
subtle connecting line
148+
- id: SC-03
149+
name: Data Mapping
150+
score: 3
151+
max: 3
152+
passed: true
153+
comment: Categories on y-axis, satisfaction values on x-axis, all data visible
154+
- id: SC-04
155+
name: Title & Legend
156+
score: 3
157+
max: 3
158+
passed: true
159+
comment: 'Title: dumbbell-basic · matplotlib · anyplot.ai; legend labels Before/After'
160+
data_quality:
161+
score: 15
162+
max: 15
163+
items:
164+
- id: DQ-01
165+
name: Feature Coverage
166+
score: 6
167+
max: 6
168+
passed: true
169+
comment: 8 departments with varying Before/After gaps, small and large improvements,
170+
full range of dumbbell distances
171+
- id: DQ-02
172+
name: Realistic Context
173+
score: 5
174+
max: 5
175+
passed: true
176+
comment: Employee satisfaction scores before/after workplace policy changes
177+
— realistic, neutral business scenario
178+
- id: DQ-03
179+
name: Appropriate Scale
180+
score: 4
181+
max: 4
182+
passed: true
183+
comment: Values 40-88 on 30-100 axis, plausible for 0-100 satisfaction scale
184+
code_quality:
185+
score: 10
186+
max: 10
187+
items:
188+
- id: CQ-01
189+
name: KISS Structure
190+
score: 3
191+
max: 3
192+
passed: true
193+
comment: No functions or classes; clean Imports → Data → Sort → Plot → Save
194+
flow
195+
- id: CQ-02
196+
name: Reproducibility
197+
score: 2
198+
max: 2
199+
passed: true
200+
comment: Fully deterministic hardcoded data
201+
- id: CQ-03
202+
name: Clean Imports
203+
score: 2
204+
max: 2
205+
passed: true
206+
comment: Only os, matplotlib.pyplot, numpy — all used
207+
- id: CQ-04
208+
name: Code Elegance
209+
score: 2
210+
max: 2
211+
passed: true
212+
comment: Clean Pythonic code; zip with strict=True; no fake UI elements
213+
- id: CQ-05
214+
name: Output & API
215+
score: 1
216+
max: 1
217+
passed: true
218+
comment: Saves as plot-{THEME}.png with facecolor; current matplotlib API
219+
library_mastery:
220+
score: 5
221+
max: 10
222+
items:
223+
- id: LM-01
224+
name: Idiomatic Usage
225+
score: 3
226+
max: 5
227+
passed: true
228+
comment: Correct Axes API throughout; legend frame manipulation; tick_params;
229+
set_axisbelow
230+
- id: LM-02
231+
name: Distinctive Features
232+
score: 2
233+
max: 5
234+
passed: false
235+
comment: Theme-adaptive edgecolors=PAGE_BG for dot halos is distinctive; zorder
236+
used intentionally. Auto-generated legend rather than custom handles limits
237+
score.
238+
verdict: REJECTED
13239
impl_tags:
14240
dependencies: []
15241
techniques:
16-
- manual-ticks
242+
- manual-ticks
243+
- custom-legend
17244
patterns:
18-
- data-generation
245+
- data-generation
246+
- explicit-figure
247+
- iteration-over-groups
19248
dataprep: []
20249
styling:
21-
- edge-highlighting
22-
review:
23-
strengths: []
24-
weaknesses: []
25-
improvements: []
26-
image_description: 'The plot shows a horizontal dumbbell chart displaying employee
27-
satisfaction scores before and after workplace policy changes across 8 departments.
28-
Each row represents a department (Sales, Finance, Product, Marketing, Operations,
29-
Engineering, Customer Support, HR) with the y-axis showing "Department" and x-axis
30-
showing "Satisfaction Score" ranging from 30 to 100. Two colored dots are connected
31-
by a thin gray line for each department: blue dots (labeled "Before") on the left
32-
and yellow dots (labeled "After") on the right. The data is sorted by the magnitude
33-
of change, with HR showing the largest improvement at the bottom. The title follows
34-
the correct format "dumbbell-basic · matplotlib · pyplots.ai". The legend is positioned
35-
in the lower right corner. All dots are clearly visible with white edge colors,
36-
and the grid lines are subtle vertical dashed lines.'
37-
verdict: APPROVED
250+
- alpha-blending
251+
- edge-highlighting
252+
- grid-styling

0 commit comments

Comments
 (0)