How to easily add a sub_axes with proper position and size in matplotlib and cartopy?
Asked Answered
P

3

7

I want to add a 2nd axes at the top right corner of a 1st axes. After googling, I found two ways to do things like this: fig.add_axes(), and mpl_toolkits.axes_grid.inset_locator.inset_axes. But the fig.add_axes() doesn't accept transform arg. So the following code throws an error. So the position can't be under the parent axes coordinates but the figure coordinates.

import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
ax2 = fig.add_axes([0.8, 0, 0.2, 0.2], transform=ax.transAxes, projection=ccrs.PlateCarree()) 

And inset_axes() doesn't accept the projection arg, so I can't add ax2 as a cartopy geo-axes.

from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})

# The following line doesn't work
ax2 = inset_axes(ax, width='20%', height='20%', axes_kwargs={'projection': ccrs.PlateCarree()})
# Doesn't work neither:
ax2 = inset_axes(ax, width='20%', height='20%', projection=ccrs.PlateCarree())

I've asked the question at matplotlib issue. It seems the following code works well as long as it's not a cartopy axes.

import matplotlib as mpl
fig, ax = plt.subplots(1, 1)
box = mpl.transforms.Bbox.from_bounds(0.8, 0.8, 0.2, 0.2)
ax2 = fig.add_axes(fig.transFigure.inverted().transform_bbox(ax.transAxes.transform_bbox(box)))

Question:

How to easily add a sub_axes with proper position and size in matplotlib and cartopy?

As I understand, after ax.set_extend(), the size of axes will change. So maybe is there a way that some point of sub_axes (eg: top right corner of ax2) can be anchored at one fixed position of the parent_axes (eg: top right corner of ax1)?

Parceling answered 6/8, 2017 at 0:35 Comment(0)
N
9

As inset_axes() doesn't accept projection arg, the roundabout way is to use InsetPosition(). This way you can create an axes in the usual way (using projection), and then "link" both axes using InsetPosition(). The main advantage over using subplots or similar is that the inset position is fixed, you can resize the figure or change the main plot area and the inset will always be in the same place relative to the main axes. This was based on this answer: specific location for inset axes, just adding the cartopy way of doing things.

import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
from shapely.geometry.polygon import LinearRing

extent = [-60, -30, -40, -10]
lonmin, lonmax, latmin, latmax = extent

fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_extent(extent, crs=ccrs.PlateCarree())
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.COASTLINE)

# inset location relative to main plot (ax) in normalized units
inset_x = 1
inset_y = 1
inset_size = 0.2

ax2 = plt.axes([0, 0, 1, 1], projection=ccrs.Orthographic(
    central_latitude=(latmin + latmax) / 2,
    central_longitude=(lonmin + lonmax) / 2))
ax2.set_global()
ax2.add_feature(cfeature.LAND)
ax2.add_feature(cfeature.OCEAN)
ax2.add_feature(cfeature.COASTLINE)

ip = InsetPosition(ax, [inset_x - inset_size / 2,
                        inset_y - inset_size / 2,
                        inset_size,
                        inset_size])
ax2.set_axes_locator(ip)

nvert = 100
lons = np.r_[np.linspace(lonmin, lonmin, nvert),
             np.linspace(lonmin, lonmax, nvert),
             np.linspace(lonmax, lonmax, nvert)].tolist()
lats = np.r_[np.linspace(latmin, latmax, nvert),
             np.linspace(latmax, latmax, nvert),
             np.linspace(latmax, latmin, nvert)].tolist()

ring = LinearRing(list(zip(lons, lats)))
ax2.add_geometries([ring], ccrs.PlateCarree(),
                   facecolor='none', edgecolor='red', linewidth=0.75)

Inset example

Noninterference answered 10/12, 2018 at 19:5 Comment(0)
P
4

I may have figured something out.

According to the answer this question. I can get the position of both axes, then reposition the 2nd axes. The code was like:

import matplotlib.pyplot as plt
from cartopy import crs as ccrs

fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
ax2 = fig.add_axes([0.8, 0.8, 0.2, 0.2], projection=ccrs.PlateCarree())
ax.set_extent([100, 120, 20, 40])
ax.coastlines()
ax2.set_global()
ax2.coastlines()
ax2.stock_img()

def reposition():
    plt.draw()
    p1 = ax.get_position()
    p2 = ax2.get_position()
    ax2.set_position([p1.x1-p2.width, p1.y1-p2.height, p2.width, p2.height])

reposition()
plt.show()

The result is just what I want. enter image description here

Parceling answered 7/8, 2017 at 1:56 Comment(0)
C
0

Since Matplotlib v3.6 the Axes.inset_axes method accepts the position keyword. So this is now much simpler:

import matplotlib.pyplot as plt
import cartopy.crs as ccrs

fig, ax = plt.subplots(1, 1, subplot_kw={'projection': ccrs.PlateCarree()})
ax_ins = ax.inset_axes([0.8, 0.8, 0.2, 0.2], projection=ccrs.PlateCarree())

ax.coastlines()
ax_ins.coastlines()

plt.show()

enter image description here

Clericals answered 25/9 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.