diff --git a/doc/api/api_changes_3.3/behaviour.rst b/doc/api/api_changes_3.3/behaviour.rst index 12493029775a..f70187b41d39 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 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 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. diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f6997433da9c..0093c09045f4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -169,8 +169,12 @@ 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 None: + 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..d25bfaa5df51 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,8 @@ 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: + title_offset_points = 6 self.titleOffsetTrans = mtransforms.ScaledTranslation( 0.0, title_offset_points / 72, self.figure.dpi_scale_trans) @@ -2648,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'): @@ -2657,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 diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 8153000a3df2..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': [6.0, validate_float], # pad from axes top to title in points + '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 1eb2eda9abe5..cc268c3c3ab5 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -5523,6 +5523,7 @@ 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) ax.xaxis.set_ticks_position('top') ax.set_title('xlabel top') fig.canvas.draw() 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..e3bbb40bcc2e 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.