Skip to content

Commit 7d134a5

Browse files
feat(plotly): implement parliament-basic (#2522)
## Implementation: `parliament-basic` - plotly Implements the **plotly** version of `parliament-basic`. **File:** `plots/parliament-basic/implementations/plotly.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20585399554)* --------- 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 6857727 commit 7d134a5

2 files changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
""" pyplots.ai
2+
parliament-basic: Parliament Seat Chart
3+
Library: plotly 6.5.0 | Python 3.13.11
4+
Quality: 93/100 | Created: 2025-12-30
5+
"""
6+
7+
import numpy as np
8+
import plotly.graph_objects as go
9+
10+
11+
# Data - Fictional parliament with neutral party names (avoiding political references)
12+
parties = [
13+
{"name": "Progressive Alliance", "seats": 145, "color": "#306998"},
14+
{"name": "Civic Union", "seats": 118, "color": "#FFD43B"},
15+
{"name": "Green Future", "seats": 52, "color": "#2CA02C"},
16+
{"name": "Liberty Party", "seats": 48, "color": "#9467BD"},
17+
{"name": "Reform Coalition", "seats": 35, "color": "#FF7F0E"},
18+
{"name": "Independent Group", "seats": 22, "color": "#E377C2"},
19+
]
20+
21+
total_seats = sum(p["seats"] for p in parties)
22+
majority_threshold = total_seats // 2 + 1
23+
24+
# Calculate seat positions in semicircular arrangement
25+
# Seats are arranged by angle across all rows, with parties grouped from left to right
26+
n_rows = 7 if total_seats <= 500 else 9
27+
inner_radius = 0.4
28+
row_spacing = 0.11
29+
30+
# Calculate seats per row (outer rows have more seats due to larger circumference)
31+
row_seats = []
32+
for row in range(n_rows):
33+
radius = inner_radius + row * row_spacing
34+
# Seats proportional to arc length (radius)
35+
seats_in_row = int(total_seats * radius / sum(inner_radius + i * row_spacing for i in range(n_rows)))
36+
row_seats.append(max(seats_in_row, 1))
37+
38+
# Adjust to match total seats exactly
39+
diff = total_seats - sum(row_seats)
40+
for i in range(abs(diff)):
41+
idx = (n_rows - 1 - i % n_rows) if diff > 0 else (i % n_rows)
42+
row_seats[idx] += 1 if diff > 0 else -1
43+
44+
# Generate all seat positions sorted by angle (left to right = pi to 0)
45+
all_seats = []
46+
for row, n_seats in enumerate(row_seats):
47+
radius = inner_radius + row * row_spacing
48+
for i in range(n_seats):
49+
# Angle from left (pi) to right (0) - seats go left to right
50+
angle = np.pi - (i + 0.5) * np.pi / n_seats
51+
all_seats.append({"x": radius * np.cos(angle), "y": radius * np.sin(angle), "angle": angle, "row": row})
52+
53+
# Sort all seats by angle (descending = left to right in parliament view)
54+
all_seats.sort(key=lambda s: -s["angle"])
55+
56+
# Assign parties to seats (parties fill seats from left to right)
57+
positions = []
58+
seat_idx = 0
59+
for party in parties:
60+
for _ in range(party["seats"]):
61+
if seat_idx < len(all_seats):
62+
seat = all_seats[seat_idx]
63+
positions.append(
64+
{"x": seat["x"], "y": seat["y"], "party": party["name"], "color": party["color"], "row": seat["row"]}
65+
)
66+
seat_idx += 1
67+
68+
# Create figure
69+
fig = go.Figure()
70+
71+
# Add seats grouped by party for legend
72+
for party in parties:
73+
party_positions = [p for p in positions if p["party"] == party["name"]]
74+
fig.add_trace(
75+
go.Scatter(
76+
x=[p["x"] for p in party_positions],
77+
y=[p["y"] for p in party_positions],
78+
mode="markers",
79+
marker=dict(size=14, color=party["color"], line=dict(color="white", width=1)),
80+
name=f"{party['name']} ({party['seats']})",
81+
hovertemplate=f"{party['name']}<br>Seats: {party['seats']}<extra></extra>",
82+
)
83+
)
84+
85+
# Add majority threshold arc (dashed line)
86+
threshold_angle = np.linspace(0, np.pi, 100)
87+
threshold_radius = 0.5 + 0.12 * (len(set(p["row"] for p in positions)) / 2)
88+
fig.add_trace(
89+
go.Scatter(
90+
x=threshold_radius * np.cos(threshold_angle),
91+
y=threshold_radius * np.sin(threshold_angle),
92+
mode="lines",
93+
line=dict(color="rgba(0,0,0,0.3)", width=2, dash="dash"),
94+
name=f"Majority ({majority_threshold})",
95+
hoverinfo="skip",
96+
)
97+
)
98+
99+
# Layout
100+
fig.update_layout(
101+
title=dict(
102+
text="parliament-basic · plotly · pyplots.ai", font=dict(size=28, color="#333"), x=0.5, xanchor="center"
103+
),
104+
xaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-1.3, 1.3], scaleanchor="y", scaleratio=1),
105+
yaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-0.15, 1.2]),
106+
legend=dict(
107+
orientation="h", yanchor="bottom", y=-0.15, xanchor="center", x=0.5, font=dict(size=16), itemsizing="constant"
108+
),
109+
template="plotly_white",
110+
plot_bgcolor="white",
111+
paper_bgcolor="white",
112+
margin=dict(l=50, r=50, t=100, b=120),
113+
)
114+
115+
# Add annotation for total seats
116+
fig.add_annotation(
117+
x=0,
118+
y=0.05,
119+
text=f"<b>{total_seats}</b><br>seats",
120+
font=dict(size=24, color="#333"),
121+
showarrow=False,
122+
align="center",
123+
)
124+
125+
# Save outputs
126+
fig.write_image("plot.png", width=1600, height=900, scale=3)
127+
fig.write_html("plot.html", include_plotlyjs="cdn")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: plotly
2+
specification_id: parliament-basic
3+
created: '2025-12-30T00:04:08Z'
4+
updated: '2025-12-30T00:11:57Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20585399554
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 6.5.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/parliament-basic/plotly/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/parliament-basic/plotly/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/parliament-basic/plotly/plot.html
13+
quality_score: 93
14+
review:
15+
strengths:
16+
- Excellent semicircular seat arrangement algorithm that properly distributes seats
17+
across concentric arcs
18+
- Clean visual design with white marker borders that distinguish individual seats
19+
- Proper horizontal legend with seat counts for each party
20+
- Smart use of Plotly hover templates for interactivity
21+
- Majority threshold line adds context as specified
22+
- Total seats annotation at center is a nice touch
23+
- Neutral fictional party names avoid political controversy
24+
- Both PNG and interactive HTML outputs generated
25+
weaknesses:
26+
- The majority threshold dashed line is quite faint (rgba 0.3 alpha); could be slightly
27+
more visible while remaining subtle

0 commit comments

Comments
 (0)