diff --git a/lib/matplotlib/legend.py b/lib/matplotlib/legend.py index 7aef9ed7e8b3..15f56959bc4f 100644 --- a/lib/matplotlib/legend.py +++ b/lib/matplotlib/legend.py @@ -585,8 +585,12 @@ def _get_loc(self): _loc = property(_get_loc, _set_loc) - def _findoffset(self, width, height, xdescent, ydescent, renderer): + def _findoffset(self): """Helper function to locate the legend.""" + renderer = (self.figure._cachedRenderer + or self._legend_box._cached_renderer) + width, height, xdescent, ydescent = self._legend_box.get_extent( + renderer) if self._loc == 0: # "best". x, y = self._find_best_position(width, height, renderer) @@ -883,6 +887,8 @@ def get_window_extent(self, renderer=None): # docstring inherited if renderer is None: renderer = self.figure._cachedRenderer + # May not be cached on the figure, so cache it ourselves. + self._cached_renderer = renderer return self._legend_box.get_window_extent(renderer=renderer) def get_tightbbox(self, renderer): diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index 229158066675..deb624f8be1a 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -263,32 +263,35 @@ def set_offset(self, xy): xy : (float, float) or callable The (x, y) coordinates of the offset in display units. These can either be given explicitly as a tuple (x, y), or by providing a - function that converts the extent into the offset. This function - must have the signature:: + function that dynamically computes an offset (taking the arguments + passed to `.OffsetBox.get_offset`). It is recommended to make such + functions take no arguments. + + Before version 3.6, the callable had to have the signature:: def offset(width, height, xdescent, ydescent, renderer) \ -> (float, float) + + For backwards compatibility, callables with arbitrary signatures + are currently accepted as long as compatible arguments are + passed in calls to `.set_offset`. This should be considered an + implementation detail, and may be deprecated in the future. """ self._offset = xy self.stale = True - def get_offset(self, width, height, xdescent, ydescent, renderer): + def get_offset(self, *args, **kwargs): """ - Return the offset as a tuple (x, y). - - The extent parameters have to be provided to handle the case where the - offset is dynamically determined by a callable (see - `~.OffsetBox.set_offset`). - - Parameters - ---------- - width, height, xdescent, ydescent - Extent parameters. - renderer : `.RendererBase` subclass + Return the (x, y) offset. + Parameters are usually not necessary. The only exception can occur + if you have defined a callable to calculate the offset dynamically (see + `~.OffsetBox.set_offset`). It is now recommended that such a + callable does not take parameters. However, for backward-compatibility, + callables with parameters are still supported; these parameters must be + provided to `.get_offset` so that we can pass them on. """ - return (self._offset(width, height, xdescent, ydescent, renderer) - if callable(self._offset) + return (self._offset(*args, **kwargs) if callable(self._offset) else self._offset) def set_width(self, width): @@ -347,8 +350,10 @@ def get_extent(self, renderer): def get_window_extent(self, renderer): # docstring inherited - w, h, xd, yd, offsets = self.get_extent_offsets(renderer) - px, py = self.get_offset(w, h, xd, yd, renderer) + w, h, xd, yd = self.get_extent(renderer) + # dynamic offset compute callables may need to access the renderer. + self._cached_renderer = renderer + px, py = self.get_offset() return mtransforms.Bbox.from_bounds(px - xd, py - yd, w, h) def draw(self, renderer): @@ -357,7 +362,7 @@ def draw(self, renderer): to the given *renderer*. """ w, h, xdescent, ydescent, offsets = self.get_extent_offsets(renderer) - px, py = self.get_offset(w, h, xdescent, ydescent, renderer) + px, py = self.get_offset() for c, (ox, oy) in zip(self.get_visible_children(), offsets): c.set_offset((px + ox, py + oy)) c.draw(renderer) @@ -538,7 +543,7 @@ def get_extent_offsets(self, renderer): def draw(self, renderer): # docstring inherited w, h, xdescent, ydescent, offsets = self.get_extent_offsets(renderer) - px, py = self.get_offset(w, h, xdescent, ydescent, renderer) + px, py = self.get_offset() for c, (ox, oy) in zip(self.get_visible_children(), offsets): c.set_offset((px + ox, py + oy)) @@ -622,21 +627,9 @@ def set_offset(self, xy): xy : (float, float) The (x, y) coordinates of the offset in display units. """ - self._offset = xy self.offset_transform.clear() self.offset_transform.translate(xy[0], xy[1]) - self.stale = True - - def get_offset(self): - """Return offset of the container.""" - return self._offset - - def get_window_extent(self, renderer): - # docstring inherited - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset() # w, h, xd, yd) - - return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) + super().set_offset(xy) def get_extent(self, renderer): """Return width, height, xdescent, ydescent of box.""" @@ -782,20 +775,9 @@ def set_offset(self, xy): xy : (float, float) The (x, y) coordinates of the offset in display units. """ - self._offset = xy self.offset_transform.clear() self.offset_transform.translate(xy[0], xy[1]) - self.stale = True - - def get_offset(self): - """Return offset of the container.""" - return self._offset - - def get_window_extent(self, renderer): - # docstring inherited - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset() - return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) + super().set_offset(xy) def get_extent(self, renderer): _, h_, d_ = renderer.get_text_width_height_descent( @@ -883,20 +865,9 @@ def set_offset(self, xy): xy : (float, float) The (x, y) coordinates of the offset in display units. """ - self._offset = xy self.offset_transform.clear() self.offset_transform.translate(xy[0], xy[1]) - self.stale = True - - def get_offset(self): - """Return offset of the container.""" - return self._offset - - def get_window_extent(self, renderer): - # docstring inherited - w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset() # w, h, xd, yd) - return mtransforms.Bbox.from_bounds(ox - xd, oy - yd, w, h) + super().set_offset(xy) def get_extent(self, renderer): # clear the offset transforms @@ -1076,29 +1047,20 @@ def set_bbox_to_anchor(self, bbox, transform=None): def get_window_extent(self, renderer): # docstring inherited - self._update_offset_func(renderer) + # Update the offset func, which depends on the dpi of the renderer + # (because of the padding). w, h, xd, yd = self.get_extent(renderer) - ox, oy = self.get_offset(w, h, xd, yd, renderer) - return Bbox.from_bounds(ox - xd, oy - yd, w, h) - - def _update_offset_func(self, renderer, fontsize=None): - """ - Update the offset func which depends on the dpi of the - renderer (because of the padding). - """ - if fontsize is None: - fontsize = renderer.points_to_pixels( - self.prop.get_size_in_points()) + fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) - def _offset(w, h, xd, yd, renderer): + def _offset(*args, **kwargs): # args are ignored; left for backcompat. bbox = Bbox.from_bounds(0, 0, w, h) - borderpad = self.borderpad * fontsize + pad = self.borderpad * fontsize bbox_to_anchor = self.get_bbox_to_anchor() - x0, y0 = _get_anchored_bbox( - self.loc, bbox, bbox_to_anchor, borderpad) + x0, y0 = _get_anchored_bbox(self.loc, bbox, bbox_to_anchor, pad) return x0 + xd, y0 + yd self.set_offset(_offset) + return super().get_window_extent(renderer) def update_frame(self, bbox, fontsize=None): self.patch.set_bounds(bbox.bounds) @@ -1110,17 +1072,13 @@ def draw(self, renderer): if not self.get_visible(): return - fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) - self._update_offset_func(renderer, fontsize) - # update the location and size of the legend bbox = self.get_window_extent(renderer) + fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) self.update_frame(bbox, fontsize) self.patch.draw(renderer) - width, height, xdescent, ydescent = self.get_extent(renderer) - - px, py = self.get_offset(width, height, xdescent, ydescent, renderer) + px, py = self.get_offset() self.get_child().set_offset((px, py)) self.get_child().draw(renderer) @@ -1230,10 +1188,6 @@ def set_zoom(self, zoom): def get_zoom(self): return self._zoom - def get_offset(self): - """Return offset of the container.""" - return self._offset - def get_children(self): return [self.image] @@ -1603,8 +1557,7 @@ def __init__(self, ref_artist, offsetbox, use_blit=False): def save_offset(self): offsetbox = self.offsetbox renderer = offsetbox.figure._cachedRenderer - w, h, xd, yd = offsetbox.get_extent(renderer) - offset = offsetbox.get_offset(w, h, xd, yd, renderer) + offset = offsetbox.get_offset() self.offsetbox_x, self.offsetbox_y = offset self.offsetbox.set_offset(offset) diff --git a/lib/mpl_toolkits/axes_grid1/inset_locator.py b/lib/mpl_toolkits/axes_grid1/inset_locator.py index 56e3b83573b1..a67126b37a22 100644 --- a/lib/mpl_toolkits/axes_grid1/inset_locator.py +++ b/lib/mpl_toolkits/axes_grid1/inset_locator.py @@ -70,18 +70,11 @@ def draw(self, renderer): def __call__(self, ax, renderer): self.axes = ax - - fontsize = renderer.points_to_pixels(self.prop.get_size_in_points()) - self._update_offset_func(renderer, fontsize) - - width, height, xdescent, ydescent = self.get_extent(renderer) - - px, py = self.get_offset(width, height, 0, 0, renderer) - bbox_canvas = Bbox.from_bounds(px, py, width, height) + bbox = self.get_window_extent(renderer) + px, py = self.get_offset() + bbox_canvas = Bbox.from_bounds(px, py, bbox.width, bbox.height) tr = ax.figure.transFigure.inverted() - bb = TransformedBbox(bbox_canvas, tr) - - return bb + return TransformedBbox(bbox_canvas, tr) class AnchoredSizeLocator(AnchoredLocatorBase):