From 9628a1923d5bcfa0bddb66e7e361c554781dd35f Mon Sep 17 00:00:00 2001 From: Akhil Gopu Date: Sat, 19 Sep 2026 17:23:03 -0500 Subject: [PATCH 1/2] FIX: Lock axes bounds in plot_joint to prevent shrinking on cursor motion (#14317) The ConnectionPatch lines between the time series and topomaps are not clipped to the timeseries axes, so matplotlib constrained layout counts their extent in the axes margins on every draw. The measured margin grows monotonically, shrinking the axes to zero on each redraw (which cursor motion triggers). Mark the connection lines as out-of-layout (set_in_layout(False)) so the layout ignores them. --- doc/changes/dev/14319.bugfix.rst | 1 + doc/changes/names.inc | 1 + mne/viz/evoked.py | 4 ++++ mne/viz/tests/test_topo.py | 14 +++++++++++++- 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 doc/changes/dev/14319.bugfix.rst diff --git a/doc/changes/dev/14319.bugfix.rst b/doc/changes/dev/14319.bugfix.rst new file mode 100644 index 00000000000..91191e9a80e --- /dev/null +++ b/doc/changes/dev/14319.bugfix.rst @@ -0,0 +1 @@ +Fix :meth:`mne.Evoked.plot_joint` interactive plots, where the axes shrank to zero when moving the cursor over the figure (the connection lines between the time series and topomaps were inflating the constrained layout, shrinking the axes on every redraw), by `Akhil Gopu`_. \ No newline at end of file diff --git a/doc/changes/names.inc b/doc/changes/names.inc index 2c6f8fa77cd..e73278e2258 100644 --- a/doc/changes/names.inc +++ b/doc/changes/names.inc @@ -5,6 +5,7 @@ .. _Adeline Fecker: https://github.com/adelinefecker .. _Adina Wagner: https://github.com/adswa .. _Adonay Nunes: https://github.com/AdoNunes +.. _Akhil Gopu: https://github.com/AkhilG-exe .. _Akhilesh S. Yadav: https://github.com/YadavAkhileshh .. _Akshay: https://github.com/Akshay0724 .. _Alan Leggitt: https://github.com/leggitta diff --git a/mne/viz/evoked.py b/mne/viz/evoked.py index 5707da73ec2..4dbddab83e3 100644 --- a/mne/viz/evoked.py +++ b/mne/viz/evoked.py @@ -2372,6 +2372,10 @@ def plot_evoked_joint( zorder=1, clip_on=False, ) + # The lines intentionally poke outside of `ts_ax`, so keep them out of the + # layout bbox reckoning; otherwise constrained layout grows the margin on + # every redraw, shrinking the axes to nothing (see #14317). + con.set_in_layout(False) ts_ax.add_artist(con) # mark times in time series plot diff --git a/mne/viz/tests/test_topo.py b/mne/viz/tests/test_topo.py index 24439626764..50d84097676 100644 --- a/mne/viz/tests/test_topo.py +++ b/mne/viz/tests/test_topo.py @@ -79,7 +79,19 @@ def _get_epochs_delayed_ssp(): def test_plot_joint(): """Test joint plot.""" evoked = _get_epochs().average() - evoked.plot_joint(ts_args=dict(time_unit="s"), topomap_args=dict(time_unit="s")) + fig = evoked.plot_joint( + ts_args=dict(time_unit="s"), topomap_args=dict(time_unit="s") + ) + # regression test for gh-14317: repeated redraws (e.g. when moving the cursor + # over the interactive figure) must not shrink the axes + figs = fig if isinstance(fig, list) else [fig] + for fig_ in figs: + ax = max(fig_.axes, key=lambda a: a.get_position().height) + fig_.canvas.draw() + pos = ax.get_position().bounds + for _ in range(5): + fig_.canvas.draw() + assert np.allclose(pos, ax.get_position().bounds) def return_inds(d): # to test function kwarg to zorder arg of evoked.plot return list(range(d.shape[0])) From 301635adbe3ad7fc235613888dc4680bd85c75e4 Mon Sep 17 00:00:00 2001 From: Akhil Gopu <137303278+AkhilG-exe@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:31:36 +0000 Subject: [PATCH 2/2] test: clean up plot_joint test by isolating sensor type and removing list logic --- mne/viz/tests/test_topo.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/mne/viz/tests/test_topo.py b/mne/viz/tests/test_topo.py index 50d84097676..64705a61c80 100644 --- a/mne/viz/tests/test_topo.py +++ b/mne/viz/tests/test_topo.py @@ -80,18 +80,14 @@ def test_plot_joint(): """Test joint plot.""" evoked = _get_epochs().average() fig = evoked.plot_joint( - ts_args=dict(time_unit="s"), topomap_args=dict(time_unit="s") + picks="grad", ts_args=dict(time_unit="s"), topomap_args=dict(time_unit="s") ) - # regression test for gh-14317: repeated redraws (e.g. when moving the cursor - # over the interactive figure) must not shrink the axes - figs = fig if isinstance(fig, list) else [fig] - for fig_ in figs: - ax = max(fig_.axes, key=lambda a: a.get_position().height) - fig_.canvas.draw() - pos = ax.get_position().bounds - for _ in range(5): - fig_.canvas.draw() - assert np.allclose(pos, ax.get_position().bounds) + ax = max(fig.axes, key=lambda a: a.get_position().height) + fig.canvas.draw() + pos = ax.get_position().bounds + for _ in range(5): + fig.canvas.draw() + assert np.allclose(pos, ax.get_position().bounds) def return_inds(d): # to test function kwarg to zorder arg of evoked.plot return list(range(d.shape[0]))