Skip to content

Commit 456ad43

Browse files
committed
ENH: Move title if x-axis is on the top of the figure
1 parent 3664c83 commit 456ad43

File tree

5 files changed

+105
-6
lines changed

5 files changed

+105
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Axes title will no longer overlap xaxis
2+
---------------------------------------
3+
4+
Previously the axes title had to be moved manually if an xaxis overlapped
5+
(usually when the xaxis was put on the top of the axes). The title can
6+
still be placed manually. However, titles that need to be moved are
7+
at ``y=1.0``, so manualy placing at 1.0 will be moved, so if the title
8+
is to be placed at 1.0, it should be set to something near 1.0, like 1.001.

examples/ticks_and_spines/tick_xlabel_top.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@
2323
fig, ax = plt.subplots()
2424

2525
ax.plot(x)
26-
ax.set_title('xlabel top', pad=24) # increase padding to make room for labels
26+
ax.set_title('xlabel top') # Note title moves to make room for ticks
2727

2828
plt.show()

lib/matplotlib/axes/_base.py

+60-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from collections import OrderedDict
22
import itertools
3+
import logging
34
import math
45
from operator import attrgetter
56
import types
@@ -32,6 +33,8 @@
3233
from matplotlib.rcsetup import cycler
3334
from matplotlib.rcsetup import validate_axisbelow
3435

36+
_log = logging.getLogger(__name__)
37+
3538
rcParams = matplotlib.rcParams
3639

3740

@@ -1077,6 +1080,8 @@ def cla(self):
10771080
# refactor this out so it can be called in ax.set_title if
10781081
# pad argument used...
10791082
self._set_title_offset_trans(title_offset_points)
1083+
# determine if the title position has been set manually:
1084+
self._autotitlepos = None
10801085

10811086
for _title in (self.title, self._left_title, self._right_title):
10821087
self._set_artist_props(_title)
@@ -2446,6 +2451,50 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval,
24462451
def _get_axis_list(self):
24472452
return (self.xaxis, self.yaxis)
24482453

2454+
def _update_title_position(self, renderer):
2455+
"""
2456+
Update the title position based on the bounding box enclosing
2457+
all the ticklabels and x-axis spine and xlabel...
2458+
"""
2459+
_log.debug('update_title_pos')
2460+
2461+
if self._autotitlepos is not None and not self._autotitlepos:
2462+
_log.debug('title position was updated manually, not adjusting')
2463+
return
2464+
2465+
titles = (self.title, self._left_title, self._right_title)
2466+
2467+
if self._autotitlepos is None:
2468+
for title in titles:
2469+
x, y = title.get_position()
2470+
if not np.isclose(y, 1.0):
2471+
self._autotitlepos = False
2472+
_log.debug('not adjusting title pos because title was'
2473+
' already placed manually: %f', y)
2474+
return
2475+
self._autotitlepos = True
2476+
2477+
for title in titles:
2478+
x, y0 = title.get_position()
2479+
y = 1.0
2480+
# need to check all our twins too...
2481+
axs = self._twinned_axes.get_siblings(self)
2482+
2483+
for ax in axs:
2484+
try:
2485+
if (ax.xaxis.get_label_position() == 'top'
2486+
or ax.xaxis.get_ticks_position() == 'top'):
2487+
bb = ax.xaxis.get_tightbbox(renderer)
2488+
top = bb.y1
2489+
# we don't need to pad because the padding is already
2490+
# in __init__: titleOffsetTrans
2491+
yn = self.transAxes.inverted().transform((0., top))[1]
2492+
y = max(y, yn)
2493+
except AttributeError:
2494+
pass
2495+
2496+
title.set_position((x, y))
2497+
24492498
# Drawing
24502499

24512500
@allow_rasterization
@@ -2459,6 +2508,7 @@ def draw(self, renderer=None, inframe=False):
24592508
if not self.get_visible():
24602509
return
24612510
renderer.open_group('axes')
2511+
24622512
# prevent triggering call backs during the draw process
24632513
self._stale = True
24642514
locator = self.get_axes_locator()
@@ -2479,6 +2529,8 @@ def draw(self, renderer=None, inframe=False):
24792529
for spine in self.spines.values():
24802530
artists.remove(spine)
24812531

