diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index 231e5be98ddf..2619496b2ae1 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -27,18 +27,6 @@ The Axes class :no-undoc-members: :show-inheritance: - -Subplots -======== - -.. autosummary:: - :toctree: _as_gen - :template: autosummary.rst - :nosignatures: - - SubplotBase - subplot_class_factory - Plotting ======== @@ -313,6 +301,7 @@ Axis labels, title, and legend Axes.get_xlabel Axes.set_ylabel Axes.get_ylabel + Axes.label_outer Axes.set_title Axes.get_title @@ -484,6 +473,9 @@ Axes position Axes.get_axes_locator Axes.set_axes_locator + Axes.get_subplotspec + Axes.set_subplotspec + Axes.reset_position Axes.get_position diff --git a/doc/api/next_api_changes/behavior/23573-AL.rst b/doc/api/next_api_changes/behavior/23573-AL.rst new file mode 100644 index 000000000000..f388b414ae9a --- /dev/null +++ b/doc/api/next_api_changes/behavior/23573-AL.rst @@ -0,0 +1,7 @@ +All Axes have ``get_subplotspec`` and ``get_gridspec`` methods now, which returns None for Axes not positioned via a gridspec +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Previously, this method was only present for Axes positioned via a gridspec. +Following this change, checking ``hasattr(ax, "get_gridspec")`` should now be +replaced by ``ax.get_gridspec() is not None``. For compatibility with older +Matplotlib releases, one can also check +``hasattr(ax, "get_gridspec") and ax.get_gridspec() is not None``. diff --git a/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst b/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst index 22aa93f89931..60c04bb381aa 100644 --- a/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst +++ b/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst @@ -328,7 +328,7 @@ are deprecated. Panning and zooming are now implemented using the Passing None to various Axes subclass factories ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Support for passing ``None`` as base class to `.axes.subplot_class_factory`, +Support for passing ``None`` as base class to ``axes.subplot_class_factory``, ``axes_grid1.parasite_axes.host_axes_class_factory``, ``axes_grid1.parasite_axes.host_subplot_class_factory``, ``axes_grid1.parasite_axes.parasite_axes_class_factory``, and diff --git a/doc/api/prev_api_changes/api_changes_3.4.0/deprecations.rst b/doc/api/prev_api_changes/api_changes_3.4.0/deprecations.rst index 3de8959bb3ef..9e09f3febe64 100644 --- a/doc/api/prev_api_changes/api_changes_3.4.0/deprecations.rst +++ b/doc/api/prev_api_changes/api_changes_3.4.0/deprecations.rst @@ -38,8 +38,8 @@ Subplot-related attributes and methods Some ``SubplotBase`` methods and attributes have been deprecated and/or moved to `.SubplotSpec`: -- ``get_geometry`` (use `.SubplotBase.get_subplotspec` instead), -- ``change_geometry`` (use `.SubplotBase.set_subplotspec` instead), +- ``get_geometry`` (use ``SubplotBase.get_subplotspec`` instead), +- ``change_geometry`` (use ``SubplotBase.set_subplotspec`` instead), - ``is_first_row``, ``is_last_row``, ``is_first_col``, ``is_last_col`` (use the corresponding methods on the `.SubplotSpec` instance instead), - ``update_params`` (now a no-op), diff --git a/doc/users/prev_whats_new/whats_new_3.0.rst b/doc/users/prev_whats_new/whats_new_3.0.rst index c7da414a062a..683a7b5c702d 100644 --- a/doc/users/prev_whats_new/whats_new_3.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.0.rst @@ -141,10 +141,10 @@ independent on the axes size or units. To revert to the previous behaviour set the axes' aspect ratio to automatic by using ``ax.set_aspect("auto")`` or ``plt.axis("auto")``. -Add ``ax.get_gridspec`` to `.SubplotBase` ------------------------------------------ +Add ``ax.get_gridspec`` to ``SubplotBase`` +------------------------------------------ -New method `.SubplotBase.get_gridspec` is added so that users can +New method ``SubplotBase.get_gridspec`` is added so that users can easily get the gridspec that went into making an axes: .. code:: diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 5b5e0b9cf642..996603300620 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -187,8 +187,8 @@ def make_layoutgrids(fig, layoutgrids, rect=(0, 0, 1, 1)): # for each axes at the local level add its gridspec: for ax in fig._localaxes: - if hasattr(ax, 'get_subplotspec'): - gs = ax.get_subplotspec().get_gridspec() + gs = ax.get_gridspec() + if gs is not None: layoutgrids = make_layoutgrids_gs(layoutgrids, gs) return layoutgrids @@ -248,24 +248,22 @@ def check_no_collapsed_axes(layoutgrids, fig): ok = check_no_collapsed_axes(layoutgrids, sfig) if not ok: return False - for ax in fig.axes: - if hasattr(ax, 'get_subplotspec'): - gs = ax.get_subplotspec().get_gridspec() - if gs in layoutgrids: - lg = layoutgrids[gs] - for i in range(gs.nrows): - for j in range(gs.ncols): - bb = lg.get_inner_bbox(i, j) - if bb.width <= 0 or bb.height <= 0: - return False + gs = ax.get_gridspec() + if gs in layoutgrids: # also implies gs is not None. + lg = layoutgrids[gs] + for i in range(gs.nrows): + for j in range(gs.ncols): + bb = lg.get_inner_bbox(i, j) + if bb.width <= 0 or bb.height <= 0: + return False return True def compress_fixed_aspect(layoutgrids, fig): gs = None for ax in fig.axes: - if not hasattr(ax, 'get_subplotspec'): + if ax.get_subplotspec() is None: continue ax.apply_aspect() sub = ax.get_subplotspec() @@ -357,7 +355,7 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, layoutgrids[sfig].parent.edit_outer_margin_mins(margins, ss) for ax in fig._localaxes: - if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout(): + if not ax.get_subplotspec() or not ax.get_in_layout(): continue ss = ax.get_subplotspec() @@ -488,8 +486,8 @@ def match_submerged_margins(layoutgrids, fig): for sfig in fig.subfigs: match_submerged_margins(layoutgrids, sfig) - axs = [a for a in fig.get_axes() if (hasattr(a, 'get_subplotspec') - and a.get_in_layout())] + axs = [a for a in fig.get_axes() + if a.get_subplotspec() is not None and a.get_in_layout()] for ax1 in axs: ss1 = ax1.get_subplotspec() @@ -620,7 +618,7 @@ def reposition_axes(layoutgrids, fig, renderer, *, wspace=wspace, hspace=hspace) for ax in fig._localaxes: - if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout(): + if ax.get_subplotspec() is None or not ax.get_in_layout(): continue # grid bbox is in Figure coordinates, but we specify in panel @@ -742,10 +740,9 @@ def reset_margins(layoutgrids, fig): for sfig in fig.subfigs: reset_margins(layoutgrids, sfig) for ax in fig.axes: - if hasattr(ax, 'get_subplotspec') and ax.get_in_layout(): - ss = ax.get_subplotspec() - gs = ss.get_gridspec() - if gs in layoutgrids: + if ax.get_in_layout(): + gs = ax.get_gridspec() + if gs in layoutgrids: # also implies gs is not None. layoutgrids[gs].reset_margins() layoutgrids[fig].reset_margins() diff --git a/lib/matplotlib/axes/__init__.py b/lib/matplotlib/axes/__init__.py index 4dd998c0d43d..f8c40889bce7 100644 --- a/lib/matplotlib/axes/__init__.py +++ b/lib/matplotlib/axes/__init__.py @@ -1,2 +1,18 @@ -from ._subplots import * +from . import _base from ._axes import * + +# Backcompat. +from ._axes import Axes as Subplot + + +class _SubplotBaseMeta(type): + def __instancecheck__(self, obj): + return (isinstance(obj, _base._AxesBase) + and obj.get_subplotspec() is not None) + + +class SubplotBase(metaclass=_SubplotBaseMeta): + pass + + +def subplot_class_factory(cls): return cls diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 7e6f1ab3e6c2..247f349ca023 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1,4 +1,4 @@ -from collections.abc import MutableSequence +from collections.abc import Iterable, MutableSequence from contextlib import ExitStack import functools import inspect @@ -18,6 +18,7 @@ import matplotlib.collections as mcoll import matplotlib.colors as mcolors import matplotlib.font_manager as font_manager +from matplotlib.gridspec import SubplotSpec import matplotlib.image as mimage import matplotlib.lines as mlines import matplotlib.patches as mpatches @@ -569,8 +570,8 @@ def __str__(self): return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format( type(self).__name__, self._position.bounds) - def __init__(self, fig, rect, - *, + def __init__(self, fig, + *args, facecolor=None, # defaults to rc axes.facecolor frameon=True, sharex=None, # use Axes instance's xaxis info @@ -589,9 +590,18 @@ def __init__(self, fig, rect, fig : `~matplotlib.figure.Figure` The Axes is built in the `.Figure` *fig*. - rect : tuple (left, bottom, width, height). - The Axes is built in the rectangle *rect*. *rect* is in - `.Figure` coordinates. + *args + ``*args`` can be a single ``(left, bottom, width, height)`` + rectangle or a single `.Bbox`. This specifies the rectangle (in + figure coordinates) where the Axes is positioned. + + ``*args`` can also consist of three numbers or a single three-digit + number; in the latter case, the digits are considered as + independent numbers. The numbers are interpreted as ``(nrows, + ncols, index)``: ``(nrows, ncols)`` specifies the size of an array + of subplots, and ``index`` is the 1-based index of the subplot + being created. Finally, ``*args`` can also directly be a + `.SubplotSpec` instance. sharex, sharey : `~.axes.Axes`, optional The x or y `~.matplotlib.axis` is shared with the x or @@ -616,10 +626,21 @@ def __init__(self, fig, rect, """ super().__init__() - if isinstance(rect, mtransforms.Bbox): - self._position = rect + if "rect" in kwargs: + if args: + raise TypeError( + "'rect' cannot be used together with positional arguments") + rect = kwargs.pop("rect") + _api.check_isinstance((mtransforms.Bbox, Iterable), rect=rect) + args = (rect,) + subplotspec = None + if len(args) == 1 and isinstance(args[0], mtransforms.Bbox): + self._position = args[0] + elif len(args) == 1 and np.iterable(args[0]): + self._position = mtransforms.Bbox.from_bounds(*args[0]) else: - self._position = mtransforms.Bbox.from_bounds(*rect) + self._position = self._originalPosition = mtransforms.Bbox.unit() + subplotspec = SubplotSpec._from_subplot_args(fig, args) if self._position.width < 0 or self._position.height < 0: raise ValueError('Width and height specified must be non-negative') self._originalPosition = self._position.frozen() @@ -632,8 +653,16 @@ def __init__(self, fig, rect, self._sharey = sharey self.set_label(label) self.set_figure(fig) + # The subplotspec needs to be set after the figure (so that + # figure-level subplotpars are taken into account), but the figure + # needs to be set after self._position is initialized. + if subplotspec: + self.set_subplotspec(subplotspec) + else: + self._subplotspec = None self.set_box_aspect(box_aspect) self._axes_locator = None # Optionally set via update(kwargs). + # placeholder for any colorbars added that use this Axes. # (see colorbar.py): self._colorbars = [] @@ -737,6 +766,19 @@ def __repr__(self): fields += [f"{name}label={axis.get_label().get_text()!r}"] return f"<{self.__class__.__name__}: " + ", ".join(fields) + ">" + def get_subplotspec(self): + """Return the `.SubplotSpec` associated with the subplot, or None.""" + return self._subplotspec + + def set_subplotspec(self, subplotspec): + """Set the `.SubplotSpec`. associated with the subplot.""" + self._subplotspec = subplotspec + self._set_position(subplotspec.get_position(self.figure)) + + def get_gridspec(self): + """Return the `.GridSpec` associated with the subplot, or None.""" + return self._subplotspec.get_gridspec() if self._subplotspec else None + @_api.delete_parameter("3.6", "args") @_api.delete_parameter("3.6", "kwargs") def get_window_extent(self, renderer=None, *args, **kwargs): @@ -4424,17 +4466,23 @@ def get_tightbbox(self, renderer=None, call_axes_locator=True, def _make_twin_axes(self, *args, **kwargs): """Make a twinx Axes of self. This is used for twinx and twiny.""" - # Typically, SubplotBase._make_twin_axes is called instead of this. if 'sharex' in kwargs and 'sharey' in kwargs: - raise ValueError("Twinned Axes may share only one axis") - ax2 = self.figure.add_axes( - self.get_position(True), *args, **kwargs, - axes_locator=_TransformedBoundsLocator( - [0, 0, 1, 1], self.transAxes)) + # The following line is added in v2.2 to avoid breaking Seaborn, + # which currently uses this internal API. + if kwargs["sharex"] is not self and kwargs["sharey"] is not self: + raise ValueError("Twinned Axes may share only one axis") + ss = self.get_subplotspec() + if ss: + twin = self.figure.add_subplot(ss, *args, **kwargs) + else: + twin = self.figure.add_axes( + self.get_position(True), *args, **kwargs, + axes_locator=_TransformedBoundsLocator( + [0, 0, 1, 1], self.transAxes)) self.set_adjustable('datalim') - ax2.set_adjustable('datalim') - self._twinned_axes.join(self, ax2) - return ax2 + twin.set_adjustable('datalim') + self._twinned_axes.join(self, twin) + return twin def twinx(self): """ @@ -4502,3 +4550,56 @@ def get_shared_x_axes(self): def get_shared_y_axes(self): """Return an immutable view on the shared y-axes Grouper.""" return cbook.GrouperView(self._shared_axes["y"]) + + def label_outer(self): + """ + Only show "outer" labels and tick labels. + + x-labels are only kept for subplots on the last row (or first row, if + labels are on the top side); y-labels only for subplots on the first + column (or last column, if labels are on the right side). + """ + self._label_outer_xaxis(check_patch=False) + self._label_outer_yaxis(check_patch=False) + + def _label_outer_xaxis(self, *, check_patch): + # see documentation in label_outer. + if check_patch and not isinstance(self.patch, mpl.patches.Rectangle): + return + ss = self.get_subplotspec() + if not ss: + return + label_position = self.xaxis.get_label_position() + if not ss.is_first_row(): # Remove top label/ticklabels/offsettext. + if label_position == "top": + self.set_xlabel("") + self.xaxis.set_tick_params(which="both", labeltop=False) + if self.xaxis.offsetText.get_position()[1] == 1: + self.xaxis.offsetText.set_visible(False) + if not ss.is_last_row(): # Remove bottom label/ticklabels/offsettext. + if label_position == "bottom": + self.set_xlabel("") + self.xaxis.set_tick_params(which="both", labelbottom=False) + if self.xaxis.offsetText.get_position()[1] == 0: + self.xaxis.offsetText.set_visible(False) + + def _label_outer_yaxis(self, *, check_patch): + # see documentation in label_outer. + if check_patch and not isinstance(self.patch, mpl.patches.Rectangle): + return + ss = self.get_subplotspec() + if not ss: + return + label_position = self.yaxis.get_label_position() + if not ss.is_first_col(): # Remove left label/ticklabels/offsettext. + if label_position == "left": + self.set_ylabel("") + self.yaxis.set_tick_params(which="both", labelleft=False) + if self.yaxis.offsetText.get_position()[0] == 0: + self.yaxis.offsetText.set_visible(False) + if not ss.is_last_col(): # Remove right label/ticklabels/offsettext. + if label_position == "right": + self.set_ylabel("") + self.yaxis.set_tick_params(which="both", labelright=False) + if self.yaxis.offsetText.get_position()[0] == 1: + self.yaxis.offsetText.set_visible(False) diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py deleted file mode 100644 index b31388a2154f..000000000000 --- a/lib/matplotlib/axes/_subplots.py +++ /dev/null @@ -1,116 +0,0 @@ -import matplotlib as mpl -from matplotlib import cbook -from matplotlib.axes._axes import Axes -from matplotlib.gridspec import SubplotSpec - - -class SubplotBase: - """ - Base class for subplots, which are :class:`Axes` instances with - additional methods to facilitate generating and manipulating a set - of :class:`Axes` within a figure. - """ - - def __init__(self, fig, *args, **kwargs): - """ - Parameters - ---------- - fig : `matplotlib.figure.Figure` - - *args : tuple (*nrows*, *ncols*, *index*) or int - The array of subplots in the figure has dimensions ``(nrows, - ncols)``, and *index* is the index of the subplot being created. - *index* starts at 1 in the upper left corner and increases to the - right. - - If *nrows*, *ncols*, and *index* are all single digit numbers, then - *args* can be passed as a single 3-digit number (e.g. 234 for - (2, 3, 4)). - - **kwargs - Keyword arguments are passed to the Axes (sub)class constructor. - """ - # _axes_class is set in the subplot_class_factory - self._axes_class.__init__(self, fig, [0, 0, 1, 1], **kwargs) - # This will also update the axes position. - self.set_subplotspec(SubplotSpec._from_subplot_args(fig, args)) - - def get_subplotspec(self): - """Return the `.SubplotSpec` instance associated with the subplot.""" - return self._subplotspec - - def set_subplotspec(self, subplotspec): - """Set the `.SubplotSpec`. instance associated with the subplot.""" - self._subplotspec = subplotspec - self._set_position(subplotspec.get_position(self.figure)) - - def get_gridspec(self): - """Return the `.GridSpec` instance associated with the subplot.""" - return self._subplotspec.get_gridspec() - - def label_outer(self): - """ - Only show "outer" labels and tick labels. - - x-labels are only kept for subplots on the last row (or first row, if - labels are on the top side); y-labels only for subplots on the first - column (or last column, if labels are on the right side). - """ - self._label_outer_xaxis(check_patch=False) - self._label_outer_yaxis(check_patch=False) - - def _label_outer_xaxis(self, *, check_patch): - # see documentation in label_outer. - if check_patch and not isinstance(self.patch, mpl.patches.Rectangle): - return - ss = self.get_subplotspec() - label_position = self.xaxis.get_label_position() - if not ss.is_first_row(): # Remove top label/ticklabels/offsettext. - if label_position == "top": - self.set_xlabel("") - self.xaxis.set_tick_params(which="both", labeltop=False) - if self.xaxis.offsetText.get_position()[1] == 1: - self.xaxis.offsetText.set_visible(False) - if not ss.is_last_row(): # Remove bottom label/ticklabels/offsettext. - if label_position == "bottom": - self.set_xlabel("") - self.xaxis.set_tick_params(which="both", labelbottom=False) - if self.xaxis.offsetText.get_position()[1] == 0: - self.xaxis.offsetText.set_visible(False) - - def _label_outer_yaxis(self, *, check_patch): - # see documentation in label_outer. - if check_patch and not isinstance(self.patch, mpl.patches.Rectangle): - return - ss = self.get_subplotspec() - label_position = self.yaxis.get_label_position() - if not ss.is_first_col(): # Remove left label/ticklabels/offsettext. - if label_position == "left": - self.set_ylabel("") - self.yaxis.set_tick_params(which="both", labelleft=False) - if self.yaxis.offsetText.get_position()[0] == 0: - self.yaxis.offsetText.set_visible(False) - if not ss.is_last_col(): # Remove right label/ticklabels/offsettext. - if label_position == "right": - self.set_ylabel("") - self.yaxis.set_tick_params(which="both", labelright=False) - if self.yaxis.offsetText.get_position()[0] == 1: - self.yaxis.offsetText.set_visible(False) - - def _make_twin_axes(self, *args, **kwargs): - """Make a twinx axes of self. This is used for twinx and twiny.""" - if 'sharex' in kwargs and 'sharey' in kwargs: - # The following line is added in v2.2 to avoid breaking Seaborn, - # which currently uses this internal API. - if kwargs["sharex"] is not self and kwargs["sharey"] is not self: - raise ValueError("Twinned Axes may share only one axis") - twin = self.figure.add_subplot(self.get_subplotspec(), *args, **kwargs) - self.set_adjustable('datalim') - twin.set_adjustable('datalim') - self._twinned_axes.join(self, twin) - return twin - - -subplot_class_factory = cbook._make_class_factory( - SubplotBase, "{}Subplot", "_axes_class") -Subplot = subplot_class_factory(Axes) # Provided for backward compatibility. diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 9223cae56fa4..ca955c95ebb8 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -188,12 +188,9 @@ def __call__(self, ax, renderer): def get_subplotspec(self): # make tight_layout happy.. - ss = getattr(self._cbar.ax, 'get_subplotspec', None) - if ss is None: - if not hasattr(self._orig_locator, "get_subplotspec"): - return None - ss = self._orig_locator.get_subplotspec - return ss() + return ( + self._cbar.ax.get_subplotspec() + or getattr(self._orig_locator, "get_subplotspec", lambda: None)()) @_docstring.interpd @@ -1460,23 +1457,19 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, def make_axes_gridspec(parent, *, location=None, orientation=None, fraction=0.15, shrink=1.0, aspect=20, **kwargs): """ - Create a `.SubplotBase` suitable for a colorbar. + Create an `~.axes.Axes` suitable for a colorbar. The axes is placed in the figure of the *parent* axes, by resizing and repositioning *parent*. - This function is similar to `.make_axes`. Primary differences are - - - `.make_axes_gridspec` should only be used with a `.SubplotBase` parent. - - - `.make_axes` creates an `~.axes.Axes`; `.make_axes_gridspec` creates a - `.SubplotBase`. + This function is similar to `.make_axes` and mostly compatible with it. + Primary differences are + - `.make_axes_gridspec` requires the *parent* to have a subplotspec. + - `.make_axes` positions the axes in figure coordinates; + `.make_axes_gridspec` positions it using a subplotspec. - `.make_axes` updates the position of the parent. `.make_axes_gridspec` - replaces the ``grid_spec`` attribute of the parent with a new one. - - While this function is meant to be compatible with `.make_axes`, - there could be some minor differences. + replaces the parent gridspec with a new one. Parameters ---------- @@ -1486,7 +1479,7 @@ def make_axes_gridspec(parent, *, location=None, orientation=None, Returns ------- - cax : `~.axes.SubplotBase` + cax : `~.axes.Axes` The child axes. kwargs : dict The reduced keyword dictionary to be passed when creating the colorbar diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 3e8f30efcf64..5f56cc4d3861 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -33,7 +33,7 @@ import matplotlib.colorbar as cbar import matplotlib.image as mimage -from matplotlib.axes import Axes, SubplotBase, subplot_class_factory +from matplotlib.axes import Axes from matplotlib.gridspec import GridSpec from matplotlib.layout_engine import ( ConstrainedLayoutEngine, TightLayoutEngine, LayoutEngine, @@ -237,7 +237,7 @@ def autofmt_xdate( Selects which ticklabels to rotate. """ _api.check_in_list(['major', 'minor', 'both'], which=which) - allsubplots = all(hasattr(ax, 'get_subplotspec') for ax in self.axes) + allsubplots = all(ax.get_subplotspec() for ax in self.axes) if len(self.axes) == 1: for label in self.axes[0].get_xticklabels(which=which): label.set_ha(ha) @@ -675,13 +675,11 @@ def add_subplot(self, *args, **kwargs): Returns ------- - `.axes.SubplotBase`, or another subclass of `~.axes.Axes` + `~.axes.Axes` - The Axes of the subplot. The returned Axes base class depends on - the projection used. It is `~.axes.Axes` if rectilinear projection - is used and `.projections.polar.PolarAxes` if polar projection - is used. The returned Axes is then a subplot subclass of the - base class. + The Axes of the subplot. The returned Axes can actually be an + instance of a subclass, such as `.projections.polar.PolarAxes` for + polar projections. Other Parameters ---------------- @@ -725,11 +723,13 @@ def add_subplot(self, *args, **kwargs): raise TypeError( "add_subplot() got an unexpected keyword argument 'figure'") - if len(args) == 1 and isinstance(args[0], SubplotBase): + if (len(args) == 1 + and isinstance(args[0], mpl.axes._base._AxesBase) + and args[0].get_subplotspec()): ax = args[0] key = ax._projection_init if ax.get_figure() is not self: - raise ValueError("The Subplot must have been created in " + raise ValueError("The Axes must have been created in " "the present figure") else: if not args: @@ -742,7 +742,7 @@ def add_subplot(self, *args, **kwargs): args = tuple(map(int, str(args[0]))) projection_class, pkw = self._process_projection_requirements( *args, **kwargs) - ax = subplot_class_factory(projection_class)(self, *args, **pkw) + ax = projection_class(self, *args, **pkw) key = (projection_class, pkw) return self._add_axes_internal(ax, key) @@ -1204,9 +1204,8 @@ def colorbar( use_gridspec : bool, optional If *cax* is ``None``, a new *cax* is created as an instance of - Axes. If *ax* is an instance of Subplot and *use_gridspec* is - ``True``, *cax* is created as an instance of Subplot using the - :mod:`.gridspec` module. + Axes. If *ax* is positioned with a subplotspec and *use_gridspec* + is ``True``, then *cax* is also positioned with a subplotspec. Returns ------- @@ -1254,7 +1253,9 @@ def colorbar( if cax is None: current_ax = self.gca() userax = False - if (use_gridspec and isinstance(ax, SubplotBase)): + if (use_gridspec + and isinstance(ax, mpl.axes._base._AxesBase) + and ax.get_subplotspec()): cax, kwargs = cbar.make_axes_gridspec(ax, **kwargs) else: cax, kwargs = cbar.make_axes(ax, **kwargs) @@ -1312,7 +1313,7 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None, return self.subplotpars.update(left, bottom, right, top, wspace, hspace) for ax in self.axes: - if hasattr(ax, 'get_subplotspec'): + if ax.get_subplotspec() is not None: ax._set_position(ax.get_subplotspec().get_position(self)) self.stale = True @@ -1359,9 +1360,7 @@ def align_xlabels(self, axs=None): """ if axs is None: axs = self.axes - axs = np.ravel(axs) - axs = [ax for ax in axs if hasattr(ax, 'get_subplotspec')] - + axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None] for ax in axs: _log.debug(' Working on: %s', ax.get_xlabel()) rowspan = ax.get_subplotspec().rowspan @@ -1421,9 +1420,7 @@ def align_ylabels(self, axs=None): """ if axs is None: axs = self.axes - axs = np.ravel(axs) - axs = [ax for ax in axs if hasattr(ax, 'get_subplotspec')] - + axs = [ax for ax in np.ravel(axs) if ax.get_subplotspec() is not None] for ax in axs: _log.debug(' Working on: %s', ax.get_ylabel()) colspan = ax.get_subplotspec().colspan diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 2dc066adefa9..06dd3f19f68a 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -211,8 +211,8 @@ def _check_gridspec_exists(figure, nrows, ncols): or create a new one """ for ax in figure.get_axes(): - if hasattr(ax, 'get_subplotspec'): - gs = ax.get_subplotspec().get_gridspec() + gs = ax.get_gridspec() + if gs is not None: if hasattr(gs, 'get_topmost_subplotspec'): # This is needed for colorbar gridspec layouts. # This is probably OK because this whole logic tree @@ -413,7 +413,7 @@ def update(self, **kwargs): raise AttributeError(f"{k} is an unknown keyword") for figmanager in _pylab_helpers.Gcf.figs.values(): for ax in figmanager.canvas.figure.axes: - if isinstance(ax, mpl.axes.SubplotBase): + if ax.get_subplotspec() is not None: ss = ax.get_subplotspec().get_topmost_subplotspec() if ss.get_gridspec() == self: ax._set_position( diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index f11d66844a4c..e57ea77e9596 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1130,13 +1130,11 @@ def subplot(*args, **kwargs): Returns ------- - `.axes.SubplotBase`, or another subclass of `~.axes.Axes` + `~.axes.Axes` - The axes of the subplot. The returned axes base class depends on - the projection used. It is `~.axes.Axes` if rectilinear projection - is used and `.projections.polar.PolarAxes` if polar projection - is used. The returned axes is then a subplot subclass of the - base class. + The Axes of the subplot. The returned Axes can actually be an instance + of a subclass, such as `.projections.polar.PolarAxes` for polar + projections. Other Parameters ---------------- @@ -1253,7 +1251,7 @@ def subplot(*args, **kwargs): for ax in fig.axes: # if we found an Axes at the position sort out if we can re-use it - if hasattr(ax, 'get_subplotspec') and ax.get_subplotspec() == key: + if ax.get_subplotspec() == key: # if the user passed no kwargs, re-use if kwargs == {}: break @@ -1560,12 +1558,11 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): Returns ------- - `.axes.SubplotBase`, or another subclass of `~.axes.Axes` + `~.axes.Axes` - The axes of the subplot. The returned axes base class depends on the - projection used. It is `~.axes.Axes` if rectilinear projection is used - and `.projections.polar.PolarAxes` if polar projection is used. The - returned axes is then a subplot subclass of the base class. + The Axes of the subplot. The returned Axes can actually be an instance + of a subclass, such as `.projections.polar.PolarAxes` for polar + projections. Notes ----- diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 268eb957f470..2985dc4e1087 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -66,7 +66,7 @@ def test_repr(): ax.set_xlabel('x') ax.set_ylabel('y') assert repr(ax) == ( - "") @@ -2712,7 +2712,7 @@ def _as_mpl_axes(self): # testing axes creation with subplot ax = plt.subplot(121, projection=prj) - assert type(ax) == mpl.axes._subplots.subplot_class_factory(PolarAxes) + assert type(ax) == PolarAxes plt.close() diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 48b4a880e089..6728a3dc7db9 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -262,7 +262,7 @@ def test_add_subplot_invalid(): fig.add_subplot(2, 2.0, 1) _, ax = plt.subplots() with pytest.raises(ValueError, - match='The Subplot must have been created in the ' + match='The Axes must have been created in the ' 'present figure'): fig.add_subplot(ax) diff --git a/lib/matplotlib/tests/test_subplots.py b/lib/matplotlib/tests/test_subplots.py index f299440ef53e..732418f19e2f 100644 --- a/lib/matplotlib/tests/test_subplots.py +++ b/lib/matplotlib/tests/test_subplots.py @@ -3,9 +3,9 @@ import numpy as np import pytest +from matplotlib.axes import Axes, SubplotBase import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal, image_comparison -import matplotlib.axes as maxes def check_shared(axs, x_shared, y_shared): @@ -122,6 +122,12 @@ def test_label_outer_span(): fig.axes, [False, True, False, True], [True, True, False, False]) +def test_label_outer_non_gridspec(): + ax = plt.axes([0, 0, 1, 1]) + ax.label_outer() # Does nothing. + check_visible([ax], [True], [True]) + + def test_shared_and_moved(): # test if sharey is on, but then tick_left is called that labels don't # re-appear. Seaborn does this just to be sure yaxis is on left... @@ -209,11 +215,6 @@ def test_dont_mutate_kwargs(): assert gridspec_kw == {'width_ratios': [1, 2]} -def test_subplot_factory_reapplication(): - assert maxes.subplot_class_factory(maxes.Axes) is maxes.Subplot - assert maxes.subplot_class_factory(maxes.Subplot) is maxes.Subplot - - @pytest.mark.parametrize("width_ratios", [None, [1, 3, 2]]) @pytest.mark.parametrize("height_ratios", [None, [1, 2]]) @check_figures_equal(extensions=['png']) @@ -251,3 +252,11 @@ def test_ratio_overlapping_kws(method, args): with pytest.raises(ValueError, match='width_ratios'): getattr(plt, method)(*args, width_ratios=[1, 2, 3], gridspec_kw={'width_ratios': [1, 2, 3]}) + + +def test_old_subplot_compat(): + fig = plt.figure() + assert isinstance(fig.add_subplot(), SubplotBase) + assert not isinstance(fig.add_axes(rect=[0, 0, 1, 1]), SubplotBase) + with pytest.raises(TypeError): + Axes(fig, [0, 0, 1, 1], rect=[0, 0, 1, 1]) diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py index 55fcdd937656..336200c6e279 100644 --- a/lib/matplotlib/tests/test_transforms.py +++ b/lib/matplotlib/tests/test_transforms.py @@ -510,7 +510,7 @@ def test_str_transform(): Affine2D().scale(1.0), Affine2D().scale(1.0))), PolarTransform( - PolarAxesSubplot(0.125,0.1;0.775x0.8), + PolarAxes(0.125,0.1;0.775x0.8), use_rmin=True, _apply_theta_transforms=False)), CompositeGenericTransform( diff --git a/lib/mpl_toolkits/axes_grid1/axes_divider.py b/lib/mpl_toolkits/axes_grid1/axes_divider.py index 251fa44b14bf..c462511757c1 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_divider.py +++ b/lib/mpl_toolkits/axes_grid1/axes_divider.py @@ -6,7 +6,6 @@ import matplotlib as mpl from matplotlib import _api -from matplotlib.axes import SubplotBase from matplotlib.gridspec import SubplotSpec import matplotlib.transforms as mtransforms from . import axes_size as Size @@ -343,10 +342,7 @@ def __call__(self, axes, renderer): renderer) def get_subplotspec(self): - if hasattr(self._axes_divider, "get_subplotspec"): - return self._axes_divider.get_subplotspec() - else: - return None + return self._axes_divider.get_subplotspec() class SubplotDivider(Divider): @@ -421,10 +417,7 @@ def __init__(self, axes, xref=None, yref=None): def _get_new_axes(self, *, axes_class=None, **kwargs): axes = self._axes if axes_class is None: - if isinstance(axes, SubplotBase): - axes_class = axes._axes_class - else: - axes_class = type(axes) + axes_class = type(axes) return axes_class(axes.get_figure(), axes.get_position(original=True), **kwargs) @@ -552,10 +545,7 @@ def get_anchor(self): return self._anchor def get_subplotspec(self): - if hasattr(self._axes, "get_subplotspec"): - return self._axes.get_subplotspec() - else: - return None + return self._axes.get_subplotspec() # Helper for HBoxDivider/VBoxDivider. diff --git a/lib/mpl_toolkits/axes_grid1/axes_rgb.py b/lib/mpl_toolkits/axes_grid1/axes_rgb.py index c94f443a8f83..34bc531cc9dc 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_rgb.py +++ b/lib/mpl_toolkits/axes_grid1/axes_rgb.py @@ -26,10 +26,7 @@ def make_rgb_axes(ax, pad=0.01, axes_class=None, **kwargs): ax_rgb = [] if axes_class is None: - try: - axes_class = ax._axes_class - except AttributeError: - axes_class = type(ax) + axes_class = type(ax) for ny in [4, 2, 0]: ax1 = axes_class(ax.get_figure(), ax.get_position(original=True), diff --git a/lib/mpl_toolkits/axes_grid1/parasite_axes.py b/lib/mpl_toolkits/axes_grid1/parasite_axes.py index b959d1f48a49..35ec67a65437 100644 --- a/lib/mpl_toolkits/axes_grid1/parasite_axes.py +++ b/lib/mpl_toolkits/axes_grid1/parasite_axes.py @@ -2,7 +2,6 @@ import matplotlib.artist as martist import matplotlib.image as mimage import matplotlib.transforms as mtransforms -from matplotlib.axes import subplot_class_factory from matplotlib.transforms import Bbox from .mpl_axes import Axes @@ -226,16 +225,9 @@ def get_tightbbox(self, renderer=None, call_axes_locator=True, return Bbox.union([b for b in bbs if b.width != 0 or b.height != 0]) -host_axes_class_factory = cbook._make_class_factory( - HostAxesBase, "{}HostAxes", "_base_axes_class") -HostAxes = host_axes_class_factory(Axes) -SubplotHost = subplot_class_factory(HostAxes) - - -def host_subplot_class_factory(axes_class): - host_axes_class = host_axes_class_factory(axes_class) - subplot_host_class = subplot_class_factory(host_axes_class) - return subplot_host_class +host_axes_class_factory = host_subplot_class_factory = \ + cbook._make_class_factory(HostAxesBase, "{}HostAxes", "_base_axes_class") +HostAxes = SubplotHost = host_axes_class_factory(Axes) def host_axes(*args, axes_class=Axes, figure=None, **kwargs): @@ -260,23 +252,4 @@ def host_axes(*args, axes_class=Axes, figure=None, **kwargs): return ax -def host_subplot(*args, axes_class=Axes, figure=None, **kwargs): - """ - Create a subplot that can act as a host to parasitic axes. - - Parameters - ---------- - figure : `matplotlib.figure.Figure` - Figure to which the subplot will be added. Defaults to the current - figure `.pyplot.gcf()`. - - *args, **kwargs - Will be passed on to the underlying ``Axes`` object creation. - """ - import matplotlib.pyplot as plt - host_subplot_class = host_subplot_class_factory(axes_class) - if figure is None: - figure = plt.gcf() - ax = host_subplot_class(figure, *args, **kwargs) - figure.add_subplot(ax) - return ax +host_subplot = host_axes diff --git a/lib/mpl_toolkits/axisartist/__init__.py b/lib/mpl_toolkits/axisartist/__init__.py index f6d4fe9252dd..47242cf7f0c5 100644 --- a/lib/mpl_toolkits/axisartist/__init__.py +++ b/lib/mpl_toolkits/axisartist/__init__.py @@ -5,10 +5,9 @@ from .grid_helper_curvelinear import GridHelperCurveLinear from .floating_axes import FloatingAxes, FloatingSubplot from mpl_toolkits.axes_grid1.parasite_axes import ( - host_axes_class_factory, parasite_axes_class_factory, - subplot_class_factory) + host_axes_class_factory, parasite_axes_class_factory) ParasiteAxes = parasite_axes_class_factory(Axes) HostAxes = host_axes_class_factory(Axes) -SubplotHost = subplot_class_factory(HostAxes) +SubplotHost = HostAxes diff --git a/lib/mpl_toolkits/axisartist/axislines.py b/lib/mpl_toolkits/axisartist/axislines.py index fdbf41580f03..9dcbb66a1a77 100644 --- a/lib/mpl_toolkits/axisartist/axislines.py +++ b/lib/mpl_toolkits/axisartist/axislines.py @@ -558,9 +558,6 @@ def new_floating_axis(self, nth_coord, value, axis_direction="bottom"): return axis -Subplot = maxes.subplot_class_factory(Axes) - - class AxesZero(Axes): def clear(self): @@ -577,4 +574,5 @@ def clear(self): self._axislines[k].set_visible(False) -SubplotZero = maxes.subplot_class_factory(AxesZero) +Subplot = Axes +SubplotZero = AxesZero diff --git a/lib/mpl_toolkits/axisartist/floating_axes.py b/lib/mpl_toolkits/axisartist/floating_axes.py index d86c3db6c85a..8ae82dbf1426 100644 --- a/lib/mpl_toolkits/axisartist/floating_axes.py +++ b/lib/mpl_toolkits/axisartist/floating_axes.py @@ -11,7 +11,6 @@ import matplotlib as mpl from matplotlib import _api, cbook -import matplotlib.axes as maxes import matplotlib.patches as mpatches from matplotlib.path import Path @@ -339,4 +338,4 @@ def adjust_axes_lim(self): FloatingAxesBase, "Floating{}") FloatingAxes = floatingaxes_class_factory( host_axes_class_factory(axislines.Axes)) -FloatingSubplot = maxes.subplot_class_factory(FloatingAxes) +FloatingSubplot = FloatingAxes diff --git a/lib/mpl_toolkits/axisartist/parasite_axes.py b/lib/mpl_toolkits/axisartist/parasite_axes.py index feeecf6d907b..4ebd6acc03be 100644 --- a/lib/mpl_toolkits/axisartist/parasite_axes.py +++ b/lib/mpl_toolkits/axisartist/parasite_axes.py @@ -1,9 +1,7 @@ from mpl_toolkits.axes_grid1.parasite_axes import ( - host_axes_class_factory, parasite_axes_class_factory, - subplot_class_factory) + host_axes_class_factory, parasite_axes_class_factory) from .axislines import Axes ParasiteAxes = parasite_axes_class_factory(Axes) -HostAxes = host_axes_class_factory(Axes) -SubplotHost = subplot_class_factory(HostAxes) +HostAxes = SubplotHost = host_axes_class_factory(Axes) diff --git a/lib/mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py b/lib/mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py index 9a501daa2e7b..42db245d843b 100644 --- a/lib/mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py +++ b/lib/mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py @@ -8,7 +8,7 @@ from mpl_toolkits.axes_grid1.parasite_axes import ParasiteAxes from mpl_toolkits.axisartist import SubplotHost -from mpl_toolkits.axes_grid1.parasite_axes import host_subplot_class_factory +from mpl_toolkits.axes_grid1.parasite_axes import host_axes_class_factory from mpl_toolkits.axisartist import angle_helper from mpl_toolkits.axisartist.axislines import Axes from mpl_toolkits.axisartist.grid_helper_curvelinear import \ @@ -59,7 +59,7 @@ def inverted(self): fig = plt.figure() - SubplotHost = host_subplot_class_factory(Axes) + SubplotHost = host_axes_class_factory(Axes) tr = MyTransform(1) grid_helper = GridHelperCurveLinear(tr) diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index bd651d1ad52b..d426ba196bed 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -56,7 +56,7 @@ def test_axes3d_repr(): ax.set_ylabel('y') ax.set_zlabel('z') assert repr(ax) == ( - "") diff --git a/tutorials/intermediate/artists.py b/tutorials/intermediate/artists.py index f76c62d8462c..c2d163e6f6fa 100644 --- a/tutorials/intermediate/artists.py +++ b/tutorials/intermediate/artists.py @@ -29,8 +29,8 @@ the containers are places to put them (:class:`~matplotlib.axis.Axis`, :class:`~matplotlib.axes.Axes` and :class:`~matplotlib.figure.Figure`). The standard use is to create a :class:`~matplotlib.figure.Figure` instance, use -the ``Figure`` to create one or more :class:`~matplotlib.axes.Axes` or -:class:`~matplotlib.axes.Subplot` instances, and use the ``Axes`` instance +the ``Figure`` to create one or more :class:`~matplotlib.axes.Axes` +instances, and use the ``Axes`` instance helper methods to create the primitives. In the example below, we create a ``Figure`` instance using :func:`matplotlib.pyplot.figure`, which is a convenience method for instantiating ``Figure`` instances and connecting them @@ -59,10 +59,7 @@ class in the Matplotlib API, and the one you will be working with most :class:`~matplotlib.image.AxesImage`, respectively). These helper methods will take your data (e.g., ``numpy`` arrays and strings) and create primitive ``Artist`` instances as needed (e.g., ``Line2D``), add them to -the relevant containers, and draw them when requested. Most of you -are probably familiar with the :class:`~matplotlib.axes.Subplot`, -which is just a special case of an ``Axes`` that lives on a regular -rows by columns grid of ``Subplot`` instances. If you want to create +the relevant containers, and draw them when requested. If you want to create an ``Axes`` at an arbitrary location, simply use the :meth:`~matplotlib.figure.Figure.add_axes` method which takes a list of ``[left, bottom, width, height]`` values in 0-1 relative figure @@ -79,8 +76,8 @@ class in the Matplotlib API, and the one you will be working with most line, = ax.plot(t, s, color='blue', lw=2) In this example, ``ax`` is the ``Axes`` instance created by the -``fig.add_subplot`` call above (remember ``Subplot`` is just a subclass of -``Axes``) and when you call ``ax.plot``, it creates a ``Line2D`` instance and +``fig.add_subplot`` call above and when you call ``ax.plot``, it creates a +``Line2D`` instance and adds it to the ``Axes``. In the interactive `IPython `_ session below, you can see that the ``Axes.lines`` list is length one and contains the same line that was returned by the ``line, = ax.plot...`` call: @@ -298,10 +295,10 @@ class in the Matplotlib API, and the one you will be working with most # In [158]: ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3]) # # In [159]: ax1 -# Out[159]: +# Out[159]: # # In [160]: print(fig.axes) -# [, ] +# [, ] # # Because the figure maintains the concept of the "current Axes" (see # :meth:`Figure.gca ` and @@ -348,7 +345,7 @@ class in the Matplotlib API, and the one you will be working with most # ================ ============================================================ # Figure attribute Description # ================ ============================================================ -# axes A list of `~.axes.Axes` instances (includes Subplot) +# axes A list of `~.axes.Axes` instances # patch The `.Rectangle` background # images A list of `.FigureImage` patches - # useful for raw pixel display pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy