feat: add axis swap — left-right goes on horizontal axis when detected
This commit is contained in:
+43
@@ -170,6 +170,46 @@ def get_uniform_dim_windows(db_path: str) -> List[str]:
|
|||||||
con.close()
|
con.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _should_swap_axes(axis_def: dict) -> bool:
|
||||||
|
"""Return True if the Y axis is 'Links–Rechts' and the X axis is not.
|
||||||
|
|
||||||
|
When true, caller should swap x/y positions and metadata so left-right
|
||||||
|
is conventionally on the horizontal axis.
|
||||||
|
"""
|
||||||
|
lr = "Links\u2013Rechts"
|
||||||
|
return axis_def.get("y_label") == lr and axis_def.get("x_label") != lr
|
||||||
|
|
||||||
|
|
||||||
|
def _swap_axes(
|
||||||
|
positions_by_window: dict,
|
||||||
|
axis_def: dict,
|
||||||
|
) -> tuple:
|
||||||
|
"""Swap x and y in all positions and axis metadata.
|
||||||
|
|
||||||
|
Pure function — returns (new_positions_by_window, new_axis_def).
|
||||||
|
"""
|
||||||
|
new_positions: dict = {}
|
||||||
|
for wid, pos_dict in positions_by_window.items():
|
||||||
|
new_positions[wid] = {ent: (y, x) for ent, (x, y) in pos_dict.items()}
|
||||||
|
|
||||||
|
new_ax = dict(axis_def)
|
||||||
|
# Swap paired scalar keys
|
||||||
|
new_ax["x_label"] = axis_def.get("y_label")
|
||||||
|
new_ax["y_label"] = axis_def.get("x_label")
|
||||||
|
|
||||||
|
# Swap paired dict keys
|
||||||
|
for x_key, y_key in [
|
||||||
|
("x_quality", "y_quality"),
|
||||||
|
("x_interpretation", "y_interpretation"),
|
||||||
|
("x_top_motions", "y_top_motions"),
|
||||||
|
("x_label_confidence", "y_label_confidence"),
|
||||||
|
]:
|
||||||
|
new_ax[x_key] = axis_def.get(y_key)
|
||||||
|
new_ax[y_key] = axis_def.get(x_key)
|
||||||
|
|
||||||
|
return new_positions, new_ax
|
||||||
|
|
||||||
|
|
||||||
@st.cache_data(show_spinner="2D posities berekenen (kan even duren)…")
|
@st.cache_data(show_spinner="2D posities berekenen (kan even duren)…")
|
||||||
def load_positions(
|
def load_positions(
|
||||||
db_path: str, window_size: str = "quarterly"
|
db_path: str, window_size: str = "quarterly"
|
||||||
@@ -210,6 +250,9 @@ def load_positions(
|
|||||||
"classify_axes failed; using generic axis labels"
|
"classify_axes failed; using generic axis labels"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if _should_swap_axes(axis_def):
|
||||||
|
positions_by_window, axis_def = _swap_axes(positions_by_window, axis_def)
|
||||||
|
|
||||||
# Filter displayed windows by window_size AFTER PCA computation.
|
# Filter displayed windows by window_size AFTER PCA computation.
|
||||||
if window_size == "annual":
|
if window_size == "annual":
|
||||||
annual_keys = set(w for w in all_available if "-Q" not in w)
|
annual_keys = set(w for w in all_available if "-Q" not in w)
|
||||||
|
|||||||
@@ -111,6 +111,21 @@ except Exception:
|
|||||||
_go.Figure = lambda *a, **kw: None
|
_go.Figure = lambda *a, **kw: None
|
||||||
sys.modules["plotly.graph_objects"] = _go
|
sys.modules["plotly.graph_objects"] = _go
|
||||||
|
|
||||||
|
# Provide a minimal streamlit stub so explorer imports succeed in the test env
|
||||||
|
try:
|
||||||
|
import streamlit as _st # noqa: F401
|
||||||
|
except Exception:
|
||||||
|
_st_stub = types.ModuleType("streamlit")
|
||||||
|
_st_stub.cache_data = lambda **kw: lambda f: f
|
||||||
|
_st_stub.plotly_chart = lambda *a, **kw: None
|
||||||
|
_st_stub.markdown = lambda *a, **kw: None
|
||||||
|
_st_stub.caption = lambda *a, **kw: None
|
||||||
|
_st_stub.error = lambda *a, **kw: None
|
||||||
|
_st_stub.warning = lambda *a, **kw: None
|
||||||
|
_st_stub.info = lambda *a, **kw: None
|
||||||
|
_st_stub.write = lambda *a, **kw: None
|
||||||
|
sys.modules["streamlit"] = _st_stub
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@@ -654,3 +669,58 @@ def test_classify_from_titles_low_confidence():
|
|||||||
label, confidence = _classify_from_titles(titles)
|
label, confidence = _classify_from_titles(titles)
|
||||||
assert label is None
|
assert label is None
|
||||||
assert confidence < 0.4
|
assert confidence < 0.4
|
||||||
|
|
||||||
|
|
||||||
|
def test_axis_swap_when_y_is_left_right():
|
||||||
|
"""When y_label is 'Links–Rechts' and x_label is not, positions must be swapped."""
|
||||||
|
from explorer import _swap_axes
|
||||||
|
|
||||||
|
positions_by_window = {
|
||||||
|
"2023": {
|
||||||
|
"VVD": (0.5, 0.8),
|
||||||
|
"PvdA": (-0.3, -0.6),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
axis_def = {
|
||||||
|
"x_label": "Progressief\u2013Conservatief",
|
||||||
|
"y_label": "Links\u2013Rechts",
|
||||||
|
"x_quality": {"2023": 0.7},
|
||||||
|
"y_quality": {"2023": 0.8},
|
||||||
|
"x_interpretation": {"2023": "prog interpretation"},
|
||||||
|
"y_interpretation": {"2023": "lr interpretation"},
|
||||||
|
"x_top_motions": {"2023": {"+": [], "-": []}},
|
||||||
|
"y_top_motions": {"2023": {"+": [], "-": []}},
|
||||||
|
"x_label_confidence": {"2023": 0.5},
|
||||||
|
"y_label_confidence": {"2023": 0.7},
|
||||||
|
}
|
||||||
|
|
||||||
|
new_pos, new_ax = _swap_axes(positions_by_window, axis_def)
|
||||||
|
|
||||||
|
# Positions swapped: (x, y) → (y, x)
|
||||||
|
assert new_pos["2023"]["VVD"] == (0.8, 0.5)
|
||||||
|
assert new_pos["2023"]["PvdA"] == (-0.6, -0.3)
|
||||||
|
|
||||||
|
# Labels swapped
|
||||||
|
assert new_ax["x_label"] == "Links\u2013Rechts"
|
||||||
|
assert new_ax["y_label"] == "Progressief\u2013Conservatief"
|
||||||
|
|
||||||
|
# Quality swapped
|
||||||
|
assert new_ax["x_quality"] == {"2023": 0.8}
|
||||||
|
assert new_ax["y_quality"] == {"2023": 0.7}
|
||||||
|
|
||||||
|
|
||||||
|
def test_axis_swap_not_applied_when_x_is_left_right():
|
||||||
|
"""When x_label is already 'Links–Rechts', no swap should occur."""
|
||||||
|
from explorer import _should_swap_axes
|
||||||
|
|
||||||
|
axis_def = {
|
||||||
|
"x_label": "Links\u2013Rechts",
|
||||||
|
"y_label": "Progressief\u2013Conservatief",
|
||||||
|
}
|
||||||
|
assert _should_swap_axes(axis_def) is False
|
||||||
|
|
||||||
|
axis_def2 = {
|
||||||
|
"x_label": "Links\u2013Rechts",
|
||||||
|
"y_label": "Links\u2013Rechts", # both LR — no swap
|
||||||
|
}
|
||||||
|
assert _should_swap_axes(axis_def2) is False
|
||||||
|
|||||||
Reference in New Issue
Block a user