From 3cc837a631a72ec7604197de1a51a7f409c5581f Mon Sep 17 00:00:00 2001 From: Michel de Ruiter Date: Mon, 25 Jan 2021 10:26:58 +0100 Subject: [PATCH 1/3] ENH: Fix support for matplotlib's constrained_layout (#25261) --- doc/source/whatsnew/v1.3.0.rst | 2 +- pandas/plotting/_matplotlib/boxplot.py | 9 +++++++-- pandas/plotting/_matplotlib/hist.py | 10 ++++++---- pandas/plotting/_matplotlib/misc.py | 6 ++++-- pandas/plotting/_matplotlib/tools.py | 3 ++- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 05b4417f427ab..9b1afd0c4f95f 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -330,7 +330,7 @@ Plotting ^^^^^^^^ - Bug in :func:`scatter_matrix` raising when 2d ``ax`` argument passed (:issue:`16253`) -- +- Support matplotlib's ``constrained_layout=True`` (:issue:`25261`) - Groupby/resample/rolling diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 7122a38db9d0a..8bef8d9ceca98 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -227,7 +227,8 @@ def _grouped_plot_by_column( byline = by[0] if len(by) == 1 else by fig.suptitle(f"Boxplot grouped by {byline}") - fig.subplots_adjust(bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) + if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): + fig.subplots_adjust(bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) return result @@ -434,7 +435,11 @@ def boxplot_frame_groupby( ) ax.set_title(pprint_thing(key)) ret.loc[key] = d - fig.subplots_adjust(bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) + if ( + not hasattr(fig, "get_constrained_layout") + or not fig.get_constrained_layout() + ): + fig.subplots_adjust(bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) else: keys, frames = zip(*grouped) if grouped.axis == 0: diff --git a/pandas/plotting/_matplotlib/hist.py b/pandas/plotting/_matplotlib/hist.py index bff434d3c2d9d..b06aa7a1d5429 100644 --- a/pandas/plotting/_matplotlib/hist.py +++ b/pandas/plotting/_matplotlib/hist.py @@ -294,9 +294,10 @@ def plot_group(group, ax): axes, xlabelsize=xlabelsize, xrot=xrot, ylabelsize=ylabelsize, yrot=yrot ) - fig.subplots_adjust( - bottom=0.15, top=0.9, left=0.1, right=0.9, hspace=0.5, wspace=0.3 - ) + if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): + fig.subplots_adjust( + bottom=0.15, top=0.9, left=0.1, right=0.9, hspace=0.5, wspace=0.3 + ) return axes @@ -454,6 +455,7 @@ def hist_frame( set_ticks_props( axes, xlabelsize=xlabelsize, xrot=xrot, ylabelsize=ylabelsize, yrot=yrot ) - fig.subplots_adjust(wspace=0.3, hspace=0.3) + if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): + fig.subplots_adjust(wspace=0.3, hspace=0.3) return axes diff --git a/pandas/plotting/_matplotlib/misc.py b/pandas/plotting/_matplotlib/misc.py index 8e81751d88fa1..c8a5f3d7f6bee 100644 --- a/pandas/plotting/_matplotlib/misc.py +++ b/pandas/plotting/_matplotlib/misc.py @@ -39,7 +39,8 @@ def scatter_matrix( fig, axes = create_subplots(naxes=naxes, figsize=figsize, ax=ax, squeeze=False) # no gaps between subplots - fig.subplots_adjust(wspace=0, hspace=0) + if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): + fig.subplots_adjust(wspace=0, hspace=0) mask = notna(df) @@ -329,7 +330,8 @@ def bootstrap_plot( for axis in axes: plt.setp(axis.get_xticklabels(), fontsize=8) plt.setp(axis.get_yticklabels(), fontsize=8) - plt.tight_layout() + if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): + plt.tight_layout() return fig diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index 7440daeb0a632..10735ae1fa857 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -29,7 +29,8 @@ def format_date_labels(ax: "Axes", rot): label.set_ha("right") label.set_rotation(rot) fig = ax.get_figure() - fig.subplots_adjust(bottom=0.2) + if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): + fig.subplots_adjust(bottom=0.2) def table( From 6b680bc3f46b8b543c9dd9aac0d909700ede7792 Mon Sep 17 00:00:00 2001 From: Michel de Ruiter Date: Mon, 25 Jan 2021 10:26:58 +0100 Subject: [PATCH 2/3] ENH: Fix support for matplotlib's constrained_layout (#25261) --- pandas/plotting/_matplotlib/boxplot.py | 15 +++++++-------- pandas/plotting/_matplotlib/hist.py | 11 +++++------ pandas/plotting/_matplotlib/misc.py | 12 ++++++++---- pandas/plotting/_matplotlib/tools.py | 14 ++++++++++++-- .../tests/plotting/frame/test_frame_subplots.py | 13 +++++++++++++ 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/pandas/plotting/_matplotlib/boxplot.py b/pandas/plotting/_matplotlib/boxplot.py index 8bef8d9ceca98..8460958936923 100644 --- a/pandas/plotting/_matplotlib/boxplot.py +++ b/pandas/plotting/_matplotlib/boxplot.py @@ -14,7 +14,11 @@ from pandas.io.formats.printing import pprint_thing from pandas.plotting._matplotlib.core import LinePlot, MPLPlot from pandas.plotting._matplotlib.style import get_standard_colors -from pandas.plotting._matplotlib.tools import create_subplots, flatten_axes +from pandas.plotting._matplotlib.tools import ( + create_subplots, + flatten_axes, + maybe_adjust_figure, +) if TYPE_CHECKING: from matplotlib.axes import Axes @@ -227,8 +231,7 @@ def _grouped_plot_by_column( byline = by[0] if len(by) == 1 else by fig.suptitle(f"Boxplot grouped by {byline}") - if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): - fig.subplots_adjust(bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) + maybe_adjust_figure(fig, bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) return result @@ -435,11 +438,7 @@ def boxplot_frame_groupby( ) ax.set_title(pprint_thing(key)) ret.loc[key] = d - if ( - not hasattr(fig, "get_constrained_layout") - or not fig.get_constrained_layout() - ): - fig.subplots_adjust(bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) + maybe_adjust_figure(fig, bottom=0.15, top=0.9, left=0.1, right=0.9, wspace=0.2) else: keys, frames = zip(*grouped) if grouped.axis == 0: diff --git a/pandas/plotting/_matplotlib/hist.py b/pandas/plotting/_matplotlib/hist.py index b06aa7a1d5429..7f51539f3d592 100644 --- a/pandas/plotting/_matplotlib/hist.py +++ b/pandas/plotting/_matplotlib/hist.py @@ -11,6 +11,7 @@ from pandas.plotting._matplotlib.tools import ( create_subplots, flatten_axes, + maybe_adjust_figure, set_ticks_props, ) @@ -294,10 +295,9 @@ def plot_group(group, ax): axes, xlabelsize=xlabelsize, xrot=xrot, ylabelsize=ylabelsize, yrot=yrot ) - if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): - fig.subplots_adjust( - bottom=0.15, top=0.9, left=0.1, right=0.9, hspace=0.5, wspace=0.3 - ) + maybe_adjust_figure( + fig, bottom=0.15, top=0.9, left=0.1, right=0.9, hspace=0.5, wspace=0.3 + ) return axes @@ -455,7 +455,6 @@ def hist_frame( set_ticks_props( axes, xlabelsize=xlabelsize, xrot=xrot, ylabelsize=ylabelsize, yrot=yrot ) - if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): - fig.subplots_adjust(wspace=0.3, hspace=0.3) + maybe_adjust_figure(fig, wspace=0.3, hspace=0.3) return axes diff --git a/pandas/plotting/_matplotlib/misc.py b/pandas/plotting/_matplotlib/misc.py index c8a5f3d7f6bee..6dcea52d5b20c 100644 --- a/pandas/plotting/_matplotlib/misc.py +++ b/pandas/plotting/_matplotlib/misc.py @@ -11,7 +11,12 @@ from pandas.io.formats.printing import pprint_thing from pandas.plotting._matplotlib.style import get_standard_colors -from pandas.plotting._matplotlib.tools import create_subplots, set_ticks_props +from pandas.plotting._matplotlib.tools import ( + create_subplots, + do_adjust_figure, + maybe_adjust_figure, + set_ticks_props, +) if TYPE_CHECKING: from matplotlib.axes import Axes @@ -39,8 +44,7 @@ def scatter_matrix( fig, axes = create_subplots(naxes=naxes, figsize=figsize, ax=ax, squeeze=False) # no gaps between subplots - if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): - fig.subplots_adjust(wspace=0, hspace=0) + maybe_adjust_figure(fig, wspace=0, hspace=0) mask = notna(df) @@ -330,7 +334,7 @@ def bootstrap_plot( for axis in axes: plt.setp(axis.get_xticklabels(), fontsize=8) plt.setp(axis.get_yticklabels(), fontsize=8) - if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): + if do_adjust_figure(fig): plt.tight_layout() return fig diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index 10735ae1fa857..4063cbc39a25a 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -23,14 +23,24 @@ from matplotlib.table import Table +def do_adjust_figure(fig): + if not hasattr(fig, "get_constrained_layout"): + return False + return not fig.get_constrained_layout() + + +def maybe_adjust_figure(fig, *args, **kwargs): + if do_adjust_figure(fig): + fig.subplots_adjust(*args, **kwargs) + + def format_date_labels(ax: "Axes", rot): # mini version of autofmt_xdate for label in ax.get_xticklabels(): label.set_ha("right") label.set_rotation(rot) fig = ax.get_figure() - if not hasattr(fig, "get_constrained_layout") or not fig.get_constrained_layout(): - fig.subplots_adjust(bottom=0.2) + maybe_adjust_figure(fig, bottom=0.2) def table( diff --git a/pandas/tests/plotting/frame/test_frame_subplots.py b/pandas/tests/plotting/frame/test_frame_subplots.py index 2e0eecbeaacaa..049f357a4647f 100644 --- a/pandas/tests/plotting/frame/test_frame_subplots.py +++ b/pandas/tests/plotting/frame/test_frame_subplots.py @@ -482,6 +482,19 @@ def test_subplots_sharex_false(self): tm.assert_numpy_array_equal(axs[0].get_xticks(), expected_ax1) tm.assert_numpy_array_equal(axs[1].get_xticks(), expected_ax2) + def test_subplots_constrained_layout(self): + # GH 25261 + idx = date_range(start="now", periods=10) + df = DataFrame(np.random.rand(10, 3), index=idx) + kwargs = {} + if hasattr(self.plt.Figure, "get_constrained_layout"): + kwargs["constrained_layout"] = True + fig, axes = self.plt.subplots(2, **kwargs) + with tm.assert_produces_warning(None): + df.plot(ax=axes[0]) + with tm.ensure_clean(return_filelike=True) as path: + self.plt.savefig(path) + @pytest.mark.parametrize( "index_name, old_label, new_label", [ From f759dd12f9f4c8e80d613332ed56a34762a70fbc Mon Sep 17 00:00:00 2001 From: Michel de Ruiter Date: Thu, 28 Jan 2021 10:35:36 +0100 Subject: [PATCH 3/3] minor improvements after review --- doc/source/whatsnew/v1.3.0.rst | 2 +- pandas/plotting/_matplotlib/tools.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 265e889c71edc..09ed60f544e30 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -341,7 +341,7 @@ Plotting ^^^^^^^^ - Bug in :func:`scatter_matrix` raising when 2d ``ax`` argument passed (:issue:`16253`) -- Support matplotlib's ``constrained_layout=True`` (:issue:`25261`) +- Prevent warnings when matplotlib's ``constrained_layout`` is enabled (:issue:`25261`) - Groupby/resample/rolling diff --git a/pandas/plotting/_matplotlib/tools.py b/pandas/plotting/_matplotlib/tools.py index 688eb9dc092d0..df94b71f5e7a9 100644 --- a/pandas/plotting/_matplotlib/tools.py +++ b/pandas/plotting/_matplotlib/tools.py @@ -19,17 +19,20 @@ if TYPE_CHECKING: from matplotlib.axes import Axes from matplotlib.axis import Axis + from matplotlib.figure import Figure from matplotlib.lines import Line2D from matplotlib.table import Table -def do_adjust_figure(fig): +def do_adjust_figure(fig: Figure): + """Whether fig has constrained_layout enabled.""" if not hasattr(fig, "get_constrained_layout"): return False return not fig.get_constrained_layout() -def maybe_adjust_figure(fig, *args, **kwargs): +def maybe_adjust_figure(fig: Figure, *args, **kwargs): + """Call fig.subplots_adjust unless fig has constrained_layout enabled.""" if do_adjust_figure(fig): fig.subplots_adjust(*args, **kwargs)