2532+
self._update_title_position(renderer)
2533+
24822534
if self.axison and not inframe:
24832535
if self._axisbelow is True:
24842536
self.xaxis.set_zorder(0.5)
@@ -2507,6 +2559,7 @@ def draw(self, renderer=None, inframe=False):
25072559
# rasterize artists with negative zorder
25082560
# if the minimum zorder is negative, start rasterization
25092561
rasterization_zorder = self._rasterization_zorder
2562+
25102563
if (rasterization_zorder is not None and
25112564
artists and artists[0].zorder < rasterization_zorder):
25122565
renderer.start_rasterizing()
@@ -2528,7 +2581,7 @@ def draw(self, renderer=None, inframe=False):
25282581
renderer.stop_rasterizing()
25292582

25302583
mimage._draw_list_compositing_images(renderer, self, artists)
2531-
2584+
25322585
renderer.close_group('axes')
25332586
self._cachedRenderer = renderer
25342587
self.stale = False
@@ -4051,6 +4104,12 @@ def get_tightbbox(self, renderer, call_axes_locator=True):
40514104
else:
40524105
self.apply_aspect()
40534106

4107+
bb_xaxis = self.xaxis.get_tightbbox(renderer)
4108+
if bb_xaxis:
4109+
bb.append(bb_xaxis)
4110+
4111+
self._update_title_position(renderer)
4112+
40544113
bb.append(self.get_window_extent(renderer))
40554114

40564115
if self.title.get_visible():
@@ -4060,10 +4119,6 @@ def get_tightbbox(self, renderer, call_axes_locator=True):
40604119
if self._right_title.get_visible():
40614120
bb.append(self._right_title.get_window_extent(renderer))
40624121

4063-
bb_xaxis = self.xaxis.get_tightbbox(renderer)
4064-
if bb_xaxis:
4065-
bb.append(bb_xaxis)
4066-
40674122
bb_yaxis = self.yaxis.get_tightbbox(renderer)
40684123
if bb_yaxis:
40694124
bb.append(bb_yaxis)

lib/matplotlib/tests/test_axes.py

+36
Original file line numberDiff line numberDiff line change
@@ -5411,6 +5411,42 @@ def test_axisbelow():
54115411
ax.set_axisbelow(setting)
54125412

54135413

5414+
@image_comparison(baseline_images=['titletwiny'], style='mpl20',
5415+
extensions=['png'])
5416+
def test_titletwiny():
5417+
# Test that title is put above xlabel if xlabel at top
5418+
fig, ax = plt.subplots()
5419+
fig.subplots_adjust(top=0.8)
5420+
ax2 = ax.twiny()
5421+
ax.set_xlabel('Xlabel')
5422+
ax2.set_xlabel('Xlabel2')
5423+
ax.set_title('Title')
5424+
5425+
5426+
def test_titlesetpos():
5427+
# Test that title stays put if we set it manually
5428+
fig, ax = plt.subplots()
5429+
fig.subplots_adjust(top=0.8)
5430+
ax2 = ax.twiny()
5431+
ax.set_xlabel('Xlabel')
5432+
ax2.set_xlabel('Xlabel2')
5433+
ax.set_title('Title')
5434+
pos = (0.5, 1.11)
5435+
ax.title.set_position(pos)
5436+
renderer = fig.canvas.get_renderer()
5437+
ax._update_title_position(renderer)
5438+
assert ax.title.get_position() == pos
5439+
5440+
5441+
def test_title_xticks_top():
5442+
# Test that title moves if xticks on top of axes.
5443+
fig, ax = plt.subplots()
5444+
ax.xaxis.set_ticks_position('top')
5445+
ax.set_title('xlabel top')
5446+
fig.canvas.draw()
5447+
assert ax.title.get_position()[1] > 1.04
5448+
5449+
54145450
def test_offset_label_color():
54155451
# Tests issue 6440
54165452
fig = plt.figure()

0 commit comments

Comments
 (0)