8000 Deprecate axes_divider.AxesLocator. · matplotlib/matplotlib@8cb92b2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8cb92b2

Browse files
committed
Deprecate axes_divider.AxesLocator.
The axes_divider module creates axes locator objects, allowing the following pattern. ``` divider = Divider(<divider-args>) # or a subclass of Divider locator = divider.new_locator(<locator-args>) ax.set_axes_locator(locator) # will call locator(ax, renderer) at draw time ``` `locator` is actually an AxesLocator instance, which gets passed `divider` and `<locator-args>`; `locator(ax, renderer)` then simply calls (something like) `divider.locate(<locator-args>, ax, renderer)`, i.e. the AxesLocator instance simply exists to hold arguments for calling back into a divider method. One issue of this approach is that all the communication between the Divider and the AxesLocator class becomes public API (e.g., the locate method), even though these are essentially internal implementation details. This makes e.g. tightening of the locate API need to go through deprecation cycles (6eeb9ba). Instead, this PR completely gets rid of the AxesLocator class, and lets new_locator return an essentially opaque callable object (a functools.partial over a private method of AxesLocator), for which the only documented API is "this can be used as an axes locator callable, i.e. passed to ax.set_axes.locator". For simplicity, this PR also cancels the deprecations put in by 6eeb9ba, because these only target APIs that will ultimately get removed anyways; it also clarifies the role of _xrefindex and _yrefindex and consolidates all their handling into append_size, letting new_horizontal and new_vertical call that method instead of manipulating these indices themselves.
1 parent 705d250 commit 8cb92b2

File tree

4 files changed

+101
-89
lines changed

4 files changed

+101
-89
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
``axes_grid1.axes_divider`` API changes
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The ``AxesLocator`` class is deprecated. The ``new_locator`` method of divider
5+
instances now instead returns an opaque callable (which can still be passed to
6+
``ax.set_axes_locator``); the ``get_subplotspec`` method of this callable is
7+
likewise deprecated (call ``Divider.get_subplotspec`` instead).
8+
9+
``Divider.locate`` is deprecated; use ``Divider.new_locator(...)(ax, renderer)``
10+
instead.

lib/mpl_toolkits/axes_grid1/axes_divider.py

