Skip to content

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

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

Merged
merged 1 commit into from
Aug 22, 2018

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Jun 25, 2018

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
Copy link
Member Author

jklymak commented Jun 25, 2018

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
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
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
Copy link
Member Author

jklymak commented Jul 2, 2018

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
Copy link
Member Author

jklymak commented Jul 2, 2018

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
Copy link
Member

@efiring efiring left a comment

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?

for title in titles:
x, y0 = title.get_position()
y = 1.0
y = 1
# need to start agin in case of window resizing
Copy link
Member

Choose a reason for hiding this comment

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

Typo in comment.

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
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
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
Copy link
Member Author

jklymak commented Aug 19, 2018

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
y = max(y, yn)
else:
bb = ax.get_window_extent(renderer)
top = np.max((top, bb.ymax))
Copy link
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.

y = self.transAxes.inverted().transform(
(0., top - dy))[1]
title.set_position((x, y))
ymax = np.max((y, ymax))
Copy link
Member

@timhoffm timhoffm Aug 19, 2018

Choose a reason for hiding this comment

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

Builtin max()?

y = 1
bt = title.get_window_extent(renderer)
dy = bt.ymin - top
if dy < 0:
Copy link
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
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 ;-)

y = self.transAxes.inverted().transform(
(0., top))[1]
title.set_position((x, y))
bt = title.get_window_extent(renderer)
Copy link
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?

Copy link
Member Author

@jklymak jklymak Aug 20, 2018

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
Copy link
Member

@timhoffm timhoffm left a comment

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.

y = max(y, yn)
else:
bb = ax.get_window_extent(renderer)
top = max((top, bb.ymax))
Copy link
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
Copy link
Member Author

jklymak commented Aug 21, 2018

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