Skip to content

Commit

Permalink
Merge pull request #2252 from rcomer/gridliner-no-destroy
Browse files Browse the repository at this point in the history
Gridliner: don't destroy and recreate artists
  • Loading branch information
greglucas authored Oct 6, 2023
2 parents 515991d + 8fd21a3 commit 93dd3b0
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 17 deletions.
74 changes: 57 additions & 17 deletions lib/cartopy/mpl/gridliner.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ def __init__(self, axes, crs, draw_labels=False, xlocator=None,
self.yline_artists = []

# List of all labels (Label objects)
self._all_labels = []

# List of active labels (used in current draw)
self._labels = []

# Draw status
Expand Down Expand Up @@ -609,6 +612,25 @@ def _draw_this_label(self, xylabel, loc):

return True

def _generate_labels(self):
"""
A generator to yield as many labels as needed, re-using existing ones
where possible.
"""
for label in self._all_labels:
yield label

while True:
# Ran out of existing labels. Create some empty ones.
new_artist = matplotlib.text.Text()
new_artist.set_figure(self.axes.figure)
new_artist.axes = self.axes

new_label = Label(new_artist, None, None, None)
self._all_labels.append(new_label)

yield new_label

def _draw_gridliner(self, nx=None, ny=None, renderer=None):
"""Create Artists for all visible elements and add to our Axes.
Expand All @@ -626,11 +648,6 @@ def _draw_gridliner(self, nx=None, ny=None, renderer=None):
return
self._drawn = True

# Clear lists of child artists
self.xline_artists.clear()
self.yline_artists.clear()
self._labels.clear()

# Inits
lon_lim, lat_lim = self._axes_domain(nx=nx, ny=ny)
transform = self._crs_transform()
Expand Down Expand Up @@ -687,9 +704,16 @@ def _draw_gridliner(self, nx=None, ny=None, renderer=None):
isinstance(crs, _RectangularProjection) and
abs(np.diff(lon_lim)) == abs(np.diff(crs.x_limits))):
nx -= 1
lon_lc = mcollections.LineCollection(lon_lines,
**collection_kwargs)
self.xline_artists.append(lon_lc)

if self.xline_artists:
# Update existing collection.
lon_lc, = self.xline_artists
lon_lc.set(segments=lon_lines, **collection_kwargs)
else:
# Create new collection.
lon_lc = mcollections.LineCollection(lon_lines,
**collection_kwargs)
self.xline_artists.append(lon_lc)

# Parallels
lon_min, lon_max = lon_lim
Expand All @@ -701,14 +725,22 @@ def _draw_gridliner(self, nx=None, ny=None, renderer=None):
n_steps)[np.newaxis, :]
lat_lines[:, :, 1] = np.array(lat_ticks)[:, np.newaxis]
if self.ylines:
lat_lc = mcollections.LineCollection(lat_lines,
**collection_kwargs)
self.yline_artists.append(lat_lc)
if self.yline_artists:
# Update existing collection.
lat_lc, = self.yline_artists
lat_lc.set(segments=lat_lines, **collection_kwargs)
else:
lat_lc = mcollections.LineCollection(lat_lines,
**collection_kwargs)
self.yline_artists.append(lat_lc)

#################
# Label drawing #
#################

# Clear drawn labels
self._labels.clear()

if not any((self.left_labels, self.right_labels, self.bottom_labels,
self.top_labels, self.inline_labels, self.geo_labels)):
return
Expand Down Expand Up @@ -770,6 +802,9 @@ def update_artist(artist, renderer):
crs_transform = self._crs_transform().transform
inverse_data_transform = self.axes.transData.inverted().transform_point

# Create a generator for the Label objects.
generate_labels = self._generate_labels()

for xylabel, lines, line_ticks, formatter, label_style in (
('x', lon_lines, lon_ticks,
self.xformatter, self.xlabel_style.copy()),
Expand Down Expand Up @@ -915,11 +950,11 @@ def update_artist(artist, renderer):
elif not y_set:
y = pt0[1]

# Add text to the plot
# Update generated label.
label = next(generate_labels)
text = formatter(tick_value)
artist = matplotlib.text.Text(x, y, text, **kw)
artist.set_figure(self.axes.figure)
artist.axes = self.axes
artist = label.artist
artist.set(x=x, y=y, text=text, **kw)

# Update loc from spine overlapping now that we have a bbox
# of the label.
Expand Down Expand Up @@ -975,8 +1010,10 @@ def update_artist(artist, renderer):
break

# Updates
label = Label(artist, this_path, xylabel, loc)
label.set_visible(visible)
label.path = this_path
label.xy = xylabel
label.loc = loc
self._labels.append(label)

# Now check overlapping of ordered visible labels
Expand Down Expand Up @@ -1263,7 +1300,10 @@ def __init__(self, artist, path, xy, loc):
self.loc = loc
self.path = path
self.xy = xy
self.priority = loc in ["left", "right", "top", "bottom"]

@property
def priority(self):
return self.loc in ["left", "right", "top", "bottom"]

def set_visible(self, value):
self.artist.set_visible(value)
Expand Down
26 changes: 26 additions & 0 deletions lib/cartopy/tests/mpl/test_gridliner.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,29 @@ def test_gridliner_title_noadjust():
ax.set_extent([-180, 180, -60, 60])
fig.draw_without_rendering()
assert ax.title.get_position() == pos


def test_gridliner_labels_zoom():
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())

# Start with a global map.
ax.set_global()
gl = ax.gridlines(draw_labels=True, auto_update=True)

fig.draw_without_rendering() # Generate child artists
labels = [a.get_text() for a in gl.bottom_label_artists if a.get_visible()]
assert labels == ['180°', '120°W', '60°W', '0°', '60°E', '120°E', '180°']
# For first draw, active labels should be all of the labels.
assert len(gl._all_labels) == 24
assert gl._labels == gl._all_labels

# Zoom in.
ax.set_extent([-20, 10.0, 45.0, 70.0])

fig.draw_without_rendering() # Update child artists
labels = [a.get_text() for a in gl.bottom_label_artists if a.get_visible()]
assert labels == ['15°W', '10°W', '5°W', '0°', '5°E']
# After zoom, we may not be using all the available labels.
assert len(gl._all_labels) == 24
assert gl._labels == gl._all_labels[:20]

0 comments on commit 93dd3b0

Please sign in to comment.