Skip to content

Commit 6052547

Browse files
feat(pygal): implement waffle-basic (#1051)
## Implementation: `waffle-basic` - pygal Implements the **pygal** version of `waffle-basic`. **File:** `plots/waffle-basic/implementations/pygal.py` **Parent Issue:** #998 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20260265977)* --------- 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 f395471 commit 6052547

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
"""
2+
waffle-basic: Basic Waffle Chart
3+
Library: pygal
4+
"""
5+
6+
import sys
7+
8+
9+
# Temporarily remove current directory from path to avoid name collision
10+
_cwd = sys.path[0] if sys.path[0] else "."
11+
if _cwd in sys.path:
12+
sys.path.remove(_cwd)
13+
14+
from pygal.graph.graph import Graph # noqa: E402
15+
from pygal.style import Style # noqa: E402
16+
17+
18+
# Restore path
19+
sys.path.insert(0, _cwd)
20+
21+
22+
class Waffle(Graph):
23+
"""Custom Waffle chart for pygal - displays proportions as colored squares in a grid."""
24+
25+
_serie_margin = 0
26+
27+
def __init__(self, *args, **kwargs):
28+
self.rows = kwargs.pop("rows", 10)
29+
self.cols = kwargs.pop("cols", 10)
30+
super().__init__(*args, **kwargs)
31+
32+
def _plot(self):
33+
"""Draw the waffle grid."""
34+
# Calculate total values and percentages
35+
total = sum(sum(v for v in serie.values if v is not None) for serie in self.series)
36+
if total == 0:
37+
return
38+
39+
total_squares = self.rows * self.cols
40+
41+
# Get plot area dimensions from the view
42+
plot_width = self.view.width
43+
plot_height = self.view.height
44+
45+
# Calculate square size with gap
46+
square_size = min(plot_width / self.cols, plot_height / self.rows) * 0.85
47+
gap = square_size * 0.12
48+
49+
# Center the grid
50+
grid_width = self.cols * (square_size + gap) - gap
51+
grid_height = self.rows * (square_size + gap) - gap
52+
53+
# Calculate the actual plot area boundaries
54+
# view.y(rows) gives the top of the plot area in SVG coordinates
55+
x_start = self.view.x(0)
56+
y_start = self.view.y(self.rows) # Top of the plot area in SVG coordinates
57+
58+
# Center the grid within the plot area
59+
x_offset = x_start + (plot_width - grid_width) / 2
60+
y_offset = y_start + (plot_height - grid_height) / 2
61+
62+
# Create a group for waffle squares
63+
plot_node = self.nodes["plot"]
64+
waffle_group = self.svg.node(plot_node, class_="waffle-chart")
65+
66+
# Assign squares to each series
67+
square_index = 0
68+
for serie_index, serie in enumerate(self.series):
69+
serie_value = sum(v for v in serie.values if v is not None)
70+
num_squares = round(serie_value / total * total_squares)
71+
72+
color = self.style.colors[serie_index % len(self.style.colors)]
73+
74+
# Create a group for this series
75+
serie_group = self.svg.node(waffle_group, class_="series serie-%d color-%d" % (serie_index, serie_index))
76+
77+
for _ in range(num_squares):
78+
if square_index >= total_squares:
79+
break
80+
81+
row = square_index // self.cols
82+
col = square_index % self.cols
83+
84+
x = x_offset + col * (square_size + gap)
85+
y = y_offset + row * (square_size + gap)
86+
87+
# Draw square with rounded corners
88+
self.svg.node(
89+
serie_group,
90+
"rect",
91+
x=x,
92+
y=y,
93+
width=square_size,
94+
height=square_size,
95+
fill=color,
96+
rx=square_size * 0.1,
97+
ry=square_size * 0.1,
98+
class_="waffle-square reactive",
99+
)
100+
101+
square_index += 1
102+
103+
def _compute(self):
104+
"""Compute the box for rendering."""
105+
# Set basic box dimensions
106+
self._box.xmin = 0
107+
self._box.xmax = self.cols
108+
self._box.ymin = 0
109+
self._box.ymax = self.rows
110+
111+
112+
# Custom style for 4800x2700 canvas
113+
custom_style = Style(
114+
background="white",
115+
plot_background="white",
116+
foreground="#333333",
117+
foreground_strong="#333333",
118+
foreground_subtle="#666666",
119+
colors=("#306998", "#FFD43B", "#4ECDC4", "#FF6B6B"),
120+
title_font_size=72,
121+
legend_font_size=48,
122+
label_font_size=36,
123+
value_font_size=32,
124+
font_family="sans-serif",
125+
)
126+
127+
# Data - Budget allocation example (values should sum to 100)
128+
categories = {"Operations": 42, "Marketing": 28, "R&D": 18, "Admin": 12}
129+
130+
# Create waffle chart
131+
chart = Waffle(
132+
width=4800,
133+
height=2700,
134+
rows=10,
135+
cols=10,
136+
style=custom_style,
137+
title="Budget Allocation · waffle-basic · pygal · pyplots.ai",
138+
show_legend=True,
139+
legend_at_bottom=True,
140+
legend_at_bottom_columns=4,
141+
margin=80,
142+
margin_bottom=250,
143+
show_x_labels=False,
144+
show_y_labels=False,
145+
)
146+
147+
# Add data series with percentages in labels
148+
for category, value in categories.items():
149+
chart.add(f"{category} ({value}%)", [value])
150+
151+
# Save outputs
152+
chart.render_to_file("plot.svg")
153+
chart.render_to_png("plot.png")
154+
155+
# Also save HTML for interactive viewing
156+
html_content = f"""<!DOCTYPE html>
157+
<html>
158+
<head>
159+
<title>waffle-basic · pygal · pyplots.ai</title>
160+
<style>
161+
body {{ margin: 0; padding: 20px; background: #f5f5f5; }}
162+
.container {{ max-width: 100%; margin: 0 auto; }}
163+
svg {{ max-width: 100%; height: auto; }}
164+
</style>
165+
</head>
166+
<body>
167+
<div class="container">
168+
{chart.render(is_unicode=True)}
169+
</div>
170+
</body>
171+
</html>"""
172+
173+
with open("plot.html", "w") as f:
174+
f.write(html_content)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Per-library metadata for pygal implementation of waffle-basic
2+
# Auto-generated by impl-generate.yml
3+
4+
library: pygal
5+
specification_id: waffle-basic
6+
7+
# Preview URLs (filled by workflow)
8+
preview_url: https://storage.googleapis.com/pyplots-images/plots/waffle-basic/pygal/plot.png
9+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/waffle-basic/pygal/plot_thumb.png
10+
preview_html: https://storage.googleapis.com/pyplots-images/plots/waffle-basic/pygal/plot.html
11+
12+
current:
13+
version: 0
14+
generated_at: 2025-12-16T07:48:26Z
15+
generated_by: claude-opus-4-5-20251101
16+
workflow_run: 20260265977
17+
issue: 998
18+
quality_score: 92
19+
# Version info (filled by workflow)
20+
python_version: "3.13.11"
21+
library_version: "unknown"
22+
23+
history: []

0 commit comments

Comments
 (0)