cartopy
cartopy copied to clipboard
Shared axes broken on pan
Description
It looks like when sharing axes, and you pan one of them interactively, the other(s) move around. This is a consequence of our new (0.18) background patch and spine, which need to be updated with the data limits. When axes are shared, the original axes gets notified of data limits properly and re-clips as appropriate. Unfortunately, this not happening for any axes sharing the original--this is because when matplotlib syncs limit changes to shared axes, it explicitly says not to emit any events for those axes. 😞
Code to reproduce
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
fig = plt.figure(figsize=[10, 5])
ax1 = fig.add_subplot(1, 2, 1, projection=ccrs.SouthPolarStereo())
ax2 = fig.add_subplot(1, 2, 2, projection=ccrs.SouthPolarStereo(), sharex=ax1)
# Limit the map to -60 degrees latitude and below.
ax1.set_extent([-180, 180, -90, -60], ccrs.PlateCarree())
ax2.set_extent([-180, 180, -90, -60], ccrs.PlateCarree())
ax1.add_feature(cfeature.LAND)
ax2.add_feature(cfeature.LAND)
plt.pause(0.01)
ax1.set_xlim(-6180148.99567504, 619506.5641910564)
plt.draw()
plt.show()
Gives:

Originally mentioned in #1620. The reason I'm opening the issue is because I'm really not sure what the cleanest way to fix this is:
-
Notify shared axes in our existing event callback. This requires using matplotlib's private
_shared_x_axes
and_shared_y_axes
. Not wild about using private attributes, especially ones that have been refactored in the past. -
Override
set_xlim
andset_ylim
onGeoAxes
. Can just set the stale flags when the limits are set. Might not even need the event callback any more. Downside is needing to match matplotlib's extensive signature and docstring just so we can hook in. (If only there was some kind of event dispatch we could use...) Also need to deal with the fact that.patch
is not available whenset_[y|x]lim
is first called, so feels...unclean. -
~Hook in somehow when axes are first shared and register event handler on limit changes on the other axes~ I didn't even try this one, but it seems terrible.
-
?
I can confirm either (1) or (2) do solve the issue. Just not sure what's the least annoying. Thoughts @QuLogic ? Any other ideas?
Well, looks like get_shared_x_axes()
, etc. are functions on the latest matplotlib, not sure how far back they go...
It looks like this is the only issue/PR open on the 0.18.x milestone.
get_shared_x_axes() goes back quite far, at least 1.4.
Do you want to open a PR with that fix?
Sure, but it will be a week or two before I can get there.
any update on this? dynamically zooming shared axes seems to be still broken in cartopy 0.19.0
Any updates? It still exists in v0.20.1.
It doesn't look like it, there are a few suggestions above if you're interested in contributing this update @zxdawn.
I've implemented a crude fix that works nicely if the axes share the same projection in EOmaps (a library for interactive cartopy-maps I'm developing)
Here's a stripped-down version of what I'm using ... maybe it helps to come up with a proper solution:
class joinaxes:
def __init__(self):
self.joined_action = False
def __call__(self, ax1, ax2):
cbx1, cby1 = self._get_callbacks(ax1)
cbx2, cby2 = self._get_callbacks(ax2)
ax2.callbacks.connect("xlim_changed", cbx1)
ax2.callbacks.connect("ylim_changed", cby1)
ax1.callbacks.connect("xlim_changed", cbx2)
ax1.callbacks.connect("ylim_changed", cby2)
def _get_callbacks(self, ax):
def xlims_changed(event_ax):
if not self.joined_action:
self.joined_action = True
ax.set_xlim(event_ax.get_xlim())
self.joined_action = False
def ylims_changed(event_ax):
if not self.joined_action:
self.joined_action = True
ax.set_ylim(event_ax.get_ylim())
self.joined_action = False
return xlims_changed, ylims_changed
from cartopy import crs as ccrs
import matplotlib.pyplot as plt
f = plt.figure()
ax = f.add_subplot(211, projection=ccrs.PlateCarree())
ax.coastlines()
ax2 = f.add_subplot(212, projection=ccrs.PlateCarree())
ax2.coastlines()
joinaxes()(ax, ax2)
@raphaelquast's solution worked for me - thanks!
Is this fixed? The code from the OP now gives me
@rcomer I did a quick check but for me (on v0.22.0) the issue still persists... (It might be able that the figure is initialized properly, but interactive pan/zoom still does not work as expected)
Thanks @raphaelquast. What Matplotlib version do you have? I have mpl 3.8.2 and cannot reproduce what you see with either TkAgg or QtAgg. (My attempts to make a video failed so I can't post for comparison).
Turns out the screencast fix was very googlable. Here is what I'm seeing:
https://github.com/SciTools/cartopy/assets/10599679/b5d8adc1-55d7-4972-9be5-df45725f04ef
@rcomer Indeed, I was still at matplotlib 3.7.2 in the env I've used for checking... sorry for that... With mpl 3.8.2 I get the same as you so it really seems to be fixed!
Thanks for checking @raphaelquast! I'll close this one then. :tada:
Just a general note here because I think it's relevant... It might be a good idea to either issue a warning or completely disallow sharing axes with different projections... (or make sure to reproject limits prior to sharing ... but this might cause troubles especially for non-global projections)