feat: add 1D party position charts for SVD components 3-10 (Task 5)
This commit is contained in:
+81
-4
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user