-
Notifications
You must be signed in to change notification settings - Fork 0
Description
User Request
[Bug]: Error while creating inset axes using mpl_toolkits.axes_grid1.inset_locator.inset_axes
Bug summary:
Unable to create the inset axes in a plot using the code (following the first example on the website as posted here).
Code for reproduction:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, (ax, ax2) = plt.subplots(1, 2, figsize=[5.5, 2.8])
axins = inset_axes(ax, width=1.3, height=0.9)
plt.show()Actual outcome (stack trace excerpt):
AttributeError: 'NoneType' object has no attribute '_get_renderer'
... at matplotlib/offsetbox.py:399 in OffsetBox.get_window_extent
... called from mpl_toolkits/axes_grid1/inset_locator.py:73 in AnchoredLocatorBase.__call__
... triggered via matplotlib/_tight_bbox.py during inline backend bbox_inches='tight'Expected outcome:
An empty box towards the top right of the first subplot (with axes ax), as shown in the demo.
Environment:
- OS: Arch linux: 6.4.2-arch1-1
- Matplotlib Version: 3.7.2
- Backend: module://matplotlib_inline.backend_inline
- Python: 3.8.17
- Jupyter lab: 3.6.5
- Installation: conda
Notes:
- Inline backend applies
bbox="tight"to figure display. - The axes created by
axes_grid1.insetlocator.inset_axesare not compatible with tight layout; however,get_window_extentshould still work.
Task reference: ID 284
Researcher Specification (Emerson Gray)
Overview:
Creating inset axes via mpl_toolkits.axes_grid1.inset_locator.inset_axes fails under IPython inline backend when bbox="tight" is applied implicitly. The failure occurs because AnchoredLocatorBase.__call__ forwards renderer=None to OffsetBox.get_window_extent, which then tries to use self.figure._get_renderer(). Since the locator object is not attached to a figure (self.figure is None), this raises AttributeError.
Minimal fix: Ensure AnchoredLocatorBase.__call__ obtains a valid renderer from the parent Axes’ figure when the passed renderer is None.
Files and exact change:
- Target file:
lib/mpl_toolkits/axes_grid1/inset_locator.py
Patch diff chunk:
*** Begin Patch
*** Update File: lib/mpl_toolkits/axes_grid1/inset_locator.py
@@
class AnchoredLocatorBase(AnchoredOffsetbox):
@@
def draw(self, renderer):
raise RuntimeError("No draw method should be called")
def __call__(self, ax, renderer):
+ # Some callers (e.g., tight bbox adjustment or inset axes setup)
+ # invoke locators with renderer=None. OffsetBox.get_window_extent
+ # will fallback to self.figure._get_renderer() if renderer is None,
+ # but this locator is not attached to a Figure (self.figure is None).
+ # Ensure a valid renderer is used.
+ if renderer is None:
+ renderer = ax.figure._get_renderer()
self.axes = ax
bbox = self.get_window_extent(renderer)
px, py = self.get_offset(bbox.width, bbox.height, 0, 0, renderer)
bbox_canvas = Bbox.from_bounds(px, py, bbox.width, bbox.height)
tr = ax.figure.transSubfigure.inverted()
return TransformedBbox(bbox_canvas, tr)
*** End PatchCompatibility and behavior:
- Inline backend with implicit
bbox_inches='tight': fixed. - Non-inline backends and non-tight usage: unaffected.
- Other
axes_grid1locators benefit similarly. - We do not attach the locator to the figure; passing a valid renderer is sufficient.
Testing plan:
- Manual repro (pre-fix failure, post-fix success):
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(5.5, 2.8))
axins = inset_axes(ax, width=1.3, height=0.9)
fig.savefig('out.png', bbox_inches='tight')- Unit test proposal: Add a smoketest to
lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.pythat creates an inset axes and saves withbbox_inches='tight'to ensure no exception.
Edge cases:
tight_layout: existing warnings unchanged; fix prevents error.constrained_layout: unaffected; the renderer derivation is safe.
Reason:
Prevent OffsetBox.get_window_extent from using self.figure when the locator is not attached to a figure, avoiding AttributeError during inline backend tight bbox processing.