10000 Deprecate axes_divider.AxesLocator. · matplotlib/matplotlib@f493723 · GitHub
[go: up one dir, main page]

Skip to content

Commit f493723

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 f493723

File tree

4 files changed

+103
-89
lines changed

4 files changed

+103
-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: 83 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,47 @@ 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", name="get_subplotspec",
207+
alternative="divider.get_subplotspec")(
208+
lambda: self.get_subplotspec())
209+
return locator
210+
211+
@_api.deprecated(
212+
"3.7", alternative="divider.new_locator(...)(ax, renderer)")
174213
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
175214
"""
215+
Implementation of ``divider.new_locator().__call__``.
216+
176217
Parameters
177218
----------
178219
nx, nx1 : int
@@ -185,6 +226,25 @@ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
185226
axes
186227
renderer
187228
"""
229+
xref = self._xrefindex
230+
yref = self._yrefindex
231+
return self._locate(
232+
nx - xref, (nx + 1 if nx1 is None else nx1) - xref,
233+
ny - yref, (ny + 1 if ny1 is None else ny1) - yref,
234+
axes, renderer)
235+
236+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
237+
"""
238+
Implementation of ``divider.new_locator().__call__``.
239+
240+
The axes locator callable returned by ``new_locator()`` is created as
241+
a `functools.partial` of this method with *nx*, *ny*, *nx1*, and *ny1*
242+
specifying the requested cell.
243+
"""
244+
nx += self._xrefindex
245+
nx1 += self._xrefindex
246+
ny += self._yrefindex
247+
ny1 += self._yrefindex
188248

189249
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
190250
x, y, w, h = self.get_position_runtime(axes, renderer)
@@ -210,43 +270,11 @@ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
210270
oy = self._calc_offsets(vsizes, k_v)
211271
x0, y0 = x, y
212272

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-
226273
x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
227274
y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
228275

229276
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
230277

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-
250278
def append_size(self, position, size):
251279
_api.check_in_list(["left", "right", "bottom", "top"],
252280
position=position)
@@ -281,6 +309,7 @@ def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None):
281309
self.append_size(d, Size._AxesDecorationsSize(use_axes, d) + pad)
282310

283311

312+
@_api.deprecated("3.7")
284313
class AxesLocator:
285314
"""
286315
A callable object which returns the position and size of a given
@@ -308,16 +337,8 @@ def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
308337
self._nx, self._ny = nx - _xrefindex, ny - _yrefindex
309338

310339
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'.")
315340
nx1 = nx + 1
316341
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'.")
321342
ny1 = ny + 1
322343

323344
self._nx1 = nx1 - _xrefindex
@@ -425,24 +446,17 @@ def new_horizontal(self, size, pad=None, pack_start=False, **kwargs):
425446
"""
426447
if pad is None:
427448
pad = mpl.rcParams["figure.subplot.wspace"] * self._xref
449+
pos = "left" if pack_start else "right"
428450
if pad:
429451
if not isinstance(pad, Size._Base):
430452
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)
453+
self.append_size(pos, pad)
436454
if not isinstance(size, Size._Base):
437455
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)
456+
self.append_size(pos, size)
457+
locator = self.new_locator(
458+
nx=0 if pack_start else len(self._horizontal) - 1,
459+
ny=self._yrefindex)
446460
ax = self._get_new_axes(**kwargs)
447461
ax.set_axes_locator(locator)
448462
return ax
@@ -457,24 +471,17 @@ def new_vertical(self, size, pad=None, pack_start=False, **kwargs):
457471
"""
458472
if pad is None:
459473
pad = mpl.rcParams["figure.subplot.hspace"] * self._yref
474+
pos = "bottom" if pack_start else "top"
460475
if pad:
461476
if not isinstance(pad, Size._Base):
462477
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)
478+
self.append_size(pos, pad)
468479
if not isinstance(size, Size._Base):
469480
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)
481+
self.append_size(pos, size)
482+
locator = self.new_locator(
483+
nx=self._xrefindex,
484+
ny=0 if pack_start else len(self._vertical) - 1)
478485
ax = self._get_new_axes(**kwargs)
479486
ax.set_axes_locator(locator)
480487
return ax
@@ -603,7 +610,7 @@ class HBoxDivider(SubplotDivider):
603610

604611
def new_locator(self, nx, nx1=None):
605612
"""
606-
Create a new `.AxesLocator` for the specified cell.
613+
Create an axes locator callable for the specified cell.
607614
608615
Parameters
609616
----------
@@ -613,22 +620,18 @@ def new_locator(self, nx, nx1=None):
613620
specified. Otherwise, location of columns spanning between *nx*
614621
to *nx1* (but excluding *nx1*-th column) is specified.
615622
"""
616-
return AxesLocator(self, nx, 0, nx1 if nx1 is not None else nx + 1, 1)
623+
return super().new_locator(nx, 0, nx1, 0)
617624

618-
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
625+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
619626
# docstring inherited
627+
nx += self._xrefindex
628+
nx1 += self._xrefindex
620629
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
621630
x, y, w, h = self.get_position_runtime(axes, renderer)
622631
summed_ws = self.get_horizontal_sizes(renderer)
623632
equal_hs = self.get_vertical_sizes(renderer)
624633
x0, y0, ox, hh = _locate(
625634
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
632635
x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
633636
y1, h1 = y0, hh
634637
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
@@ -642,7 +645,7 @@ class VBoxDivider(SubplotDivider):
642645

643646
def new_locator(self, ny, ny1=None):
644647
"""
645-
Create a new `.AxesLocator` for the specified cell.
648+
Create an axes locator callable for the specified cell.
646649
647650
Parameters
648651
----------
@@ -652,22 +655,18 @@ def new_locator(self, ny, ny1=None):
652655
specified. Otherwise, location of rows spanning between *ny*
653656
to *ny1* (but excluding *ny1*-th row) is specified.
654657
"""
655-
return AxesLocator(self, 0, ny, 1, ny1 if ny1 is not None else ny + 1)
658+
return super().new_locator(0, ny, 0, ny1)
656659

657-
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
660+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
658661
# docstring inherited
662+
ny += self._yrefindex
663+
ny1 += self._yrefindex
659664
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
660665
x, y, w, h = self.get_position_runtime(axes, renderer)
661666
summed_hs = self.get_vertical_sizes(renderer)
662667
equal_ws = self.get_horizontal_sizes(renderer)
663668
y0, x0, oy, ww = _locate(
664669
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
671670
x1, w1 = x0, ww
672671
y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
673672
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