Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c73f4c4

Browse filesBrowse files
committed
Merge SubplotBase into AxesBase.
1 parent 9b1fcf6 commit c73f4c4
Copy full SHA for c73f4c4

File tree

Expand file treeCollapse file tree

27 files changed

+258
-314
lines changed
Filter options
Expand file treeCollapse file tree

27 files changed

+258
-314
lines changed

‎doc/api/axes_api.rst

Copy file name to clipboardExpand all lines: doc/api/axes_api.rst
+4-12Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,6 @@ The Axes class
2727
:no-undoc-members:
2828
:show-inheritance:
2929

30-
31-
Subplots
32-
========
33-
34-
.. autosummary::
35-
:toctree: _as_gen
36-
:template: autosummary.rst
37-
:nosignatures:
38-
39-
SubplotBase
40-
subplot_class_factory
41-
4230
Plotting
4331
========
4432

@@ -313,6 +301,7 @@ Axis labels, title, and legend
313301
Axes.get_xlabel
314302
Axes.set_ylabel
315303
Axes.get_ylabel
304+
Axes.label_outer
316305

317306
Axes.set_title
318307
Axes.get_title
@@ -484,6 +473,9 @@ Axes position
484473
Axes.get_axes_locator
485474
Axes.set_axes_locator
486475

476+
Axes.get_subplotspec
477+
Axes.set_subplotspec
478+
487479
Axes.reset_position
488480

489481
Axes.get_position
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
All Axes have ``get_subplotspec`` and ``get_gridspec`` methods now, which returns None for Axes not positioned via a gridspec
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Previously, this method was only present for Axes positioned via a gridspec.
4+
Following this change, checking ``hasattr(ax, "get_gridspec")`` should now be
5+
replaced by ``ax.get_gridspec() is not None``. For compatibility with older
6+
Matplotlib releases, one can also check
7+
``hasattr(ax, "get_gridspec") and ax.get_gridspec() is not None``.

‎doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst

Copy file name to clipboardExpand all lines: doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ are deprecated. Panning and zooming are now implemented using the
328328

329329
Passing None to various Axes subclass factories
330330
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
331-
Support for passing ``None`` as base class to `.axes.subplot_class_factory`,
331+
Support for passing ``None`` as base class to ``axes.subplot_class_factory``,
332332
``axes_grid1.parasite_axes.host_axes_class_factory``,
333333
``axes_grid1.parasite_axes.host_subplot_class_factory``,
334334
``axes_grid1.parasite_axes.parasite_axes_class_factory``, and

‎doc/api/prev_api_changes/api_changes_3.4.0/deprecations.rst

Copy file name to clipboardExpand all lines: doc/api/prev_api_changes/api_changes_3.4.0/deprecations.rst
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ Subplot-related attributes and methods
3838
Some ``SubplotBase`` methods and attributes have been deprecated and/or moved
3939
to `.SubplotSpec`:
4040

41-
- ``get_geometry`` (use `.SubplotBase.get_subplotspec` instead),
42-
- ``change_geometry`` (use `.SubplotBase.set_subplotspec` instead),
41+
- ``get_geometry`` (use ``SubplotBase.get_subplotspec`` instead),
42+
- ``change_geometry`` (use ``SubplotBase.set_subplotspec`` instead),
4343
- ``is_first_row``, ``is_last_row``, ``is_first_col``, ``is_last_col`` (use the
4444
corresponding methods on the `.SubplotSpec` instance instead),
4545
- ``update_params`` (now a no-op),

‎doc/users/prev_whats_new/whats_new_3.0.rst

Copy file name to clipboardExpand all lines: doc/users/prev_whats_new/whats_new_3.0.rst
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ independent on the axes size or units. To revert to the previous behaviour
141141
set the axes' aspect ratio to automatic by using ``ax.set_aspect("auto")`` or
142142
``plt.axis("auto")``.
143143

144-
Add ``ax.get_gridspec`` to `.SubplotBase`
145-
-----------------------------------------
144+
Add ``ax.get_gridspec`` to ``SubplotBase``
145+
------------------------------------------
146146

147-
New method `.SubplotBase.get_gridspec` is added so that users can
147+
New method ``SubplotBase.get_gridspec`` is added so that users can
148148
easily get the gridspec that went into making an axes:
149149

150150
.. code::

‎lib/matplotlib/_constrained_layout.py

Copy file name to clipboardExpand all lines: lib/matplotlib/_constrained_layout.py
+18-21Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ def make_layoutgrids(fig, layoutgrids, rect=(0, 0, 1, 1)):
187187

