From 23338c7eb4b315cd4af0c57b61afc80f8c2086f9 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 6 Feb 2022 22:26:15 +0100 Subject: [PATCH] Deprecate auto-removal of overlapping Axes by plt.subplot{,2grid}. In particular, note that the OO add_subplot does not have this behavior. --- .../deprecations/22418-AL.rst | 6 ++++ lib/matplotlib/pyplot.py | 29 ++++++++++--------- lib/matplotlib/tests/test_axes.py | 2 ++ lib/matplotlib/tests/test_pyplot.py | 22 +++++++++----- 4 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/22418-AL.rst diff --git a/doc/api/next_api_changes/deprecations/22418-AL.rst b/doc/api/next_api_changes/deprecations/22418-AL.rst new file mode 100644 index 000000000000..8e7fb763223b --- /dev/null +++ b/doc/api/next_api_changes/deprecations/22418-AL.rst @@ -0,0 +1,6 @@ +Auto-removal of overlapping Axes by ``plt.subplot`` and ``plt.subplot2grid`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Previously, `.pyplot.subplot` and `.pyplot.subplot2grid` would automatically +remove preexisting Axes that overlap with the newly added Axes. This behavior +was deemed confusing, and is now deprecated. Explicitly call ``ax.remove()`` +on Axes that need to be removed. diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 66885c2216b0..49f833085015 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1303,13 +1303,13 @@ def subplot(*args, **kwargs): fig.sca(ax) - bbox = ax.bbox - axes_to_delete = [] - for other_ax in fig.axes: - if other_ax == ax: - continue - if bbox.fully_overlaps(other_ax.bbox): - axes_to_delete.append(other_ax) + axes_to_delete = [other for other in fig.axes + if other != ax and ax.bbox.fully_overlaps(other.bbox)] + if axes_to_delete: + _api.warn_deprecated( + "3.6", message="Auto-removal of overlapping axes is deprecated " + "since %(since)s and will be removed %(removal)s; explicitly call " + "ax.remove() as needed.") for ax_to_del in axes_to_delete: delaxes(ax_to_del) @@ -1596,13 +1596,14 @@ def subplot2grid(shape, loc, rowspan=1, colspan=1, fig=None, **kwargs): subplotspec = gs.new_subplotspec(loc, rowspan=rowspan, colspan=colspan) ax = fig.add_subplot(subplotspec, **kwargs) - bbox = ax.bbox - axes_to_delete = [] - for other_ax in fig.axes: - if other_ax == ax: - continue - if bbox.fully_overlaps(other_ax.bbox): - axes_to_delete.append(other_ax) + + axes_to_delete = [other for other in fig.axes + if other != ax and ax.bbox.fully_overlaps(other.bbox)] + if axes_to_delete: + _api.warn_deprecated( + "3.6", message="Auto-removal of overlapping axes is deprecated " + "since %(since)s and will be removed %(removal)s; explicitly call " + "ax.remove() as needed.") for ax_to_del in axes_to_delete: delaxes(ax_to_del) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a5f5ddad93c4..2f5bf4c528f8 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -442,6 +442,8 @@ def test_inverted_cla(): assert not ax.xaxis_inverted() assert ax.yaxis_inverted() + for ax in fig.axes: + ax.remove() # 5. two shared axes. Inverting the leader axis should invert the shared # axes; clearing the leader axis should bring axes in shared # axes back to normal. diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 25c52ae9967c..13bfcbeafa15 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -206,8 +206,8 @@ def test_subplot_replace_projection(): ax = plt.subplot(1, 2, 1) ax1 = plt.subplot(1, 2, 1) ax2 = plt.subplot(1, 2, 2) - # This will delete ax / ax1 as they fully overlap - ax3 = plt.subplot(1, 2, 1, projection='polar') + with pytest.warns(MatplotlibDeprecationWarning): + ax3 = plt.subplot(1, 2, 1, projection='polar') ax4 = plt.subplot(1, 2, 1, projection='polar') assert ax is not None assert ax1 is ax @@ -228,6 +228,7 @@ def test_subplot_kwarg_collision(): ax1 = plt.subplot(projection='polar', theta_offset=0) ax2 = plt.subplot(projection='polar', theta_offset=0) assert ax1 is ax2 + ax1.remove() ax3 = plt.subplot(projection='polar', theta_offset=1) assert ax1 is not ax3 assert ax1 not in plt.gcf().axes @@ -274,6 +275,8 @@ def test_subplot_projection_reuse(): assert ax1 is plt.gca() # make sure we get it back if we ask again assert ax1 is plt.subplot(111) + # remove it + ax1.remove() # create a polar plot ax2 = plt.subplot(111, projection='polar') assert ax2 is plt.gca() @@ -281,6 +284,7 @@ def test_subplot_projection_reuse(): assert ax1 not in plt.gcf().axes # assert we get it back if no extra parameters passed assert ax2 is plt.subplot(111) + ax2.remove() # now check explicitly setting the projection to rectilinear # makes a new axes ax3 = plt.subplot(111, projection='rectilinear') @@ -302,15 +306,19 @@ def test_subplot_polar_normalization(): def test_subplot_change_projection(): + created_axes = set() ax = plt.subplot() + created_axes.add(ax) projections = ('aitoff', 'hammer', 'lambert', 'mollweide', 'polar', 'rectilinear', '3d') for proj in projections: - ax_next = plt.subplot(projection=proj) - assert ax_next is plt.subplot() - assert ax_next.name == proj - assert ax is not ax_next - ax = ax_next + ax.remove() + ax = plt.subplot(projection=proj) + assert ax is plt.subplot() + assert ax.name == proj + created_axes.add(ax) + # Check that each call created a new Axes. + assert len(created_axes) == 1 + len(projections) def test_polar_second_call():