Skip to content

Commit 803e83d

Browse files
feat(pygal): implement venn-basic (#2495)
## Implementation: `venn-basic` - pygal Implements the **pygal** version of `venn-basic`. **File:** `plots/venn-basic/implementations/pygal.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20584842103)* --------- 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 cb11a16 commit 803e83d

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
""" pyplots.ai
2+
venn-basic: Venn Diagram
3+
Library: pygal 3.1.0 | Python 3.13.11
4+
Quality: 78/100 | Created: 2025-12-29
5+
"""
6+
7+
import cairosvg
8+
import numpy as np
9+
import pygal
10+
from pygal.style import Style
11+
12+
13+
# Data: Three product categories with overlapping features
14+
set_labels = ["Product A", "Product B", "Product C"]
15+
set_sizes = [100, 80, 60]
16+
17+
# Overlaps: AB=30, AC=20, BC=25, ABC=10
18+
ab_overlap = 30
19+
ac_overlap = 20
20+
bc_overlap = 25
21+
abc_overlap = 10
22+
23+
# Calculate exclusive regions
24+
only_a = set_sizes[0] - ab_overlap - ac_overlap + abc_overlap # 60
25+
only_b = set_sizes[1] - ab_overlap - bc_overlap + abc_overlap # 35
26+
only_c = set_sizes[2] - ac_overlap - bc_overlap + abc_overlap # 25
27+
only_ab = ab_overlap - abc_overlap # 20
28+
only_ac = ac_overlap - abc_overlap # 10
29+
only_bc = bc_overlap - abc_overlap # 15
30+
31+
# Custom style for Venn diagram (scaled for 3600x3600 canvas)
32+
custom_style = Style(
33+
background="white",
34+
plot_background="white",
35+
foreground="#333333",
36+
foreground_strong="#333333",
37+
foreground_subtle="#666666",
38+
colors=("#306998", "#FFD43B", "#4CAF50"), # Python Blue, Python Yellow, Green
39+
opacity=0.4,
40+
opacity_hover=0.5,
41+
title_font_size=56,
42+
legend_font_size=44, # Increased legend font size for better visibility
43+
label_font_size=28,
44+
major_label_font_size=24,
45+
value_font_size=32,
46+
stroke_width=4,
47+
)
48+
49+
# Create XY chart with larger margins to prevent label cut-off
50+
chart = pygal.XY(
51+
width=3600,
52+
height=3600,
53+
style=custom_style,
54+
fill=True,
55+
stroke=True,
56+
show_dots=False,
57+
show_legend=True,
58+
legend_at_bottom=True,
59+
legend_box_size=36, # Larger legend box for better visibility
60+
title="venn-basic · pygal · pyplots.ai",
61+
show_x_guides=False,
62+
show_y_guides=False,
63+
show_x_labels=False,
64+
show_y_labels=False,
65+
x_title=None,
66+
y_title=None,
67+
margin=250, # Larger margin to prevent label and circle cut-off
68+
spacing=15,
69+
explicit_size=True,
70+
)
71+
72+
# Circle parameters for 3-set Venn diagram (smaller radius to fit within canvas)
73+
r = 0.75
74+
n_points = 150
75+
theta = np.linspace(0, 2 * np.pi, n_points)
76+
77+
# Circle centers in equilateral triangle arrangement (tighter to fit within margins)
78+
cx_a, cy_a = -0.4, 0.2 # Top left
79+
cx_b, cy_b = 0.4, 0.2 # Top right
80+
cx_c, cy_c = 0.0, -0.45 # Bottom center
81+
82+
# Generate circle points for each set
83+
circle_a = [(cx_a + r * np.cos(t), cy_a + r * np.sin(t)) for t in theta]
84+
circle_b = [(cx_b + r * np.cos(t), cy_b + r * np.sin(t)) for t in theta]
85+
circle_c = [(cx_c + r * np.cos(t), cy_c + r * np.sin(t)) for t in theta]
86+
87+
# Add circles as series (legend shows set sizes)
88+
chart.add(f"{set_labels[0]} (n={set_sizes[0]})", circle_a)
89+
chart.add(f"{set_labels[1]} (n={set_sizes[1]})", circle_b)
90+
chart.add(f"{set_labels[2]} (n={set_sizes[2]})", circle_c)
91+
92+
# Render to SVG
93+
svg_content = chart.render().decode("utf-8")
94+
95+
# Coordinate transformation: scale data coordinates to SVG pixels
96+
scale = 3600 * 0.35 # Scale factor for data to SVG conversion
97+
center_x = 3600 / 2
98+
center_y = 3600 / 2
99+
100+
# Region count labels (positioned inside each region)
101+
count_style = 'font-size="52px" font-weight="bold" fill="#222" text-anchor="middle" dominant-baseline="middle"'
102+
name_style = 'font-size="40px" font-weight="bold" fill="#333" text-anchor="middle" dominant-baseline="middle"'
103+
104+
labels = [
105+
# Region counts - adjusted for new circle positions
106+
(center_x + (cx_a - 0.35) * scale, center_y - (cy_a + 0.25) * scale, str(only_a), count_style),
107+
(center_x + (cx_b + 0.35) * scale, center_y - (cy_b + 0.25) * scale, str(only_b), count_style),
108+
(center_x + cx_c * scale, center_y - (cy_c - 0.4) * scale, str(only_c), count_style),
109+
(center_x, center_y - (cy_a + 0.35) * scale, str(only_ab), count_style),
110+
(
111+
center_x + ((cx_a + cx_c) / 2 - 0.2) * scale,
112+
center_y - ((cy_a + cy_c) / 2 - 0.08) * scale,
113+
str(only_ac),
114+
count_style,
115+
),
116+
(
117+
center_x + ((cx_b + cx_c) / 2 + 0.2) * scale,
118+
center_y - ((cy_b + cy_c) / 2 - 0.08) * scale,
119+
str(only_bc),
120+
count_style,
121+
),
122+
(center_x, center_y - ((cy_a + cy_c) / 2) * scale, str(abc_overlap), count_style),
123+
# Set name labels (positioned further INSIDE circles to prevent cut-off)
124+
(center_x + cx_a * scale, center_y - (cy_a + 0.35) * scale, set_labels[0], name_style),
125+
(center_x + cx_b * scale, center_y - (cy_b + 0.35) * scale, set_labels[1], name_style),
126+
(center_x + cx_c * scale, center_y - (cy_c - 0.45) * scale, set_labels[2], name_style),
127+
]
128+
129+
# Build and insert SVG text elements
130+
text_elements = "\n".join(f'<text x="{x:.0f}" y="{y:.0f}" {s}>{v}</text>' for x, y, v, s in labels)
131+
svg_content = svg_content.replace("</svg>", f"{text_elements}\n</svg>")
132+
133+
# Save SVG (with labels)
134+
with open("plot.svg", "w", encoding="utf-8") as f:
135+
f.write(svg_content)
136+
137+
# Render to PNG using cairosvg (includes the manually added labels)
138+
cairosvg.svg2png(bytestring=svg_content.encode("utf-8"), write_to="plot.png")
139+
140+
# Create interactive HTML version
141+
html_content = f"""<!DOCTYPE html>
142+
<html>
143+
<head>
144+
<meta charset="utf-8">
145+
<title>venn-basic - pygal - pyplots.ai</title>
146+
<style>
147+
body {{ margin: 0; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; }}
148+
svg {{ max-width: 100%; height: auto; }}
149+
</style>
150+
</head>
151+
<body>
152+
{svg_content}
153+
</body>
154+
</html>"""
155+
156+
with open("plot.html", "w", encoding="utf-8") as f:
157+
f.write(html_content)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library: pygal
2+
specification_id: venn-basic
3+
created: '2025-12-29T23:29:36Z'
4+
updated: '2025-12-29T23:48:14Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20584842103
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 3.1.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/venn-basic/pygal/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/venn-basic/pygal/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/venn-basic/pygal/plot.html
13+
quality_score: 78
14+
review:
15+
strengths:
16+
- Creative workaround using XY chart to draw circles since pygal lacks native Venn
17+
support
18+
- All 7 regions are properly labeled with correct counts
19+
- Good color palette with transparency for overlapping regions
20+
- Legend includes set totals which adds useful information
21+
- Mathematically correct calculation of exclusive regions
22+
- HTML output provided for interactivity
23+
weaknesses:
24+
- Rendering artifact visible as a thin vertical line on the right edge of the yellow
25+
circle
26+
- Implementation relies heavily on manual SVG text injection rather than pygal native
27+
features
28+
- Circles are not proportional to set sizes as mentioned in spec
29+
- Bottom margin has excess whitespace; circles could be more centered

0 commit comments

Comments
 (0)