188188
# for each axes at the local level add its gridspec:
189189
for ax in fig._localaxes:
190-
if hasattr(ax, 'get_subplotspec'):
191-
gs = ax.get_subplotspec().get_gridspec()
190+
gs = ax.get_gridspec()
191+
if gs is not None:
192192
layoutgrids = make_layoutgrids_gs(layoutgrids, gs)
193193

194194
return layoutgrids
@@ -248,24 +248,22 @@ def check_no_collapsed_axes(layoutgrids, fig):
248248
ok = check_no_collapsed_axes(layoutgrids, sfig)
249249
if not ok:
250250
return False
251-
252251
for ax in fig.axes:
253-
if hasattr(ax, 'get_subplotspec'):
254-
gs = ax.get_subplotspec().get_gridspec()
255-
if gs in layoutgrids:
256-
lg = layoutgrids[gs]
257-
for i in range(gs.nrows):
258-
for j in range(gs.ncols):
259-
bb = lg.get_inner_bbox(i, j)
260-
if bb.width <= 0 or bb.height <= 0:
261-
return False
252+
gs = ax.get_gridspec()
253+
if gs in layoutgrids: # also implies gs is not None.
254+
lg = layoutgrids[gs]
255+
for i in range(gs.nrows):
256+
for j in range(gs.ncols):
257+
bb = lg.get_inner_bbox(i, j)
258+
if bb.width <= 0 or bb.height <= 0:
259+
return False
262260
return True
263261

264262

265263
def compress_fixed_aspect(layoutgrids, fig):
266264
gs = None
267265
for ax in fig.axes:
268-
if not hasattr(ax, 'get_subplotspec'):
266+
if ax.get_subplotspec() is None:
269267
continue
270268
ax.apply_aspect()
271269
sub = ax.get_subplotspec()
@@ -357,7 +355,7 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0,
357355
layoutgrids[sfig].parent.edit_outer_margin_mins(margins, ss)
358356

359357
for ax in fig._localaxes:
360-
if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout():
358+
if not ax.get_subplotspec() or not ax.get_in_layout():
361359
continue
362360

363361
ss = ax.get_subplotspec()
@@ -488,8 +486,8 @@ def match_submerged_margins(layoutgrids, fig):
488486
for sfig in fig.subfigs:
489487
match_submerged_margins(layoutgrids, sfig)
490488

491-
axs = [a for a in fig.get_axes() if (hasattr(a, 'get_subplotspec')
492-
and a.get_in_layout())]
489+
axs = [a for a in fig.get_axes()
490+
if a.get_subplotspec() is not None and a.get_in_layout()]
493491

494492
for ax1 in axs:
495493
ss1 = ax1.get_subplotspec()
@@ -620,7 +618,7 @@ def reposition_axes(layoutgrids, fig, renderer, *,
620618
wspace=wspace, hspace=hspace)
621619

622620
for ax in fig._localaxes:
623-
if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout():
621+
if ax.get_subplotspec() is None or not ax.get_in_layout():
624622
continue
625623

626624
# grid bbox is in Figure coordinates, but we specify in panel
@@ -742,10 +740,9 @@ def reset_margins(layoutgrids, fig):
742740
for sfig in fig.subfigs:
743741
reset_margins(layoutgrids, sfig)
744742
for ax in fig.axes:
745-
if hasattr(ax, 'get_subplotspec') and ax.get_in_layout():
746-
ss = ax.get_subplotspec()
747-
gs = ss.get_gridspec()
748-
if gs in layoutgrids:
743+
if ax.get_in_layout():
744+
gs = ax.get_gridspec()
745+
if gs in layoutgrids: # also implies gs is not None.
749746
layoutgrids[gs].reset_margins()
750747
layoutgrids[fig].reset_margins()
751748

‎lib/matplotlib/axes/__init__.py

Copy file name to clipboard
+17-1Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,18 @@
1-
from ._subplots import *
1+
from . import _base
22
from ._axes import *
3+
4+
# Backcompat.
5+
from ._axes import Axes as Subplot
6+
7+
8+
class _SubplotBaseMeta(type):
9+
def __instancecheck__(self, obj):
10+
return (isinstance(obj, _base._AxesBase)
11+
and obj.get_subplotspec() is not None)
12+
13+
14+
class SubplotBase(metaclass=_SubplotBaseMeta):
15+
pass
16+
17+
18+
def subplot_class_factory(cls): return cls

