Skip to content

Commit d06a3df

Browse files
authored
ENH: allow title autopositioning to be turned off (#17127)
* ENH: allow title autopositioning to be turned off * DOC
1 parent e39c67b commit d06a3df

File tree

11 files changed

+92
-30
lines changed

11 files changed

+92
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
`~.axes.Axes.set_title` gains a y keyword argument to control auto positioning
2+
------------------------------------------------------------------------------
3+
`~.axes.Axes.set_title` tries to auto-position the title to avoid any
4+
decorators on the top x-axis. This is not always desirable so now
5+
*y* is an explicit keyword argument of `~.axes.Axes.set_title`. It
6+
defaults to *None* which means to use auto-positioning. If a value is
7+
supplied (i.e. the pre-3.0 default was ``y=1.0``) then auto-positioning is
8+
turned off. This can also be set with the new rcParameter :rc:`axes.titley`.

examples/text_labels_and_annotations/titles_demo.py

+45-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""
2-
===========
3-
Titles Demo
4-
===========
2+
=================
3+
Title positioning
4+
=================
55
6-
matplotlib can display plot titles centered, flush with the left side of
6+
Matplotlib can display plot titles centered, flush with the left side of
77
a set of axes, and flush with the right side of a set of axes.
88
99
"""
@@ -16,3 +16,44 @@
1616
plt.title('Right Title', loc='right')
1717

1818
plt.show()
19+
20+
###########################################################################
21+
# The vertical position is automatically chosen to avoid decorations
22+
# (i.e. labels and ticks) on the topmost x-axis:
23+
24+
fig, axs = plt.subplots(1, 2, constrained_layout=True)
25+
26+
ax = axs[0]
27+
ax.plot(range(10))
28+
ax.xaxis.set_label_position('top')
29+
ax.set_xlabel('X-label')
30+
ax.set_title('Center Title')
31+
32+
ax = axs[1]
33+
ax.plot(range(10))
34+
ax.xaxis.set_label_position('top')
35+
ax.xaxis.tick_top()
36+
ax.set_xlabel('X-label')
37+
ax.set_title('Center Title')
38+
plt.show()
39+
40+
###########################################################################
41+
# Automatic positioning can be turned off by manually specifying the
42+
# *y* kwarg for the title or setting :rc:`axes.titley` in the rcParams.
43+
44+
fig, axs = plt.subplots(1, 2, constrained_layout=True)
45+
46+
ax = axs[0]
47+
ax.plot(range(10))
48+
ax.xaxis.set_label_position('top')
49+
ax.set_xlabel('X-label')
50+
ax.set_title('Manual y', y=1.0, pad=-14)
51+
52+
plt.rcParams['axes.titley'] = 1.0 # y is in axes-relative co-ordinates.
53+
plt.rcParams['axes.titlepad'] = -14 # pad is in points...
54+
ax = axs[1]
55+
ax.plot(range(10))
56+
ax.set_xlabel('X-label')
57+
ax.set_title('rcParam y')
58+
59+
plt.show()

lib/matplotlib/axes/_axes.py

+15-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ def get_title(self, loc="center"):
114114
title = cbook._check_getitem(titles, loc=loc.lower())
115115
return title.get_text()
116116

117-
def set_title(self, label, fontdict=None, loc=None, pad=None, **kwargs):
117+
def set_title(self, label, fontdict=None, loc=None, pad=None, *, y=None,
118+
**kwargs):
118119
"""
119120
Set a title for the axes.
120121
@@ -140,6 +141,11 @@ def set_title(self, label, fontdict=None, loc=None, pad=None, **kwargs):
140141
loc : {'center', 'left', 'right'}, default: :rc:`axes.titlelocation`
141142
Which title to set.
142143
144+
y : float, default: :rc:`axes.titley`
145+
Vertical axes loation for the title (1.0 is the top). If
146+
None (the default), y is determined automatically to avoid
147+
decorators on the axes.
148+
143149
pad : float, default: :rc:`axes.titlepad`
144150
The offset of the title from the top of the axes, in points.
145151
@@ -157,6 +163,14 @@ def set_title(self, label, fontdict=None, loc=None, pad=None, **kwargs):
157163
if loc is None:
158164
loc = rcParams['axes.titlelocation']
159165

166+
if y is None:
167+
y = rcParams['axes.titley']
168+
if y is None:
169+
y = 1.0
170+
else:
171+
self._autotitlepos = False
172+
kwargs['y'] = y
173+
160174
titles = {'left': self._left_title,
161175
'center': self.title,
162176
'right': self._right_title}

lib/matplotlib/axes/_base.py

+10-15
Original file line numberDiff line numberDiff line change
@@ -1122,19 +1122,26 @@ def cla(self):
11221122
size=mpl.rcParams['axes.titlesize'],
11231123
weight=mpl.rcParams['axes.titleweight'])
11241124

