Skip to content

FIX: move title(s) up if subscripts hang too low. - #11502

Merged
timhoffm merged 1 commit into
matplotlib:masterfrom
jklymak:fix-title-bottom-vs-baseline
Aug 22, 2018
Merged

timhoffm merged 1 commit into
matplotlib:masterfrom
jklymak:fix-title-bottom-vs-baseline

Conversation

@jklymak

@jklymak jklymak commented Jun 25, 2018 •

Copy link
Copy Markdown
Member

PR Summary

Partially adresses #11468. See also #11499 for multiline fixes...

EDIT:

Currently if a title has a large subscript, usually because it is math text (though large title font sizes can do the same thing), it will overhang the axes.

This PR adjusts the title position so this doesn't happen. It moves all three titles (left, right and centre) to the same level to maintain consistency of the baseline on the axes.

Note that the new version can overspill the top of the figure unless tight_layout or constrained_layout are used. But that overspill is still better than spilling down over the axes in my opinion.

oldnew

Code

import matplotlib
import matplotlib.pyplot as plt

fig, axs = plt.subplots(1, 2, figsize=(9, 2.5), constrained_layout=True)
ax = axs[0]
ax.set_title('$\sum_{i} x_i$')
ax.set_title('New way', loc='left')
ax.set_xticklabels('')

ax = axs[1]
tt = ax.set_title('$\sum_{i} x_i$')
x, y = tt.get_position()
tt.set_position((x, 1.01))
ax.set_title('Old Way', loc='left')
ax.set_xticklabels('')
plt.show()

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@jklymak

jklymak commented Jun 25, 2018

Copy link
Copy Markdown
Member Author

OK, so this "only" fails 50 tests - all of which have titles (i.e. about 16 individual tests). If this is an OK approach I will regenerate the test images.

Pros: large subscript titles won't drop down below the top of the axes.

Cons: The baseline of a title like "platypus" would be displaced vertically from the top of the axes relative to a title like "aardvark"

@timhoffm

Copy link
Copy Markdown
Member

I don't think the con is even true:

fd_bottom = {'verticalalignment': 'bottom'}
fd_baseline = {'verticalalignment': 'baseline'}

_, axs = plt.subplots(1, 2)
axs[0].plot([1, 3, 2])
axs[1].plot([1, 3, 2])

axs[0].set_title('baseline', loc='left', fontdict=fd_baseline)
axs[0].set_title('ggggggg', loc='center', fontdict=fd_baseline)
axs[1].set_title('bottom', loc='left', fontdict=fd_bottom)
axs[1].set_title('ggggggggg', loc='center', fontdict=fd_bottom)

grafik

So, 👍

@tacaswell tacaswell added this to the v3.1 milestone Jun 29, 2018
@tacaswell

Copy link
Copy Markdown
Member

👍

Can you check if those tests are actually testing titles? Any which are not should probably have the title removed (to reduce our exposure to font-related changes in general).

Is this just a matter of tuning the padding just right to make it match?

@jklymak
jklymak force-pushed the fix-title-bottom-vs-baseline branch from 2c7594e to 7108a60 Compare July 2, 2018 16:09
@jklymak

jklymak commented Jul 2, 2018 •

Copy link
Copy Markdown
Member Author

OK: I decided I didn't like this approach because it allowed no way to align the titles (at their baselines) without manually doing verticalalign='baseline'. This algorithm is a bit more complicated, but does the right thing (I'd argue). Note that if any of the three titles (left, centre, and right) are manually moved, this all turns off, so the user still has manual placement ability.

This is also going to fail far fewer tests....

After:

old

@jklymak

jklymak commented Jul 2, 2018 •

Copy link
Copy Markdown
Member Author

This only fails two tests in tight_layout where for some reason the x/y line gets shifted by a point for some funky reason. It happens locally as well, so I can re-produce. Its a little funky and likely to do with pixel snapping, but is harmless to the test...

@jklymak jklymak changed the title FIX: move title verticalalignment to bottom from baseline FIX: move title(s) up if subscripts hang too low. Jul 3, 2018
@jklymak
jklymak force-pushed the fix-title-bottom-vs-baseline branch from d44c84d to 9e0a8b3 Compare July 3, 2018 20:25

@efiring efiring left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the two minor specific comments, this looks good to me.
But doesn't it need one added test like the illustrations that are given above to motivate the PR?

Comment thread lib/matplotlib/axes/_base.py Outdated
x, y0 = title.get_position()
y = 1.0
y = 1
# need to start agin in case of window resizing

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment.

Comment thread lib/matplotlib/axes/_base.py Outdated
y = 1
bt = title.get_window_extent(renderer)
# need to do twice to get right for manual resizing
for nn in range(1):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is doing it only once. Does it really need to be done twice?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once works!

@jklymak

jklymak commented Aug 19, 2018

Copy link
Copy Markdown
Member Author

Thanks @efiring test added...

@jklymak
jklymak force-pushed the fix-title-bottom-vs-baseline branch 2 times, most recently from ac877a4 to cb636e6 Compare August 19, 2018 04:18
Comment thread lib/matplotlib/axes/_base.py Outdated
y = max(y, yn)
else:
bb = ax.get_window_extent(renderer)
top = np.max((top, bb.ymax))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that it's particular important, but I would just use the builtin max(). It's easier to read and also faster for just two numbers.

Comment thread lib/matplotlib/axes/_base.py Outdated
y = self.transAxes.inverted().transform(
(0., top - dy))[1]
title.set_position((x, y))
ymax = np.max((y, ymax))

@timhoffm timhoffm Aug 19, 2018 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builtin max()?

Comment thread lib/matplotlib/axes/_base.py Outdated
y = 1
bt = title.get_window_extent(renderer)
dy = bt.ymin - top
if dy < 0:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS this dy value is not used again. In that case, it would be more clear to:

if title.get_window_extent(renderer).ymin < top:
    # title overlaps the Axes
    ...

IMO the temporary variables do not add to the clarity of the code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, leftovers from debugging where the temporary variables were used a few more times ;-)

Comment thread lib/matplotlib/axes/_base.py Outdated
y = self.transAxes.inverted().transform(
(0., top))[1]
title.set_position((x, y))
bt = title.get_window_extent(renderer)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a short comment, why you do the setting of the position in two steps?

@jklymak jklymak Aug 20, 2018 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, yes, it should be exactly the same w/ or w/o the extra block, but emperically its not with the state that get_extents is in now... 😢 Comment added...

@jklymak
jklymak force-pushed the fix-title-bottom-vs-baseline branch from cb636e6 to a04c64c Compare August 20, 2018 16:27

@timhoffm timhoffm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave it up to you, if you want to update the max() usages.

Comment thread lib/matplotlib/axes/_base.py Outdated
y = max(y, yn)
else:
bb = ax.get_window_extent(renderer)
top = max((top, bb.ymax))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max(top, bb.ymax) would work as well, i.e. multiple arguments instead of a tuple. It's even slightly more clean.

TST: Add test for large subscript
@jklymak
jklymak force-pushed the fix-title-bottom-vs-baseline branch from a04c64c to e38c544 Compare August 21, 2018 22:02
@jklymak

jklymak commented Aug 21, 2018

Copy link
Copy Markdown
Member Author

Thanks @timhoffm , fixed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants