Skip to content

Why doesn't matplotlib save the whole figure by default? #17118

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
rraadd88 opened this issue Apr 13, 2020 · 3 comments
Closed

Why doesn't matplotlib save the whole figure by default? #17118

rraadd88 opened this issue Apr 13, 2020 · 3 comments
Labels
Community support Users in need of help.

Comments

@rraadd88
Copy link

savefig saves cropped images by default.

Bug summary

It's probably an issue that almost everybody who uses matplotlib would have encountered. If you generate a figure - which often contains axis labels and legends - and save it with default settings, you'll get a cropped image.

It's a duplicate of my question on stackoverflow.

Code for reproduction

import matplotlib.pyplot as plt
def plot():
    plt.figure(figsize=[3,3],linewidth=5,edgecolor='r') 
    ax=plt.subplot()
    ax.plot(range(10),range(10),label='label')
    ax.set_xlabel('xlabel\nxlabel\nxlabel')
    ax.set_ylabel('ylabel\nylabel\nylabel')
    ax.legend(bbox_to_anchor=[1,1])
plot()    
plt.savefig('no_tight_layout.png')

Actual outcome

no_tight_layout.png

Expected outcome

If I generate the same plot in a jupyter notebook, without using tight_layout option, I see that all the elements of the plot are contained within the figure boundaries (shown in red).

jupyter notebook

This figure is generated in the output cell of a jupyter notebook(!).
Then why it is not saved as it is? Why the saved image is by default different from the image jupyter notebook?
In my opinion this is a very fundamental issue with matplotlib.
Would't it make the lives of the users easier, if by default, all the elements are contained in the saved figure without the need of any workarounds?

(Thanks to stackoverflow), we know few workarounds but each has a caveat of its own..

Workaround #1: from within matplotlib: use of tight_layout option.

plot()    
plt.savefig('tight_layout.png',bbox_inches='tight')

tight_layout.png

It works for simple figures.
However, in my experience, it is not reliable option in the case of more complex, multi-panel figures.
tight_layout often fails with errors such as these:

UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.
UserWarning: tight_layout not applied: number of rows in subplot specifications must be multiples of one another.

Workaround #2: from outside of matplotlib: save the image in SVG format and then convert to png. For example using --export-area-drawing option in the inkscape command line UI or "resize to page" option in the inkscape's GUI.

However, in this case you have to depend on external softwares which are difficult to add as dependencies in python packages (currently conda only hosts Windows version of inkscape).

So my question is..

Why doesn't matplotlib save the whole figure by default?


Matplotlib version

  • Operating system: Ubuntu 18.04
  • Matplotlib version: 3.1.2 (this is issue reproducible in previous version too.)
  • Matplotlib backend (print(matplotlib.get_backend())):
  • Python version: 3.6.5
  • Jupyter version (if applicable): 1.0
  • Other libraries:
@ImportanceOfBeingErnest
Copy link
Member

Matplot does save "the whole figure". The concept is, like in a lot of other software, to define a figure like an empty sheet of fixed size. If elements that are later added overflow the borders of the sheet, it they will be cropped.
The reason this is useful is that the output will be deterministic. If you create a matplotlib figure, e.g. via plt.figure(figsize=(5,4)), you can be sure that is has 5 by 4 inches in size.

For the common case of elements overflowing, matplotlib provides 4 ways to handle those:

  1. Manually make sure that everything fits onto the figure. E.g. adjust the subplot parameters with fig.subplots_adjust
  2. Use fig.tight_layout() to let the subplot parameters be adjusted automatically.
  3. Use constrained_layout to let an algorithm find the best positions of the elements within the figure (in most cases this will give similar results as tight_layout).
  4. Use bbox_inches='tight' argument to savefig - this option is only available when saving the figure - to let the figure expand (or crop) to contain all the artists within. This is the only of those options that actually changes the figure size.

Notes:

  • Option No. 4 is what you observe in the jupyter notebook. Because you are using IPykernel's inline backend, the figure you get in the output cell is produced with the bbox_inches='tight' option.
  • `tight_layout* as well as constrained_layout can be set via rcParams. So if you find yourself in the situation that you always want to apply those, you can do so by changing your rc file.

@jklymak
Copy link
Member

jklymak commented Apr 13, 2020

constrained_layout is explicitly meant to work on the more complicated layouts.

@rraadd88
Copy link
Author

rraadd88 commented Apr 13, 2020

Thanks for your replies. They help a lot!
I'm closing the issue.

@QuLogic QuLogic added the Community support Users in need of help. label Jun 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community support Users in need of help.
Projects
None yet
Development

No branches or pull requests

4 participants