Skip to content

Commit

Permalink
Merge pull request #9498 from jklymak/fixtitle
Browse files Browse the repository at this point in the history
Move title up if x-axis is on the top of the figure.

Merging as it received 2 approvals, and the small patch that I pushed was also reviewed by @efiring (and actually modified on one of his remarks).

Thanks @jklymak :).
  • Loading branch information
afvincent committed Apr 3, 2018
2 parents 8c39032 + c2db893 commit 7b30275
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 5 deletions.
15 changes: 15 additions & 0 deletions doc/users/next_whats_new/title_will_not_overlap_xaxis.rst
@@ -0,0 +1,15 @@
Axes title will no longer overlap xaxis
---------------------------------------

Previously an axes title had to be moved manually if an xaxis overlapped
(usually when the xaxis was put on the top of the axes). Now, the title
will be automatically moved above the xaxis and its decorators (including
the xlabel) if they are at the top.

If desired, the title can still be placed manually. There is a slight kludge;
the algorithm checks if the y-position of the title is 1.0 (the default),
and moves if it is. If the user places the title in the default location
(i.e. ``ax.title.set_position(0.5, 1.0)``), the title will still be moved
above the xaxis. If the user wants to avoid this, they can
specify a number that is close (i.e. ``ax.title.set_position(0.5, 1.01)``)
and the title will not be moved via this algorithm.
2 changes: 1 addition & 1 deletion examples/ticks_and_spines/tick_xlabel_top.py
Expand Up @@ -23,6 +23,6 @@
fig, ax = plt.subplots()

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

plt.show()
63 changes: 59 additions & 4 deletions lib/matplotlib/axes/_base.py
@@ -1,5 +1,6 @@
from collections import OrderedDict
import itertools
import logging
import math
from operator import attrgetter
import types
Expand Down Expand Up @@ -32,6 +33,8 @@
from matplotlib.rcsetup import cycler
from matplotlib.rcsetup import validate_axisbelow

_log = logging.getLogger(__name__)

rcParams = matplotlib.rcParams


Expand Down Expand Up @@ -1077,6 +1080,8 @@ def cla(self):
# 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:
self._autotitlepos = None

for _title in (self.title, self._left_title, self._right_title):
self._set_artist_props(_title)
Expand Down Expand Up @@ -2446,6 +2451,50 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval,
def _get_axis_list(self):
return (self.xaxis, self.yaxis)

def _update_title_position(self, renderer):
"""
Update the title position based on the bounding box enclosing
all the ticklabels and x-axis spine and xlabel...
"""
_log.debug('update_title_pos')

if self._autotitlepos is not None and not self._autotitlepos:
_log.debug('title position was updated manually, not adjusting')
return

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

if self._autotitlepos is None:
for title in titles:
x, y = title.get_position()
if not np.isclose(y, 1.0):
self._autotitlepos = False
_log.debug('not adjusting title pos because title was'
' already placed manually: %f', y)
return
self._autotitlepos = True

for title in titles:
x, y0 = title.get_position()
y = 1.0
# need to check all our twins too...
axs = self._twinned_axes.get_siblings(self)

for ax in axs:
try:
if (ax.xaxis.get_label_position() == 'top'
or ax.xaxis.get_ticks_position() == 'top'):
bb = ax.xaxis.get_tightbbox(renderer)
top = bb.ymax
# we don't need to pad because the padding is already
# in __init__: titleOffsetTrans
yn = self.transAxes.inverted().transform((0., top))[1]
y = max(y, yn)
except AttributeError:
pass

title.set_position((x, y))

# Drawing

@allow_rasterization
Expand All @@ -2459,6 +2508,7 @@ def draw(self, renderer=None, inframe=False):
if not self.get_visible():
return
renderer.open_group('axes')

# prevent triggering call backs during the draw process
self._stale = True
locator = self.get_axes_locator()
Expand All @@ -2479,6 +2529,8 @@ def draw(self, renderer=None, inframe=False):
for spine in self.spines.values():
artists.remove(spine)

self._update_title_position(renderer)

if self.axison and not inframe:
if self._axisbelow is True:
self.xaxis.set_zorder(0.5)
Expand Down Expand Up @@ -2507,6 +2559,7 @@ def draw(self, renderer=None, inframe=False):
# rasterize artists with negative zorder
# if the minimum zorder is negative, start rasterization
rasterization_zorder = self._rasterization_zorder

if (rasterization_zorder is not None and
artists and artists[0].zorder < rasterization_zorder):
renderer.start_rasterizing()
Expand Down Expand Up @@ -4051,6 +4104,12 @@ def get_tightbbox(self, renderer, call_axes_locator=True):
else:
self.apply_aspect()

bb_xaxis = self.xaxis.get_tightbbox(renderer)
if bb_xaxis:
bb.append(bb_xaxis)

self._update_title_position(renderer)

bb.append(self.get_window_extent(renderer))

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

bb_xaxis = self.xaxis.get_tightbbox(renderer)
if bb_xaxis:
bb.append(bb_xaxis)

bb_yaxis = self.yaxis.get_tightbbox(renderer)
if bb_yaxis:
bb.append(bb_yaxis)
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Expand Up @@ -5411,6 +5411,42 @@ def test_axisbelow():
ax.set_axisbelow(setting)


@image_comparison(baseline_images=['titletwiny'], style='mpl20',
extensions=['png'])
def test_titletwiny():
# Test that title is put above xlabel if xlabel at top
fig, ax = plt.subplots()
fig.subplots_adjust(top=0.8)
ax2 = ax.twiny()
ax.set_xlabel('Xlabel')
ax2.set_xlabel('Xlabel2')
ax.set_title('Title')


def test_titlesetpos():
# Test that title stays put if we set it manually
fig, ax = plt.subplots()
fig.subplots_adjust(top=0.8)
ax2 = ax.twiny()
ax.set_xlabel('Xlabel')
ax2.set_xlabel('Xlabel2')
ax.set_title('Title')
pos = (0.5, 1.11)
ax.title.set_position(pos)
renderer = fig.canvas.get_renderer()
ax._update_title_position(renderer)
assert ax.title.get_position() == pos


def test_title_xticks_top():
# Test that title moves if xticks on top of axes.
fig, ax = plt.subplots()
ax.xaxis.set_ticks_position('top')
ax.set_title('xlabel top')
fig.canvas.draw()
assert ax.title.get_position()[1] > 1.04


def test_offset_label_color():
# Tests issue 6440
fig = plt.figure()
Expand Down

0 comments on commit 7b30275

Please sign in to comment.