The problem here is that the inline backend (which is provided by IPython) is applying the bbox="tight" argument to savefig (well, print_figure, but same idea)
The axes created by axes_grid1.insetlocator.inset_axes are not compatible with tight_layout.
Now, when you just call fig.tight_layout(), you get a warning but it doesn't raise, so there may be a way of at least detecting and warning rather than erroring, but not sure there is a good way of just not applying the tight layout with this backend...
Workarounds include:
Use a backend other than inline (e.g. ipympl which is designed to give an interactive experience in Jupyter (%matplotlib widget to enable if installed)
Use Axes.inset_axes instead of the mpl_toolkits method. This does not have the automatic locating of the axes_grid1 version, but may be sufficient for your use, just a little more manual to place the axes.
The axes created by axes_grid1.insetlocator.inset_axes are not compatible with tight_layout.
Agreed - but it seems that get_window_extent should work. For some reason we aren't plumbing the figure into the inset_axes. Recently, we stopped passing the renderer by default to get_window_extent because artists are supposed to know their figure. However, apparently not for this artist. This probably bisects to #22745
Unfortunately it is still more complicated than simply setting figure on the offending object (which is the mpl_toolkits.axes_grid1.inset_locator.AnchoredSizeLocator, which inherits
(transitively) from OffsetBox)
While that gets a bit further, the get_offset method is still called, and that needs a renderer with a points_to_pixels method...
I can update __call__ to add the logic to get the renderer from the figure if it is passed in as None, and then it seems to work, but still a little skeptical about whether a better arrangement of plumbing these bits together exists.
Setting the figure manually feels a little iffy, but so does calling add_artist, honestly, for something that is not really drawn or used...
And the renderer logic is also a little iffy as the figure is often still not set, so perhaps resolving that earlier in the call stack is a better idea?
But regardless, it works, at least for axes_grid1.inset_locator.inset_axes as the entry point (I suspect if you tried to use the lower level things, it would still be easy to get tripped up, so if there was a path that would resolve that, that would be ideal, just not sure what that actually looks like)
The above looks close, but I don't think you need to plumb the figure in? Just do renderer = ax.figure._get_renderer() since axes have to have figures?
Ah, yes, that does make sense... it was a path function where I added that first but still had another failure, but I now see that I'm pretty sure the fix for the second will also fix the first, rendering the first fix moot. (and then yes, the iffiness of setting figure of an artist that is not actually in the axes goes away if you just get the ax.figure's renderer.)
Activity
ksunden commented on Jul 11, 2023
The problem here is that the inline backend (which is provided by IPython) is applying the bbox="tight" argument to savefig (well, print_figure, but same idea)
The axes created by axes_grid1.insetlocator.inset_axes are not compatible with tight_layout.
Now, when you just call fig.tight_layout(), you get a warning but it doesn't raise, so there may be a way of at least detecting and warning rather than erroring, but not sure there is a good way of just not applying the tight layout with this backend...
Workarounds include:
jklymak commented on Jul 11, 2023
Agreed - but it seems that get_window_extent should work. For some reason we aren't plumbing the figure into the inset_axes. Recently, we stopped passing the renderer by default to get_window_extent because artists are supposed to know their figure. However, apparently not for this artist. This probably bisects to #22745
ksunden commented on Jul 11, 2023
Unfortunately it is still more complicated than simply setting figure on the offending object (which is the mpl_toolkits.axes_grid1.inset_locator.AnchoredSizeLocator, which inherits
(transitively) from OffsetBox)
While that gets a bit further, the get_offset method is still called, and that needs a renderer with a points_to_pixels method...
I can update __call__ to add the logic to get the renderer from the figure if it is passed in as None, and then it seems to work, but still a little skeptical about whether a better arrangement of plumbing these bits together exists.
Setting the figure manually feels a little iffy, but so does calling add_artist, honestly, for something that is not really drawn or used...
And the renderer logic is also a little iffy as the figure is often still not set, so perhaps resolving that earlier in the call stack is a better idea?
But regardless, it works, at least for axes_grid1.inset_locator.inset_axes as the entry point (I suspect if you tried to use the lower level things, it would still be easy to get tripped up, so if there was a path that would resolve that, that would be ideal, just not sure what that actually looks like)
jklymak commented on Jul 11, 2023
The above looks close, but I don't think you need to plumb the figure in? Just do renderer = ax.figure._get_renderer() since axes have to have figures?
ksunden commented on Jul 12, 2023
Ah, yes, that does make sense... it was a path function where I added that first but still had another failure, but I now see that I'm pretty sure the fix for the second will also fix the first, rendering the first fix moot. (and then yes, the iffiness of setting figure of an artist that is not actually in the axes goes away if you just get the ax.figure's renderer.)
Revert "fix(mpl): remove `bbox_inches=tight` from plt.savefig"
BrunoGomesCoelho commented on Aug 8, 2023
Just came across the original bug but with zoomed_inset_axes instead of inset_axes; Does the PR #26291 also solve the issue with that? Thanks!
@ksunden
ksunden commented on Aug 8, 2023
Yes, since the fix was applied to the base class that both normal and zoomed inset_axes refer to subclasses of, it should fix that as well.
BrunoGomesCoelho commented on Aug 9, 2023
Great, thanks!
Temporarily frozen matplotlib=3.7.1 to avoid bug matplotlib/matplotli…
Pin matplotlib to prevent bug
data and analysis for PRL 130, 207001 (2023) (#5)
ENV: Avoid matplotlib 3.7.2 (matplotlib/matplotlib#26287)
rcomer commented on Sep 23, 2023
Looks like I broke this one with #25499. Sorry about that.