From c88e7c8f17e3fc513fac5c66e48e3b23add67c05 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 22 Mar 2020 19:12:14 -0700 Subject: [PATCH 1/4] ENH/FIX: Don't auto reposition axes if axes.titlepad not None Automatic title repositioning is now suppressed if the title padding is set to a non-None value by the user (either in the pad kwarg, or the rcParam axes.titlepad) --- doc/api/api_changes_3.3/behaviour.rst | 14 +++++++++++++- lib/matplotlib/axes/_axes.py | 6 ++++++ lib/matplotlib/axes/_base.py | 11 ++++++----- lib/matplotlib/rcsetup.py | 2 +- lib/matplotlib/tests/test_axes.py | 15 +++++++++++++++ lib/matplotlib/tests/test_tightlayout.py | 4 ++-- matplotlibrc.template | 4 ++-- 7 files changed, 45 insertions(+), 11 deletions(-) diff --git a/doc/api/api_changes_3.3/behaviour.rst b/doc/api/api_changes_3.3/behaviour.rst index 12493029775a..f1ef9c8426d9 100644 --- a/doc/api/api_changes_3.3/behaviour.rst +++ b/doc/api/api_changes_3.3/behaviour.rst @@ -98,7 +98,7 @@ deprecation warning. `~.Axes.errorbar` now color cycles when only errorbar color is set ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Previously setting the *ecolor* would turn off automatic color cycling for the plot, leading to the +Previously setting the *ecolor* would turn off automatic color cycling for the plot, leading to the lines and markers defaulting to whatever the first color in the color cycle was in the case of multiple plot calls. @@ -171,3 +171,15 @@ The default method used to format `.Slider` values has been changed to use a values are displayed with an appropriate number of significant digits even if they are much smaller or much bigger than 1. To restore the old behavior, explicitly pass a "%1.2f" as the *valfmt* parameter to `.Slider`. + +:rc:`axes.titlepad` and *pad* argument of `~.Axes.set_title` now default to ``None`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since 3.0, Axes titles are automatically repositioned, primarily to avoid +xlabels or xticks at the top of the axes. The user could turn this +off using the optional *y* argument to `~.Axes.set_title`). However, that +made it impossible to manually set the *pad* argument (without also setting the +*y* argument). Now *pad* defaults to ``None`` and the +rcParam :rc:`axes.titlepad` defaults to ``None``, which will +allow automatic title placement. However, if these are set to a float, then +the automatic placement of the title is turned off. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f6997433da9c..0dd36c26af35 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -169,8 +169,14 @@ def set_title(self, label, fontdict=None, loc=None, pad=None, **kwargs): titlecolor = rcParams['axes.titlecolor'] if not cbook._str_lower_equal(titlecolor, 'auto'): default["color"] = titlecolor + if pad is None: pad = rcParams['axes.titlepad'] + if pad is not None: + self._autotitlepos = False + else: + pad = 6 # default. + self._set_title_offset_trans(float(pad)) title.set_text(label) title.update(default) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 61a0d64cd86f..526ad5c2724d 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1139,11 +1139,9 @@ def cla(self): verticalalignment='baseline', horizontalalignment='right', ) - title_offset_points = mpl.rcParams['axes.titlepad'] - # refactor this out so it can be called in ax.set_title if - # pad argument used... - self._set_title_offset_trans(title_offset_points) - # determine if the title position has been set manually: + # this needs to be called in case cla is not called on + # axes later... + self._set_title_offset_trans(mpl.rcParams['axes.titlepad']) self._autotitlepos = None for _title in (self.title, self._left_title, self._right_title): @@ -1200,6 +1198,9 @@ def _set_title_offset_trans(self, title_offset_points): Set the offset for the title either from :rc:`axes.titlepad` or from set_title kwarg ``pad``. """ + if title_offset_points is None: + # dummy default in case cla hasn't been called. + title_offset_points = 0 self.titleOffsetTrans = mtransforms.ScaledTranslation( 0.0, title_offset_points / 72, self.figure.dpi_scale_trans) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 8153000a3df2..0e387970253f 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1219,7 +1219,7 @@ def _convert_validator_spec(key, conv): 'axes.titlelocation': ['center', ['left', 'center', 'right']], # alignment of axes title 'axes.titleweight': ['normal', validate_fontweight], # font weight of axes title 'axes.titlecolor': ['auto', validate_color_or_auto], # font color of axes title - 'axes.titlepad': [6.0, validate_float], # pad from axes top to title in points + 'axes.titlepad': [None, validate_float_or_None], # pad from axes top to title in points; None means allow auto position of title 'axes.grid': [False, validate_bool], # display grid or not 'axes.grid.which': ['major', ['minor', 'both', 'major']], # set whether the grid is drawn on # 'major' 'minor' or 'both' ticks diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 1eb2eda9abe5..6b7f909966d6 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5523,6 +5523,8 @@ def test_titlesetpos(): def test_title_xticks_top(): # Test that title moves if xticks on top of axes. fig, ax = plt.subplots() + # set to the new default (from 5, which will suppress title reposition) + mpl.rcParams['axes.titlepad'] = None ax.xaxis.set_ticks_position('top') ax.set_title('xlabel top') fig.canvas.draw() @@ -5532,6 +5534,8 @@ def test_title_xticks_top(): def test_title_xticks_top_both(): # Test that title moves if xticks on top of axes. fig, ax = plt.subplots() + # set to the new default (from 5, which will suppress title reposition) + mpl.rcParams['axes.titlepad'] = None ax.tick_params(axis="x", bottom=True, top=True, labelbottom=True, labeltop=True) ax.set_title('xlabel top') @@ -5539,6 +5543,17 @@ def test_title_xticks_top_both(): assert ax.title.get_position()[1] > 1.04 +def test_title_noauto_pad(): + # test that if we pad, then the title does not autopos + + fig, ax = plt.subplots() + ax.set_title("Title 1", pad=-20) + ax.tick_params(axis="x", + bottom=True, top=True, labelbottom=True, labeltop=True) + fig.canvas.draw() + assert ax.title.get_position()[1] == 1.0 + + def test_offset_label_color(): # Tests issue 6440 fig = plt.figure() diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index 674cec00b06b..8c38d27d9974 100644 --- a/lib/matplotlib/tests/test_tightlayout.py +++ b/lib/matplotlib/tests/test_tightlayout.py @@ -19,7 +19,7 @@ def example_plot(ax, fontsize=12): ax.set_title('Title', fontsize=fontsize) -@image_comparison(['tight_layout1']) +@image_comparison(['tight_layout1'], tol=1.87) def test_tight_layout1(): """Test tight_layout for a single subplot.""" fig, ax = plt.subplots() @@ -115,7 +115,7 @@ def test_tight_layout6(): h_pad=0.45) -@image_comparison(['tight_layout7']) +@image_comparison(['tight_layout7'], tol=1.87) def test_tight_layout7(): # tight layout with left and right titles fontsize = 24 diff --git a/matplotlibrc.template b/matplotlibrc.template index 2596a67f309b..5881f1337bfb 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -326,7 +326,7 @@ #mathtext.sf: sans #mathtext.tt: monospace #mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix' - # 'stixsans'] when a symbol can not be found in one of the + # 'stixsans'] when a symbol can not be found in one of the # custom math fonts. Select 'None' to not perform fallback # and replace the missing character by a dummy symbol. #mathtext.default: it # The default font to use for math. @@ -352,7 +352,7 @@ #axes.titleweight: normal # font weight of title #axes.titlecolor: auto # color of the axes title, auto falls back to # text.color as default value -#axes.titlepad: 6.0 # pad between axes and title in points +#axes.titlepad: None # pad between axes and title in points, None means allow automatic positioning #axes.labelsize: medium # fontsize of the x any y labels #axes.labelpad: 4.0 # space between label and axis #axes.labelweight: normal # weight of the x and y labels From a4a45708c35821f23b0c5b9fc98a091186f4c6d4 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 13 Apr 2020 16:20:45 -0700 Subject: [PATCH 2/4] FIX: do this with offset from top --- lib/matplotlib/axes/_axes.py | 4 +--- lib/matplotlib/axes/_base.py | 34 ++++++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 0dd36c26af35..0093c09045f4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -172,9 +172,7 @@ def set_title(self, label, fontdict=None, loc=None, pad=None, **kwargs): if pad is None: pad = rcParams['axes.titlepad'] - if pad is not None: - self._autotitlepos = False - else: + if pad is None: pad = 6 # default. self._set_title_offset_trans(float(pad)) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 526ad5c2724d..d25bfaa5df51 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1199,8 +1199,7 @@ def _set_title_offset_trans(self, title_offset_points): or from set_title kwarg ``pad``. """ if title_offset_points is None: - # dummy default in case cla hasn't been called. - title_offset_points = 0 + title_offset_points = 6 self.titleOffsetTrans = mtransforms.ScaledTranslation( 0.0, title_offset_points / 72, self.figure.dpi_scale_trans) @@ -2649,7 +2648,7 @@ def _update_title_position(self, renderer): else: ax.apply_aspect() axs = axs + [ax] - top = 0 + top = 0.0 for ax in axs: if (ax.xaxis.get_ticks_position() in ['top', 'unknown'] or ax.xaxis.get_label_position() == 'top'): @@ -2658,21 +2657,24 @@ def _update_title_position(self, renderer): bb = ax.get_window_extent(renderer) if bb is not None: top = max(top, bb.ymax) - if title.get_window_extent(renderer).ymin < top: - _, y = self.transAxes.inverted().transform((0, top)) - title.set_position((x, y)) - # empirically, this doesn't always get the min to top, - # so we need to adjust again. + _, y = self.transAxes.inverted().transform((0, top)) + title.set_position((x, y)) + if 0: if title.get_window_extent(renderer).ymin < top: - _, y = self.transAxes.inverted().transform( - (0., 2 * top - title.get_window_extent(renderer).ymin)) + _, y = self.transAxes.inverted().transform((0, top)) title.set_position((x, y)) - - ymax = max(title.get_position()[1] for title in titles) - for title in titles: - # now line up all the titles at the highest baseline. - x, _ = title.get_position() - title.set_position((x, ymax)) + # empirically, this doesn't always get the min to top, + # so we need to adjust again. + if title.get_window_extent(renderer).ymin < top: + _, y = self.transAxes.inverted().transform( + (0., 2 * top - title.get_window_extent(renderer).ymin)) + title.set_position((x, y)) + + ymax = max(title.get_position()[1] for title in titles) + for title in titles: + # now line up all the titles at the highest baseline. + x, _ = title.get_position() + title.set_position((x, ymax)) # Drawing @martist.allow_rasterization From edf17a8ea185c9b49ba9e45b64da9ff42ff62ac2 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 13 Apr 2020 16:26:00 -0700 Subject: [PATCH 3/4] TST --- lib/matplotlib/rcsetup.py | 2 +- lib/matplotlib/tests/test_axes.py | 14 -------------- matplotlibrc.template | 2 +- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 0e387970253f..d626907ee48b 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1219,7 +1219,7 @@ def _convert_validator_spec(key, conv): 'axes.titlelocation': ['center', ['left', 'center', 'right']], # alignment of axes title 'axes.titleweight': ['normal', validate_fontweight], # font weight of axes title 'axes.titlecolor': ['auto', validate_color_or_auto], # font color of axes title - 'axes.titlepad': [None, validate_float_or_None], # pad from axes top to title in points; None means allow auto position of title + 'axes.titlepad': [6.0, validate_float], # pad from axes top to title in points; 'axes.grid': [False, validate_bool], # display grid or not 'axes.grid.which': ['major', ['minor', 'both', 'major']], # set whether the grid is drawn on # 'major' 'minor' or 'both' ticks diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 6b7f909966d6..cc268c3c3ab5 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5524,7 +5524,6 @@ def test_title_xticks_top(): # Test that title moves if xticks on top of axes. fig, ax = plt.subplots() # set to the new default (from 5, which will suppress title reposition) - mpl.rcParams['axes.titlepad'] = None ax.xaxis.set_ticks_position('top') ax.set_title('xlabel top') fig.canvas.draw() @@ -5534,8 +5533,6 @@ def test_title_xticks_top(): def test_title_xticks_top_both(): # Test that title moves if xticks on top of axes. fig, ax = plt.subplots() - # set to the new default (from 5, which will suppress title reposition) - mpl.rcParams['axes.titlepad'] = None ax.tick_params(axis="x", bottom=True, top=True, labelbottom=True, labeltop=True) ax.set_title('xlabel top') @@ -5543,17 +5540,6 @@ def test_title_xticks_top_both(): assert ax.title.get_position()[1] > 1.04 -def test_title_noauto_pad(): - # test that if we pad, then the title does not autopos - - fig, ax = plt.subplots() - ax.set_title("Title 1", pad=-20) - ax.tick_params(axis="x", - bottom=True, top=True, labelbottom=True, labeltop=True) - fig.canvas.draw() - assert ax.title.get_position()[1] == 1.0 - - def test_offset_label_color(): # Tests issue 6440 fig = plt.figure() diff --git a/matplotlibrc.template b/matplotlibrc.template index 5881f1337bfb..e3bbb40bcc2e 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -352,7 +352,7 @@ #axes.titleweight: normal # font weight of title #axes.titlecolor: auto # color of the axes title, auto falls back to # text.color as default value -#axes.titlepad: None # pad between axes and title in points, None means allow automatic positioning +#axes.titlepad: 6.0 # pad between axes and title in points #axes.labelsize: medium # fontsize of the x any y labels #axes.labelpad: 4.0 # space between label and axis #axes.labelweight: normal # weight of the x and y labels From 9271fe614524dbcb84b6b1975a9de531a1f411ef Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Mon, 13 Apr 2020 16:28:26 -0700 Subject: [PATCH 4/4] DOC --- doc/api/api_changes_3.3/behaviour.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api/api_changes_3.3/behaviour.rst b/doc/api/api_changes_3.3/behaviour.rst index f1ef9c8426d9..f70187b41d39 100644 --- a/doc/api/api_changes_3.3/behaviour.rst +++ b/doc/api/api_changes_3.3/behaviour.rst @@ -172,14 +172,14 @@ values are displayed with an appropriate number of significant digits even if they are much smaller or much bigger than 1. To restore the old behavior, explicitly pass a "%1.2f" as the *valfmt* parameter to `.Slider`. -:rc:`axes.titlepad` and *pad* argument of `~.Axes.set_title` now default to ``None`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:rc:`axes.titlepad` and *pad* argument of `~.Axes.set_title` now work +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Since 3.0, Axes titles are automatically repositioned, primarily to avoid xlabels or xticks at the top of the axes. The user could turn this off using the optional *y* argument to `~.Axes.set_title`). However, that made it impossible to manually set the *pad* argument (without also setting the -*y* argument). Now *pad* defaults to ``None`` and the -rcParam :rc:`axes.titlepad` defaults to ``None``, which will -allow automatic title placement. However, if these are set to a float, then -the automatic placement of the title is turned off. +*y* argument). Now the *pad* argument and :rc:`axes.titlepad` are used, and +are relative to the top decorator on the axis. If users want the old +behavior of the pad being relative to the top of axis, set ``y=1.001`` +in `~.Axes.set_title` to bypas the auto-positioning.