270

I'm struggling to deal with my plot margins in matplotlib. I've used the code below to produce my chart:

plt.imshow(g)
c = plt.colorbar()
c.set_label("Number of Slabs")
plt.savefig("OutputToUse.png")

However, I get an output figure with lots of white space on either side of the plot. I've searched google and read the matplotlib documentation, but I can't seem to find how to reduce this.

robintw
  • 27,571
  • 51
  • 138
  • 205
  • 1
    Is the problem the amount of whitespace in the `extent` of the `imshow` figure, or the amount of border whitespace in the resultant png, around the figure, generated by `savefig`? – unutbu Oct 28 '10 at 14:45
  • I think both - there seems to be a lot of space in both the viewing window and in the PNG. However, the important output is the png file produced by `savefig` - so that is what I'd like to sort. – robintw Oct 28 '10 at 15:36
  • I've just been cropping them in GIMP afterward. :/ – endolith Nov 28 '11 at 17:42
  • ax.margins(x=0.01) is what you're looking for! – kkk Nov 09 '20 at 12:52

13 Answers13

369

One way to automatically do this is the bbox_inches='tight' kwarg to plt.savefig.

E.g.

import matplotlib.pyplot as plt
import numpy as np
data = np.arange(3000).reshape((100,30))
plt.imshow(data)
plt.savefig('test.png', bbox_inches='tight')

Another way is to use fig.tight_layout()

import matplotlib.pyplot as plt
import numpy as np

xs = np.linspace(0, 1, 20); ys = np.sin(xs)

fig = plt.figure()
axes = fig.add_subplot(1,1,1)
axes.plot(xs, ys)

# This should be called after all axes have been added
fig.tight_layout()
fig.savefig('test.png')
Rafa Viotti
  • 9,998
  • 4
  • 42
  • 62
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • 8
    Is there any way to make this the default? – endolith Jul 08 '12 at 01:08
  • 1
    If you have multiple subplots and want to save each of them, you can use this with `fig.savefig()` too. (`plt.savefig()` will not work in that case.) – Abhranil Das Apr 21 '13 at 12:07
  • 2
    All this does is crop the image after it's been rendered; if you're trying to enforce a particular resolution, the image will come out *smaller*. – detly Jan 19 '15 at 11:23
  • 5
    @detly - Yep. That's exactly what it does (though it can crop "out" as well and make the image larger, as well). For what you're wanting, have a look at `fig.tight_layout()`. That function didn't exist when this answer was originally written, otherwise I'd mention it more prominently. – Joe Kington Jan 19 '15 at 12:33
  • 2
    If someone have a problem, use ```fig = plt.gcf()``` – KyungHoon Kim Feb 24 '15 at 07:17
  • when running this code for saving without the axis, the figure is saved with white border in it. `import matplotlib.pyplot as plt import numpy as np data = np.arange(3000).reshape((100,30)) plt.imshow(data) plt.axis('off') plt.tight_layout() plt.savefig('test.png', bbox_inches='tight', pad_inches = 0)` – shahar_m Jan 08 '17 at 14:00
  • 1
    Neither `bbox_inches='tight'`, `fig.tight_layout()` nor `plt.autoscale()` worked for me with _matplotlib ver 2.0.1_ + _python 2.7.13_. What worked for me is awful hardcoded workaround `plt.xlim([min, max])`. Source: http://stackoverflow.com/questions/20214497/annoying-white-space-in-bar-chart-matplotlib-python – Manavalan Gajapathy May 05 '17 at 21:37
  • Add this line to your style file, `savefig.bbox: tight`, to make all plots saved tight. – PatrickT Jan 14 '22 at 15:17
210

You can adjust the spacing around matplotlib figures using the subplots_adjust() function:

import matplotlib.pyplot as plt
plt.plot(whatever)
plt.subplots_adjust(left=0.1, right=0.9, top=0.9, bottom=0.1)

This will work for both the figure on screen and saved to a file, and it is the right function to call even if you don't have multiple plots on the one figure.

The numbers are fractions of the figure dimensions, and will need to be adjusted to allow for the figure labels.

