Skip to content

Commit 8ed0981

Browse files
feat(letsplot): implement venn-labeled-items (#5380)
## Implementation: `venn-labeled-items` - python/letsplot Implements the **python/letsplot** version of `venn-labeled-items`. **File:** `plots/venn-labeled-items/implementations/python/letsplot.py` **Parent Issue:** #5364 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24923774465)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 70290b5 commit 8ed0981

2 files changed

Lines changed: 382 additions & 0 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
""" anyplot.ai
2+
venn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items
3+
Library: letsplot 4.9.0 | Python 3.14.4
4+
Quality: 87/100 | Created: 2026-04-25
5+
"""
6+
7+
import os
8+
9+
import numpy as np
10+
import pandas as pd
11+
from lets_plot import (
12+
LetsPlot,
13+
aes,
14+
coord_fixed,
15+
element_blank,
16+
element_rect,
17+
geom_polygon,
18+
geom_text,
19+
ggplot,
20+
ggsave,
21+
ggsize,
22+
theme,
23+
theme_void,
24+
)
25+
26+
27+
LetsPlot.setup_html()
28+
29+
# Theme tokens
30+
THEME = os.getenv("ANYPLOT_THEME", "light")
31+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
32+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
33+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
34+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
35+
36+
# Okabe-Ito categorical (positions 1, 2, 3)
37+
COLOR_A = "#009E73"
38+
COLOR_B = "#D55E00"
39+
COLOR_C = "#0072B2"
40+
41+
# Geometry — three equally sized overlapping circles
42+
RADIUS = 1.5
43+
centers = {"A": (-0.85, 0.50), "B": (0.85, 0.50), "C": (0.00, -1.00)}
44+
circle_meta = [
45+
{"key": "A", "name": "Overhyped", "color": COLOR_A},
46+
{"key": "B", "name": "Actually Useful", "color": COLOR_B},
47+
{"key": "C", "name": "Secretly Loved", "color": COLOR_C},
48+
]
49+
50+
theta = np.linspace(0, 2 * np.pi, 240)
51+
52+
# Items distributed across the seven interior zones (+ outside)
53+
items = [
54+
# A — Overhyped
55+
("NFTs", -2.00, 1.05),
56+
("Metaverse", -2.05, 0.55),
57+
("Smart Fridges", -1.95, 0.05),
58+
# B — Actually Useful
59+
("Google Maps", 2.00, 1.05),
60+
("Spreadsheets", 2.05, 0.55),
61+
("Calendar Apps", 1.95, 0.05),
62+
# C — Secretly Loved
63+
("Roller Skating", -0.75, -2.05),
64+
("Soap Operas", 0.75, -2.05),
65+
# A ∩ B
66+
("ChatGPT", 0.00, 1.20),
67+
("Smartwatches", 0.00, 0.85),
68+
# A ∩ C
69+
("Crocs", -1.05, -0.20),
70+
("Vinyl Records", -1.00, -0.60),
71+
# B ∩ C
72+
("Dolly Parton", 1.05, -0.20),
73+
("Spotify", 1.00, -0.60),
74+
# A ∩ B ∩ C
75+
("Sourdough", 0.00, 0.15),
76+
("TikTok", 0.00, -0.30),
77+
# outside
78+
("Beige Walls", -3.10, -2.50),
79+
]
80+
items_df = pd.DataFrame(items, columns=["label", "x", "y"])
81+
82+
# Plot
83+
plot = ggplot() + coord_fixed(xlim=[-4.0, 4.0], ylim=[-3.5, 3.5])
84+
85+
# Three overlapping circles with semi-transparent fills
86+
for c in circle_meta:
87+
cx, cy = centers[c["key"]]
88+
circle_df = pd.DataFrame({"x": cx + RADIUS * np.cos(theta), "y": cy + RADIUS * np.sin(theta)})
89+
plot = plot + geom_polygon(
90+
aes(x="x", y="y"), data=circle_df, fill=c["color"], color=c["color"], alpha=0.22, size=1.4
91+
)
92+
93+
# Item labels (serif, INK)
94+
plot = plot + geom_text(aes(x="x", y="y", label="label"), data=items_df, size=8, family="serif", color=INK)
95+
96+
# Category names outside each circle, in the circle's color
97+
plot = plot + geom_text(
98+
x=-2.40, y=1.95, label="Overhyped", size=13, family="serif", fontface="bold", color=COLOR_A, hjust=1.0
99+
)
100+
plot = plot + geom_text(
101+
x=2.40, y=1.95, label="Actually Useful", size=13, family="serif", fontface="bold", color=COLOR_B, hjust=0.0
102+
)
103+
plot = plot + geom_text(
104+
x=0.00, y=-2.85, label="Secretly Loved", size=13, family="serif", fontface="bold", color=COLOR_C, hjust=0.5
105+
)
106+
107+
# Hint label for the "outside" item cluster
108+
plot = plot + geom_text(
109+
x=-3.10,
110+
y=-2.20,
111+
label="(neither here nor there)",
112+
size=6,
113+
family="serif",
114+
fontface="italic",
115+
color=INK_MUTED,
116+
hjust=0.5,
117+
)
118+
119+
# Editorial kicker + canonical anyplot.ai title line
120+
plot = plot + geom_text(
121+
x=0, y=3.20, label="Chartgeist 2026", size=18, family="serif", fontface="bold_italic", color=INK, hjust=0.5
122+
)
123+
plot = plot + geom_text(
124+
x=0, y=2.70, label="venn-labeled-items · letsplot · anyplot.ai", size=7, family="serif", color=INK_MUTED, hjust=0.5
125+
)
126+
127+
# Theme — gridless editorial background
128+
plot = (
129+
plot
130+
+ theme_void()
131+
+ theme(
132+
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
133+
panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
134+
legend_position="none",
135+
axis_text=element_blank(),
136+
axis_title=element_blank(),
137+
axis_ticks=element_blank(),
138+
)
139+
)
140+
141+
plot = plot + ggsize(1200, 1200)
142+
143+
# Save
144+
ggsave(plot, f"plot-{THEME}.png", path=".", scale=3)
145+
ggsave(plot, f"plot-{THEME}.html", path=".")
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
library: letsplot
2+
language: python
3+
specification_id: venn-labeled-items
4+
created: '2026-04-25T05:45:00Z'
5+
updated: '2026-04-25T05:50:27Z'
6+
generated_by: claude-opus
7+
workflow_run: 24923774465
8+
issue: 5364
9+
python_version: 3.14.4
10+
library_version: 4.9.0
11+
preview_url_light: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/python/letsplot/plot-light.png
12+
preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/python/letsplot/plot-dark.png
13+
preview_html_light: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/python/letsplot/plot-light.html
14+
preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/venn-labeled-items/python/letsplot/plot-dark.html
15+
quality_score: 87
16+
review:
17+
strengths:
18+
- Full spec compliance — all 7 interior zones plus outside populated with correct
19+
Okabe-Ito fills
20+
- 'Strong editorial aesthetic matching the WIRED Chartgeist spec: bold-italic serif
21+
title, colored category labels outside circles, italic aside for outside zone'
22+
- Correct theme adaptation with INK token for item labels and fixed data colors
23+
across both renders
24+
- Clean deterministic flat-script code with correct high-resolution output via scale=3
25+
weaknesses:
26+
- Item labels are size=8 and hint is size=6, below the ~16pt guideline for a 3600x3600
27+
canvas — increase to size=11 for items and size=8 for hint
28+
- ABC zone has Sourdough/TikTok positioned close together (y=0.15 and y=-0.30) —
29+
minor crowding in triple intersection
30+
- axis_text/axis_title/axis_ticks element_blank calls are redundant after theme_void()
31+
— minor code smell
32+
image_description: |-
33+
Light render (plot-light.png):
34+
Background: Warm off-white #FAF8F1 — correct
35+
Chrome: Title "Chartgeist 2026" bold-italic serif — clearly readable; subtitle "venn-labeled-items · letsplot · anyplot.ai" visible; category labels in Okabe-Ito accent colors outside circles — readable
36+
Data: Three circles in Okabe-Ito positions 1-3 (#009E73, #D55E00, #0072B2) with alpha=0.22; item labels in dark INK (#1A1A17); 17 items across all 7 zones plus outside
37+
Legibility verdict: PASS
38+
39+
Dark render (plot-dark.png):
40+
Background: Warm near-black #1A1A17 — correct
41+
Chrome: Title "Chartgeist 2026" in light INK (#F0EFE8) — readable; subtitle visible; category labels retain Okabe-Ito accent colors — legible on dark surface; no dark-on-dark failures observed
42+
Data: Circle fill colors identical to light render (Okabe-Ito unchanged); item labels adapted to light INK (#F0EFE8) and readable against darkened circle regions
43+
Legibility verdict: PASS
44+
criteria_checklist:
45+
visual_quality:
46+
score: 26
47+
max: 30
48+
items:
49+
- id: VQ-01
50+
name: Text Legibility
51+
score: 6
52+
max: 8
53+
passed: true
54+
comment: Title legible in both themes; item labels (size=8) and hint (size=6)
55+
below ~16pt guideline for 3600x3600 canvas
56+
- id: VQ-02
57+
name: No Overlap
58+
score: 5
59+
max: 6
60+
passed: true
61+
comment: Good distribution; minor crowding in ABC zone (Sourdough/TikTok y=0.15
62+
and y=-0.30)
63+
- id: VQ-03
64+
name: Element Visibility
65+
score: 5
66+
max: 6
67+
passed: true
68+
comment: Circles visible in both themes; alpha=0.22 keeps fills subtle and
69+
readable
70+
- id: VQ-04
71+
name: Color Accessibility
72+
score: 2
73+
max: 2
74+
passed: true
75+
comment: Okabe-Ito CVD-safe palette; no red-green sole signal
76+
- id: VQ-05
77+
name: Layout & Canvas
78+
score: 4
79+
max: 4
80+
passed: true
81+
comment: Square 3600x3600 appropriate for symmetric Venn; generous whitespace;
82+
nothing cut off
83+
- id: VQ-06
84+
name: Axis Labels & Title
85+
score: 2
86+
max: 2
87+
passed: true
88+
comment: Editorial title plus canonical anyplot.ai subtitle present
89+
- id: VQ-07
90+
name: Palette Compliance
91+
score: 2
92+
max: 2
93+
passed: true
94+
comment: 'Correct Okabe-Ito order; backgrounds #FAF8F1/#1A1A17; data colors
95+
identical across themes'
96+
design_excellence:
97+
score: 14
98+
max: 20
99+
items:
100+
- id: DE-01
101+
name: Aesthetic Sophistication
102+
score: 6
103+
max: 8
104+
passed: true
105+
comment: Bold-italic serif headline, colored category labels, witty item selection,
106+
italic outside zone aside — genuine editorial intent
107+
- id: DE-02
108+
name: Visual Refinement
109+
score: 4
110+
max: 6
111+
passed: true
112+
comment: theme_void() removes all chrome cleanly; colored category names create
113+
visual anchoring
114+
- id: DE-03
115+
name: Data Storytelling
116+
score: 4
117+
max: 6
118+
passed: true
119+
comment: WIRED Chartgeist framing creates cultural commentary context; outside
120+
zone with Beige Walls adds satirical punch
121+
spec_compliance:
122+
score: 15
123+
max: 15
124+
items:
125+
- id: SC-01
126+
name: Plot Type
127+
score: 5
128+
max: 5
129+
passed: true
130+
comment: Symmetric three-circle Venn with labeled items
131+
- id: SC-02
132+
name: Required Features
133+
score: 4
134+
max: 4
135+
passed: true
136+
comment: Semi-transparent fills; text-only placement; category names outside;
137+
all zones; outside zone
138+
- id: SC-03
139+
name: Data Mapping
140+
score: 3
141+
max: 3
142+
passed: true
143+
comment: 17 items in all zones; correct zone placement
144+
- id: SC-04
145+
name: Title & Legend
146+
score: 3
147+
max: 3
148+
passed: true
149+
comment: Editorial title + anyplot.ai canonical subtitle; no redundant legend
150+
data_quality:
151+
score: 15
152+
max: 15
153+
items:
154+
- id: DQ-01
155+
name: Feature Coverage
156+
score: 6
157+
max: 6
158+
passed: true
159+
comment: All 7 interior zones plus outside; single zones, pairwise, and triple
160+
intersection represented
161+
- id: DQ-02
162+
name: Realistic Context
163+
score: 5
164+
max: 5
165+
passed: true
166+
comment: Pop-culture tech/media/food domain; neutral relatable items
167+
- id: DQ-03
168+
name: Appropriate Scale
169+
score: 4
170+
max: 4
171+
passed: true
172+
comment: 17 items within 10-25 spec range; sensible coordinate geometry
173+
code_quality:
174+
score: 10
175+
max: 10
176+
items:
177+
- id: CQ-01
178+
name: KISS Structure
179+
score: 3
180+
max: 3
181+
passed: true
182+
comment: Flat script, no functions or classes
183+
- id: CQ-02
184+
name: Reproducibility
185+
score: 2
186+
max: 2
187+
passed: true
188+
comment: Fully deterministic geometric computation
189+
- id: CQ-03
190+
name: Clean Imports
191+
score: 2
192+
max: 2
193+
passed: true
194+
comment: All imported names are used
195+
- id: CQ-04
196+
name: Code Elegance
197+
score: 2
198+
max: 2
199+
passed: true
200+
comment: Loop over circle_meta; clean data definition; no fake UI
201+
- id: CQ-05
202+
name: Output & API
203+
score: 1
204+
max: 1
205+
passed: true
206+
comment: Saves plot-{THEME}.png (scale=3) and plot-{THEME}.html
207+
library_mastery:
208+
score: 7
209+
max: 10
210+
items:
211+
- id: LM-01
212+
name: Idiomatic Usage
213+
score: 4
214+
max: 5
215+
passed: true
216+
comment: Correct ggplot grammar; coord_fixed; theme_void + theme layering;
217+
geom_polygon/geom_text
218+
- id: LM-02
219+
name: Distinctive Features
220+
score: 3
221+
max: 5
222+
passed: true
223+
comment: HTML export; LetsPlot.setup_html(); ggsize with scale=3; coord_fixed
224+
verdict: APPROVED
225+
impl_tags:
226+
dependencies: []
227+
techniques:
228+
- layer-composition
229+
- html-export
230+
- annotations
231+
patterns:
232+
- data-generation
233+
- iteration-over-groups
234+
dataprep: []
235+
styling:
236+
- minimal-chrome
237+
- alpha-blending

0 commit comments

Comments
 (0)