Repository navigation
[Bug]: Inset Axes Failing for Geographic Plot #29720
Copy link
Copy link
Closed
Milestone
Description
Bug summary
I'm trying to work with the inset_axes() function for the first time, using geographic data plotted with matplotlib and CartoPy, but have been running into a variety of issues. For simplicity's sake, point #3 on #29174 is a good representation of what I am trying to do (recreated below).
Code for reproduction
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
main_map_proj = ccrs.TransverseMercator()
pcarree = ccrs.PlateCarree()
fig, ax = plt.subplots(subplot_kw={"projection": main_map_proj})
ax.set_extent([-50, 15, 50, 70], crs=pcarree)
ax.add_feature(cfeature.OCEAN.with_scale('50m'), facecolor="lightsteelblue", zorder=1,
edgecolor="k", lw=0.2)
inset_ax = ax.inset_axes((0.05, 0.05, 0.3, 0.3), projection=pcarree)
inset_extent = [-44, -38, 63, 66]
inset_ax.set_extent(inset_extent, crs=pcarree)
inset_ax.add_feature(cfeature.OCEAN, facecolor="lightsteelblue", zorder=1)
ax.indicate_inset_zoom(inset_ax, edgecolor="black", transform=pcarree)
x = [inset_extent[0], inset_extent[1], inset_extent[1], inset_extent[0]]
y = [inset_extent[2], inset_extent[2], inset_extent[3], inset_extent[3]]
for a in ax, inset_ax:
a.scatter(x, y, c=range(4), s=150, transform=pcarree)
plt.show()Actual outcome
ValueError: Axes should be an instance of GeoAxes, got <class 'NoneType'>.
This is happening even though I am verifying that both ax and inset_ax are GeoAxes using type().
Expected outcome
Additional information
I am posting this in matplotlib as it seems to be more of an issue with the inset_axes() function rather than the GeoAxes class.
Full Stack Trace >
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File \personalpath\to\env\site-packages\IPython\core\formatters.py:402, in BaseFormatter.__call__(self, obj)
400 pass
401 else:
--> 402 return printer(obj)
403 # Finally look for special method names
404 method = get_real_method(obj, self.print_method)
File \personalpath\to\env\site-packages\IPython\core\pylabtools.py:170, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
167 from matplotlib.backend_bases import FigureCanvasBase
168 FigureCanvasBase(fig)
--> 170 fig.canvas.print_figure(bytes_io, **kw)
171 data = bytes_io.getvalue()
172 if fmt == 'svg':
File \personalpath\to\env\site-packages\matplotlib\backend_bases.py:2155, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
2152 # we do this instead of `self.figure.draw_without_rendering`
2153 # so that we can inject the orientation
2154 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2155 self.figure.draw(renderer)
2156 if bbox_inches:
2157 if bbox_inches == "tight":
File \personalpath\to\env\site-packages\matplotlib\artist.py:94, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
92 @wraps(draw)
93 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 94 result = draw(artist, renderer, *args, **kwargs)
95 if renderer._rasterizing:
96 renderer.stop_rasterizing()
File \personalpath\to\env\site-packages\matplotlib\artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
68 if artist.get_agg_filter() is not None:
69 renderer.start_filter()
---> 71 return draw(artist, renderer)
72 finally:
73 if artist.get_agg_filter() is not None:
File \personalpath\to\env\site-packages\matplotlib\figure.py:3257, in Figure.draw(self, renderer)
3254 # ValueError can occur when resizing a window.
3256 self.patch.draw(renderer)
-> 3257 mimage._draw_list_compositing_images(
3258 renderer, self, artists, self.suppressComposite)
3260 renderer.close_group('figure')
3261 finally:
File \personalpath\to\env\site-packages\matplotlib\image.py:134, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
132 if not_composite or not has_images:
133 for a in artists:
--> 134 a.draw(renderer)
135 else:
136 # Composite any adjacent images together
137 image_group = []
File \personalpath\to\env\site-packages\matplotlib\artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
68 if artist.get_agg_filter() is not None:
69 renderer.start_filter()
---> 71 return draw(artist, renderer)
72 finally:
73 if artist.get_agg_filter() is not None:
File \personalpath\to\env\site-packages\cartopy\mpl\geoaxes.py:524, in GeoAxes.draw(self, renderer, **kwargs)
519 self.imshow(img, extent=extent, origin=origin,
520 transform=factory.crs, *factory_args[1:],
521 **factory_kwargs)
522 self._done_img_factory = True
--> 524 return super().draw(renderer=renderer, **kwargs)
File \personalpath\to\env\site-packages\matplotlib\artist.py:71, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
68 if artist.get_agg_filter() is not None:
69 renderer.start_filter()
---> 71 return draw(artist, renderer)
72 finally:
73 if artist.get_agg_filter() is not None:
File \personalpath\to\env\site-packages\matplotlib\axes\_base.py:3210, in _AxesBase.draw(self, renderer)
3207 if artists_rasterized:
3208 _draw_rasterized(self.get_figure(root=True), artists_rasterized, renderer)
-> 3210 mimage._draw_list_compositing_images(
3211 renderer, self, artists, self.get_figure(root=True).suppressComposite)
3213 renderer.close_group('axes')
3214 self.stale = False
File \personalpath\to\env\site-packages\matplotlib\image.py:134, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
132 if not_composite or not has_images:
133 for a in artists:
--> 134 a.draw(renderer)
135 else:
136 # Composite any adjacent images together
137 image_group = []
File \personalpath\to\env\site-packages\matplotlib\artist.py:38, in _prevent_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
35 renderer.stop_rasterizing()
36 renderer._rasterizing = False
---> 38 return draw(artist, renderer, *args, **kwargs)
File \personalpath\to\env\site-packages\matplotlib\inset.py:230, in InsetIndicator.draw(self, renderer)
226 conn_same_style = []
228 # Figure out which connectors have the same style as the box, so should
229 # be drawn as a single path.
--> 230 for conn in self.connectors or []:
231 if conn.get_visible():
232 drawn = False
File \personalpath\to\env\site-packages\matplotlib\inset.py:221, in InsetIndicator.connectors(self)
219 if self._auto_update_bounds:
220 self._rectangle.set_bounds(self._bounds_from_inset_ax())
--> 221 self._update_connectors()
222 return tuple(self._connectors)
File \personalpath\to\env\site-packages\matplotlib\inset.py:192, in InsetIndicator._update_connectors(self)
189 pos = self._inset_ax.get_position()
190 bboxins = pos.transformed(self.get_figure(root=False).transSubfigure)
191 rectbbox = transforms.Bbox.from_bounds(x, y, width, height).transformed(
--> 192 self._rectangle.get_transform())
193 x0 = rectbbox.x0 < bboxins.x0
194 x1 = rectbbox.x1 < bboxins.x1
File \personalpath\to\env\site-packages\matplotlib\patches.py:309, in Patch.get_transform(self)
307 def get_transform(self):
308 """Return the `~.transforms.Transform` applied to the `Patch`."""
--> 309 return self.get_patch_transform() + artist.Artist.get_transform(self)
File \personalpath\to\env\site-packages\matplotlib\artist.py:454, in Artist.get_transform(self)
451 self._transform = IdentityTransform()
452 elif (not isinstance(self._transform, Transform)
453 and hasattr(self._transform, '_as_mpl_transform')):
--> 454 self._transform = self._transform._as_mpl_transform(self.axes)
455 return self._transform
File \personalpath\to\env\site-packages\cartopy\crs.py:267, in CRS._as_mpl_transform(self, axes)
265 import cartopy.mpl.geoaxes as geoaxes
266 if not isinstance(axes, geoaxes.GeoAxes):
--> 267 raise ValueError(
268 'Axes should be an instance of GeoAxes, got %s' % type(axes)
269 )
270 return (
271 geoaxes.InterProjectionTransform(self, axes.projection) +
272 axes.transData
273 )
ValueError: Axes should be an instance of GeoAxes, got <class 'NoneType'>Operating system
No response
Matplotlib Version
3.10.1
Matplotlib Backend
No response
Python version
3.12.9
Jupyter version
No response
Installation
conda
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels

Activity
rcomer commentedon Mar 9, 2025
Hi @moss-xyz #29174 is included in the version 3.11 milestone so this will start working when that version is released. If you need the functionality earlier, you could try installing a nightly build.
rcomer commentedon Mar 9, 2025
In v3.10.1 you can get around this specific error with
However then you fall foul of the 2nd point in #29174. I do not see a way around that because the method that sets the transform on the connector gets called within
draw, so a transform set manually will be overwritten.moss-xyz commentedon Mar 9, 2025
Ah beaut thanks for the quick response @rcomer - I'm not in any rush, so I'll wait for the 3.11 build to be released (or install a nightly if I'm getting impatient).