How to Set the Aspect Ratio in Matplotlib

How can I set the aspect ratio in matplotlib?

Third times the charm. My guess is that this is a bug and Zhenya's answer suggests it's fixed in the latest version. I have version 0.99.1.1 and I've created the following solution:

import matplotlib.pyplot as plt
import numpy as np

def forceAspect(ax,aspect=1):
im = ax.get_images()
extent = im[0].get_extent()
ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_xlabel('xlabel')
ax.set_aspect(2)
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')
forceAspect(ax,aspect=1)
fig.savefig('force.png')

This is 'force.png':
Sample Image

Below are my unsuccessful, yet hopefully informative attempts.

Second Answer:

My 'original answer' below is overkill, as it does something similar to axes.set_aspect(). I think you want to use axes.set_aspect('auto'). I don't understand why this is the case, but it produces a square image plot for me, for example this script:

import matplotlib.pyplot as plt
import numpy as np

data = np.random.rand(10,20)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.imshow(data)
ax.set_aspect('equal')
fig.savefig('equal.png')
ax.set_aspect('auto')
fig.savefig('auto.png')

Produces an image plot with 'equal' aspect ratio:
Sample Image
and one with 'auto' aspect ratio:
Sample Image

The code provided below in the 'original answer' provides a starting off point for an explicitly controlled aspect ratio, but it seems to be ignored once an imshow is called.

Original Answer:

Here's an example of a routine that will adjust the subplot parameters so that you get the desired aspect ratio:

import matplotlib.pyplot as plt

def adjustFigAspect(fig,aspect=1):
'''
Adjust the subplot parameters so that the figure has the correct
aspect ratio.
'''
xsize,ysize = fig.get_size_inches()
minsize = min(xsize,ysize)
xlim = .4*minsize/xsize
ylim = .4*minsize/ysize
if aspect < 1:
xlim *= aspect
else:
ylim /= aspect
fig.subplots_adjust(left=.5-xlim,
right=.5+xlim,
bottom=.5-ylim,
top=.5+ylim)

fig = plt.figure()
adjustFigAspect(fig,aspect=.5)
ax = fig.add_subplot(111)
ax.plot(range(10),range(10))

fig.savefig('axAspect.png')

This produces a figure like so:
Sample Image

I can imagine if your having multiple subplots within the figure, you would want to include the number of y and x subplots as keyword parameters (defaulting to 1 each) to the routine provided. Then using those numbers and the hspace and wspace keywords, you can make all the subplots have the correct aspect ratio.

matplotlib.pyplot, preserve aspect ratio of the plot

Does it help to use:

plt.axis('equal')

Aspect ratio of a plot

There are a few options. As suggested in the comments, you can increase the figure size manually, making sure the width is three time bigger than the height.

fig, ax = plt.subplots(figsize=(height, height*3))

If you want to use figaspect you can do the following:

from matplotlib.figure import figaspect

w, h = figaspect(1/3)
fig, ax = plt.subplots(figsize=(w,h))

plt.show()

This creates a figure which is 3 times wider than it is tall and gives:

Sample Image

You can also change the axes aspect ratio using ax.set_aspect(). This will not change the figure size:

fig, ax = plt.subplots()

ax.set_aspect(1/3)

plt.show()

Which gives:

Sample Image

Python: set aspect ratio of figure to 1

Add the aspect kw to your fig, ax statement:

fig, ax = plt.subplots(subplot_kw={'aspect': 1})

set matplotlib 3d plot aspect ratio

My understanding is basically that this isn't implemented yet (see this bug in GitHub). I'm also hoping that it is implemented soon. See This link for a possible solution (I haven't tested it myself).

How to set same aspect ratio for subplots in matplotlib

Short answer: use the figsize keyword argument when making subplots:

import numpy as np
import matplotlib.pyplot as plt

xdata = np.arange(0,2,.01)
ydata1 = xdata
ydata2 = xdata ** 2

fig = plt.figure(figsize=(10,6))
ax = fig.subplots(1,2)

ax[0].plot(xdata,ydata1)
ax[1].plot(xdata,ydata2)

# Squares
ax[0].plot([0,1,1,0],[0,0,1,1])
ax[1].plot([0,1,1,0],[0,0,1,1])

plt.show(fig)

figsize is a tuple in inches, with the first element as the width in the x-direction, the second element as the width in the y-direction.

It applies to the whole figure; you will need to tweak it to get a suitable shape for your subplots.

Sample Image

Longer answer: .set_aspect(num) sets the y-axis to x-axis ratio for each plot.

So, when you used .set_aspect(.5) on each axis, you told matplotlib to reconfigure each axis such that the y-axis is 0.5 times as big as the x-axis. This means that a 1x1 square actually looks like a rectangle on each plot:

import numpy as np
import matplotlib.pyplot as plt

xdata = np.arange(0,2,.01)
ydata1 = xdata
ydata2 = xdata ** 2

fig, ax = plt.subplots(1,2)

ax[0].plot(xdata,ydata1)
ax[1].plot(xdata,ydata2)

# Squares
ax[0].plot([0,1,1,0],[0,0,1,1])
ax[1].plot([0,1,1,0],[0,0,1,1])

ax[0].set_aspect(.5)
ax[1].set_aspect(.5)
plt.show(fig)

That's why your plots are different shapes; the axis limits are different, but you've set the size of shapes on both plots to be the same.

Sample Image

how to share axis in matplotlib and use only one equal aspect ratio

A second approach, aside from manually adjusting the figure size is to use an inset_axes that is a child of the parent. You can even set up sharing between the two, though you'll have to remove the tick labels if that is what you want:

fig, ax1 = plt.subplots(constrained_layout=True)

ax1.pcolor(Y, Z, vp_mean.reshape(nz, nx, ny, order='F')[:,ix,:].T, cmap="jet", vmin=vp_min, vmax=vp_max)
ax1.plot(iy*h*np.ones(nz) / 1000, z, "k--")
ax1.set_ylabel(r'Depth ($km$)')
ax1.set_xlabel(r'Y ($km$)')
ax1.set_aspect('equal')

ax2 = ax1.inset_axes([1.05, 0, 0.3, 1], transform=ax1.transAxes)
ax1.get_shared_y_axes().join(ax1, ax2)

lines = []
for e in range(ensemble_size):
lines.append( ax2.plot(vp[:,e].reshape(nz, nx, ny, order='F')[:,ix,iy], z, "b", alpha=0.25, label="models") )
lines.append( ax2.plot(vp_mean.reshape(nz, nx, ny, order='F')[:,ix,iy], z, "r", alpha=1.0, label="average model") )
plt.setp(lines[1:ensemble_size], label="_")
ax2.set_xlabel(r'$V_p$ ($m/s$)')

This has the advantage that the second axes will always be there, even if you zoom the first axes.

Sample Image



Related Topics



Leave a reply



Submit