-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Move title up if x-axis is on the top of the figure #9498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from collections import OrderedDict | ||
import itertools | ||
import logging | ||
import math | ||
from operator import attrgetter | ||
import types | ||
|
@@ -32,6 +33,8 @@ | |
from matplotlib.rcsetup import cycler | ||
from matplotlib.rcsetup import validate_axisbelow | ||
|
||
_log = logging.getLogger(__name__) | ||
|
||
rcParams = matplotlib.rcParams | ||
|
||
|
||
|
@@ -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) | ||
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By curiosity, when is an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ummm, I forget why exactly, but I think it was possible for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough for me, no worry ;). |
||
pass | ||
|
||
title.set_position((x, y)) | ||
|
||
# Drawing | ||
|
||
@allow_rasterization | ||
|
@@ -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() | ||
|
@@ -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) | ||
|
@@ -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() | ||
|
@@ -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(): | ||
|
@@ -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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just by curiosity, is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1.04 was just large enough to definitely not be at 1.0. No good reason for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, I just wanted to be sure that I was not missing something. |
||
|
||
|
||
def test_offset_label_color(): | ||
# Tests issue 6440 | ||
fig = plt.figure() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very dumb question incoming 🐑... With this implementation, there is no way to pin the title y-position at 1.0 and still avoid the auto title positioning, is there not? But in case anybody would like to do so (for whichever good or bad reason ^^), I guess that a reasonable workaround would be to pin the title y-position at something like 1.001, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thats correct. I could do something like find the first call that sets the title at 1.0 and then flag future calls to
set_position
as being "pinned", even if it is y=1.0. But setting to 1.01 will give the same result.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, setting
y=1.001
is a good enough solution. Maybe it should be put in the “what's new” entry (just for the workaround to be documented somewhere)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!