From 8ce56007b75ed088a388176ac6fc19834c64413a Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Fri, 11 May 2018 03:28:40 -0700 Subject: [PATCH] Group figure.subplot.* rc to a single rcParam. The idea is to make it easier to restore a figure's subplotparams to the rc-provided defaults. Specifically, this can now be done with fig.subplots_adjust(**rcParams["figure.subplot"]) or fig.subplots_adjust(**dict(rcParams["figure.subplot"], left=...)) if some values need to be overridden. Also make Figure.clf() restore the subplotsparams to these rc-provided defaults (that is the original motivation for the change). Note that this PR runs into a limitation of the rcParams API: it would be nicer if the validators took the rcParams as parameters (e.g. implicitly by being methods of the RcParams class), so that they can actually depend on another key in the same instance. --- .../2018-02-15-AL-deprecations.rst | 3 + lib/matplotlib/__init__.py | 5 ++ lib/matplotlib/cbook/deprecation.py | 7 +- lib/matplotlib/figure.py | 20 +++--- .../mpl-data/stylelib/_classic_test.mplstyle | 12 +--- .../mpl-data/stylelib/classic.mplstyle | 12 +--- .../stylelib/fivethirtyeight.mplstyle | 4 +- lib/matplotlib/rcsetup.py | 70 ++++++++++++++----- lib/matplotlib/testing/decorators.py | 2 +- lib/matplotlib/tests/test_figure.py | 8 +++ lib/matplotlib/tests/test_rcparams.py | 2 +- matplotlibrc.template | 11 +-- 12 files changed, 95 insertions(+), 61 deletions(-) diff --git a/doc/api/next_api_changes/2018-02-15-AL-deprecations.rst b/doc/api/next_api_changes/2018-02-15-AL-deprecations.rst index 51198eef7a4e..3e460ba36410 100644 --- a/doc/api/next_api_changes/2018-02-15-AL-deprecations.rst +++ b/doc/api/next_api_changes/2018-02-15-AL-deprecations.rst @@ -36,4 +36,7 @@ The following classes, methods, functions, and attributes are deprecated: - ``text.Annotation.arrow``, The following rcParams are deprecated: + +- ``figure.subplot.left``, ``figure.subplot.right``, etc (instead, set the + single ``figure.subplot`` rcParam to the appropriate dict value). - ``pgf.debug`` (the pgf backend relies on logging), diff --git a/lib/matplotlib/__init__.py b/lib/matplotlib/__init__.py index 09856d9e1cfc..1a706a378be4 100644 --- a/lib/matplotlib/__init__.py +++ b/lib/matplotlib/__init__.py @@ -906,6 +906,11 @@ def __getitem__(self, key): mplDeprecation) return None + elif key.startswith('figure.subplot.'): + cbook.warn_deprecated( + "3.0", + "{} is deprecated; use figure.subplot instead".format(key)) + val = dict.__getitem__(self, key) if inverse_alt is not None: return inverse_alt(val) diff --git a/lib/matplotlib/cbook/deprecation.py b/lib/matplotlib/cbook/deprecation.py index 9c8c83225ba7..53769e325172 100644 --- a/lib/matplotlib/cbook/deprecation.py +++ b/lib/matplotlib/cbook/deprecation.py @@ -48,7 +48,7 @@ def _generate_deprecation_message( def warn_deprecated( since, message='', name='', alternative='', pending=False, - obj_type='attribute', addendum='', *, removal=''): + obj_type='attribute', addendum='', *, removal='', stacklevel=2): """ Used to display deprecation warning in a standard way. @@ -88,6 +88,9 @@ def warn_deprecated( addendum : str, optional Additional text appended directly to the final message. + stacklevel : int, optional + The stack level used by the `warnings.warn` call. + Examples -------- @@ -100,7 +103,7 @@ def warn_deprecated( """ message = _generate_deprecation_message( since, message, name, alternative, pending, obj_type, removal=removal) - warnings.warn(message, mplDeprecation, stacklevel=2) + warnings.warn(message, mplDeprecation, stacklevel=stacklevel) def deprecated(since, message='', name='', alternative='', pending=False, diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b97655e8fb39..821945261fe7 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -228,20 +228,18 @@ def reset(): self.hspace = thishspace if self.validate: - if self.left >= self.right: + if not self.left < self.right: reset() - raise ValueError('left cannot be >= right') - - if self.bottom >= self.top: + raise ValueError('One must have right < left') + if not self.bottom < self.top: reset() - raise ValueError('bottom cannot be >= top') + raise ValueError('One must have bottom < top') def _update_this(self, s, val): if val is None: val = getattr(self, s, None) if val is None: - key = 'figure.subplot.' + s - val = rcParams[key] + val = rcParams['figure.subplot'][s] setattr(self, s, val) @@ -1406,8 +1404,11 @@ def clf(self, keep_observers=False): """ Clear the figure. - Set *keep_observers* to True if, for example, - a gui widget is tracking the axes in the figure. + Parameters + ---------- + keep_observers : bool, optional + Set *keep_observers* to True if, for example, + a gui widget is tracking the axes in the figure. """ self.suppressComposite = None self.callbacks = cbook.CallbackRegistry() @@ -1429,6 +1430,7 @@ def clf(self, keep_observers=False): if not keep_observers: self._axobservers = [] self._suptitle = None + self.subplotpars.update(**rcParams['figure.subplot']) if self.get_constrained_layout(): layoutbox.nonetree(self._layoutbox) self.stale = True diff --git a/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle b/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle index c42222ad8f19..a5969c48c531 100644 --- a/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/_classic_test.mplstyle @@ -307,16 +307,8 @@ figure.autolayout : False # When True, automatically adjust subplot # parameters to make the plot fit the figure figure.frameon : True -# The figure subplot parameters. All dimensions are a fraction of the -# figure width or height -figure.subplot.left : 0.125 # the left side of the subplots of the figure -figure.subplot.right : 0.9 # the right side of the subplots of the figure -figure.subplot.bottom : 0.1 # the bottom of the subplots of the figure -figure.subplot.top : 0.9 # the top of the subplots of the figure -figure.subplot.wspace : 0.2 # the amount of width reserved for space between subplots, - # expressed as a fraction of the average axis width -figure.subplot.hspace : 0.2 # the amount of height reserved for space between subplots, - # expressed as a fraction of the average axis height +# The figure subplot parameters (see matplotlib.figure.SubplotParams). +figure.subplot : {"left": 0.125, "right": 0.9, "bottom": 0.1, "top": 0.9, "wspace": 0.2, "hspace": 0.2} ### IMAGES image.aspect : equal # equal | auto | a number diff --git a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle index 94ae5bf7a4f3..82a0984a4933 100644 --- a/lib/matplotlib/mpl-data/stylelib/classic.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/classic.mplstyle @@ -311,16 +311,8 @@ figure.autolayout : False # When True, automatically adjust subplot # parameters to make the plot fit the figure figure.frameon : True -# The figure subplot parameters. All dimensions are a fraction of the -# figure width or height -figure.subplot.left : 0.125 # the left side of the subplots of the figure -figure.subplot.right : 0.9 # the right side of the subplots of the figure -figure.subplot.bottom : 0.1 # the bottom of the subplots of the figure -figure.subplot.top : 0.9 # the top of the subplots of the figure -figure.subplot.wspace : 0.2 # the amount of width reserved for space between subplots, - # expressed as a fraction of the average axis width -figure.subplot.hspace : 0.2 # the amount of height reserved for space between subplots, - # expressed as a fraction of the average axis height +# The figure subplot parameters (see matplotlib.figure.SubplotParams). +figure.subplot : {"left": 0.125, "right": 0.9, "bottom": 0.1, "top": 0.9, "wspace": 0.2, "hspace": 0.2} ### IMAGES image.aspect : equal # equal | auto | a number diff --git a/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle b/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle index 738db39f5f80..44cf365dae9c 100644 --- a/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle +++ b/lib/matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle @@ -34,7 +34,5 @@ font.size:14.0 savefig.edgecolor: f0f0f0 savefig.facecolor: f0f0f0 -figure.subplot.left: 0.08 -figure.subplot.right: 0.95 -figure.subplot.bottom: 0.07 +figure.subplot : {"left": 0.08, "right": 0.95, "bottom": 0.07} figure.facecolor: f0f0f0 diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 7f2e9806310d..a56f93cd6259 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -15,17 +15,17 @@ """ import six +import ast from collections import Iterable, Mapping from functools import reduce import operator import os -import warnings import re +import warnings from matplotlib import cbook -from matplotlib.cbook import mplDeprecation, deprecated, ls_mapper -from matplotlib.fontconfig_pattern import parse_fontconfig_pattern from matplotlib.colors import is_color_like +from matplotlib.fontconfig_pattern import parse_fontconfig_pattern # Don't let the original cycler collide with our validating cycler from cycler import Cycler, cycler as ccycler @@ -926,7 +926,8 @@ def validate_webagg_address(s): # ls_mapper, and a list of possible strings read from Line2D.set_linestyle _validate_named_linestyle = ValidateInStrings( 'linestyle', - [*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''], + [*cbook.ls_mapper.keys(), *cbook.ls_mapper.values(), + 'None', 'none', ' ', ''], ignorecase=True) @@ -971,6 +972,45 @@ def _validate_linestyle(ls): "sequence.".format(ls)) +def _validate_subplot(s): + d = ast.literal_eval(s) if isinstance(s, str) else s + try: + # These imports must be delayed because they are not available at + # startup (so we just assume that the default we provide ourselves are + # valid). + from matplotlib import rcParams + from matplotlib.figure import SubplotParams + except ImportError: + pass + else: + d = {**rcParams["figure.subplot"], **d} + SubplotParams(**d) + for key, value in d.items(): + dict.__setitem__(rcParams, "figure.subplot.{}".format(key), value) + return d + + +def _validate_subplot_key(key): + def validator(s): + val = ast.literal_eval(s) if isinstance(s, str) else s + try: + # See above re: delayed imports. + from matplotlib import rcParams + from matplotlib.figure import SubplotParams + except ImportError: + pass + else: + cbook.warn_deprecated( + "3.0", "figure.subplot.{} is deprecated; set the " + "corresponding key in figure.subplot instead.".format(key), + stacklevel=4) + d = {**rcParams["figure.subplot"], key: val} + SubplotParams(**d) + dict.__setitem__(rcParams, "figure.subplot", d) + return val + return validator + + # a map from key -> value, converter defaultParams = { 'backend': ['Agg', validate_backend], # agg is certainly @@ -1317,18 +1357,16 @@ def _validate_linestyle(ls): 'figure.autolayout': [False, validate_bool], 'figure.max_open_warning': [20, validate_int], - 'figure.subplot.left': [0.125, ValidateInterval(0, 1, closedmin=True, - closedmax=True)], - 'figure.subplot.right': [0.9, ValidateInterval(0, 1, closedmin=True, - closedmax=True)], - 'figure.subplot.bottom': [0.11, ValidateInterval(0, 1, closedmin=True, - closedmax=True)], - 'figure.subplot.top': [0.88, ValidateInterval(0, 1, closedmin=True, - closedmax=True)], - 'figure.subplot.wspace': [0.2, ValidateInterval(0, 1, closedmin=True, - closedmax=False)], - 'figure.subplot.hspace': [0.2, ValidateInterval(0, 1, closedmin=True, - closedmax=False)], + 'figure.subplot': [{'left': 0.125, 'right': 0.9, + 'bottom': 0.11, 'top': 0.88, + 'wspace': 0.2, 'hspace': 0.2}, + _validate_subplot], + 'figure.subplot.left': [0.125, _validate_subplot_key('left')], + 'figure.subplot.right': [0.9, _validate_subplot_key('right')], + 'figure.subplot.bottom': [0.11, _validate_subplot_key('bottom')], + 'figure.subplot.top': [0.88, _validate_subplot_key('top')], + 'figure.subplot.wspace': [0.2, _validate_subplot_key('wspace')], + 'figure.subplot.hspace': [0.2, _validate_subplot_key('hspace')], # do constrained_layout. 'figure.constrained_layout.use': [False, validate_bool], diff --git a/lib/matplotlib/testing/decorators.py b/lib/matplotlib/testing/decorators.py index 4bf5d081d9cd..8f1258746923 100644 --- a/lib/matplotlib/testing/decorators.py +++ b/lib/matplotlib/testing/decorators.py @@ -31,7 +31,7 @@ def _do_cleanup(original_units_registry, original_settings): plt.close('all') mpl.rcParams.clear() - mpl.rcParams.update(original_settings) + dict.update(mpl.rcParams, original_settings) matplotlib.units.registry.clear() matplotlib.units.registry.update(original_units_registry) warnings.resetwarnings() # reset any warning filters set in tests diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index d0079012a5e6..d8370e49db0d 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -383,3 +383,11 @@ def test_fspath(fmt, tmpdir): # All the supported formats include the format name (case-insensitive) # in the first 100 bytes. assert fmt.encode("ascii") in file.read(100).lower() + + +def test_clf_subplotpars(): + fig = plt.figure() + orig_subplot_vars = vars(fig.subplotpars).copy() + fig.subplots_adjust(left=0.1) + fig.clf() + assert vars(fig.subplotpars) == orig_subplot_vars diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 14b589780c0a..dbb9938c9ce7 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -476,7 +476,7 @@ def test_if_rctemplate_is_up_to_date(): continue if k in deprecated: continue - if "verbose" in k: + if "verbose" in k or "figure.subplot." in k: continue found = False for line in rclines: diff --git a/matplotlibrc.template b/matplotlibrc.template index f4549755ed01..edf6c7dcd8aa 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -436,15 +436,8 @@ backend : $TEMPLATE_BACKEND #figure.max_open_warning : 20 ## The maximum number of figures to open through ## the pyplot interface before emitting a warning. ## If less than one this feature is disabled. -## The figure subplot parameters. All dimensions are a fraction of the -#figure.subplot.left : 0.125 ## the left side of the subplots of the figure -#figure.subplot.right : 0.9 ## the right side of the subplots of the figure -#figure.subplot.bottom : 0.11 ## the bottom of the subplots of the figure -#figure.subplot.top : 0.88 ## the top of the subplots of the figure -#figure.subplot.wspace : 0.2 ## the amount of width reserved for space between subplots, - ## expressed as a fraction of the average axis width -#figure.subplot.hspace : 0.2 ## the amount of height reserved for space between subplots, - ## expressed as a fraction of the average axis height +## The figure subplot parameters (see matplotlib.figure.SubplotParams). +#figure.subplot : {"left": 0.125, "right": 0.9, "bottom": 0.1, "top": 0.9, "wspace": 0.2, "hspace": 0.2} ## Figure layout #figure.autolayout : False ## When True, automatically adjust subplot