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)
|
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…")
|
@st.cache_data(show_spinner="Moties laden…")
|
||||||
def load_motions_df(db_path: str) -> pd.DataFrame:
|
def load_motions_df(db_path: str) -> pd.DataFrame:
|
||||||
"""Load the full motions table as a pandas DataFrame (read-only)."""
|
"""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
|
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):
|
if comp_sel in (1, 2):
|
||||||
|
# Components 1-2 use 2D coords from political compass
|
||||||
_render_party_axis_chart(
|
_render_party_axis_chart(
|
||||||
party_coords, comp_sel, theme, bootstrap_data=bootstrap_data
|
party_coords, comp_sel, theme, bootstrap_data=bootstrap_data
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
st.caption(
|
# Components 3-10 use 1D scores from SVD
|
||||||
"_Partijposities zijn alleen beschikbaar voor de eerste twee SVD-assen._"
|
# 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)
|
# 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]
|
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