The main problem is that after setting the xlim of the axis (in particular decreasing xmax from 512 to 50), the figure doesn't rescale to focus on the region of the new xlim (0 to 50). Instead, the plot in the figure is scaled smaller with big right margin after xmax although nothing gets plotted. Strangely this only happens when I save the figures in pdfs.
Here are screenshots of the two pdf images I generated. The first one is the original image without changing the xlim.
The second one here is the image by setting xmax to 50 from 512.
Here is part of my code:
fig = plt.figure(facecolor='white')
ax = plt.axes()
...
lines=ax.plot(*a, clip_on=False)
ax.set_xlim([0, 50])
...
pp = PdfPages(outfile + '.pdf')
pp.savefig(bbox_inches='tight')
What would be the right way to remove the extra right margin space after xlim?
Related
I have plotted matplotlib.pyplot plot. I have already removed the axes and title of the plot such that in jupyter notebook it looks like an image.
But I need to save that plot as an image to my local disk with required pixel resolution. In my case it's 40 X 98.
I have tried plt.savefig but I can't get the measurements accurately. I have provided my code snippet below. (spectrum) is my 2D array which is to be plotted as a fucntion of x and y axes.
spect = 20 * np.log10(spectrum)
fig, ax = plt.subplots(figsize=(1,1))
ax = sns.heatmap(spect,cmap='viridis',cbar=False,xticklabels=False, yticklabels=False)
ax.invert_yaxis()
plt.savefig('sample.png',bbox_inches = 'tight', pad_inches = 0)
Try adjusting the dpi argument in savefig. From the docs:
"dpi : the resolution in dots per inch".
https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.savefig.html
The actual number of pixels may vary depending on your screen resolution.
For a more detailed explanation, see this answer:
Specifying and saving a figure with exact size in pixels
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.
subplots by hand.
I am referring following link
http://nbviewer.jupyter.org/github/jakevdp/PythonDataScienceHandbook/blob/master/notebooks/04.08-Multiple-Subplots.ipynb
The most basic method of creating an axes is to use the plt.axes function. As we've seen previously, by default this creates a standard axes object that fills the entire figure. plt.axes also takes an optional argument that is a list of four numbers in the figure coordinate system. These numbers represent [left, bottom, width, height] in the figure coordinate system, which ranges from 0 at the bottom left of the figure to 1 at the top right of the figure.
For example, we might create an inset axes at the top-right corner of another axes by setting the x and y position to 0.65 (that is, starting at 65% of the width and 65% of the height of the figure) and the x and y extents to 0.2 (that is, the size of the axes is 20% of the width and 20% of the height of the figure):
ax1 = plt.axes() # standard axes
ax2 = plt.axes([0.65, 0.65, 0.2, 0.2])
Here above example I am expecting ax2 at location starting (0.65,0.65) as we have bottom and left at 0.65 and 0.65 but i am observing (0.65, 0.7) and lenght and height is 0.2 i.e, right vertical line at location 0.85 but i am observing at 0.9? Why is this differnces. Kindly explain.
The numbers given to plt.axes are in figure units, where the figure is 1 unit wide and 1 unit heigh.
Let me just highlight the important part:
For example, we might create an inset axes at the top-right corner of another axes by setting the x and y position to 0.65 (that is, starting at 65% of the width and 65% of the height of the figure) and the x and y extents to 0.2 (that is, the size of the axes is 20% of the width and 20% of the height of the figure)
Maybe an image helps more to understand that
That said there is a little subtlety when this code is being used with the inline backend in IPython or jupyter. In that case the size of the figure shown in the output may slightly differ from the original figure as it is cropped or expanded to fit nicely to everything that is drawn inside. This is equivalent to the bbox_to_inched="tight" option of savefig. Hence, if you want to verify that the subplot is indeed placed at 65% of figure size and is 20% large, you would need to run the code as a script outside the notebook, or save the figure plt.savefig("test.png") (without using the bbox_to_inched="tight" option).
A final note: While add_axes may be an easy option to add an axes in figure coordintes, it is often desireable to add an inset in axes coordinates, i.e. in percentage of the axes width and height instead of the figure width and height. This can be done as follows:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
fig, ax1 = plt.subplots()
ax2 = plt.axes([0, 0, 1, 1])
ip = InsetPosition(ax1, [0.4, 0.1, 0.3, 0.7])
#posx, posy, width, height in coordinates of ax1
ax2.set_axes_locator(ip)
ax2.plot([1,2,3,4])
plt.show()
I'm plotting data and saving it to a file.
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(10,10))
plt.gca().set_aspect(1)
plt.scatter(range(10), range(0,20,2))
fig.savefig("test.jpg")
Is there an easy way to make the figure size larger, but without having to guess height/width values to keep aspect ratio as the axis?
It would be even harder if I had multiple subplots and I want to keep their fixed axis ratios. I want to control the figure size (scale), but not introduce empty borders in the figure.
One way is to set new figure size with .set_size_inches:
zoom = 2
w, h = fig.get_size_inches()
fig.set_size_inches(w * zoom, h * zoom)
This should change the size while keeping proportions.
Another way is to increase dpi if you need different size for saved figure only:
fig, ax = plt.subplots(figsize=(10,10))
ax.set_aspect(1)
ax.scatter(range(10), range(0,20,2))
dpi = fig.get_dpi() # This will get dpi that is set matplotlibrc
fig.savefig("test.jpg", dpi=dpi*2) # Multiply dpi by whatever ration you want to increase the figure
I am stuck in a rather complicated situation. I am plotting some data as an image with imshow(). Unfortunately my script is long and a little messy, so it is difficult to make a working example, but I am showing the key steps. This is how I get the data for my image from a bigger array, written in a file:
data = np.tril(np.loadtxt('IC-heatmap-20K.mtx'), 1)
#
#Here goes lot's of other stuff, where I define start and end
#
chrdata = data[start:end, start:end]
chrdata = ndimage.rotate(chrdata, 45, order=0, reshape=True,
prefilter=False, cval=0)
ax1 = host_subplot(111)
#I don't really need host_subplot() in this case, I could use something more common;
#It is just divider.append_axes("bottom", ...) is really convenient.
plt.imshow(chrdata, origin='lower', interpolation='none',
extent=[0, length*resolution, 0, length*resolution]) #resolution=20000
So the values I am interested in are all in a triangle with the top angle in the middle of the top side of a square. At the same time I plot some data (lot's of coloured lines in this case) along with the image near it's bottom.
So at first this looks OK, but is actually is not: all pixels in the image are not square, but elongated with their height being bigger, than their width. This is how they look if I zoom in:
This doesn't happen, If I don't set extent when calling imshow(), but I need it so that coordinates in the image and other plots (coloured lines at the bottom in this case), where identical (see Converting coordinates of a picture in matplotlib?).
I tried to fix it using aspect. I tried to do that and it fixed the pixels' shape, but I got a really weird picture:
The thing is, later in the code I explicitly set this:
ax1.set_ylim(0*resolution, length*resolution) #resolution=20000
But after setting aspect I get absolutely different y limits. And the worst thing: ax1 is now wider, than axes of another plot at the bottom, so that their coordinates do not match anymore! I add it in this way:
axPlotx = divider.append_axes("bottom", size=0.1, pad=0, sharex=ax1)
I would really appreciate help with getting it fixed: square pixels, identical coordinates in two (or more, in other cases) plots. As I see it, the axes of the image need to become wider (as aspect does), the ylims should apply and the width of the second axes should be identical to the image's.
Thanks for reading this probably unclear explanation, please, let me know, if I should clarify anything.
UPDATE
As suggested in the comments, I tried to use
ax1.set(adjustable='box-forced')
And it did help with the image itself, but it caused two axes to get separated by white space. Is there any way to keep them close to each other?
Re-edited my entire answer as I found the solution to your problem. I solved it using the set_adjustable("box_forced") option as suggested by the comment of tcaswell.
import numpy
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import host_subplot, make_axes_locatable
#Calculate aspect ratio
def determine_aspect(shape, extent):
dx = (extent[1] - extent[0]) / float(shape[1])
dy = (extent[3] - extent[2]) / float(shape[0])
return dx / dy
data = numpy.random.random((30,60))
shape = data.shape
extent = [-10, 10, -20, 20]
x_size, y_size = 6, 6
fig = plt.figure(figsize = (x_size, y_size))
ax = host_subplot(1, 1, 1)
ax.imshow(data, extent = extent, interpolation = "None", aspect = determine_aspect(shape, extent))
#Determine width and height of the subplot frame
bbox = ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
width, height = bbox.width, bbox.height
#Calculate distance, the second plot needs to be elevated by
padding = (y_size - (height - width)) / float(1 / (2. * determine_aspect(shape, extent)))
#Create second image in subplot with shared x-axis
divider = make_axes_locatable(ax)
axPlotx = divider.append_axes("bottom", size = 0.1, pad = -padding, sharex = ax)
#Turn off yticks for axPlotx and xticks for ax
axPlotx.set_yticks([])
plt.setp(ax.get_xticklabels(), visible=False)
#Make the plot obey the frame
ax.set_adjustable("box-forced")
fig.savefig("test.png", dpi=300, bbox_inches = "tight")
plt.show()
This results in the following image where the x-axis is shared:
Hope that helps!