Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Bug]: Inset Axes Failing for Geographic Plot #29720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
moss-xyz opened this issue Mar 9, 2025 · 3 comments
Closed

[Bug]: Inset Axes Failing for Geographic Plot #29720

moss-xyz opened this issue Mar 9, 2025 · 3 comments
Milestone

Comments

@moss-xyz
Copy link

moss-xyz commented Mar 9, 2025

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

Image

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

@rcomer
Copy link
Member

rcomer commented 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
Copy link
Member

rcomer commented Mar 9, 2025

In v3.10.1 you can get around this specific error with

ins = ax.indicate_inset_zoom(inset_ax, edgecolor="black", transform=pcarree)
ins.rectangle.axes =  ax

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
Copy link
Author

moss-xyz commented 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).

@moss-xyz moss-xyz closed this as completed Mar 9, 2025
@rcomer rcomer added this to the v3.11.0 milestone Mar 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants