feat: add 1D party position charts for SVD components 3-10 (Task 5)

main
Sven Geboers 4 weeks ago
parent 5b3cf23d36
commit bda803089a
  1. 85
      explorer.py

@ -1352,6 +1352,76 @@ def _render_party_axis_chart(
st.plotly_chart(fig, use_container_width=True)
def _render_party_axis_chart_1d(
party_coords: Dict[str, Tuple[float, ...]],
comp_sel: int,
theme: dict,
) -> None:
"""Render a 1D horizontal bar chart of party positions on SVD component `comp_sel`.
Args:
party_coords: Dict mapping party name to tuple of scores (score_for_comp,)
comp_sel: SVD component number (1-indexed)
theme: Dict with label, positive_pole, negative_pole, flip
"""
import plotly.graph_objects as go
if not party_coords:
st.caption("_Partijdata niet beschikbaar voor deze as._")
return
# Extract scores and parties
parties = list(party_coords.keys())
scores = [coords[0] for coords in party_coords.values()]
# Apply flip if needed
flip = theme.get("flip", False)
if flip:
scores = [-s for s in scores]
# Get party colors
party_colors = [PARTY_COLOURS.get(p, "#9E9E9E") for p in parties]
# Sort by score for better visualization
sorted_indices = np.argsort(scores)
sorted_parties = [parties[i] for i in sorted_indices]
sorted_scores = [scores[i] for i in sorted_indices]
sorted_colors = [party_colors[i] for i in sorted_indices]
# Create horizontal bar chart
fig = go.Figure()
fig.add_trace(
go.Bar(
y=sorted_parties,
x=sorted_scores,
orientation="h",
marker_color=sorted_colors,
text=[f"{s:.2f}" for s in sorted_scores],
textposition="outside",
)
)
# Update layout
label = theme.get("label", f"As {comp_sel}")
positive_pole = theme.get("positive_pole", "Positief")
negative_pole = theme.get("negative_pole", "Negatief")
fig.update_layout(
title=f"Partijposities — {label}",
xaxis_title=f"{negative_pole} ← → {positive_pole}",
yaxis_title="",
height=max(400, len(parties) * 25),
margin=dict(l=150),
showlegend=False,
)
# Add vertical line at x=0
fig.add_vline(x=0, line_dash="dash", line_color="gray", opacity=0.5)
st.plotly_chart(fig, use_container_width=True)
@st.cache_data(show_spinner="Moties laden…")
def load_motions_df(db_path: str) -> pd.DataFrame:
"""Load the full motions table as a pandas DataFrame (read-only)."""
@ -2823,15 +2893,22 @@ def build_svd_components_tab(db_path: str) -> None:
p: coords for p, coords in party_coords.items() if p in valid_parties
}
# Only render party axis chart for components 1 and 2 (which have 2D coords)
# Render party axis chart
if comp_sel in (1, 2):
# Components 1-2 use 2D coords from political compass
_render_party_axis_chart(
party_coords, comp_sel, theme, bootstrap_data=bootstrap_data
)
else:
st.caption(
"_Partijposities zijn alleen beschikbaar voor de eerste twee SVD-assen._"
)
# Components 3-10 use 1D scores from SVD
# Extract 1D scores for this component
party_1d_coords = {}
idx = comp_sel - 1 # Convert to 0-indexed
for party, scores in party_scores.items():
if scores and len(scores) > idx:
party_1d_coords[party] = (scores[idx],)
_render_party_axis_chart_1d(party_1d_coords, comp_sel, theme)
# Batch-fetch motion details (title, date, policy_area, url, body_text, voting_results)
motion_ids = [m.get("motion_id") for m in motions if m.get("motion_id") is not None]

Loading…
Cancel
Save