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

Skip to content

Commit 105f186

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 cc85fb6 commit 105f186

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
@@ -99,6 +101,9 @@ def get_anchor(self):
99101
"""Return the anchor."""
100102
return self._anchor
101103

104+
def get_subplotspec(self):
105+
return None
106+
102107
def set_horizontal(self, h):
103108
"""
104109
Parameters
@@ -162,8 +167,44 @@ def _calc_offsets(sizes, k):
162167
# the resulting cumulative offset positions.
163168
return np.cumsum([0, *(sizes @ [k, 1])])
164169

170+
def new_locator(self, nx, ny, nx1=None, ny1=None):
171+
"""
172+
Return an axes locator callable for the specified cell.
173+
174+
Parameters
175+
----------
176+
nx, nx1 : int
177+
Integers specifying the column-position of the
178+
cell. When *nx1* is None, a single *nx*-th column is
179+
specified. Otherwise, location of columns spanning between *nx*
180+
to *nx1* (but excluding *nx1*-th column) is specified.
181+
ny, ny1 : int
182+
Same as *nx* and *nx1*, but for row positions.
183+
"""
184+
if nx1 is None:
185+
nx1 = nx + 1
186+
if ny1 is None:
187+
ny1 = ny + 1
188+
# append_size("left") adds a new size at the beginning of the
189+
# horizontal size lists; this shift transforms e.g.
190+
# new_locator(nx=2, ...) into effectively new_locator(nx=3, ...). To
191+
# take that into account, instead of recording nx, we record
192+
# nx-self._xrefindex, where _xrefindex is shifted by 1 by each
193+
# append_size("left"), and re-add self._xrefindex back to nx in
194+
# _locate, when the actual axes position is computed. Ditto for y.
195+
xref = self._xrefindex
196+
yref = self._yrefindex
197+
locator = functools.partial(
198+
self._locate, nx - xref, ny - yref, nx1 - xref, ny1 - yref)
199+
locator.get_subplotspec = self.get_subplotspec
200+
return locator
201+
202+
@_api.deprecated(
203+
"3.7", alternative="divider.new_locator(...)(ax, renderer)")
165204
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
166205
"""
206+
Implementation of ``divider.new_locator().__call__``.
207+
167208
Parameters
168209
----------
169210
nx, nx1 : int
@@ -176,6 +217,25 @@ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
176217
axes
177218
renderer
178219
"""
220+
xref = self._xrefindex
221+
yref = self._yrefindex
222+
return self._locate(
223+
nx - xref, (nx + 1 if nx1 is None else nx1) - xref,
224+
ny - yref, (ny + 1 if ny1 is None else ny1) - yref,
225+
axes, renderer)
226+
227+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
228+
"""
229+
Implementation of ``divider.new_locator().__call__``.
230+
231+
The axes locator callable returned by ``new_locator()`` is created as
232+
a `functools.partial` of this method with *nx*, *ny*, *nx1*, and *ny1*
233+
specifying the requested cell.
234+
"""
235+
nx += self._xrefindex
236+
nx1 += self._xrefindex
237+
ny += self._yrefindex
238+
ny1 += self._yrefindex
179239

180240
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
181241
x, y, w, h = self.get_position_runtime(axes, renderer)
@@ -201,43 +261,11 @@ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
201261
oy = self._calc_offsets(vsizes, k_v)
202262
x0, y0 = x, y
203263

204-
if nx1 is None:
205-
_api.warn_deprecated(
206-
"3.5", message="Support for passing nx1=None to mean nx+1 is "
207-
"deprecated since %(since)s; in a future version, nx1=None "
208-
"will mean 'up to the last cell'.")
209-
nx1 = nx + 1
210-
if ny1 is None:
211-
_api.warn_deprecated(
212-
"3.5", message="Support for passing ny1=None to mean ny+1 is "
213-
"deprecated since %(since)s; in a future version, ny1=None "
214-
"will mean 'up to the last cell'.")
215-
ny1 = ny + 1
216-
217264
x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
218265
y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
219266

220267
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
221268

222-
def new_locator(self, nx, ny, nx1=None, ny1=None):
223-
"""
224-
Return a new `.AxesLocator` for the specified cell.
225-
226-
Parameters
227-
----------
228-
nx, nx1 : int
229-
Integers specifying the column-position of the
230-
cell. When *nx1* is None, a single *nx*-th column is
231-
specified. Otherwise, location of columns spanning between *nx*
232-
to *nx1* (but excluding *nx1*-th column) is specified.
233-
ny, ny1 : int
234-
Same as *nx* and *nx1*, but for row positions.
235-
"""
236-
return AxesLocator(
237-
self, nx, ny,
238-
nx1 if nx1 is not None else nx + 1,
239-
ny1 if ny1 is not None else ny + 1)
240-
241269
def append_size(self, position, size):
242270
_api.check_in_list(["left", "right", "bottom", "top"],
243271
position=position)
@@ -272,6 +300,7 @@ def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None):
272300
self.append_size(d, Size._AxesDecorationsSize(use_axes, d) + pad)
273301

274302

303+
@_api.deprecated("3.7")
275304
class AxesLocator:
276305
"""
277306
A callable object which returns the position and size of a given
@@ -299,16 +328,8 @@ def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
299328
self._nx, self._ny = nx - _xrefindex, ny - _yrefindex
300329

