22

I would like Matplotlib/Pyplot to generate plots with a consistent canvas size. That is, the figures can well have different sizes to accomodate the axis descriptions, but the plotting area (the rectangle within which the curves are drawn) should always have the same size.

Is there a simple way to achieve that? The option figsize of pyplot.figure() seems to set the overall size of the figure, not that of the canvas, so I get a different canvas size whenever the axis description occupies more or less space.

Dario
  • 223
  • 1
  • 2
  • 5
  • It's possible that `gridspec` allows you to control what you want (see the last 3 [examples here](http://matplotlib.org/examples/pylab_examples/demo_tight_layout.html) ). Could you add some examples showing different axis descriptions that cause problems? – Bonlenfum Apr 17 '13 at 13:33
  • This is a cost of the way that MPL de-couples the description of what to draw and _how_ to draw it. Pretty sure doing this by hand, and studiously avoiding `tight_layout,` is the best way to go with this. – tacaswell Apr 17 '13 at 19:15

2 Answers2

21

This is one of my biggest frustrations with Matplotlib. I often work with raster data where for example i want to add a colormap, legend and some title. Any simple example from the matplotlib gallery doing so will result in a different resolution and therefore resampled data. Especially when doing image analysis you dont want any (unwanted) resampling.

Here is what i usually do, although i would love to know if there are simpler or better ways.

Lets start with loading a picture and outputting it just as it is with the same resolution:

import matplotlib.pyplot as plt
import urllib2

# load the image
img = plt.imread(urllib2.urlopen('http://upload.wikimedia.org/wikipedia/en/thumb/5/56/Matplotlib_logo.svg/500px-Matplotlib_logo.svg.png'))

# get the dimensions
ypixels, xpixels, bands = img.shape

# get the size in inches
dpi = 72.
xinch = xpixels / dpi
yinch = ypixels / dpi

# plot and save in the same size as the original
fig = plt.figure(figsize=(xinch,yinch))

ax = plt.axes([0., 0., 1., 1.], frameon=False, xticks=[],yticks=[])
ax.imshow(img, interpolation='none')

plt.savefig('D:\\mpl_logo.png', dpi=dpi, transparent=True)

Note that i manually defined the axes position so that spans the entire figure.

In a similar way as above you could add some margin around the image to allow for labels or colorbars etc.

This example adds a 20% margin above the image, which is then used for plotting a title:

fig = plt.figure(figsize=(xinch,yinch/.8))

ax = plt.axes([0., 0., 1., .8], frameon=False, xticks=[],yticks=[])
ax.imshow(img, interpolation='none')
ax.set_title('Matplotlib is fun!', size=16, weight='bold')

plt.savefig('D:\\mpl_logo_with_title.png', dpi=dpi)

So the figure y-size (height) is increased and the y-size of the axes is decreased equally. This gives a larger (overall) output image, but the axes area will still be the same size.

It might be nice the have a figure or axes property like .set_scale() to force a true 1-on-x output.

Rutger Kassies
  • 61,630
  • 17
  • 112
  • 97
  • Can you use some vector format instead? It _should_ (I think) draw each pixel as a rectangle. Or just _way_ over sample. – tacaswell Apr 17 '13 at 19:16
  • 3
    Thank you, your suggestion worked! By the way, I think I did find a slightly more elegant way using subplot_adjust(0,0,1,1) immediately after calling figure(). Then, you don't need to define the axes positions because they are already as big as the figure. Finally, you can call savefig() with the argument bbox_inches='tight' if you don't want to manually specify the margins. In this case, the plotting area will be exactly as defined by (xinch,yinch) and the figure will be large enough to accomodate the axis descriptions. (As pointed out by tcaswell, do not use tight_layout!) – Dario Apr 18 '13 at 14:21
  • That sounds easier indeed, thanks for the tips. I'll try your suggestions later. @tcaswell, thanks, that might work in some situations, i think pcolor should be able to do that quite easily. Any other post-processing would be more difficult though. – Rutger Kassies Apr 18 '13 at 14:33
  • @Dario If you do that, then you have pushed all of your axis labels off. If you are doing raster images, you might do better using PIL to just save the images. – tacaswell Apr 18 '13 at 15:33
  • @tcaswell It pushes to labels off, but you get them back if you call savefig(bbox_inches='tight'). Or am I missing your point? – Dario Apr 18 '13 at 19:53
  • @Dario That is a terrifying abuse of the system. Does this work on every backend? – tacaswell Apr 18 '13 at 21:14
4

You can specify the following settings in Matplotlib rc:

import matplotlib
matplotlib.rcParams['figure.figsize'] = [10, 10] # for square canvas
matplotlib.rcParams['figure.subplot.left'] = 0
matplotlib.rcParams['figure.subplot.bottom'] = 0
matplotlib.rcParams['figure.subplot.right'] = 1
matplotlib.rcParams['figure.subplot.top'] = 1
Piotr Jurkiewicz
  • 1,653
  • 21
  • 25