From 261f7062860df88d08451d810d1036853390673d Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 11 Sep 2020 00:12:02 +0200 Subject: [PATCH] Prepare for merging SubplotBase into AxesBase. It should be possible to merge SubplotBase into AxesBase, with all Axes having a `get_subplotspec()` method -- it's just that that method would return None for non-gridspec-positioned Axes. The advantage of doing so is that we could get rid of the rather hackish subplot_class_factory, and the dynamically generated AxesSubplot class, which appears nowhere in the docs. - Deprecate most Subplot-specific API: while it's fine for all axes to have a `get_subplotspec` which may return None, it would be a bit weird if they all also have e.g. a `is_last_row` for which it's not clear what value to return for non-gridspec-positioned Axes. Moving that to the SubplotSpec seems natural enough (and these are pretty low-level anyways). - Make most parameters to AxesBase keyword-only, so that we can later overload the positional parameters to be either a rect or a subplot triplet (which should ideally be passed packed as a single parameter rather than unpacked, but at least during the deprecation period it would be a pain to differentiate whether, in `Axes(fig, a, b, c)`, `b` was intended to be the second index of a subplot triplet or a `facecolor`...) Due to the order of calls during initialization, Axes3D self-adding to the figure was problematic. This is already getting removed in another PR, so I included the same change here without API changes notes just to get the tests to pass. However I can put a note in for this PR if it ends up being ready for merge first. --- .../next_api_changes/behavior/18564-AL.rst | 8 ++++ .../deprecations/18564-AL.rst | 16 +++++++ examples/userdemo/demo_gridspec06.py | 9 ++-- lib/matplotlib/axes/_base.py | 1 + lib/matplotlib/axes/_subplots.py | 45 ++++++++++++++----- lib/matplotlib/colorbar.py | 2 - lib/matplotlib/figure.py | 7 ++- lib/matplotlib/gridspec.py | 17 ++++++- lib/matplotlib/tests/test_collections.py | 2 +- lib/matplotlib/tests/test_colorbar.py | 6 +-- lib/matplotlib/tests/test_figure.py | 2 +- lib/mpl_toolkits/mplot3d/axes3d.py | 2 - lib/mpl_toolkits/tests/test_mplot3d.py | 2 +- tutorials/intermediate/gridspec.py | 9 ++-- 14 files changed, 93 insertions(+), 35 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/18564-AL.rst create mode 100644 doc/api/next_api_changes/deprecations/18564-AL.rst diff --git a/doc/api/next_api_changes/behavior/18564-AL.rst b/doc/api/next_api_changes/behavior/18564-AL.rst new file mode 100644 index 000000000000..15617667fce0 --- /dev/null +++ b/doc/api/next_api_changes/behavior/18564-AL.rst @@ -0,0 +1,8 @@ +Axes3D no longer adds itself to figure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +New `.Axes3D` objects previously added themselves to figures when they were +created, which lead to them being added twice if +``fig.add_subplot(111, projection='3d')`` was called. Now ``ax = Axes3d(fig)`` +will need to be explicitly added to the figure with ``fig.add_axes(ax)``, as +also needs to be done for normal `.axes.Axes`. diff --git a/doc/api/next_api_changes/deprecations/18564-AL.rst b/doc/api/next_api_changes/deprecations/18564-AL.rst new file mode 100644 index 000000000000..e5314e54bb3b --- /dev/null +++ b/doc/api/next_api_changes/deprecations/18564-AL.rst @@ -0,0 +1,16 @@ +Subplot-related attributes and methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Some ``SubplotBase`` attributes have been deprecated and/or moved to +`.SubplotSpec`: ``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), ``figbox`` (use +``ax.get_subplotspec().get_geometry(ax.figure)`` instead to recompute the +geometry, or ``ax.get_position()`` to read its current value), ``numRows``, +``numCols`` (use the ``nrows`` and ``ncols`` attribute on the `.GridSpec` +instead). + +Axes constructor +~~~~~~~~~~~~~~~~ +Parameters of the Axes constructor other than *fig* and *rect* will become +keyword-only in a future version. diff --git a/examples/userdemo/demo_gridspec06.py b/examples/userdemo/demo_gridspec06.py index 29830a1f20ef..80138b3d651c 100644 --- a/examples/userdemo/demo_gridspec06.py +++ b/examples/userdemo/demo_gridspec06.py @@ -29,9 +29,10 @@ def squiggle_xy(a, b, c, d): # show only the outside spines for ax in fig.get_axes(): - ax.spines['top'].set_visible(ax.is_first_row()) - ax.spines['bottom'].set_visible(ax.is_last_row()) - ax.spines['left'].set_visible(ax.is_first_col()) - ax.spines['right'].set_visible(ax.is_last_col()) + ss = ax.get_subplotspec() + ax.spines['top'].set_visible(ss.is_first_row()) + ax.spines['bottom'].set_visible(ss.is_last_row()) + ax.spines['left'].set_visible(ss.is_first_col()) + ax.spines['right'].set_visible(ss.is_last_col()) plt.show() diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 652dc29f3354..2b03167218fd 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -460,6 +460,7 @@ 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) + @cbook._make_keyword_only("3.4", "facecolor") def __init__(self, fig, rect, facecolor=None, # defaults to rc axes.facecolor frameon=True, diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index f7172a63dd5f..750fbaff2577 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -33,12 +33,10 @@ def __init__(self, fig, *args, **kwargs): **kwargs Keyword arguments are passed to the Axes (sub)class constructor. """ - - self.figure = fig - self._subplotspec = SubplotSpec._from_subplot_args(fig, args) - self.update_params() # _axes_class is set in the subplot_class_factory - self._axes_class.__init__(self, fig, self.figbox, **kwargs) + 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 __reduce__(self): # get the first axes class which does not inherit from a subplotbase @@ -49,12 +47,15 @@ def __reduce__(self): (axes_class,), self.__getstate__()) + @cbook.deprecated( + "3.4", alternative="get_subplotspec", + addendum="(get_subplotspec returns a SubplotSpec instance.)") def get_geometry(self): """Get the subplot geometry, e.g., (2, 2, 3).""" rows, cols, num1, num2 = self.get_subplotspec().get_geometry() return rows, cols, num1 + 1 # for compatibility - # COVERAGE NOTE: Never used internally or from examples + @cbook.deprecated("3.4", alternative="set_subplotspec") def change_geometry(self, numrows, numcols, num): """Change subplot geometry, e.g., from (1, 1, 1) to (2, 2, 3).""" self._subplotspec = GridSpec(numrows, numcols, @@ -69,16 +70,33 @@ def get_subplotspec(self): 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() + @cbook.deprecated( + "3.4", alternative="get_subplotspec().get_position(self.figure)") + @property + def figbox(self): + return self.get_subplotspec().get_position(self.figure) + + @cbook.deprecated("3.4", alternative="get_gridspec().nrows") + @property + def numRows(self): + return self.get_gridspec().nrows + + @cbook.deprecated("3.4", alternative="get_gridspec().ncols") + @property + def numCols(self): + return self.get_gridspec().ncols + + @cbook.deprecated("3.4") def update_params(self): """Update the subplot position from ``self.figure.subplotpars``.""" - self.figbox, _, _, self.numRows, self.numCols = \ - self.get_subplotspec().get_position(self.figure, - return_all=True) + # Now a no-op, as figbox/numRows/numCols are (deprecated) auto-updating + # properties. @cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start") @property @@ -90,15 +108,19 @@ def rowNum(self): def colNum(self): return self.get_subplotspec().colspan.start + @cbook.deprecated("3.4", alternative="ax.get_subplotspec().is_first_row()") def is_first_row(self): return self.get_subplotspec().rowspan.start == 0 + @cbook.deprecated("3.4", alternative="ax.get_subplotspec().is_last_row()") def is_last_row(self): return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows + @cbook.deprecated("3.4", alternative="ax.get_subplotspec().is_first_col()") def is_first_col(self): return self.get_subplotspec().colspan.start == 0 + @cbook.deprecated("3.4", alternative="ax.get_subplotspec().is_last_col()") def is_last_col(self): return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols @@ -109,8 +131,9 @@ def label_outer(self): x-labels are only kept for subplots on the last row; y-labels only for subplots on the first column. """ - lastrow = self.is_last_row() - firstcol = self.is_first_col() + ss = self.get_subplotspec() + lastrow = ss.is_last_row() + firstcol = ss.is_first_col() if not lastrow: for label in self.get_xticklabels(which="both"): label.set_visible(False) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index f75612b74eb0..ba0b9a7040c4 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1540,8 +1540,6 @@ def make_axes_gridspec(parent, *, location=None, orientation=None, aspect = 1 / aspect parent.set_subplotspec(ss_main) - parent.update_params() - parent._set_position(parent.figbox) parent.set_anchor(loc_settings["panchor"]) fig = parent.get_figure() diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b9c523c8e72e..cf820369aecc 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -607,7 +607,7 @@ def autofmt_xdate( "3.3", message="Support for passing which=None to mean " "which='major' is deprecated since %(since)s and will be " "removed %(removal)s.") - allsubplots = all(hasattr(ax, 'is_last_row') for ax in self.axes) + allsubplots = all(hasattr(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) @@ -615,7 +615,7 @@ def autofmt_xdate( else: if allsubplots: for ax in self.get_axes(): - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): for label in ax.get_xticklabels(which=which): label.set_ha(ha) label.set_rotation(rotation) @@ -2420,8 +2420,7 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None, self.subplotpars.update(left, bottom, right, top, wspace, hspace) for ax in self.axes: if isinstance(ax, SubplotBase): - ax.update_params() - ax.set_position(ax.figbox) + ax._set_position(ax.get_subplotspec().get_position(self)) self.stale = True def ginput(self, n=1, timeout=30, show_clicks=True, diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 0f6221581ed0..e3fcc625e5de 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -489,8 +489,8 @@ def update(self, **kwargs): if isinstance(ax, mpl.axes.SubplotBase): ss = ax.get_subplotspec().get_topmost_subplotspec() if ss.get_gridspec() == self: - ax.update_params() - ax._set_position(ax.figbox) + ax._set_position( + ax.get_subplotspec().get_position(ax.figure)) def get_subplot_params(self, figure=None): """ @@ -764,6 +764,19 @@ def colspan(self): c1, c2 = sorted([self.num1 % ncols, self.num2 % ncols]) return range(c1, c2 + 1) + def is_first_row(self): + return self.rowspan.start == 0 + + def is_last_row(self): + return self.rowspan.stop == self.get_gridspec().nrows + + def is_first_col(self): + return self.colspan.start == 0 + + def is_last_col(self): + return self.colspan.stop == self.get_gridspec().ncols + + @cbook._delete_parameter("3.4", "return_all") def get_position(self, figure, return_all=False): """ Update the subplot position from ``figure.subplotpars``. diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 3159e7f2bba7..39f70ab847f3 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -365,7 +365,7 @@ def test_polycollection_close(): [[3., 0.], [3., 1.], [4., 1.], [4., 0.]]] fig = plt.figure() - ax = Axes3D(fig) + ax = fig.add_axes(Axes3D(fig)) colors = ['r', 'g', 'b', 'y', 'k'] zpos = list(range(5)) diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index 8307fceb3384..655f4940f37c 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -224,13 +224,13 @@ def test_remove_from_figure(use_gridspec): fig, ax = plt.subplots() sc = ax.scatter([1, 2], [3, 4], cmap="spring") sc.set_array(np.array([5, 6])) - pre_figbox = np.array(ax.figbox) + pre_position = ax.get_position() cb = fig.colorbar(sc, use_gridspec=use_gridspec) fig.subplots_adjust() cb.remove() fig.subplots_adjust() - post_figbox = np.array(ax.figbox) - assert (pre_figbox == post_figbox).all() + post_position = ax.get_position() + assert (pre_position.get_points() == post_position.get_points()).all() def test_colorbarbase(): diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 295a6c80452c..f1abc04ff52e 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -170,7 +170,7 @@ def test_gca(): # Changing the projection will throw a warning assert fig.gca(polar=True) is not ax3 assert fig.gca(polar=True) is not ax2 - assert fig.gca().get_geometry() == (1, 1, 1) + assert fig.gca().get_subplotspec().get_geometry() == (1, 1, 0, 0) fig.sca(ax1) assert fig.gca(projection='rectilinear') is ax1 diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d256e6210c41..61539fcb5a96 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -131,8 +131,6 @@ def __init__( pseudo_bbox = self.transLimits.inverted().transform([(0, 0), (1, 1)]) self._pseudo_w, self._pseudo_h = pseudo_bbox[1] - pseudo_bbox[0] - self.figure.add_axes(self) - # mplot3d currently manages its own spines and needs these turned off # for bounding box calculations for k in self.spines.keys(): diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index a2f5c0df8694..5ac5b1e62e4d 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -700,7 +700,7 @@ def test_add_collection3d_zs_scalar(): @mpl3d_image_comparison(['axes3d_labelpad.png'], remove_text=False) def test_axes3d_labelpad(): fig = plt.figure() - ax = Axes3D(fig) + ax = fig.add_axes(Axes3D(fig)) # labelpad respects rcParams assert ax.xaxis.labelpad == mpl.rcParams['axes.labelpad'] # labelpad can be set in set_label diff --git a/tutorials/intermediate/gridspec.py b/tutorials/intermediate/gridspec.py index 7fcf733d075b..bc19a618c8d9 100644 --- a/tutorials/intermediate/gridspec.py +++ b/tutorials/intermediate/gridspec.py @@ -246,10 +246,11 @@ def squiggle_xy(a, b, c, d, i=np.arange(0.0, 2*np.pi, 0.05)): # show only the outside spines for ax in fig11.get_axes(): - ax.spines['top'].set_visible(ax.is_first_row()) - ax.spines['bottom'].set_visible(ax.is_last_row()) - ax.spines['left'].set_visible(ax.is_first_col()) - ax.spines['right'].set_visible(ax.is_last_col()) + ss = ax.get_subplotspec() + ax.spines['top'].set_visible(ss.is_first_row()) + ax.spines['bottom'].set_visible(ss.is_last_row()) + ax.spines['left'].set_visible(ss.is_first_col()) + ax.spines['right'].set_visible(ss.is_last_col()) plt.show() 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