301330
if nx1 is None:
302-
_api.warn_deprecated(
303-
"3.5", message="Support for passing nx1=None to mean nx+1 is "
304-
"deprecated since %(since)s; in a future version, nx1=None "
305-
"will mean 'up to the last cell'.")
306331
nx1 = nx + 1
307332
if ny1 is None:
308-
_api.warn_deprecated(
309-
"3.5", message="Support for passing ny1=None to mean ny+1 is "
310-
"deprecated since %(since)s; in a future version, ny1=None "
311-
"will mean 'up to the last cell'.")
312333
ny1 = ny + 1
313334

314335
self._nx1 = nx1 - _xrefindex
@@ -416,24 +437,17 @@ def new_horizontal(self, size, pad=None, pack_start=False, **kwargs):
416437
"""
417438
if pad is None:
418439
pad = mpl.rcParams["figure.subplot.wspace"] * self._xref
440+
pos = "left" if pack_start else "right"
419441
if pad:
420442
if not isinstance(pad, Size._Base):
421443
pad = Size.from_any(pad, fraction_ref=self._xref)
422-
if pack_start:
423-
self._horizontal.insert(0, pad)
424-
self._xrefindex += 1
425-
else:
426-
self._horizontal.append(pad)
444+
self.append_size(pos, pad)
427445
if not isinstance(size, Size._Base):
428446
size = Size.from_any(size, fraction_ref=self._xref)
429-
if pack_start:
430-
self._horizontal.insert(0, size)
431-
self._xrefindex += 1
432-
locator = self.new_locator(nx=0, ny=self._yrefindex)
433-
else:
434-
self._horizontal.append(size)
435-
locator = self.new_locator(
436-
nx=len(self._horizontal) - 1, ny=self._yrefindex)
447+
self.append_size(pos, size)
448+
locator = self.new_locator(
449+
nx=0 if pack_start else len(self._horizontal) - 1,
450+
ny=self._yrefindex)
437451
ax = self._get_new_axes(**kwargs)
438452
ax.set_axes_locator(locator)
439453
return ax
@@ -448,24 +462,17 @@ def new_vertical(self, size, pad=None, pack_start=False, **kwargs):
448462
"""
449463
if pad is None:
450464
pad = mpl.rcParams["figure.subplot.hspace"] * self._yref
465+
pos = "bottom" if pack_start else "top"
451466
if pad:
452467
if not isinstance(pad, Size._Base):
453468
pad = Size.from_any(pad, fraction_ref=self._yref)
454-
if pack_start:
455-
self._vertical.insert(0, pad)
456-
self._yrefindex += 1
457-
else:
458-
self._vertical.append(pad)
469+
self.append_size(pos, pad)
459470
if not isinstance(size, Size._Base):
460471
size = Size.from_any(size, fraction_ref=self._yref)
461-
if pack_start:
462-
self._vertical.insert(0, size)
463-
self._yrefindex += 1
464-
locator = self.new_locator(nx=self._xrefindex, ny=0)
465-
else:
466-
self._vertical.append(size)
467-
locator = self.new_locator(
468-
nx=self._xrefindex, ny=len(self._vertical) - 1)
472+
self.append_size(pos, size)
473+
locator = self.new_locator(
474+
nx=self._xrefindex,
475+
ny=0 if pack_start else len(self._vertical) - 1)
469476
ax = self._get_new_axes(**kwargs)
470477
ax.set_axes_locator(locator)
471478
return ax
@@ -579,7 +586,7 @@ class HBoxDivider(SubplotDivider):
579586

