Skip to content

Repeated calls to tight_layout needed with aspect='equal' #18313

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

Closed
JoElfner opened this issue Aug 21, 2020 · 7 comments
Closed

Repeated calls to tight_layout needed with aspect='equal' #18313

JoElfner opened this issue Aug 21, 2020 · 7 comments
Labels
status: inactive Marked by the “Stale” Github Action topic: geometry manager LayoutEngine, Constrained layout, Tight layout

Comments

@JoElfner
Copy link

JoElfner commented Aug 21, 2020

Bug report

Producing 4 subplots with a rectangular figure (height > width) and setting the subplots to ax.set_aspect('equal') causes fig.tight_layout(pad=0., h_pad=0., w_pad=0.) to introduce a huge vertical gap between the subplots on the first call.
Each subsequent call to tight_layout reduces the gap slowly, until the set arguments pad and h_pad are satisfied.

Code for reproduction

import matplotlib.pyplot as plt
import numpy as np

randarr = np.random.rand(200, 4)
txtbox = 'this is some\ntext with multiple\nlines and absolutely\nno meaning'

fig, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, figsize=(16/2.54, 25/2.54))
a1.scatter(randarr[:, 0] * 3, randarr[:, 1] * 3, label='a\nb')
a2.scatter(randarr[:, 1] * 2, randarr[:, 2] * 2, label='b\nc')
a3.scatter(randarr[:, 2] * 2, randarr[:, 3] * 2, label='c\nd')
a4.scatter(randarr[:, 3] / 3, randarr[:, 0] / 3, label='d\ne')

_ = [_ax.set_aspect('equal') for _ax in (a1, a2, a3, a4)]
fig.tight_layout(pad=0., h_pad=1., w_pad=1.)

To reduce the vertical gap until padding is satisfied:

for _ in range(10):
    fig.tight_layout(pad=0., h_pad=1., w_pad=1.)    

And it seems that for plots with a high complexity, calls to fig.canvas.draw are required between the calls to tight_layout. But I cannot reproduce this reliably:

for _ in range(10):
    fig.tight_layout(pad=0., h_pad=1., w_pad=1.)    
    fig.canvas.draw()

Actual outcome
The outcome of the code without repeated calls to tight_layout:
scttr_false_output

Expected outcome

The in my opinion correct output can be produced with:

import matplotlib.pyplot as plt
import numpy as np

randarr = np.random.rand(200, 4)
txtbox = 'this is some\ntext with multiple\nlines and absolutely\nno meaning'

fig, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, figsize=(16/2.54, 25/2.54))
a1.scatter(randarr[:, 0] * 3, randarr[:, 1] * 3, label='a\nb')
a2.scatter(randarr[:, 1] * 2, randarr[:, 2] * 2, label='b\nc')
a3.scatter(randarr[:, 2] * 2, randarr[:, 3] * 2, label='c\nd')
a4.scatter(randarr[:, 3] / 3, randarr[:, 0] / 3, label='d\ne')

_ = [_ax.set_aspect('equal') for _ax in (a1, a2, a3, a4)]
for _ in range(10):
    fig.tight_layout(pad=0., h_pad=1., w_pad=1.)

Expected output:
scttr_correct_output

Matplotlib version

  • Operating system: Windows 10 64bit
  • Matplotlib version: 3.3.1
  • Matplotlib backend: Qt5Agg
  • Python version: 3.7.7
  • Other libraries: Numpy v 1.19.1

All packages are installed from the default conda channel.

@jklymak
Copy link
Member

jklymak commented Aug 21, 2020

You are asking for two constraints that can't be solved at the same time: 1) that the figure is a certain size and the axes fill the figure, 2) that the axes be a fixed aspect ratio. Given that you have set the aspect ratio to equal, there has to be extra space unless your figure size is tuned to match. So where to put that space? tight_layout tends to put it between the axes and centre the axes in their allotted space. You would like them to contract and be close together, but that is harder than it sounds to do automatically and generally. You may wish to look at #17246 to see how hard, and that PR still has problems.

So yes this would be nice to fix, but unfortunately its not easy. You may want to look at image_grid (https://matplotlib.org/gallery/axes_grid1/simple_axesgrid.html) which makes a ready-made set of axes that do this automatically, but I don't think they will obey tight_layout.

@jklymak jklymak added the topic: geometry manager LayoutEngine, Constrained layout, Tight layout label Aug 21, 2020
@JoElfner
Copy link
Author

Thanks! Yeah, I understand that this is a really hard thing to implement. Any other way to achieve it? My usecase for this is: I need my figures to span exactly the text width of an A4 paper size, which typically is either 16cm or 17cm, while the vertical spacing should be minimized as much as possible. Is there any way to define the figure width while leaving the height flexible?

ImageGrid seems to be setting sharex=True and sharey=True, even though share_all=False is set. For Grid in contrast, sharing axes can be disabled, but the same vertical spacing problem occurs as with subplots.

@jklymak
Copy link
Member

jklymak commented Aug 22, 2020

I'd start by making the aspect ratio of the figure closer to one-to-one (for a 2x2 grid of axes). That won't be perfect depending on tick labels and other labels, but will be closer. After that you can adjust by hand.

When #17246 gets completed, the idea will be to call that, and then call fig.savefig('boo', bbox_inches='tight') so that the figure size gets cut down to the right size. OTOH, that may not get you exactly 16 cm; in that case the are pdf trim tools you could use after saving....

@JoElfner
Copy link
Author

Yep, this was a fairly extreme example to show what I wanted to show. :)
I'm looking forward to #17246. Many thanks for working on it!

@github-actions
Copy link

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Aug 18, 2023
@QuLogic
Copy link
Member

QuLogic commented Aug 19, 2023

I think this seems to work alright with the compressed layout engine (pass layout='compressed' to subplots) instead of tight layout?

@jklymak
Copy link
Member

jklymak commented Aug 19, 2023

Should work. Lets close.

@jklymak jklymak closed this as completed Aug 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: inactive Marked by the “Stale” Github Action topic: geometry manager LayoutEngine, Constrained layout, Tight layout
Projects
None yet
Development

No branches or pull requests

3 participants