Skip to content

Commit adbefde

Browse files
feat(plotnine): implement venn-basic (#2477)
## Implementation: `venn-basic` - plotnine Implements the **plotnine** version of `venn-basic`. **File:** `plots/venn-basic/implementations/plotnine.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20584330726)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 801b554 commit adbefde

2 files changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
""" pyplots.ai
2+
venn-basic: Venn Diagram
3+
Library: plotnine 0.15.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-29
5+
"""
6+
7+
import math
8+
9+
import numpy as np
10+
import pandas as pd
11+
from plotnine import (
12+
aes,
13+
element_blank,
14+
element_rect,
15+
element_text,
16+
geom_polygon,
17+
geom_text,
18+
ggplot,
19+
labs,
20+
scale_fill_manual,
21+
scale_x_continuous,
22+
scale_y_continuous,
23+
theme,
24+
)
25+
26+
27+
# Data - Three overlapping sets representing skills in a tech team
28+
# Set A: Python developers (100 people)
29+
# Set B: Data Scientists (80 people)
30+
# Set C: ML Engineers (60 people)
31+
# Overlaps: A∩B=30, A∩C=20, B∩C=25, A∩B∩C=10
32+
33+
set_labels = ["Python\nDevelopers", "Data\nScientists", "ML\nEngineers"]
34+
# Region counts (exclusive to each region)
35+
# A only: 100 - 30 - 20 + 10 = 60
36+
# B only: 80 - 30 - 25 + 10 = 35
37+
# C only: 60 - 20 - 25 + 10 = 25
38+
# A∩B only: 30 - 10 = 20
39+
# A∩C only: 20 - 10 = 10
40+
# B∩C only: 25 - 10 = 15
41+
# A∩B∩C: 10
42+
43+
region_counts = {"A_only": 60, "B_only": 35, "C_only": 25, "AB_only": 20, "AC_only": 10, "BC_only": 15, "ABC": 10}
44+
45+
46+
# Circle positions for 3-set Venn diagram
47+
# Circles arranged in a triangle formation with significant overlap
48+
radius = 5
49+
offset = radius * 0.6 # Controls overlap amount
50+
n_points = 100
51+
52+
# Circle centers
53+
centers = {
54+
"A": (-offset, offset * 0.5), # Top-left
55+
"B": (offset, offset * 0.5), # Top-right
56+
"C": (0, -offset * 0.9), # Bottom
57+
}
58+
59+
# Colors with transparency for overlap visibility
60+
colors = {
61+
"A": "#306998", # Python Blue
62+
"B": "#FFD43B", # Python Yellow
63+
"C": "#4B8BBE", # Lighter blue
64+
}
65+
66+
# Create circle polygons
67+
circle_rows = []
68+
circle_id = 0
69+
for label, (cx, cy) in centers.items():
70+
angles = np.linspace(0, 2 * math.pi, n_points + 1)
71+
x_coords = cx + radius * np.cos(angles)
72+
y_coords = cy + radius * np.sin(angles)
73+
for i in range(len(x_coords)):
74+
circle_rows.append({"x": x_coords[i], "y": y_coords[i], "circle_id": circle_id, "set": label})
75+
circle_id += 1
76+
77+
circle_df = pd.DataFrame(circle_rows)
78+
79+
# Calculate positions for region labels
80+
# A only - left side of circle A
81+
label_A_only = {
82+
"x": centers["A"][0] - radius * 0.45,
83+
"y": centers["A"][1] + radius * 0.2,
84+
"label": str(region_counts["A_only"]),
85+
}
86+
87+
# B only - right side of circle B
88+
label_B_only = {
89+
"x": centers["B"][0] + radius * 0.45,
90+
"y": centers["B"][1] + radius * 0.2,
91+
"label": str(region_counts["B_only"]),
92+
}
93+
94+
# C only - bottom of circle C
95+
label_C_only = {"x": centers["C"][0], "y": centers["C"][1] - radius * 0.5, "label": str(region_counts["C_only"])}
96+
97+
# AB intersection (top center)
98+
label_AB = {"x": 0, "y": centers["A"][1] + radius * 0.35, "label": str(region_counts["AB_only"])}
99+
100+
# AC intersection (bottom-left)
101+
label_AC = {
102+
"x": centers["A"][0] + radius * 0.35,
103+
"y": (centers["A"][1] + centers["C"][1]) / 2 - radius * 0.1,
104+
"label": str(region_counts["AC_only"]),
105+
}
106+
107+
# BC intersection (bottom-right)
108+
label_BC = {
109+
"x": centers["B"][0] - radius * 0.35,
110+
"y": (centers["B"][1] + centers["C"][1]) / 2 - radius * 0.1,
111+
"label": str(region_counts["BC_only"]),
112+
}
113+
114+
# ABC intersection (center)
115+
centroid_x = (centers["A"][0] + centers["B"][0] + centers["C"][0]) / 3
116+
centroid_y = (centers["A"][1] + centers["B"][1] + centers["C"][1]) / 3
117+
label_ABC = {"x": centroid_x, "y": centroid_y, "label": str(region_counts["ABC"])}
118+
119+
# Create label dataframes
120+
count_labels_df = pd.DataFrame([label_A_only, label_B_only, label_C_only, label_AB, label_AC, label_BC, label_ABC])
121+
122+
# Set name labels - positioned outside circles
123+
set_name_labels = [
124+
{"x": centers["A"][0] - radius * 0.8, "y": centers["A"][1] + radius * 0.9, "label": set_labels[0]},
125+
{"x": centers["B"][0] + radius * 0.8, "y": centers["B"][1] + radius * 0.9, "label": set_labels[1]},
126+
{"x": centers["C"][0], "y": centers["C"][1] - radius * 1.1, "label": set_labels[2]},
127+
]
128+
set_name_df = pd.DataFrame(set_name_labels)
129+
130+
# Plot
131+
plot = (
132+
ggplot()
133+
# Draw circles with transparency for overlap visibility
134+
+ geom_polygon(
135+
aes(x="x", y="y", fill="set", group="circle_id"), data=circle_df, alpha=0.45, color="#333333", size=1.5
136+
)
137+
# Region count labels (larger, bold)
138+
+ geom_text(aes(x="x", y="y", label="label"), data=count_labels_df, size=18, fontweight="bold", color="#000000")
139+
# Set name labels (outside circles)
140+
+ geom_text(aes(x="x", y="y", label="label"), data=set_name_df, size=14, fontweight="bold", color="#333333")
141+
# Colors (legend hidden via theme)
142+
+ scale_fill_manual(values=colors)
143+
# Axis scaling for proper aspect ratio
144+
+ scale_x_continuous(limits=(-12, 12))
145+
+ scale_y_continuous(limits=(-12, 10))
146+
# Title
147+
+ labs(title="venn-basic · plotnine · pyplots.ai")
148+
# Clean theme
149+
+ theme(
150+
figure_size=(12, 12),
151+
plot_title=element_text(size=24, ha="center", color="#333333"),
152+
axis_title=element_blank(),
153+
axis_text=element_blank(),
154+
axis_ticks=element_blank(),
155+
axis_line=element_blank(),
156+
panel_grid_major=element_blank(),
157+
panel_grid_minor=element_blank(),
158+
panel_background=element_rect(fill="#FFFFFF"),
159+
plot_background=element_rect(fill="#FFFFFF"),
160+
legend_position="none", # Hide legend - labels are on the plot
161+
)
162+
)
163+
164+
# Save
165+
plot.save("plot.png", dpi=300)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: plotnine
2+
specification_id: venn-basic
3+
created: '2025-12-29T22:49:39Z'
4+
updated: '2025-12-29T22:58:31Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20584330726
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 0.15.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/venn-basic/plotnine/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/venn-basic/plotnine/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent implementation of a Venn diagram using plotnine grammar of graphics
17+
approach
18+
- Clean, readable layout with well-positioned count labels in each region
19+
- Good use of transparency (alpha=0.45) to show overlap regions clearly
20+
- Realistic tech team scenario with mathematically consistent intersection counts
21+
- Proper calculation of exclusive region counts from set totals and intersections
22+
- Clean theme with hidden axes and grid appropriate for a Venn diagram
23+
weaknesses:
24+
- 'Two of the three colors are shades of blue (#306998 and #4B8BBE), which could
25+
be confusing for colorblind users'
26+
- Figure size is 12x12 (square) instead of 16x9 as recommended in library guidelines

0 commit comments

Comments
 (0)