580587
def new_locator(self, nx, nx1=None):
581588
"""
582-
Create a new `.AxesLocator` for the specified cell.
589+
Create an axes locator callable for the specified cell.
583590
584591
Parameters
585592
----------
@@ -589,22 +596,18 @@ def new_locator(self, nx, nx1=None):
589596
specified. Otherwise, location of columns spanning between *nx*
590597
to *nx1* (but excluding *nx1*-th column) is specified.
591598
"""
592-
return AxesLocator(self, nx, 0, nx1 if nx1 is not None else nx + 1, 1)
599+
return super().new_locator(nx, 0, nx1, 0)
593600

594-
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
601+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
595602
# docstring inherited
603+
nx += self._xrefindex
604+
nx1 += self._xrefindex
596605
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
597606
x, y, w, h = self.get_position_runtime(axes, renderer)
598607
summed_ws = self.get_horizontal_sizes(renderer)
599608
equal_hs = self.get_vertical_sizes(renderer)
600609
x0, y0, ox, hh = _locate(
601610
x, y, w, h, summed_ws, equal_hs, fig_w, fig_h, self.get_anchor())
602-
if nx1 is None:
603-
_api.warn_deprecated(
604-
"3.5", message="Support for passing nx1=None to mean nx+1 is "
605-
"deprecated since %(since)s; in a future version, nx1=None "
606-
"will mean 'up to the last cell'.")
607-
nx1 = nx + 1
608611
x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
609612
y1, h1 = y0, hh
610613
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
@@ -618,7 +621,7 @@ class VBoxDivider(SubplotDivider):
618621

619622
def new_locator(self, ny, ny1=None):
620623
"""
621-
Create a new `.AxesLocator` for the specified cell.
624+
Create an axes locator callable for the specified cell.
622625
623626
Parameters
624627
----------
@@ -628,22 +631,18 @@ def new_locator(self, ny, ny1=None):
628631
specified. Otherwise, location of rows spanning between *ny*
629632
to *ny1* (but excluding *ny1*-th row) is specified.
630633
"""
631-
return AxesLocator(self, 0, ny, 1, ny1 if ny1 is not None else ny + 1)
634+
return super().new_locator(0, ny, 0, ny1)
632635

633-
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
636+
def _locate(self, nx, ny, nx1, ny1, axes, renderer):
634637
# docstring inherited
638+
ny += self._yrefindex
639+
ny1 += self._yrefindex
635640
fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
636641
x, y, w, h = self.get_position_runtime(axes, renderer)
637642
summed_hs = self.get_vertical_sizes(renderer)
638643
equal_ws = self.get_horizontal_sizes(renderer)
639644
y0, x0, oy, ww = _locate(
640645
y, x, h, w, summed_hs, equal_ws, fig_h, fig_w, self.get_anchor())
641-
if ny1 is None:
642-
_api.warn_deprecated(
643-
"3.5", message="Support for passing ny1=None to mean ny+1 is "
644-
"deprecated since %(since)s; in a future version, ny1=None "
645-
"will mean 'up to the last cell'.")
646-
ny1 = ny + 1
647646
x1, w1 = x0, ww
648647
y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
649648
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)

lib/mpl_toolkits/axes_grid1/tests/test_axes_grid1.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,9 +569,14 @@ def test_grid_axes_position(direction):
569569
fig = plt.figure()
570570
grid = Grid(fig, 111, (2, 2), direction=direction)
571571
loc = [ax.get_axes_locator() for ax in np.ravel(grid.axes_row)]
572-
assert loc[1]._nx > loc[0]._nx and loc[2]._ny < loc[0]._ny
573-
assert loc[0]._nx == loc[2]._nx and loc[0]._ny == loc[1]._ny
574-
assert loc[3]._nx == loc[1]._nx and loc[3]._ny == loc[2]._ny
572+
# Test nx.
573+
assert loc[1].args[0] > loc[0].args[0]
574+
assert loc[0].args[0] == loc[2].args[0]
575+
assert loc[3].args[0] == loc[1].args[0]
576+
# Test ny.
577+
assert loc[2].args[1] < loc[0].args[1]
578+
assert loc[0].args[1] == loc[1].args[1]
579+
assert loc[3].args[1] == loc[2].args[1]
575580

576581

577582
@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