diff --git a/doc/changes/dev/13855.bugfix.rst b/doc/changes/dev/13855.bugfix.rst new file mode 100644 index 00000000000..422409d9ec1 --- /dev/null +++ b/doc/changes/dev/13855.bugfix.rst @@ -0,0 +1,3 @@ +Fixed incorrect label orientation for nodes in the 0–90° polar range (the +12–3 o'clock quadrant) of the connectivity circle plot, by +:newcontrib:`Pavel Popov`. diff --git a/doc/changes/names.inc b/doc/changes/names.inc index ff8921c3e76..b87dbbfb04c 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -258,6 +258,7 @@ .. _Paul Pasler: https://github.com/ppasler .. _Paul Roujansky: https://github.com/paulroujansky .. _Pavel Navratil: https://github.com/navrpa13 +.. _Pavel Popov: https://github.com/paavalipopov .. _Peter Molfese: https://github.com/pmolfese .. _Phillip Alday: https://palday.bitbucket.io .. _Pierre Ablin: https://pierreablin.com diff --git a/mne/viz/circle.py b/mne/viz/circle.py index 67a47c0d5fd..21d8b2b2696 100644 --- a/mne/viz/circle.py +++ b/mne/viz/circle.py @@ -338,7 +338,9 @@ def _plot_connectivity_circle( # Draw node labels angles_deg = 180 * node_angles / np.pi for name, angle_rad, angle_deg in zip(node_names, node_angles, angles_deg): - if angle_deg >= 270: + if ( + angle_deg >= 270 or angle_deg < 90 + ): # [0, 90] and [270, 360] cover right half ha = "left" else: # Flip the label, so text is always upright diff --git a/mne/viz/tests/test_circle.py b/mne/viz/tests/test_circle.py index c5f3719746b..092a9cdd481 100644 --- a/mne/viz/tests/test_circle.py +++ b/mne/viz/tests/test_circle.py @@ -4,9 +4,11 @@ import matplotlib +import numpy as np import pytest from mne.viz import plot_channel_labels_circle +from mne.viz.circle import _plot_connectivity_circle @pytest.mark.filterwarnings( @@ -33,3 +35,50 @@ def test_plot_channel_labels_circle(): plot_channel_labels_circle( dict(brain=["big", "great", "smart"]), colors=dict(big="r", great="y") ) + + +def test_plot_connectivity_circle_label_orientation(): + """Labels in the 0-90 deg polar range (12-3 o'clock) must not be flipped. + + Regression test: previously the condition ``angle_deg >= 270`` missed the + [0, 90) range, incorrectly adding 180 degrees to those labels and setting + ha='right', which caused them to point inward instead of outward. + """ + # 9 nodes → uniform angles: 0, 40, 80, 120, 160, 200, 240, 280, 320 degrees. + # This guarantees coverage of all four quadrants, including the previously + # broken 0–90 range (nodes n0 at 0°, n1 at 40°, and n2 at 80°). + n_nodes = 9 + con = np.ones((n_nodes, n_nodes)) + con[::2, ::2] = 0 # add some zeros to avoid 0-div in normalization + node_names = [f"n{i}" for i in range(n_nodes)] + + fig, ax = _plot_connectivity_circle(con, node_names, show=False) + + texts = [c for c in ax.get_children() if isinstance(c, matplotlib.text.Text)] + label_texts = {t.get_text(): t for t in texts if t.get_text() in node_names} + + # node_angles defaults to np.linspace(0, 2*pi, n_nodes, endpoint=False) + angles_deg = np.linspace(0, 360, n_nodes, endpoint=False) + + assert len(label_texts) == n_nodes, ( + f"Expected {n_nodes} label texts, found {len(label_texts)}" + ) + + for angle, name in zip(angles_deg, node_names, strict=True): + t = label_texts[name] + ha = t.get_ha() + + if angle >= 270 or angle < 90: + # Right half of circle: text must extend outward to the right. + # ha='left' anchors the left edge at the node, text goes rightward. + assert ha == "left", ( + f"Node '{name}' at {angle:.1f}° (right half) should have " + f"ha='left', got '{ha}'" + ) + else: + # Left half: text is flipped 180° so it stays upright; ha='right' + # anchors the right edge at the node, text extends leftward/outward. + assert ha == "right", ( + f"Node '{name}' at {angle:.1f}° (left half) should have " + f"ha='right', got '{ha}'" + )