Lines changed: 81 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
Helper classes to adjust the positions of multiple axes at drawing time.
33
"""
44

5+
import functools
6+
57
import numpy as np
68

79
import matplotlib as mpl
@@ -171,8 +173,45 @@ def get_position_runtime(self, ax, renderer):
171173
else:
172174
return self._locator(ax, renderer).bounds
173175

176+
def new_locator(self, nx, ny, nx1=None, ny1=None):
177+
"""
178+
Return an axes locator callable for the specified cell.
179+
180+
Parameters
181+
----------
182+
nx, nx1 : int
183+
Integers specifying the column-position of the
184+
cell. When *nx1* is None, a single *nx*-th column is
185+
specified. Otherwise, location of columns spanning between *nx*
186+
to *nx1* (but excluding *nx1*-th column) is specified.
187+
ny, ny1 : int
188+
Same as *nx* and *nx1*, but for row positions.
189+
"""
190+
if nx1 is None:
191+
nx1 = nx + 1
192+
if ny1 is None:
193+
ny1 = ny + 1
194+
# append_size("left") adds a new size at the beginning of the
195+
# horizontal size lists; this shift transforms e.g.
196+
# new_locator(nx=2, ...) into effectively new_locator(nx=3, ...). To
197+
# take that into account, instead of recording nx, we record
198+
# nx-self._xrefindex, where _xrefindex is shifted by 1 by each
199+
# append_size("left"), and re-add self._xrefindex back to nx in
200+
# _locate, when the actual axes position is computed. Ditto for y.
201+
xref = self._xrefindex
202+
yref = self._yrefindex
203+
locator = functools.partial(
204+
self._locate, nx - xref, ny - yref, nx1 - xref, ny1 - yref)
205+
locator.get_subplotspec = _api.deprecated(
206+
"3.7", alternative="divider.get_subplotspec")(self.get_subplotspec)
207+
return locator
208+
209+
@_api.deprecated(
210+
"3.7", alternative="divider.new_locator(...)(ax, renderer)")
174211
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
175212
"""
213+
Implementation of ``divider.new_locator().__call__``.
214+
176215
Parameters
177216
----------
178217
nx, nx1 : int
@@ -185,6 +224,25 @@ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
185224
axes
186225
renderer
187226
"""
227+
xref = self._xrefindex
228+
yref = self._yrefindex
229+
return self._locate(
230+
nx - xref, (nx + 1 if nx1 is None else nx1) - xref,
231+
ny - yref, (ny + 1 if ny1 is None else ny1) - yref,
232+
axes, renderer)
233+
234+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
235+
"""
236+
Implementation of ``divider.new_locator().__call__``.
237+
238+
The axes locator callable returned by ``new_locator()`` is created as
239+
a `functools.partial` of this method with *nx*, *ny*, *nx1*, and *ny1*
240+
specifying the requested cell.
241+
"""
242+
nx += self._xrefindex
243+
nx1 += self._xrefindex
244+
ny += self._yrefindex
245+
ny1 += self._yrefindex
188246

189247
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
190248
x, y, w, h = self.get_position_runtime(axes, renderer)
@@ -210,43 +268,11 @@ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
210268
oy = self._calc_offsets(vsizes, k_v)
211269
x0, y0 = x, y
212270

213-
if nx1 is None:
214-
_api.warn_deprecated(
215-
"3.5", message="Support for passing nx1=None to mean nx+1 is "
216-
"deprecated since %(since)s; in a future version, nx1=None "
217-
"will mean 'up to the last cell'.")
218-
nx1 = nx + 1
219-
if ny1 is None:
220-
_api.warn_deprecated(
221-
"3.5", message="Support for passing ny1=None to mean ny+1 is "
222-
"deprecated since %(since)s; in a future version, ny1=None "
223-
"will mean 'up to the last cell'.")
224-
ny1 = ny + 1
225-
226271
x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
227272
y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
228273

229274
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
230275

231-
def new_locator(self, nx, ny, nx1=None, ny1=None):
232-
"""
233-
Return a new `.AxesLocator` for the specified cell.
234-
235-
Parameters
236-
----------
237-
nx, nx1 : int
238-
Integers specifying the column-position of the
239-
cell. When *nx1* is None, a single *nx*-th column is
240-
specified. Otherwise, location of columns spanning between *nx*
241-
to *nx1* (but excluding *nx1*-th column) is specified.
242-
ny, ny1 : int
243-
Same as *nx* and *nx1*, but for row positions.
244-
"""
245-
return AxesLocator(
246-
self, nx, ny,
247-
nx1 if nx1 is not None else nx + 1,
248-
ny1 if ny1 is not None else ny + 1)
249-
250276
def append_size(self, position, size):
251277
_api.check_in_list(["left", "right", "bottom", "top"],
252278
position=position)
@@ -281,6 +307,7 @@ def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None):
281307
self.append_size(d, Size._AxesDecorationsSize(use_axes, d) + pad)
282308

283309

310+
@_api.deprecated("3.7")
284311
class AxesLocator:
285312
"""
286313
A callable object which returns the position and size of a given
@@ -308,16 +335,8 @@ def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
308335
self._nx, self._ny = nx - _xrefindex, ny - _yrefindex
309336

310337
if nx1 is None:
311-
_api.warn_deprecated(
312-
"3.5", message="Support for passing nx1=None to mean nx+1 is "
313-
"deprecated since %(since)s; in a future version, nx1=None "
314-
"will mean 'up to the last cell'.")
315338
nx1 = nx + 1
316339
if ny1 is None:
317-
_api.warn_deprecated(
318-
"3.5", message="Support for passing ny1=None to mean ny+1 is "
319-
"deprecated since %(since)s; in a future version, ny1=None "
320-
"will mean 'up to the last cell'.")
321340
ny1 = ny + 1
322341