1125+
y = mpl.rcParams['axes.titley']
1126+
if y is None:
1127+
y = 1.0
1128+
self._autotitlepos = True
1129+
else:
1130+
self._autotitlepos = False
1131+
11251132
self.title = mtext.Text(
1126-
x=0.5, y=1.0, text='',
1133+
x=0.5, y=y, text='',
11271134
fontproperties=props,
11281135
verticalalignment='baseline',
11291136
horizontalalignment='center',
11301137
)
11311138
self._left_title = mtext.Text(
1132-
x=0.0, y=1.0, text='',
1139+
x=0.0, y=y, text='',
11331140
fontproperties=props.copy(),
11341141
verticalalignment='baseline',
11351142
horizontalalignment='left', )
11361143
self._right_title = mtext.Text(
1137-
x=1.0, y=1.0, text='',
1144+
x=1.0, y=y, text='',
11381145
fontproperties=props.copy(),
11391146
verticalalignment='baseline',
11401147
horizontalalignment='right',
@@ -1143,8 +1150,6 @@ def cla(self):
11431150
# refactor this out so it can be called in ax.set_title if
11441151
# pad argument used...
11451152
self._set_title_offset_trans(title_offset_points)
1146-
# determine if the title position has been set manually:
1147-
self._autotitlepos = None
11481153

11491154
for _title in (self.title, self._left_title, self._right_title):
11501155
self._set_artist_props(_title)
@@ -2622,16 +2627,6 @@ def _update_title_position(self, renderer):
26222627

26232628
titles = (self.title, self._left_title, self._right_title)
26242629

2625-
if self._autotitlepos is None:
2626-
for title in titles:
2627-
x, y = title.get_position()
2628-
if not np.isclose(y, 1.0):
2629-
self._autotitlepos = False
2630-
_log.debug('not adjusting title pos because a title was '
2631-
'already placed manually: %f', y)
2632-
return
2633-
self._autotitlepos = True
2634-
26352630
for title in titles:
26362631
x, _ = title.get_position()
26372632
# need to start again in case of window resizing

lib/matplotlib/mpl-data/stylelib/classic.mplstyle

+2-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ mathtext.sf : sans\-serif
155155
mathtext.fontset : cm # Should be 'cm' (Computer Modern), 'stix',
156156
# 'stixsans' or 'custom'
157157
mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix'
158-
# 'stixsans'] when a symbol can not be found in one of the
158+
# 'stixsans'] when a symbol can not be found in one of the
159159
# custom math fonts. Select 'None' to not perform fallback
160160
# and replace the missing character by a dummy.
161161

@@ -175,6 +175,7 @@ axes.grid : False # display grid or not
175175
axes.grid.which : major
176176
axes.grid.axis : both
177177
axes.titlesize : large # fontsize of the axes title
178+
axes.titley : 1.0 # at the top, no autopositioning.
178179
axes.titlepad : 5.0 # pad between axes and title in points
179180
axes.titleweight : normal # font weight for axes title
180181
axes.labelsize : medium # fontsize of the x any y labels

lib/matplotlib/pyplot.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2931,9 +2931,9 @@ def sci(im):
29312931

29322932
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
29332933
@_copy_docstring_and_deprecators(Axes.set_title)
2934-
def title(label, fontdict=None, loc=None, pad=None, **kwargs):
2934+
def title(label, fontdict=None, loc=None, pad=None, *, y=None, **kwargs):
29352935
return gca().set_title(
2936-
label, fontdict=fontdict, loc=loc, pad=pad, **kwargs)
2936+
label, fontdict=fontdict, loc=loc, pad=pad, y=y, **kwargs)
29372937

29382938

29392939
# Autogenerated by boilerplate.py. Do not edit as changes will be lost.

