Skip to content

Commit 181ae2f

Browse files
feat(letsplot): implement parliament-basic (#2546)
## Implementation: `parliament-basic` - letsplot Implements the **letsplot** version of `parliament-basic`. **File:** `plots/parliament-basic/implementations/letsplot.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20585406402)* --------- 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 6f5c21d commit 181ae2f

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# ruff: noqa: F405
2+
"""pyplots.ai
3+
parliament-basic: Parliament Seat Chart
4+
Library: lets-plot | Python 3.13
5+
Quality: pending | Created: 2025-12-30
6+
"""
7+
8+
import os
9+
10+
import numpy as np
11+
import pandas as pd
12+
from lets_plot import * # noqa: F403
13+
14+
15+
LetsPlot.setup_html()
16+
17+
# Data - Board of Directors composition (neutral, non-political)
18+
groups = [
19+
"Finance Committee",
20+
"Technology Board",
21+
"Operations Division",
22+
"Research Council",
23+
"Marketing Team",
24+
"Legal Advisory",
25+
]
26+
seats = [85, 72, 58, 35, 95, 55]
27+
# Colorblind-safe palette with distinct colors
28+
colors = ["#306998", "#FFD43B", "#E69F00", "#56B4E9", "#DC143C", "#9370DB"]
29+
30+
total_seats = sum(seats)
31+
n_rows = 5
32+
33+
# Calculate seat positions in semicircular arrangement (inline, KISS)
34+
row_weights = np.array([i + 1 for i in range(n_rows)])
35+
seats_per_row = (row_weights / row_weights.sum() * total_seats).astype(int)
36+
seats_per_row[-1] += total_seats - seats_per_row.sum() # Adjust for rounding
37+
38+
x_positions = []
39+
y_positions = []
40+
group_labels = []
41+
42+
group_index = 0
43+
remaining_in_group = seats[0]
44+
45+
for row_idx, row_seats in enumerate(seats_per_row):
46+
radius = 0.5 + row_idx * 0.12 # Radius increases for outer rows
47+
angles = np.linspace(np.pi, 0, row_seats) # Semicircle from left to right
48+
49+
for angle in angles:
50+
x = radius * np.cos(angle)
51+
y = radius * np.sin(angle)
52+
x_positions.append(x)
53+
y_positions.append(y)
54+
group_labels.append(groups[group_index])
55+
56+
remaining_in_group -= 1
57+
if remaining_in_group == 0 and group_index < len(seats) - 1:
58+
group_index += 1
59+
remaining_in_group = seats[group_index]
60+
61+
x_coords, y_coords, group_assignment = x_positions, y_positions, group_labels
62+
63+
# Create DataFrame
64+
df = pd.DataFrame({"x": x_coords, "y": y_coords, "group": group_assignment})
65+
66+
# Create legend labels with seat counts
67+
group_seat_counts = dict(zip(groups, seats, strict=True))
68+
df["group_label"] = df["group"].apply(lambda g: f"{g} ({group_seat_counts[g]})")
69+
70+
# Create color mapping for labels
71+
label_colors = {f"{g} ({s})": c for g, s, c in zip(groups, seats, colors, strict=True)}
72+
color_values = [label_colors[label] for label in df["group_label"].unique()]
73+
74+
# Build the plot
75+
plot = (
76+
ggplot(df, aes(x="x", y="y", color="group_label"))
77+
+ geom_point(size=5, alpha=0.9)
78+
+ scale_color_manual(values=color_values)
79+
+ coord_fixed(ratio=1)
80+
+ theme_void()
81+
+ labs(title="parliament-basic · lets-plot · pyplots.ai", color="Division (Seats)")
82+
+ ggsize(1600, 900)
83+
+ theme(
84+
plot_title=element_text(size=24, hjust=0.5),
85+
legend_title=element_text(size=18),
86+
legend_text=element_text(size=14),
87+
legend_position="right",
88+
plot_margin=[40, 40, 40, 40],
89+
)
90+
)
91+
92+
# Add majority line annotation (horizontal line at y=0)
93+
plot = plot + geom_hline(yintercept=0, color="#333333", size=1.5, alpha=0.6)
94+
95+
# Add total seats annotation using geom_text with a data point
96+
annotation_df = pd.DataFrame(
97+
{"x": [0], "y": [-0.15], "label": [f"Total: {total_seats} seats | Majority: {total_seats // 2 + 1}"]}
98+
)
99+
plot = plot + geom_text(data=annotation_df, mapping=aes(x="x", y="y", label="label"), size=14, color="#333333")
100+
101+
# Save as PNG and HTML
102+
current_dir = os.getcwd()
103+
ggsave(plot, os.path.join(current_dir, "plot.png"), scale=3)
104+
ggsave(plot, os.path.join(current_dir, "plot.html"))
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: letsplot
2+
specification_id: parliament-basic
3+
created: '2025-12-30T00:08:54Z'
4+
updated: '2025-12-30T00:27:32Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20585406402
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 4.8.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/parliament-basic/letsplot/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/parliament-basic/letsplot/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/parliament-basic/letsplot/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent semicircular parliament visualization with clean concentric arcs
17+
- Uses neutral non-political data context (board divisions instead of political
18+
parties)
19+
- Colorblind-safe palette with distinct, visually appealing colors
20+
- Legend clearly shows division names with seat counts in parentheses
21+
- Includes majority threshold annotation and total seats information
22+
- Good use of theme_void for clean presentation
23+
- Proper aspect ratio with coord_fixed
24+
weaknesses:
25+
- Title uses lets-plot with hyphen instead of letsplot (one word)
26+
- Seats are distributed by row rather than strictly left-to-right across the semicircle

0 commit comments

Comments
 (0)