From 6460a21555ba6557e1f6f06f4d677d9c19148169 Mon Sep 17 00:00:00 2001 From: Michael Waskom Date: Sun, 31 Jul 2022 17:14:40 -0400 Subject: [PATCH] Workaround for matplotlib rc_context issue (#2925) * Workaround for matplotlib rc_context issue Fixes #2914 * Add some additional comments about this workaround --- doc/whatsnew/v0.12.0.rst | 2 ++ seaborn/axisgrid.py | 10 +++++----- seaborn/utils.py | 29 +++++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/doc/whatsnew/v0.12.0.rst b/doc/whatsnew/v0.12.0.rst index 013200b90f..f26825f5ff 100644 --- a/doc/whatsnew/v0.12.0.rst +++ b/doc/whatsnew/v0.12.0.rst @@ -92,6 +92,8 @@ Other updates - |Fix| Subplot titles will no longer be reset when calling :meth:`FacetGrid.map` or :meth:`FacetGrid.map_dataframe` (:pr:`2705`). +- |Fix| Added a workaround for a matplotlib issue that caused figure-level functions to freeze when `plt.show` was called (:pr:`2925`). + - |Fix| Improved robustness to numerical errors in :func:`kdeplot` (:pr:`2862`). - |Defaults| The `patch.facecolor` rc param is no longer set by :func:`set_palette` (or :func:`set_theme`). This should have no general effect, because the matplotlib default is now `"C0"` (:pr:`2906`). diff --git a/seaborn/axisgrid.py b/seaborn/axisgrid.py index 3bb74f07fd..39fe48145e 100644 --- a/seaborn/axisgrid.py +++ b/seaborn/axisgrid.py @@ -11,7 +11,9 @@ from ._oldcore import VectorPlotter, variable_type, categorical_order from . import utils -from .utils import _check_argument, adjust_legend_subtitles, _draw_figure +from .utils import ( + adjust_legend_subtitles, _check_argument, _draw_figure, _disable_autolayout +) from .palettes import color_palette, blend_palette from ._docstrings import ( DocstringComponents, @@ -389,8 +391,7 @@ def __init__( # --- Initialize the subplot grid - # Disable autolayout so legend_out works properly - with mpl.rc_context({"figure.autolayout": False}): + with _disable_autolayout(): fig = plt.figure(figsize=figsize) if col_wrap is None: @@ -1215,8 +1216,7 @@ def __init__( # Create the figure and the array of subplots figsize = len(x_vars) * height * aspect, len(y_vars) * height - # Disable autolayout so legend_out works - with mpl.rc_context({"figure.autolayout": False}): + with _disable_autolayout(): fig = plt.figure(figsize=figsize) axes = fig.subplots(len(y_vars), len(x_vars), diff --git a/seaborn/utils.py b/seaborn/utils.py index 77c439d9c7..30fc182049 100644 --- a/seaborn/utils.py +++ b/seaborn/utils.py @@ -4,6 +4,7 @@ import inspect import warnings import colorsys +from contextlib import contextmanager from urllib.request import urlopen, urlretrieve import numpy as np @@ -782,7 +783,7 @@ def _assign_default_kwargs(kws, call_func, source_func): # This exists so that axes-level functions and figure-level functions can # both call a Plotter method while having the default kwargs be defined in # the signature of the axes-level function. - # An alternative would be to have a decorator on the method that sets its + # An alternative would be to have a decorator on the method that sets its # defaults based on those defined in the axes-level function. # Then the figure-level function would not need to worry about defaults. # I am not sure which is better. @@ -797,7 +798,12 @@ def _assign_default_kwargs(kws, call_func, source_func): def adjust_legend_subtitles(legend): - """Make invisible-handle "subtitles" entries look more like titles.""" + """ + Make invisible-handle "subtitles" entries look more like titles. + + Note: This function is not part of the public API and may be changed or removed. + + """ # Legend title not in rcParams until 3.0 font_size = plt.rcParams.get("legend.title_fontsize", None) hpackers = legend.findobj(mpl.offsetbox.VPacker)[0].get_children() @@ -834,3 +840,22 @@ def _deprecate_ci(errorbar, ci): warnings.warn(msg, FutureWarning, stacklevel=3) return errorbar + + +@contextmanager +def _disable_autolayout(): + """Context manager for preventing rc-controlled auto-layout behavior.""" + # This is a workaround for an issue in matplotlib, for details see + # https://github.com/mwaskom/seaborn/issues/2914 + # The only affect of this rcParam is to set the default value for + # layout= in plt.figure, so we could just do that instead. + # But then we would need to own the complexity of the transition + # from tight_layout=True -> layout="tight". This seems easier, + # but can be removed when (if) that is simpler on the matplotlib side, + # or if the layout algorithms are improved to handle figure legends. + orig_val = mpl.rcParams["figure.autolayout"] + try: + mpl.rcParams["figure.autolayout"] = False + yield + finally: + mpl.rcParams["figure.autolayout"] = orig_val