‎lib/matplotlib/axes/_base.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axes/_base.py
+119-18Lines changed: 119 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from collections.abc import MutableSequence
1+
from collections.abc import Iterable, MutableSequence
22
from contextlib import ExitStack
33
import functools
44
import inspect
@@ -18,6 +18,7 @@
1818
import matplotlib.collections as mcoll
1919
import matplotlib.colors as mcolors
2020
import matplotlib.font_manager as font_manager
21+
from matplotlib.gridspec import SubplotSpec
2122
import matplotlib.image as mimage
2223
import matplotlib.lines as mlines
2324
import matplotlib.patches as mpatches
@@ -569,8 +570,8 @@ def __str__(self):
569570
return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format(
570571
type(self).__name__, self._position.bounds)
571572

572-
def __init__(self, fig, rect,
573-
*,
573+
def __init__(self, fig,
574+
*args,
574575
facecolor=None, # defaults to rc axes.facecolor
575576
frameon=True,
576577
sharex=None, # use Axes instance's xaxis info
@@ -589,9 +590,18 @@ def __init__(self, fig, rect,
589590
fig : `~matplotlib.figure.Figure`
590591
The Axes is built in the `.Figure` *fig*.
591592
592-
rect : tuple (left, bottom, width, height).
593-
The Axes is built in the rectangle *rect*. *rect* is in
594-
`.Figure` coordinates.
593+
*args
594+
``*args`` can be a single ``(left, bottom, width, height)``
595+
rectangle or a single `.Bbox`. This specifies the rectangle (in
596+
figure coordinates) where the Axes is positioned.
597+
598+
``*args`` can also consist of three numbers or a single three-digit
599+
number; in the latter case, the digits are considered as
600+
independent numbers. The numbers are interpreted as ``(nrows,
601+
ncols, index)``: ``(nrows, ncols)`` specifies the size of an array
602+
of subplots, and ``index`` is the 1-based index of the subplot
603+
being created. Finally, ``*args`` can also directly be a
604+
`.SubplotSpec` instance.
595605
596606
sharex, sharey : `~.axes.Axes`, optional
597607
The x or y `~.matplotlib.axis` is shared with the x or
@@ -616,10 +626,21 @@ def __init__(self, fig, rect,
616626
"""
617627

618628
super().__init__()
619-
if isinstance(rect, mtransforms.Bbox):
620-
self._position = rect
629+
if "rect" in kwargs:
630+
if args:
631+
raise TypeError(
632+
"'rect' cannot be used together with positional arguments")
633+
rect = kwargs.pop("rect")
634+
_api.check_isinstance((mtransforms.Bbox, Iterable), rect=rect)
635+
args = (rect,)
636+
subplotspec = None
637+
if len(args) == 1 and isinstance(args[0], mtransforms.Bbox):
638+
self._position = args[0]
639+
elif len(args) == 1 and np.iterable(args[0]):
640+
self._position = mtransforms.Bbox.from_bounds(*args[0])
621641
else:
622-
self._position = mtransforms.Bbox.from_bounds(*rect)
642+
self._position = self._originalPosition = mtransforms.Bbox.unit()
643+
subplotspec = SubplotSpec._from_subplot_args(fig, args)
623644
if self._position.width < 0 or self._position.height < 0:
624645
raise ValueError('Width and height specified must be non-negative')
625646
self._originalPosition = self._position.frozen()
@@ -632,8 +653,16 @@ def __init__(self, fig, rect,
632653
self._sharey = sharey
633654
self.set_label(label)
634655
self.set_figure(fig)
656+
# The subplotspec needs to be set after the figure (so that
657+
# figure-level subplotpars are taken into account), but the figure
658+
# needs to be set after self._position is initialized.
659+
if subplotspec:
660+
self.set_subplotspec(subplotspec)
661+
else:
662+
self._subplotspec = None
635663
self.set_box_aspect(box_aspect)
636664
self._axes_locator = None # Optionally set via update(kwargs).
665+
637666
# placeholder for any colorbars added that use this Axes.
638667
# (see colorbar.py):
639668
self._colorbars = []
@@ -737,6 +766,19 @@ def __repr__(self):
737766
fields += [f"{name}label={axis.get_label().get_text()!r}"]
738767
return f"<{self.__class__.__name__}: " + ", ".join(fields) + ">"
739768

769+
def get_subplotspec(self):
770+
"""Return the `.SubplotSpec` associated with the subplot, or None."""
771+
return self._subplotspec
772+
773+
def set_subplotspec(self, subplotspec):
774+
"""Set the `.SubplotSpec`. associated with the subplot."""
775+
self._subplotspec = subplotspec
776+
self._set_position(subplotspec.get_position(self.figure))
777+
778+
def get_gridspec(self):
779+
"""Return the `.GridSpec` associated with the subplot, or None."""
780+
return self._subplotspec.get_gridspec() if self._subplotspec else None
781+
740782
@_api.delete_parameter("3.6", "args")
741783
@_api.delete_parameter("3.6", "kwargs")
742784
def get_window_extent(self, renderer=None, *args, **kwargs):
@@ -4424,17 +4466,23 @@ def get_tightbbox(self, renderer=None, call_axes_locator=True,
44244466

44254467
def _make_twin_axes(self, *args, **kwargs):
44264468
"""Make a twinx Axes of self. This is used for twinx and twiny."""
4427-
# Typically, SubplotBase._make_twin_axes is called instead of this.
44284469
if 'sharex' in kwargs and 'sharey' in kwargs:
4429-
raise ValueError("Twinned Axes may share only one axis")
4430-
ax2 = self.figure.add_axes(
4431-
self.get_position(True), *args, **kwargs,
4432-
axes_locator=_TransformedBoundsLocator(
4433-
[0, 0, 1, 1], self.transAxes))
4470+
# The following line is added in v2.2 to avoid breaking Seaborn,
4471+
# which currently uses this internal API.
4472+
if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
4473+
raise ValueError("Twinned Axes may share only one axis")
4474+
ss = self.get_subplotspec()
4475+
if ss:
4476+
twin = self.figure.add_subplot(ss, *args, **kwargs)
4477+
else:
4478+
twin = self.figure.add_axes(
4479+
self.get_position(True), *args, **kwargs,
4480+
axes_locator=_TransformedBoundsLocator(
4481+
[0, 0, 1, 1], self.transAxes))
44344482
self.set_adjustable('datalim')
4435-
ax2.set_adjustable('datalim')
4436-
self._twinned_axes.join(self, ax2)
4437-
return ax2
4483+
twin.set_adjustable('datalim')
4484+
self._twinned_axes.join(self, twin)
4485+
return twin
44384486

44394487
def twinx(self):
44404488
"""
@@ -4502,3 +4550,56 @@ def get_shared_x_axes(self):
45024550
def get_shared_y_axes(self):
45034551
"""Return an immutable view on the shared y-axes Grouper."""
45044552
return cbook.GrouperView(self._shared_axes["y"])
4553+
4554+
def label_outer(self):
4555+
"""
4556+
Only show "outer" labels and tick labels.
4557+
4558+
x-labels are only kept for subplots on the last row (or first row, if
4559+
labels are on the top side); y-labels only for subplots on the first
4560+
column (or last column, if labels are on the right side).
4561+
"""
4562+
self._label_outer_xaxis(check_patch=False)
4563+
self._label_outer_yaxis(check_patch=False)
4564+
4565+
def _label_outer_xaxis(self, *, check_patch):
4566+
# see documentation in label_outer.
4567+
if check_patch and not isinstance(self.patch, mpl.patches.Rectangle):
4568+
return
4569+
ss = self.get_subplotspec()
4570+
if not ss:
4571+
return
4572+
label_position = self.xaxis.get_label_position()
4573+
if not ss.is_first_row(): # Remove top label/ticklabels/offsettext.
4574+
if label_position == "top":
4575+
self.set_xlabel("")
4576+
self.xaxis.set_tick_params(which="both", labeltop=False)
4577+
if self.xaxis.offsetText.get_position()[1] == 1:
4578+
self.xaxis.offsetText.set_visible(False)
4579+
if not ss.is_last_row(): # Remove bottom label/ticklabels/offsettext.
4580+
if label_position == "bottom":
4581+
self.set_xlabel("")
4582+
self.xaxis.set_tick_params(which="both", labelbottom=False)
4583+
if self.xaxis.offsetText.get_position()[1] == 0:
4584+
self.xaxis.offsetText.set_visible(False)
4585+
4586+
def _label_outer_yaxis(self, *, check_patch):
4587+
# see documentation in label_outer.
4588+
if check_patch and not isinstance(self.patch, mpl.patches.Rectangle):
4589+
return
4590+
ss = self.get_subplotspec()
4591+
if not ss:
4592+
return
4593+
label_position = self.yaxis.get_label_position()
4594+
if not ss.is_first_col(): # Remove left label/ticklabels/offsettext.
4595+
if label_position == "left":
4596+
self.set_ylabel("")
4597+
self.yaxis.set_tick_params(which="both", labelleft=False)
4598+
if self.yaxis.offsetText.get_position()[0] == 0:
4599+
self.yaxis.offsetText.set_visible(False)
4600+
if not ss.is_last_col(): # Remove right label/ticklabels/offsettext.
4601+
if label_position == "right":
4602+
self.set_ylabel("")
4603+
self.yaxis.set_tick_params(which="both", labelright=False)
4604+
if self.yaxis.offsetText.get_position()[0] == 1:
4605+
self.yaxis.offsetText.set_visible(False)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.