I have a problem with plotting multiple subplots. I would like to set the PHYSICAL aspect ratio of the subplots to a fixed value.
In my example I have 12 subplots (4 rows and 3 columns) on a landscape A4 figure. There all subplots are nicely placed on the whole figure, and for all subplots the height is nearly equal to the width.
But if I change the layout of my figure to portrait, the subplots are stretched vertically.
And this is exactly what should not happen. I would like to have the same height and width of the subplots as on the landscape figure. Is there a possibility that the aspect ratio of the subplots stay the same?
Thanks in advance,
Peter
EDIT:
I have found a workaround. But this just works for non-logarithmic axes...
aspectratio=1.0
ratio_default=(ax.get_xlim()[1]-ax.get_xlim()[0])/(ax.get_ylim()[1]-ax.get_ylim()[0])
ax.set_aspect(ratio_default*aspectratio)
Actually, what you're wanting is quite simple... You just need to make sure that adjustable is set to 'box' on your axes, and you have a set aspect ratio for the axes (anything other than 'auto').
You can either do this with the adjustable kwarg when you create the subplots. Alternatively, you can do this after their creation by calling ax.set_adjustable('box'), or by calling ax.set_aspect(aspect, adjustable='box') (where aspect is either 'equal' or a number).
Now, regardless of how the figure is resized, the subplots will maintain the same aspect ratio.
For example:
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1, adjustable='box', aspect=0.3)
ax2 = fig.add_subplot(2,1,2)
ax1.plot(range(10))
ax2.plot(range(10))
plt.show()
Now, compare how the top subplot responds to resizing, vs. how the bottom subplot responds:
The initial plot
Resized to a vertical layout:
Resized to a horizontal layout:
Your workaround works for me. After plotting the data, I call the following function:
def fixed_aspect_ratio(ratio):
'''
Set a fixed aspect ratio on matplotlib plots
regardless of axis units
'''
xvals,yvals = gca().axes.get_xlim(),gca().axes.get_ylim()
xrange = xvals[1]-xvals[0]
yrange = yvals[1]-yvals[0]
gca().set_aspect(ratio*(xrange/yrange), adjustable='box')
In reply to the remark about the solution not working for logarithmic plots in the edit to the original question - you need to adapt as follows:
def fixed_aspect_ratio_loglog(ratio):
'''
Set a fixed aspect ratio on matplotlib loglog plots
regardless of axis units
'''
xvals,yvals = gca().axes.get_xlim(),gca().axes.get_ylim()
xrange = log(xvals[1])-log(xvals[0])
yrange = log(yvals[1])-log(yvals[0])
gca().set_aspect(ratio*(xrange/yrange), adjustable='box')
(Adaptation for semilog plots should now be obvious)
Related
Python produces an aspect ratio that is suitable for its content e.g., respects the structure of the font of each label, axis title, etc. This is the basic code using Jupyter Notebook:
fig, ax = plt.subplots()
ax.boxplot(dataLipid)
ax.set_title("Lipid contact analysis")
plt.xticks([1,2,3,4,5],["x4 Monomers","x2 Monomers\nDimer","x2 Dimers","Monomer\nTrimer", "x4mer"])
plt.show()
However, I want to save the image as a tiff, with a dpi of 600, and a width of 8.3cm (maximum height is an A4 page, but the nature of my question will make that irrelevant).
I'm using the code:
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
This produces the following:
All good so far. Next, the Royal Soc. of Chemistry expect a single column image to be 8.3 cm in width (height, no more than the page).
My question:
Is there any way for Python to calculate the height of the figure given only the wdith, whilst maintaining the correct aspect ratio for the fonts, titles and ticks etc.? If I specify width=height, the image looks terrible:
fig.set_size_inches(3.26,3.26)
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
Or is this a case where I define the size of the figure first, then adjust the font sizes as a separate step? I'm looking more for a one-fix solution as I have multiple figures of different size requirements (all being dpi=600 though) to produce.
Here you go:
dataLipid = np.random.uniform(0,1,(100,5)) * 90000
fig, ax = plt.subplots()
ax.boxplot(dataLipid)
ax.set_title("Lipid contact analysis")
plt.xticks([1,2,3,4,5],["x4 Monomers","x2 Monomers\nDimer","x2 Dimers","Monomer\nTrimer", "x4mer"])
fig.set_size_inches(3.26,3.26)
# rotate ticks
plt.xticks(rotation=45)
# set bottom margin
plt.subplots_adjust(left=0.2, bottom=0.3)
fig.savefig("bar.tiff", dpi=600, format="tiff", pil_kwards = {"compression":"tiff_lzm"})
There is no general solution as far as I know. So setting the correct margin depends on your content and your data. Rotating the ticks is always a good option to make them readable in case of close spacing.
You can use the Axes.set_aspect method.
# square plot
ax.set_aspect(1)
Also have a look at the tight_layout method to ensure everything is redrawn to fit in the figure.
I'm trying to create imshow subplots with the same pixel size without having the figure height automatically scaled, but I haven't been able to figure out how.
Ideally, I'm looking for a plot similar to the second picture, without the extra white space (ylim going from -0.5 to 4.5) and maybe centered vertically. My pictures will always have the same width, so maybe if I could fix the subplot width instead of the height that would help. Does anyone have any ideas?
close('all')
f,ax=subplots(1,2)
ax[0].imshow(random.rand(30,4),interpolation='nearest')
ax[1].imshow(random.rand(4,4),interpolation='nearest')
tight_layout()
f,ax=subplots(1,2)
ax[0].imshow(random.rand(30,4),interpolation='nearest')
ax[1].imshow(random.rand(4,4),interpolation='nearest')
ax[1].set_ylim((29.5,-0.5))
tight_layout()
Plot without ylim adjustment:
Plot with ylim adjustment:
In principle you can just make the figure size small enough in width, such that it constrains the widths of the subplots. E.g. figsize=(2,7) would work here.
For an automated solution, you may adjust the subplot parameters, such that the left and right margin constrain the subplot width. This is shown in the code below.
It assumes that there is one row of subplots, and that all images have the same pixel number in horizontal direction.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(1,2)
im1 = ax[0].imshow(np.random.rand(30,4))
im2 = ax[1].imshow(np.random.rand(4,4))
def adjustw(images, wspace="auto"):
fig = images[0].axes.figure
if wspace=="auto":
wspace = fig.subplotpars.wspace
top = fig.subplotpars.top
bottom = fig.subplotpars.bottom
shapes = np.array([im.get_array().shape for im in images])
w,h = fig.get_size_inches()
imw = (top-bottom)*h/shapes[:,0].max()*shapes[0,1] #inch
n = len(shapes)
left = -((n+(n-1)*wspace)*imw/w - 1)/2.
right = 1.-left
fig.subplots_adjust(left=left, right=right, wspace=wspace)
adjustw([im1, im2], wspace=1)
plt.show()
If you need to use tight_layout(), do so before calling the function. Also you would then definitely need to set the only free parameter here, wspace to something other than "auto". wspace=1 means to have as much space between the plots as their width.
The result is a figure where the subplots have the same size in width.
What I would like to achive are plots with equal scale aspect ratio, and fixed width, but a dynamically chosen height.
To make this more concrete, consider the following plotting example:
import matplotlib as mpl
import matplotlib.pyplot as plt
def example_figure(slope):
# Create a new figure
fig = plt.figure()
ax = fig.add_subplot(111)
# Set axes to equal aspect ratio
ax.set_aspect('equal')
# Plot a line with a given slope,
# starting from the origin
ax.plot([x * slope for x in range(5)])
# Output the result
return fig
This example code will result in figures of different widths, depending on the data:
example_figure(1).show()
example_figure(2).show()
Matplotlib seems to fit the plots into a certain height, and then chooses the width to accomodate the aspect ratio. The ideal outcome for me would be the opposite -- the two plots above would have the same width, but the second plot would be twice as tall as the first.
Bonus — Difficulty level: Gridspec
In the long run, I would like to create a grid in which one of the plots has a fixed aspect ratio, and I would again like to align the graphs exactly.
# Create a 2x1 grid
import matplotlib.gridspec as gridspec
gs = gridspec.GridSpec(2, 1)
# Create the overall graphic, containing
# the top and bottom figures
fig = plt.figure()
ax1 = fig.add_subplot(gs[0, :], aspect='equal')
ax2 = fig.add_subplot(gs[1, :])
# Plot the lines as before
ax1.plot(range(5))
ax2.plot(range(5))
# Show the figure
fig.show()
The result is this:
So again, my question is: How does one create graphs that vary flexibly in height depending on the data, while having a fixed width?
Two points to avoid potential misunderstandings:
In the above example, both graphs have the same x-axis. This cannot be
taken for granted.
I am aware of the height_ratios option in the gridspec. I can compute
the dimensions of the data, and set the ratios, but this unfortunately
does not control the graphs directly, but rather their bounding boxes,
so (depending on the axis labels), graphs of different widths still occur.
Ideally, the plots' canvas would be aligned exactly.
Another unsolved question is similar, but slightly more convoluted.
Any ideas and suggestions are very welcome, and I'm happy to specify the question further, if required. Thank you very much for considering this!
Have you tried to fix the width with fig.set_figwidth()?
I have a graph in which I've set the axis labels to scientific notation using
formatter = mpl.ticker.FormatStrFormatter('%4.2e')
axis2.yaxis.set_major_formatter(formatter)
However, the axes.patch (or whatever is the right way to express the 'canvas' extent of the plot) doesn't adjust so the tick labels and axis label are clipped:
How do I adjust the extent of the axes portion of the plot. Changing the page size (figsize = ...) doesn't do it, since that just scales the overall plot area, resulting in the same clipping problem.
You can use the method tight_layout, which will accommodate the plot in the figure available space.
Example
from pylab import *
f = figure()
f.add_subplot(111)
f.tight_layout()
show()
Hope it helps.
Cheers
Just call fig.tight_layout() (assuming you have a Figure object defined).
This one is a quick and easy one for the matplotlib community. I was looking to plot an L-shaped gridspec layout, which I have done:
Ignoring a few layout issues I have for the moment, what I have is that the x-axis in the gs[0] plot (top left) shares the x-axis with the gs[2] plot (bottom left) and the gs[2] shares its y axis with the gs[3] plot. Now, what I was hoping to do was update the w-space and h-space to be tighter. So that the axes are almost touching, so perhaps wspace=0.02, hspace=0.02 or something similar.
I was also hoping that the bottom right hand plot was to be longer in the horizontal orientation, keeping the two left hand plots square in shape. Or as close to square as possible. If someone could run through all of the parameters I would be very appreciative. I can tinker then in my own time.
To change the spacings of the plot with grid spec:
gs = gridspec.GridSpec(2, 2,width_ratios=[1,1.5],height_ratios=[1,1])
This changes the relative size of plot gs[0] and gs[2] to gs1 and gs[3], whereas something like:
gs = gridspec.GridSpec(2, 2,width_ratios=[1,1],height_ratios=[1,2])
will change the relative sizes of plot gs[0] and gs1 to gs[2] and gs[3].
The following will tighten up the plots:
gs.update(hspace=0.01, wspace=0.01)
This gave me the following plot:
I also used the following to remove the axis labels where needed:
nullfmt = plt.NullFormatter()
ax.yaxis.set_major_formatter(nullfmt)
ax.xaxis.set_major_formatter(nullfmt)