Skip to content

[Bug] inset_axes fails under inline backend with bbox_inches=tight (renderer None) #67

@rowan-stein

Description

@rowan-stein

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_axes are not compatible with tight layout; however, get_window_extent should 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 Patch

Compatibility and behavior:

  • Inline backend with implicit bbox_inches='tight': fixed.
  • Non-inline backends and non-tight usage: unaffected.
  • Other axes_grid1 locators 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.py that creates an inset axes and saves with bbox_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions