fix: correct axis 4 and 5 pole labels and flip orientation in SVD_THEMES
Axes 4 and 5 had inverted sign conventions relative to actual party votes. Diagnostic confirmed SP/PvdD scored negative on axis 4 (free trade motions) and FVD scored negative on axis 5 (secular motions), opposite to their voting behaviour. Fix: swap positive_pole/negative_pole for both axes and set correct flip direction so progressive parties appear on the left.
This commit is contained in:
+57
-25
@@ -251,21 +251,27 @@ def load_party_axis_scores(db_path: str) -> Dict[str, List[float]]:
|
||||
|
||||
|
||||
def _render_party_axis_chart(
|
||||
party_scores: Dict[str, List[float]], comp_sel: int
|
||||
party_scores: Dict[str, List[float]], comp_sel: int, theme: dict
|
||||
) -> 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).
|
||||
When theme['flip'] is True the scores are negated so that the progressive/left
|
||||
side always appears on the left of the chart.
|
||||
"""
|
||||
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
|
||||
flip = theme.get("flip", False)
|
||||
data: list[dict] = []
|
||||
for party, vec in party_scores.items():
|
||||
if axis_idx < len(vec):
|
||||
data.append({"party": party, "score": vec[axis_idx]})
|
||||
score = vec[axis_idx]
|
||||
if flip:
|
||||
score = -score
|
||||
data.append({"party": party, "score": score})
|
||||
|
||||
if not data:
|
||||
st.caption("_Geen partijscores voor deze as._")
|
||||
@@ -276,9 +282,17 @@ def _render_party_axis_chart(
|
||||
colours = [PARTY_COLOURS.get(p, "#9E9E9E") for p in parties]
|
||||
hover = [f"{p}: {s:.3f}" for p, s in zip(parties, scores)]
|
||||
|
||||
# Determine axis labels: left = progressive pole, right = conservative pole
|
||||
pos_pole = theme.get("positive_pole", "")
|
||||
neg_pole = theme.get("negative_pole", "")
|
||||
left_label = pos_pole if flip else neg_pole
|
||||
right_label = neg_pole if flip else pos_pole
|
||||
|
||||
fig = go.Figure()
|
||||
# Baseline
|
||||
x_min, x_max = min(scores) * 1.15, max(scores) * 1.15
|
||||
if x_min == x_max:
|
||||
x_min, x_max = x_min - 1, x_max + 1
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[x_min, x_max],
|
||||
@@ -297,7 +311,7 @@ def _render_party_axis_chart(
|
||||
mode="markers+text",
|
||||
text=parties,
|
||||
textposition="top center",
|
||||
marker={"size": 12, "color": colours},
|
||||
marker={"size": 18, "color": colours},
|
||||
hovertext=hover,
|
||||
hoverinfo="text",
|
||||
showlegend=False,
|
||||
@@ -307,12 +321,13 @@ def _render_party_axis_chart(
|
||||
height=160,
|
||||
margin={"l": 10, "r": 10, "t": 10, "b": 30},
|
||||
xaxis={
|
||||
"title": "← Negatieve pool | Positieve pool →",
|
||||
"title": f"← {left_label} | {right_label} →",
|
||||
"zeroline": True,
|
||||
"zerolinecolor": "#aaaaaa",
|
||||
},
|
||||
yaxis={"visible": False, "range": [-1, 2]},
|
||||
plot_bgcolor="white",
|
||||
plot_bgcolor="rgba(0,0,0,0)",
|
||||
paper_bgcolor="rgba(0,0,0,0)",
|
||||
)
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
@@ -784,6 +799,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Breed coalitiebeleid: zorg, defensie, multilateralisme, inclusie",
|
||||
"negative_pole": "Radicale PVV-eis tot onmiddellijke uitzetting migranten",
|
||||
"flip": True,
|
||||
},
|
||||
2: {
|
||||
"label": "Nationalistisch migratiebeleid versus progressief internationaal solidariteitsdenken",
|
||||
@@ -799,6 +815,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Asielbeperking, nationaal belang, restrictief migratiebeleid",
|
||||
"negative_pole": "Pro-Palestina, progressieve zorgrechten, anti-discriminatie minderheden",
|
||||
"flip": False,
|
||||
},
|
||||
3: {
|
||||
"label": "Humanitaire solidariteit en inclusie versus nationalistische handhaving en deregulering",
|
||||
@@ -815,6 +832,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Internationale solidariteit, inclusie en pragmatische overheidsinterventie",
|
||||
"negative_pole": "Strikte handhaving, deregulering en nationalistisch eigenbelang boven humanitaire verplichtingen",
|
||||
"flip": True,
|
||||
},
|
||||
4: {
|
||||
"label": "Publieke voorzieningen beschermen versus liberale marktwerking",
|
||||
@@ -830,8 +848,9 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
"voorzieningen betaalbaar en toegankelijk te houden, of dat vrije markt en open handel "
|
||||
"leidend moeten zijn."
|
||||
),
|
||||
"positive_pole": "Staatsbescherming van betaalbare publieke voorzieningen voor iedereen",
|
||||
"negative_pole": "Vrije handel, open economie en marktgerichte arbeidsmigratie",
|
||||
"positive_pole": "Vrije handel, open economie en marktgerichte arbeidsmigratie",
|
||||
"negative_pole": "Staatsbescherming van betaalbare publieke voorzieningen voor iedereen",
|
||||
"flip": False,
|
||||
},
|
||||
5: {
|
||||
"label": "Christelijk-conservatief sociaal beleid versus seculier progressief",
|
||||
@@ -844,8 +863,9 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
"consistent vanuit een christelijk-sociale visie stemmen tegenover partijen als D66, "
|
||||
"GroenLinks-PvdA en SP die een seculier-progressief beleid voorstaan."
|
||||
),
|
||||
"positive_pole": "Christelijk-conservatief: gezin, kerk, leven, traditionele waarden",
|
||||
"negative_pole": "Seculier-progressief: individuele autonomie, progressieve sociale rechten",
|
||||
"positive_pole": "Seculier-progressief: individuele autonomie, progressieve sociale rechten",
|
||||
"negative_pole": "Christelijk-conservatief: gezin, kerk, leven, traditionele waarden",
|
||||
"flip": True,
|
||||
},
|
||||
6: {
|
||||
"label": "Christelijk-sociaal beschermingsbeleid versus links-progressieve systeemkritiek",
|
||||
@@ -860,6 +880,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Christelijk-sociaal beschermingsbeleid voor pgb, kinderen en geloofsgroepen",
|
||||
"negative_pole": "Links-progressieve systeemkritiek op zorg, arbeid en internationale solidariteit",
|
||||
"flip": False,
|
||||
},
|
||||
7: {
|
||||
"label": "Liberaal investeren en defensie versus linkse bescherming en controle",
|
||||
@@ -873,6 +894,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Gerichte liberale investeringen in sport, wetenschap en defensie",
|
||||
"negative_pole": "Collectieve bescherming, parlementaire controle en anti-marktwerking in zorg",
|
||||
"flip": False,
|
||||
},
|
||||
8: {
|
||||
"label": "Confessioneel-sociaal coalitiebeleid versus procedurele blokkade en handhaving",
|
||||
@@ -888,6 +910,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Coalitie christelijk-sociaal beleid: defensie, stikstofmaatwerk, bouw en ethiek",
|
||||
"negative_pole": "Procedurele blokkade coffeeshop, handhavingsdoelstelling en topsportderegulering",
|
||||
"flip": False,
|
||||
},
|
||||
9: {
|
||||
"label": "Brede coalitiemeerderheid versus links marktingrijpen zorg",
|
||||
@@ -903,6 +926,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Breed gedragen beleid door centrum-rechts meerderheidsstemmen",
|
||||
"negative_pole": "Socialistische inkomensregulering en marktingrijpen in de zorg",
|
||||
"flip": False,
|
||||
},
|
||||
10: {
|
||||
"label": "Gereguleerde kennismigratie en natuur-landbouwtransitie versus institutionele veiligheid",
|
||||
@@ -918,6 +942,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
),
|
||||
"positive_pole": "Beperkte kennismigratie, natuur-landbouwtransitie en Gaza-humanitair",
|
||||
"negative_pole": "Institutionele veiligheidssturing, economisch nationalisme en Woo-beperking",
|
||||
"flip": True,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -985,7 +1010,7 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
|
||||
# Party axis chart
|
||||
party_scores = load_party_axis_scores(db_path)
|
||||
_render_party_axis_chart(party_scores, comp_sel)
|
||||
_render_party_axis_chart(party_scores, 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]
|
||||
@@ -1021,21 +1046,28 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
pos_motions = [m for m in motions if float(m.get("score", 0.0)) >= 0]
|
||||
neg_motions = [m for m in motions if float(m.get("score", 0.0)) < 0]
|
||||
|
||||
pos_pole = (
|
||||
theme.get("positive_pole", "Positieve pool") if theme else "Positieve pool"
|
||||
)
|
||||
neg_pole = (
|
||||
theme.get("negative_pole", "Negatieve pool") if theme else "Negatieve pool"
|
||||
)
|
||||
flip = theme.get("flip", False) if theme else False
|
||||
pos_pole = theme.get("positive_pole", "") if theme else ""
|
||||
neg_pole = theme.get("negative_pole", "") if theme else ""
|
||||
|
||||
pcol, ncol = st.columns(2)
|
||||
# Determine which pole goes left (progressive) and which goes right
|
||||
if flip:
|
||||
left_pole, right_pole = pos_pole, neg_pole
|
||||
left_motions, right_motions = pos_motions, neg_motions
|
||||
left_arrow, right_arrow = "▲", "▼"
|
||||
else:
|
||||
left_pole, right_pole = neg_pole, pos_pole
|
||||
left_motions, right_motions = neg_motions, pos_motions
|
||||
left_arrow, right_arrow = "▼", "▲"
|
||||
|
||||
with pcol:
|
||||
st.success(f"▲ **Positieve pool:** {pos_pole}")
|
||||
for m in pos_motions:
|
||||
lcol, rcol = st.columns(2)
|
||||
|
||||
with lcol:
|
||||
st.markdown(f"**← {left_pole}**")
|
||||
for m in left_motions:
|
||||
mid = m.get("motion_id")
|
||||
raw_title = m.get("title") or f"Motie #{mid}"
|
||||
with st.expander(f"▲ {raw_title[:80]}"):
|
||||
with st.expander(f"{left_arrow} {raw_title[:80]}"):
|
||||
row = motion_details.get(int(mid)) if mid is not None else None
|
||||
if row:
|
||||
try:
|
||||
@@ -1052,12 +1084,12 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
else:
|
||||
st.caption("_Geen metadata beschikbaar_")
|
||||
|
||||
with ncol:
|
||||
st.error(f"▼ **Negatieve pool:** {neg_pole}")
|
||||
for m in neg_motions:
|
||||
with rcol:
|
||||
st.markdown(f"**{right_pole} →**")
|
||||
for m in right_motions:
|
||||
mid = m.get("motion_id")
|
||||
raw_title = m.get("title") or f"Motie #{mid}"
|
||||
with st.expander(f"▼ {raw_title[:80]}"):
|
||||
with st.expander(f"{right_arrow} {raw_title[:80]}"):
|
||||
row = motion_details.get(int(mid)) if mid is not None else None
|
||||
if row:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user