user3521099
user3521099

Reputation:

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

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.

Demo code:

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')

no_tight_layout.png

(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?
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?

Upvotes: 6

Views: 6771

Answers (1)

user3521099
user3521099

Reputation:

In short, its a feature than an issue of matplotlib. By default, matplotlib sets a fixed size for a figure, so the overflowing elements are often cropped.

There are 4 options matplotlib provides to deal with cropping of the elements:

  1. plt.subplots_adjust manual adjustments of elements retrospectively. Figure size unchanged.
  2. plt.tight_layout automatic adjustments. Figure size unchanged.
  3. plt.constrained_layout automatic adjustments. Often similar to fig.tight_layout. Figure size unchanged.
  4. plt.savefig(..,bbox_inches='tight'): figure size is adjusted to contain all elements as they are (what you see in jupyter notebook's outputs). Figure size changes.

Option 4 is what I needed because I wanted to save the figure as I see in jupyter notebook's output, regardless of the figure size.

I got this answer on matplotlib's github page (thanks Elan Ernest @ImportanceOfBeingErnest).

Upvotes: 12

Related Questions