You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was playing with @timhoffm's new example at #28546 and found that, with QtAgg, when I hit the "configure subplots" button the image subplots immediately move downwards.
Code for reproduction
importmatplotlib.pyplotaspltimportnumpyasnpfrommatplotlibimportcolorsnp.random.seed(19680801)
datasets= [
(i+1)/10*np.random.rand(10, 20)
foriinrange(4)
]
fig, axs=plt.subplots(2, 2)
fig.suptitle('Multiple images')
# create a single norm to be shared across all imagesnorm=colors.Normalize(vmin=np.max(datasets), vmax=np.min(datasets))
images= []
forax, datainzip(axs.flat, datasets):
images.append(ax.imshow(data, norm=norm))
fig.colorbar(images[0], ax=axs, orientation='horizontal', fraction=.1)
plt.show()
Actual outcome
Initially I get the expected plot
but when I hit the "Configure subplots" button, without changing any subplot settings I have
Expected outcome
Subplots don't move until I tell them!
Additional information
I have reproduced this back to v3.6.3 with QtAgg (I haven't tested further back). I cannot reproduce with TkAgg.
diff --git i/lib/matplotlib/backends/backend_qt.py w/lib/matplotlib/backends/backend_qt.py
index 9089e982ce..d3cbc349ea 100644
--- i/lib/matplotlib/backends/backend_qt.py+++ w/lib/matplotlib/backends/backend_qt.py@@ -904,12 +904,11 @@ class SubplotToolQt(QtWidgets.QDialog):
self._figure = targetfig
self._defaults = {}
self._export_values_dialog = None
- self.update_from_current_subplotpars()
def update_from_current_subplotpars(self):
self._defaults = {spinbox: getattr(self._figure.subplotpars, name)
for name, spinbox in self._spinboxes.items()}
- self._reset() # Set spinbox current values without triggering signals.+ self._reset(subplots_adjust=False) # Set spinbox current values without triggering signals.
def _export_values(self):
# Explicitly round to 3 decimals (which is also the spinbox precision)
@@ -950,13 +949,14 @@ class SubplotToolQt(QtWidgets.QDialog):
spinbox.blockSignals(False)
self._figure.canvas.draw_idle()
- def _reset(self):+ def _reset(self, *, subplots_adjust=True):
for spinbox, value in self._defaults.items():
spinbox.setRange(0, 1)
spinbox.blockSignals(True)
spinbox.setValue(value)
spinbox.blockSignals(False)
- self._on_value_changed()+ if subplots_adjust:+ self._on_value_changed()
class ToolbarQt(ToolContainerBase, QtWidgets.QToolBar):
but that is really papering over a more serious issue (present for all backends), which is that the slider values don't actually match what's necessary to position the axes where desired. Indeed, if one slightly changes any of the slider (or, on qt, spinbox) values and then bring them back to the original value, then the axes again get messed up as in the original report here (whereas we would want such an action to not move the axes at all).
So I think the proper fix is really to ensure that changing a subplot parameter and then bringing it back to the same value (whether via a slider on non-qt backends, or via a spinbox on qt) restores the original axes layout.
I looked a bit more into this (sorry for the slightly rambling writeup below). What happens is that when passing multiple axes as the ax kwarg of colorbar() (axes from which space will be stolen), then the colorbar will be created using make_axes, not make_axes_gridspec, and the original axes get repositioned in absolute (figure fraction) coordinates, overriding the subplot-based positioning. As a result, when configure_subplots tries to reposition them using subplotparams, they end up in the wrong position because they were never correctly associated with a nested SubplotSpec (which is what happens in the single axes case).
I suspect it should be possible to still use make_axes_gridspec in that case, but this requires carefully working out what is the common SubplotSpec that is the union of the SubplotSpecs for each axes from which space is stolen, and then constructing the correct nested subgridspec from that from that...
I suspect it should be possible to still use make_axes_gridspec in that case...
I think one could do that. I question adding that complexity to the code in service of making the subplots adjustable post-facto. If one is using the sliders, one can just slide "bottom" until it looks good again.
If someone wanted to add code complexity, I'd have a different slider tool for constrained layout that adjusts the CL parameters instead of forcing the figure to drop CL. EDIT: we should actually disable that popup if the figure is on CL, because it doesn't do anything.
Yes, I was thinking about that... probably easiest is to set a message in the toolbar; otherwise one needs to pop a small figure with the relevant figtext() (or even rely on gui-specific methods to raise real error message windows)...
or even rely on gui-specific methods to raise real error message windows
At least for the Qt backend change that's straight forward. One could also add a popup_error() function to backends. The default would then only write to stderr, but backends could override this and use the GUI-specific messages.
The problem is that one may call set_layout_engine() and change that state, but the toolbar won't be notified about that.
Sure but that is really mixing metaphors. If someone knows how to switch layout engine midstream it seems less likely they would be confused about this.
Activity
rcomer commentedon Jul 12, 2024
I wanted to check how this would behave without the colorbar but got #28555.
rcomer commentedon Jul 12, 2024
If I leave out the norm, the problem is the same. If I leave out the norm and the colorbar there is no problem.
anntzer commentedon Jul 10, 2025
One can "fix" the issue with something like
but that is really papering over a more serious issue (present for all backends), which is that the slider values don't actually match what's necessary to position the axes where desired. Indeed, if one slightly changes any of the slider (or, on qt, spinbox) values and then bring them back to the original value, then the axes again get messed up as in the original report here (whereas we would want such an action to not move the axes at all).
So I think the proper fix is really to ensure that changing a subplot parameter and then bringing it back to the same value (whether via a slider on non-qt backends, or via a spinbox on qt) restores the original axes layout.
anntzer commentedon Jul 28, 2025
I looked a bit more into this (sorry for the slightly rambling writeup below). What happens is that when passing multiple axes as the ax kwarg of colorbar() (axes from which space will be stolen), then the colorbar will be created using make_axes, not make_axes_gridspec, and the original axes get repositioned in absolute (figure fraction) coordinates, overriding the subplot-based positioning. As a result, when configure_subplots tries to reposition them using subplotparams, they end up in the wrong position because they were never correctly associated with a nested SubplotSpec (which is what happens in the single axes case).
I suspect it should be possible to still use make_axes_gridspec in that case, but this requires carefully working out what is the common SubplotSpec that is the union of the SubplotSpecs for each axes from which space is stolen, and then constructing the correct nested subgridspec from that from that...
jklymak commentedon Jul 28, 2025
I think one could do that. I question adding that complexity to the code in service of making the subplots adjustable post-facto. If one is using the sliders, one can just slide "bottom" until it looks good again.
If someone wanted to add code complexity, I'd have a different slider tool for constrained layout that adjusts the CL parameters instead of forcing the figure to drop CL. EDIT: we should actually disable that popup if the figure is on CL, because it doesn't do anything.
anntzer commentedon Jul 28, 2025
is perhaps a bit brutal but perhaps a good start?
timhoffm commentedon Jul 29, 2025
An error message would be more user friendly rather than silently doing nothing.
anntzer commentedon Jul 29, 2025
Yes, I was thinking about that... probably easiest is to set a message in the toolbar; otherwise one needs to pop a small figure with the relevant figtext() (or even rely on gui-specific methods to raise real error message windows)...
jklymak commentedon Jul 29, 2025
Or just grey the button out so it's obvious it won't work? Maybe hard to keep checking if it should be active or not
anntzer commentedon Jul 29, 2025
The problem is that one may call set_layout_engine() and change that state, but the toolbar won't be notified about that.
timhoffm commentedon Jul 29, 2025
At least for the Qt backend change that's straight forward. One could also add a
popup_error()function to backends. The default would then only write to stderr, but backends could override this and use the GUI-specific messages.jklymak commentedon Jul 29, 2025
Sure but that is really mixing metaphors. If someone knows how to switch layout engine midstream it seems less likely they would be confused about this.
anntzer commentedon Jul 29, 2025
I'd probably just stick with setting an error message in the statusbar as the simplest way forward, we can always improve things later...