diff --git a/explorer.py b/explorer.py index 6d74eeb..9db4131 100644 --- a/explorer.py +++ b/explorer.py @@ -203,6 +203,73 @@ def load_party_map(db_path: str) -> Dict[str, str]: return {} +def _render_party_axis_chart( + party_scores: Dict[str, List[float]], comp_sel: int +) -> None: + """Render a 1D horizontal Plotly scatter of party positions on SVD axis `comp_sel`. + + Each party is plotted at its score on a single horizontal axis (y=0). + """ + if not party_scores: + st.caption("_Partijdata niet beschikbaar voor deze as._") + return + + axis_idx = comp_sel - 1 # 0-based index into the 50-dim vector + data: list[dict] = [] + for party, vec in party_scores.items(): + if axis_idx < len(vec): + data.append({"party": party, "score": vec[axis_idx]}) + + if not data: + st.caption("_Geen partijscores voor deze as._") + return + + scores = [d["score"] for d in data] + parties = [d["party"] for d in data] + colours = [PARTY_COLOURS.get(p, "#9E9E9E") for p in parties] + hover = [f"{p}: {s:.3f}" for p, s in zip(parties, scores)] + + fig = go.Figure() + # Baseline + x_min, x_max = min(scores) * 1.15, max(scores) * 1.15 + fig.add_trace( + go.Scatter( + x=[x_min, x_max], + y=[0, 0], + mode="lines", + line={"color": "#cccccc", "width": 1}, + hoverinfo="skip", + showlegend=False, + ) + ) + # Party markers + fig.add_trace( + go.Scatter( + x=scores, + y=[0] * len(scores), + mode="markers+text", + text=parties, + textposition="top center", + marker={"size": 12, "color": colours}, + hovertext=hover, + hoverinfo="text", + showlegend=False, + ) + ) + fig.update_layout( + height=160, + margin={"l": 10, "r": 10, "t": 10, "b": 30}, + xaxis={ + "title": "← Negatieve pool | Positieve pool →", + "zeroline": True, + "zerolinecolor": "#aaaaaa", + }, + yaxis={"visible": False, "range": [-1, 2]}, + plot_bgcolor="white", + ) + 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)."""