Axes class - set explicitly size (width/height) of axes in given units
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 thetight_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:
Related videos on Youtube
Gabriel
Updated on February 11, 2021Comments
-
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 almost 7 yearsThank 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 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 almost 7 yearsI 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 almost 7 yearsI 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 almost 7 yearsHello 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 almost 7 yearsYou can add an axes for the colorbar (
add_axes
) and make some space for it usingsubplots_adjust
. -
ImportanceOfBeingErnest over 5 yearsMy answer will not give inaccurate results. But of course you may justify your claim with an example.
-
BongaCodeBuddy over 5 yearsOk, 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 almost 4 yearsPlease see here on how to write a good answer.