Axes class - set explicitly size (width/height) of axes in given units
Asked Answered
I

5

41

I want to to create a figure using matplotlib where I can explicitly specify the size of the axes, i.e. I want to set the width and height of the axes bbox.

I have looked around all over and I cannot find a solution for this. What I typically find is how to adjust the size of the complete Figure (including ticks and labels), for example using fig, ax = plt.subplots(figsize=(w, h))

This is very important for me as I want to have a 1:1 scale of the axes, i.e. 1 unit in paper is equal to 1 unit in reality. For example, if xrange is 0 to 10 with major tick = 1 and x axis is 10cm, then 1 major tick = 1cm. I will save this figure as pdf to import it to a latex document.

This question brought up a similar topic but the answer does not solve my problem (using plt.gca().set_aspect('equal', adjustable='box') code)

From this other question I see that it is possible to get the axes size, but not how to modify them explicitly.

Any ideas how I can set the axes box size and not just the figure size. The figure size should adapt to the axes size.

Thanks!

For those familiar with pgfplots in latex, it will like to have something similar to the scale only axis option (see here for example).

Indefeasible answered 7/7, 2017 at 11:47 Comment(0)
F
42

The axes size is determined by the figure size and the figure spacings, which can be set using figure.subplots_adjust(). In reverse this means that you can set the axes size by setting the figure size taking into acount the figure spacings:

import matplotlib.pyplot as plt

def set_size(w,h, ax=None):
    """ w, h: width, height in inches """
    if not ax: ax=plt.gca()
    l = ax.figure.subplotpars.left
    r = ax.figure.subplotpars.right
    t = ax.figure.subplotpars.top
    b = ax.figure.subplotpars.bottom
    figw = float(w)/(r-l)
    figh = float(h)/(t-b)
    ax.figure.set_size_inches(figw, figh)
          
fig, ax=plt.subplots()

ax.plot([1,3,2])

set_size(5,5)

plt.show()
Francoisefrancolin answered 7/7, 2017 at 12:48 Comment(8)
Thank you very much for the help @Francoisefrancolin !!! this seems to work very well in the generated pdf (like magic!). An important note is that the set_size should be executed just before saving the figure in order to account for all changes in the labels and ticks. However I noticed two problems: (1) if the size of the figure is too small the labels are clipped and (2) when I print the pdf the size does not correspond to the reality scale. Why is that? Note that I call the pdf in a latex document as a figure with \includegraphics and then I print the generated pdf.Indefeasible
(1) That is to be expected. You may want to set larger spacings using fig.subplots_adjust(). (2) by printing you mean print to paper using an ink or laser printer? That would possibly have totally different reasons. First check if the size in the pdf itself is correct. I assume this to be the case, next check if the size in the latex is correct. This may or may not be the case. Finally the printed paper may have a different scale depending on the printer settings.Francoisefrancolin
I use the pdf measure tool to check the size of the latex pdf and it is right but not on paper after printing in a laser printer.Indefeasible
I see. But as the latex pdf has the correct size, I think we cannot give support on printing issues on StackOverflow. Check your printer settings, look for similar issues on other pages, possibly on superuser.stackexchange or ask a question there.Francoisefrancolin
Hello again @Francoisefrancolin I discovered that the function does not work when using a colorbar in the figure. How you can account for the colorbar?Indefeasible
You can add an axes for the colorbar (add_axes) and make some space for it using subplots_adjust.Francoisefrancolin
I'm getting very different sizes for the axes using this method. Maybe it's because I have different axis limits for each of my plots? Here are the different plots, including their differing sizes imgur.com/a/h29DSVSMartyr
That's really cool, I needed similar thing because I have two axes with images and they are a little bit small. But there is a lot of space around it so I will use this to calculate corrected dimentions of the axes (something like this chat.openai.com/share/6a997d1c-5c9a-447a-977b-fb8a39783919). @Skeleton Bow: maybe the big labels are not included in the dimentions... Did you solve that?Lierne
A
7

It appears that Matplotlib has helper classes that allow you to define axes with a fixed size Demo fixed size axes

Almswoman answered 9/7, 2018 at 16:43 Comment(0)
G
4

I have found that ImportanceofBeingErnests answer which modifies that figure size to adjust the axes size provides inconsistent results with the paticular matplotlib settings I use to produce publication ready plots. Slight errors were present in the final figure size, and I was unable to find a way to solve the issue with his approach. For most use cases I think this is not a problem, however the errors were noticeable when combining multiple pdf's for publication.

In lieu of developing a minimum working example to find the real issue I am having with the figure resizing approach I instead found a work around which uses the fixed axes size utilising the divider class.