lib/matplotlib/rcsetup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1219,7 +1219,8 @@ def _convert_validator_spec(key, conv):
12191219
'axes.titlelocation': ['center', ['left', 'center', 'right']], # alignment of axes title
12201220
'axes.titleweight': ['normal', validate_fontweight], # font weight of axes title
12211221
'axes.titlecolor': ['auto', validate_color_or_auto], # font color of axes title
1222-
'axes.titlepad': [6.0, validate_float], # pad from axes top to title in points
1222+
'axes.titley': [None, validate_float_or_None], # title location, axes units, None means auto
1223+
'axes.titlepad': [6.0, validate_float], # pad from axes top decoration to title in points
12231224
'axes.grid': [False, validate_bool], # display grid or not
12241225
'axes.grid.which': ['major', ['minor', 'both', 'major']], # set whether the grid is drawn on
12251226
# 'major' 'minor' or 'both' ticks

lib/matplotlib/tests/test_axes.py

+2
Original file line numberDiff line numberDiff line change
@@ -5522,6 +5522,7 @@ def test_titlesetpos():
55225522

55235523
def test_title_xticks_top():
55245524
# Test that title moves if xticks on top of axes.
5525+
mpl.rcParams['axes.titley'] = None
55255526
fig, ax = plt.subplots()
55265527
ax.xaxis.set_ticks_position('top')
55275528
ax.set_title('xlabel top')
@@ -5531,6 +5532,7 @@ def test_title_xticks_top():
55315532

55325533
def test_title_xticks_top_both():
55335534
# Test that title moves if xticks on top of axes.
5535+
mpl.rcParams['axes.titley'] = None
55345536
fig, ax = plt.subplots()
55355537
ax.tick_params(axis="x",
55365538
bottom=True, top=True, labelbottom=True, labeltop=True)

lib/matplotlib/tests/test_text.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ def test_annotation_units(fig_test, fig_ref):
618618
def test_large_subscript_title():
619619
# Remove this line when this test image is regenerated.
620620
plt.rcParams['text.kerning_factor'] = 6
621+
plt.rcParams['axes.titley'] = None
621622

622623
fig, axs = plt.subplots(1, 2, figsize=(9, 2.5), constrained_layout=True)
623624
ax = axs[0]
@@ -626,9 +627,7 @@ def test_large_subscript_title():
626627
ax.set_xticklabels('')
627628

628629
ax = axs[1]
629-
tt = ax.set_title(r'$\sum_{i} x_i$')
630-
x, y = tt.get_position()
631-
tt.set_position((x, 1.01))
630+
tt = ax.set_title(r'$\sum_{i} x_i$', y=1.01)
632631
ax.set_title('Old Way', loc='left')
633632
ax.set_xticklabels('')
634633

lib/matplotlib/tests/test_tightlayout.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def example_plot(ax, fontsize=12):
1919
ax.set_title('Title', fontsize=fontsize)
2020

2121

22-
@image_comparison(['tight_layout1'])
22+
@image_comparison(['tight_layout1'], tol=1.9)
2323
def test_tight_layout1():
2424
"""Test tight_layout for a single subplot."""
2525
fig, ax = plt.subplots()
@@ -115,7 +115,7 @@ def test_tight_layout6():
115115
h_pad=0.45)
116116

117117

118-
@image_comparison(['tight_layout7'])
118+
@image_comparison(['tight_layout7'], tol=1.9)
119119
def test_tight_layout7():
120120
# tight layout with left and right titles
121121
fontsize = 24

matplotlibrc.template

+2-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@
326326
#mathtext.sf: sans
327327
#mathtext.tt: monospace
328328
#mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix'
329-
# 'stixsans'] when a symbol can not be found in one of the
329+
# 'stixsans'] when a symbol can not be found in one of the
330330
# custom math fonts. Select 'None' to not perform fallback
331331
# and replace the missing character by a dummy symbol.
332332
#mathtext.default: it # The default font to use for math.
@@ -352,6 +352,7 @@
352352
#axes.titleweight: normal # font weight of title
353353
#axes.titlecolor: auto # color of the axes title, auto falls back to
354354
# text.color as default value
355+
#axes.titley: None # position title (axes relative units). None implies auto
355356
#axes.titlepad: 6.0 # pad between axes and title in points
356357
#axes.labelsize: medium # fontsize of the x any y labels
357358
#axes.labelpad: 4.0 # space between label and axis

0 commit comments

Comments
 (0)