DaveP
  • 6,952
  • 1
  • 24
  • 37
  • 16
    The values assigned to the parameters and not how much to change it by, they are where to set the margin. In other words, if you want to bring the right edge margin in by 10%, you should set right=0.9, not right=0.1 http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.subplots_adjust – drootang Nov 18 '11 at 16:18
  • 3
    It makes sense to point out that obviously you can specify negative values in plt.subplots_adjust(). Doing so even allows you to draw outside the figure area and also to deal with annoying margins. – surchs Feb 12 '14 at 15:02
  • 1
    This also works on `GridSpec` objects by calling the `update` method (see http://stackoverflow.com/a/20058199/1030876). – Aaron Voelker Feb 19 '17 at 21:44
  • This just position the subplot on the canvas `left=0.1,right=0.9` just makes left margin 10% of right marging, – MortenB Feb 01 '23 at 11:20
74

All you need is

plt.tight_layout()

before your output.

In addition to cutting down the margins, this also tightly groups the space between any subplots:

x = [1,2,3]
y = [1,4,9]
import matplotlib.pyplot as plt
fig = plt.figure()
subplot1 = fig.add_subplot(121)
subplot1.plot(x,y)
subplot2 = fig.add_subplot(122)
subplot2.plot(y,x)
fig.tight_layout()
plt.show()
user2065406
  • 841
  • 6
  • 2
  • 9
    I think this is really the best method. It doesn't require saving the figure like `bbox='tight' and fixes all kinds of other layout issues in cramped figures. – dshepherd Apr 30 '13 at 23:59
  • 2
    this should be the correct answer because it behaves as you'd expect as it applies to the FIGURE instead of the image. – Majid alDosari Nov 12 '15 at 18:47
  • Strangely enough, this also changes the width of the *actual plot* (i.e. the peaks are closer together) compared to `bbox_inches='tight'`, which just clips the white space around the edges but leaves the plot alone. I created the figure with `plt.figure(figsize=(10,3))`. – Fritz Jan 20 '20 at 14:13
32

Sometimes, the plt.tight_layout() doesn't give me the best view or the view I want. Then why don't plot with arbitrary margin first and do fixing the margin after plot? Since we got nice WYSIWYG from there.

import matplotlib.pyplot as plt

fig,ax = plt.subplots(figsize=(8,8))

plt.plot([2,5,7,8,5,3,5,7,])
plt.show()

Change border and spacing GUI here

Then paste settings into margin function to make it permanent:

fig,ax = plt.subplots(figsize=(8,8))

plt.plot([2,5,7,8,5,3,5,7,])
fig.subplots_adjust(
    top=0.981,
    bottom=0.049,
    left=0.042,
    right=0.981,
    hspace=0.2,
    wspace=0.2
)
plt.show()
anugrahandi
  • 461
  • 4
  • 4
17

Just use ax = fig.add_axes([left, bottom, width, height]) if you want exact control of the figure layout. eg.

left = 0.05
bottom = 0.05
width = 0.9
height = 0.9
ax = fig.add_axes([left, bottom, width, height])
keineahnung2345
  • 2,635
  • 4
  • 13
  • 28
mason
  • 241
  • 3
  • 4
17

In case anybody wonders how how to get rid of the rest of the white margin after applying plt.tight_layout() or fig.tight_layout(): With the parameter pad (which is 1.08 by default), you're able to make it even tighter: "Padding between the figure edge and the edges of subplots, as a fraction of the font size." So for example

plt.tight_layout(pad=0.05)

will reduce it to a very small margin. Putting 0 doesn't work for me, as it makes the box of the subplot be cut off a little, too.

ascripter
  • 5,665
  • 12
  • 45
  • 68
Oberwaschlappen
  • 171
  • 1
  • 3
  • I used `pad=0.5` instead because I immediately ran into a case where this clips content: if the highest y-axis tick mark is near the top of the plot area then its label will be cut off. Nice tip though, I came looking for this. – lehiester Apr 16 '21 at 01:47
10
plt.savefig("circle.png", bbox_inches='tight',pad_inches=-1)
Mark Hall
  • 53,938
  • 9
  • 94
  • 111
Tian Chu
  • 125
  • 1
  • 3
7

With recent matplotlib versions you might want to try Constrained Layout:

constrained_layout automatically adjusts subplots and decorations like legends and colorbars so that they fit in the figure window while still preserving, as best they can, the logical layout requested by the user.

constrained_layout is similar to tight_layout, but uses a constraint solver to determine the size of axes that allows them to fit.

constrained_layout needs to be activated before any axes are added to a figure.

Too bad pandas does not handle it well...

Michel de Ruiter
  • 7,131
  • 5
  • 49
  • 74
5

inspired by Sammys answer above:

margins = {  #     vvv margin in inches
    "left"   :     1.5 / figsize[0],
    "bottom" :     0.8 / figsize[1],
    "right"  : 1 - 0.3 / figsize[0],
    "top"    : 1 - 1   / figsize[1]
}
fig.subplots_adjust(**margins)

Where figsize is the tuple that you used in fig = pyplot.figure(figsize=...)

michaelosthege
  • 621
  • 5
  • 15
4

The problem with matplotlibs subplots_adjust is that the values you enter are relative to the x and y figsize of the figure. This example is for correct figuresizing for printing of a pdf:

For that, I recalculate the relative spacing to absolute values like this:

pyplot.subplots_adjust(left = (5/25.4)/figure.xsize, bottom = (4/25.4)/figure.ysize, right = 1 - (1/25.4)/figure.xsize, top = 1 - (3/25.4)/figure.ysize)

for a figure of 'figure.xsize' inches in x-dimension and 'figure.ysize' inches in y-dimension. So the whole figure has a left margin of 5 mm, bottom margin of 4 mm, right of 1 mm and top of 3 mm within the labels are placed. The conversion of (x/25.4) is done because I needed to convert mm to inches.

Note that the pure chart size of x will be "figure.xsize - left margin - right margin" and the pure chart size of y will be "figure.ysize - bottom margin - top margin" in inches

Other sniplets (not sure about these ones, I just wanted to provide the other parameters)

pyplot.figure(figsize = figureSize, dpi = None)

and

pyplot.savefig("outputname.eps", dpi = 100)
Sammy
  • 51
  • 1
  • 3
    Where did you get `xsize` and `ysize` from. I use those properties and I get `AttributeError: 'Figure' object has no attribute 'xsize'` – cj5 May 16 '14 at 14:56
2

For me, the answers above did not work with matplotlib.__version__ = 1.4.3 on Win7. So, if we are only interested in the image itself (i.e., if we don't need annotations, axis, ticks, title, ylabel etc), then it's better to simply save the numpy array as image instead of savefig.

from pylab import *

ax = subplot(111)
ax.imshow(some_image_numpyarray)
imsave('test.tif', some_image_numpyarray)

# or, if the image came from tiff or png etc
RGBbuffer = ax.get_images()[0].get_array()
imsave('test.tif', RGBbuffer)

Also, using opencv drawing functions (cv2.line, cv2.polylines), we can do some drawings directly on the numpy array. http://docs.opencv.org/2.4/modules/core/doc/drawing_functions.html

otterb
  • 2,660
  • 2
  • 29
  • 48
2
# import pyplot
import matplotlib.pyplot as plt

# your code to plot the figure

# set tight margins
plt.margins(0.015, tight=True)
BhushanDhamale
  • 1,245
  • 2
  • 10
  • 12
0

compact matplotlib template for subplots

As I have the same hussle each time I set up a new plot, I decided to document a template for this on stackoverflow.

template

import matplotlib.pylab as plt

def plot_figure_tempalte():
    nrows=2
    ncols=3

    fig, axs = plt.subplots(
        nrows=nrows, ncols=ncols, figsize=(14.9, 10.5),  # A6: 145mm x 105mm
        gridspec_kw=dict(
            left=.1, bottom=.05, right=.99, top=.8, wspace=.1, hspace=.3
        )
    )

    for rr in range(nrows):
        for cc in range(ncols):
            axs[rr, cc].set_title(f"row {rr}, col {cc}")

    fig.savefig("output.png", dpi=300)


if __name__ == "__main__":
    plot_figure_tempalte()

output.png

enter image description here

Markus Dutschke
  • 9,341
  • 4
  • 63
  • 58