from mpl_toolkits.axes_grid1 import Divider, Size
def fix_axes_size_incm(axew, axeh):
    axew = axew/2.54
    axeh = axeh/2.54

    #lets use the tight layout function to get a good padding size for our axes labels.
    fig = plt.gcf()
    ax = plt.gca()
    fig.tight_layout()
    #obtain the current ratio values for padding and fix size
    oldw, oldh = fig.get_size_inches()
    l = ax.figure.subplotpars.left
    r = ax.figure.subplotpars.right
    t = ax.figure.subplotpars.top
    b = ax.figure.subplotpars.bottom

    #work out what the new  ratio values for padding are, and the new fig size.
    neww = axew+oldw*(1-r+l)
    newh = axeh+oldh*(1-t+b)
    newr = r*oldw/neww
    newl = l*oldw/neww
    newt = t*oldh/newh
    newb = b*oldh/newh

    #right(top) padding, fixed axes size, left(bottom) pading
    hori = [Size.Scaled(newr), Size.Fixed(axew), Size.Scaled(newl)]
    vert = [Size.Scaled(newt), Size.Fixed(axeh), Size.Scaled(newb)]

    divider = Divider(fig, (0.0, 0.0, 1., 1.), hori, vert, aspect=False)
    # the width and height of the rectangle is ignored.

    ax.set_axes_locator(divider.new_locator(nx=1, ny=1))

    #we need to resize the figure now, as we have may have made our axes bigger than in.
    fig.set_size_inches(neww,newh)

Things worth noting:

  • Once you call set_axes_locator() on an axis instance you break the tight_layout() function.
  • The original figure size you choose will be irrelevent, and the final figure size is determined by the axes size you choose and the size of the labels/tick labels/outward ticks.
  • This approach doesn't work with colour scale bars.
  • This is my first ever stack overflow post.
Garfish answered 28/8, 2018 at 8:0 Comment(2)
My answer will not give inaccurate results. But of course you may justify your claim with an example.Francoisefrancolin
Ok, after further testing I can see that with base rcParams then your code works without inaccuracy for an output pdf file (the interactive plot is wrong for at least 1x1 inch). I doubt I'll put any more time into working out which particular parameter causes the issues as I have a solution that works for my use case, but if I were to guess it is 'text.usetex = True'. I edited my answer to reflect this.Garfish
S
3

another method using fig.add_axes was quite accurate. I have included 1 cm grid aswell

import matplotlib.pyplot as plt
import matplotlib as mpl

# This example fits a4 paper with 5mm margin printers

# figure settings
figure_width = 28.7 # cm
figure_height = 20 # cm
left_right_magrin = 1 # cm
top_bottom_margin = 1 # cm

# Don't change
left   = left_right_magrin / figure_width # Percentage from height
bottom = top_bottom_margin / figure_height # Percentage from height
width  = 1 - left*2
height = 1 - bottom*2
cm2inch = 1/2.54 # inch per cm

# specifying the width and the height of the box in inches
fig = plt.figure(figsize=(figure_width*cm2inch,figure_height*cm2inch))
ax = fig.add_axes((left, bottom, width, height))

# limits settings (important)
plt.xlim(0, figure_width * width)
plt.ylim(0, figure_height * height)

# Ticks settings
ax.xaxis.set_major_locator(mpl.ticker.MultipleLocator(5))
ax.xaxis.set_minor_locator(mpl.ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(mpl.ticker.MultipleLocator(5))
ax.yaxis.set_minor_locator(mpl.ticker.MultipleLocator(1))

# Grid settings
ax.grid(color="gray", which="both", linestyle=':', linewidth=0.5)

# your Plot (consider above limits)
ax.plot([1,2,3,5,6,7,8,9,10,12,13,14,15,17])

# save figure ( printing png file had better resolution, pdf was lighter and better on screen)
plt.show()
fig.savefig('A4_grid_cm.png', dpi=1000)
fig.savefig('tA4_grid_cm.pdf')

result:

enter image description here

Scrubby answered 11/6, 2020 at 17:6 Comment(1)
Please see here on how to write a good answer.Spile
M
1

I tried some of this answers, but what I wanted was a better plt.subplots that let me specify the size of the axes instead of the whole figure. Turns out jupyter and pyplot are keen on resizing things, so I did my own version:

def subjectively_better_subplots(nrows, ncols, margin= 2, header = 2, subheight = 8, subwidth= 8): ## now it is in cm

    m = margin /2.54
    h = header /2.54

    a = subheight/2.54
    b = subwidth /2.54

    ## Here I calculate the figure size that you need for these parameters, as OP asked for. 

    width = ncols * (m + b+ m)
    height = nrows * (h +a +h)

    axarr = np.empty((nrows, ncols), dtype =object)
    
    #print(height, width)

    fig = plt.figure(figsize=(width, height))

    for i in range(nrows):
        for j in range(ncols):
            axarr[i,j] = fig.add_axes([(m+j*(2*m+b))/width,
                (height - (i+1)*(2*h+a)+h)/height,
                b/width,
                a/height])

    return fig, axarr

It should return fig and axs just like subplots, so it can quickly replace a subplots call.

Multinational answered 26/2 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.