diff --git a/doc/api/next_api_changes/behavior/18564-AL.rst b/doc/api/next_api_changes/behavior/18564-AL.rst index 15617667fce0..438949180e6c 100644 --- a/doc/api/next_api_changes/behavior/18564-AL.rst +++ b/doc/api/next_api_changes/behavior/18564-AL.rst @@ -1,8 +1,25 @@ -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`. +Axes3D automatically adding self to Figure is deprecated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +New `.Axes3D` objects previously added themselves to figures when they +were created, unlike all other Axes classes, which lead to them being +added twice if ``fig.add_subplot(111, projection='3d')`` was called. + +This behavior is now deprecated and will warn. The new keyword argument +*auto_add_to_figure* controls the behavior and can be used to suppress the +warning. The default value will change to False in mpl3.5, and any +non-False value will be an error in mpl3.6. + +In the future, `.Axes3D` will need to be explicitly added to the +figure :: + + fig = Figure() + # create Axes3D + ax = Axes3d(fig) + # add to Figure + fig.add_axes(ax) + +as needs to be done for other `.axes.Axes` sub-classes. Or, a 3-d +projection can be made via:: + + fig.add_subplot(projection='3d') diff --git a/examples/pyplots/whats_new_99_mplot3d.py b/examples/pyplots/whats_new_99_mplot3d.py index 9c6cf2a82c79..28a383aa7659 100644 --- a/examples/pyplots/whats_new_99_mplot3d.py +++ b/examples/pyplots/whats_new_99_mplot3d.py @@ -17,7 +17,8 @@ Z = np.sin(R) fig = plt.figure() -ax = Axes3D(fig) +ax = Axes3D(fig, auto_add_to_figure=False) +fig.add_axes(ax) ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.viridis) plt.show() diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f4e5183e3838..be3c0783800d 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1517,7 +1517,8 @@ def _process_projection_requirements( raise TypeError( f"projection must be a string, None or implement a " f"_as_mpl_axes method, not {projection!r}") - + if projection_class.__name__ == 'Axes3D': + kwargs.setdefault('auto_add_to_figure', False) return projection_class, kwargs def get_default_bbox_extra_artists(self): diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py index 9fe73e3b892a..05473ed89235 100644 --- a/lib/matplotlib/tests/test_collections.py +++ b/lib/matplotlib/tests/test_collections.py @@ -391,7 +391,7 @@ def test_polycollection_close(): [[3., 0.], [3., 1.], [4., 1.], [4., 0.]]] fig = plt.figure() - ax = fig.add_axes(Axes3D(fig)) + ax = fig.add_axes(Axes3D(fig, auto_add_to_figure=False)) colors = ['r', 'g', 'b', 'y', 'k'] zpos = list(range(5)) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 066501aef826..4ddbf47fa432 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -64,6 +64,15 @@ def __init__( Other axes to share z-limits with. proj_type : {'persp', 'ortho'} The projection type, default 'persp'. + auto_add_to_figure : bool, default: True + Prior to Matplotlib 3.4 Axes3D would add themselves + to their host Figure on init. Other Axes class do not + do this. + + This behavior is deprecated in 3.4, the default will + change to False in 3.5. The keyword will be undocumented + and a non-False value will be an error in 3.6. + **kwargs Other optional keyword arguments: @@ -97,6 +106,8 @@ def __init__( self._shared_z_axes.join(self, sharez) self._adjustable = 'datalim' + auto_add_to_figure = kwargs.pop('auto_add_to_figure', True) + super().__init__( fig, rect, frameon=True, box_aspect=box_aspect, *args, **kwargs ) @@ -129,6 +140,18 @@ def __init__( # for bounding box calculations self.spines[:].set_visible(False) + if auto_add_to_figure: + _api.warn_deprecated( + "3.4", removal="3.6", message="Axes3D(fig) adding itself " + "to the figure is deprecated since %(since)s. " + "Pass the keyword argument auto_add_to_figure=False " + "and use fig.add_axes(ax) to suppress this warning. " + "The default value of auto_add_to_figure will change to " + "False in mpl3.5 and True values will " + "no longer work %(removal)s. This is consistent with " + "other Axes classes.") + fig.add_axes(self) + def set_axis_off(self): self._axis3don = False self.stale = True diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index 2996be45b929..cfcc383db1a4 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -6,12 +6,14 @@ from mpl_toolkits.mplot3d import Axes3D, axes3d, proj3d, art3d import matplotlib as mpl from matplotlib.backend_bases import MouseButton +from matplotlib.cbook import MatplotlibDeprecationWarning from matplotlib import cm from matplotlib import colors as mcolors from matplotlib.testing.decorators import image_comparison, check_figures_equal from matplotlib.testing.widgets import mock_event from matplotlib.collections import LineCollection, PolyCollection from matplotlib.patches import Circle + import matplotlib.pyplot as plt import numpy as np @@ -725,7 +727,7 @@ def test_add_collection3d_zs_scalar(): @mpl3d_image_comparison(['axes3d_labelpad.png'], remove_text=False) def test_axes3d_labelpad(): fig = plt.figure() - ax = fig.add_axes(Axes3D(fig)) + ax = fig.add_axes(Axes3D(fig, auto_add_to_figure=False)) # labelpad respects rcParams assert ax.xaxis.labelpad == mpl.rcParams['axes.labelpad'] # labelpad can be set in set_label @@ -1148,7 +1150,8 @@ def test_inverted_cla(): def test_ax3d_tickcolour(): fig = plt.figure() - ax = Axes3D(fig) + with pytest.warns(MatplotlibDeprecationWarning): + ax = Axes3D(fig) ax.tick_params(axis='x', colors='red') ax.tick_params(axis='y', colors='red')