323342
self._nx1 = nx1 - _xrefindex
@@ -425,24 +444,17 @@ def new_horizontal(self, size, pad=None, pack_start=False, **kwargs):
425444
"""
426445
if pad is None:
427446
pad = mpl.rcParams["figure.subplot.wspace"] * self._xref
447+
pos = "left" if pack_start else "right"
428448
if pad:
429449
if not isinstance(pad, Size._Base):
430450
pad = Size.from_any(pad, fraction_ref=self._xref)
431-
if pack_start:
432-
self._horizontal.insert(0, pad)
433-
self._xrefindex += 1
434-
else:
435-
self._horizontal.append(pad)
451+
self.append_size(pos, pad)
436452
if not isinstance(size, Size._Base):
437453
size = Size.from_any(size, fraction_ref=self._xref)
438-
if pack_start:
439-
self._horizontal.insert(0, size)
440-
self._xrefindex += 1
441-
locator = self.new_locator(nx=0, ny=self._yrefindex)
442-
else:
443-
self._horizontal.append(size)
444-
locator = self.new_locator(
445-
nx=len(self._horizontal) - 1, ny=self._yrefindex)
454+
self.append_size(pos, size)
455+
locator = self.new_locator(
456+
nx=0 if pack_start else len(self._horizontal) - 1,
457+
ny=self._yrefindex)
446458
ax = self._get_new_axes(**kwargs)
447459
ax.set_axes_locator(locator)
448460
return ax
@@ -457,24 +469,17 @@ def new_vertical(self, size, pad=None, pack_start=False, **kwargs):
457469
"""
458470
if pad is None:
459471
pad = mpl.rcParams["figure.subplot.hspace"] * self._yref
472+
pos = "bottom" if pack_start else "top"
460473
if pad:
461474
if not isinstance(pad, Size._Base):
462475
pad = Size.from_any(pad, fraction_ref=self._yref)
463-
if pack_start:
464-
self._vertical.insert(0, pad)
465-
self._yrefindex += 1
466-
else:
467-
self._vertical.append(pad)
476+
self.append_size(pos, pad)
468477
if not isinstance(size, Size._Base):
469478
size = Size.from_any(size, fraction_ref=self._yref)
470-
if pack_start:
471-
self._vertical.insert(0, size)
472-
self._yrefindex += 1
473-
locator = self.new_locator(nx=self._xrefindex, ny=0)
474-
else:
475-
self._vertical.append(size)
476-
locator = self.new_locator(
477-
nx=self._xrefindex, ny=len(self._vertical) - 1)
479+
self.append_size(pos, size)
480+
locator = self.new_locator(
481+
nx=self._xrefindex,
482+
ny=0 if pack_start else len(self._vertical) - 1)
478483
ax = self._get_new_axes(**kwargs)
479484
ax.set_axes_locator(locator)
480485
return ax
@@ -603,7 +608,7 @@ class HBoxDivider(SubplotDivider):
603608

604609
def new_locator(self, nx, nx1=None):
605610
"""
606-
Create a new `.AxesLocator` for the specified cell.
611+
Create an axes locator callable for the specified cell.
607612
608613
Parameters
609614
----------
@@ -613,22 +618,18 @@ def new_locator(self, nx, nx1=None):
613618
specified. Otherwise, location of columns spanning between *nx*
614619
to *nx1* (but excluding *nx1*-th column) is specified.
615620
"""
616-
return AxesLocator(self, nx, 0, nx1 if nx1 is not None else nx + 1, 1)
621+
return super().new_locator(nx, 0, nx1, 0)
617622

