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

99,004

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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

Share:
99,004

Related videos on Youtube

Gabriel
Author by

Gabriel

Updated on February 11, 2021

Comments

  • Gabriel
    Gabriel about 3 years

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

  • Gabriel
    Gabriel almost 7 years
    Thank you very much for the help @ImportanceOfBeingErnest !!! 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.
  • ImportanceOfBeingErnest
    ImportanceOfBeingErnest almost 7 years
    (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.
  • Gabriel
    Gabriel almost 7 years
    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.
  • ImportanceOfBeingErnest
    ImportanceOfBeingErnest almost 7 years
    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.
  • Gabriel
    Gabriel almost 7 years
    Hello again @ImportanceOfBeingErnest I discovered that the function does not work when using a colorbar in the figure. How you can account for the colorbar?
  • ImportanceOfBeingErnest
    ImportanceOfBeingErnest almost 7 years
    You can add an axes for the colorbar (add_axes) and make some space for it using subplots_adjust.
  • ImportanceOfBeingErnest
    ImportanceOfBeingErnest over 5 years
    My answer will not give inaccurate results. But of course you may justify your claim with an example.
  • BongaCodeBuddy
    BongaCodeBuddy over 5 years
    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.
  • AzyCrw4282
    AzyCrw4282 almost 4 years
    Please see here on how to write a good answer.