618-
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
623+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
619624
# docstring inherited
625+
nx += self._xrefindex
626+
nx1 += self._xrefindex
620627
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
621628
x, y, w, h = self.get_position_runtime(axes, renderer)
622629
summed_ws = self.get_horizontal_sizes(renderer)
623630
equal_hs = self.get_vertical_sizes(renderer)
624631
x0, y0, ox, hh = _locate(
625632
x, y, w, h, summed_ws, equal_hs, fig_w, fig_h, self.get_anchor())
626-
if nx1 is None:
627-
_api.warn_deprecated(
628-
"3.5", message="Support for passing nx1=None to mean nx+1 is "
629-
"deprecated since %(since)s; in a future version, nx1=None "
630-
"will mean 'up to the last cell'.")
631-
nx1 = nx + 1
632633
x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
633634
y1, h1 = y0, hh
634635
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
@@ -642,7 +643,7 @@ class VBoxDivider(SubplotDivider):
642643

643644
def new_locator(self, ny, ny1=None):
644645
"""
645-
Create a new `.AxesLocator` for the specified cell.
646+
Create an axes locator callable for the specified cell.
646647
647648
Parameters
648649
----------
@@ -652,22 +653,18 @@ def new_locator(self, ny, ny1=None):
652653
specified. Otherwise, location of rows spanning between *ny*
653654
to *ny1* (but excluding *ny1*-th row) is specified.
654655
"""
655-
return AxesLocator(self, 0, ny, 1, ny1 if ny1 is not None else ny + 1)
656+
return super().new_locator(0, ny, 0, ny1)
656657

657-
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
658+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
658659
# docstring inherited
660+
ny += self._yrefindex
661+
ny1 += self._yrefindex
659662
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
660663
x, y, w, h = self.get_position_runtime(axes, renderer)
661664
summed_hs = self.get_vertical_sizes(renderer)
662665
equal_ws = self.get_horizontal_sizes(renderer)
663666
y0, x0, oy, ww = _locate(
664667
y, x, h, w, summed_hs, equal_ws, fig_h, fig_w, self.get_anchor())
665-
if ny1 is None:
666-
_api.warn_deprecated(
667-
"3.5", message="Support for passing ny1=None to mean ny+1 is "
668-
"deprecated since %(since)s; in a future version, ny1=None "
669-
"will mean 'up to the last cell'.")
670-
ny1 = ny + 1
671668
x1, w1 = x0, ww
672669
y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
673670
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)

lib/mpl_toolkits/tests/test_axes_grid1.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -561,9 +561,14 @@ def test_grid_axes_position(direction):
561561
fig = plt.figure()
562562
grid = Grid(fig, 111, (2, 2), direction=direction)
563563
loc = [ax.get_axes_locator() for ax in np.ravel(grid.axes_row)]
564-
assert loc[1]._nx > loc[0]._nx and loc[2]._ny < loc[0]._ny
565-
assert loc[0]._nx == loc[2]._nx and loc[0]._ny == loc[1]._ny
566-
assert loc[3]._nx == loc[1]._nx and loc[3]._ny == loc[2]._ny
564+
# Test nx.
565+
assert loc[1].args[0] > loc[0].args[0]
566+
assert loc[0].args[0] == loc[2].args[0]
567+
assert loc[3].args[0] == loc[1].args[0]
568+
# Test ny.
569+
assert loc[2].args[1] < loc[0].args[1]
570+
assert loc[0].args[1] == loc[1].args[1]
571+
assert loc[3].args[1] == loc[2].args[1]
567572

568573

569574
@pytest.mark.parametrize('rect, ngrids, error, message', (

tutorials/toolkits/axes_grid.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@
299299
horiz = [...] # Some other horizontal constraints.
300300
divider = Divider(fig, rect, horiz, vert)
301301
302-
then use `.Divider.new_locator` to create an `.AxesLocator` instance for a
302+
then use `.Divider.new_locator` to create an axes locator callable for a
303303
given grid entry::
304304
305305
locator = divider.new_locator(nx=0, ny=1) # Grid entry (1, 0).
@@ -308,7 +308,7 @@
308308
309309
ax.set_axes_locator(locator)
310310
311-
The `.AxesLocator` is a callable object that returns the location and size of
311+
The axes locator callable returns the location and size of
312312
the cell at the first column and the second row.
313313
314314
Locators that spans over multiple cells can be created with, e.g.::

0 commit comments

Comments
 (0)
0