The objective is to insert a sub_figure in a simple plot as follows:
import numpy as np
from matplotlib import pyplot as plt
X = np.linspace(-6, 6, 1024)
Y = np.sinc(X)
X_detail = np.linspace(-3, 3, 1024)
Y_detail = np.sinc(X_detail)
plt.plot(X, Y, c = 'k')
sub_axes = plt.axes([0.6,0.6,0.25,0.25])
sub_axes.plot(X_detail, Y_detail, c = 'k')
plt.setp(sub_axes)
plt.show()
The code above gives the following output:
The matplotlib documentation says the argument the matplotlib.pyplot.axes() function takes is a list defined as rect=[left, bottom, width, height] where the coordinates left, bottom, width, height are added as normalized (0,1) values.
Can anyone explain that to me ?
The last two co-ordinates are for the size of the sub_figure, that much I get, now what is the deal with the first two ?
The confusion appears to be coming from the different coordinate systems that matplotlib uses. Here is a link to the (fairly exhaustive) tutorial on the subject: https://matplotlib.org/users/transforms_tutorial.html. I will summarize the key point that affect you directly here.
The coordinates you see on your axes are called the data space or data coordinates. This is basically the xlim and ylim of the plots. Note that these are totally independent for the two plots and are not affected by the size or position of your figure.
When you say sub_axes = plt.axes([0.6,0.6,0.25,0.25]), you are specifying the coordinates in figure space or figure coordinates. This is very similar conceptually to axis space or axis coordinates, except that it applies to the whole figure rather than just an individual set of axes.
In this case, the origin of your sub-axes is at (0.6, 0.6) relative to the bottom left corner of the figure. Where the upper-right corner of the figure is (1, 1). As expected, the sub-axes start just a bit above and to the right of the middle of the figure window.
Similarly, the width is (0.25, 0.25), meaning that the sub-axes are 1/4 the size of your figure in each dimension. This can also be interpreted to mean that the upper right-hand corner of the sub-axes is at (0.85, 0.85) in figure space, which looks about right.
You can do some tests. No matter how you pan or zoom on the main axes, the sub-axes are not affected. However, if you resize your figure, both sets of axes will change size to compensate. The sub-axes should always have the same aspect ratio as the figure itself because of how you sized them.
Related
I'm trying to add an Axes to a matplotlib figure (that I will than use to show an image).
At the moment I'm using normalized coordinates, but since the size of my figure could change, I would like to specify the position in pixels or inches to be more robust.
I tried for example:
fig = plt.figure(figsize=(2, 2)) # Creates a 2"x 2" image
ax = fig.add_axes([1, 1, 1, 1], transform="figure-inches") # Creates an axes with origin at (1",1") and size 1"x 1"
I expect the axes to occupy the top-right quarter of my figure. However what I obtain is different, with the axes' origin in the top-right corner and the rest of the axes outside the Figure.
I tried to read the add_axes documentation as well as the Transform tutorial. However I find this part of the documentation a bit confusiong.
How can I create an Axes specifying it's size and posizion in physical units instead of normalized ones?
Using matplotlib 2.2.2 with gridspec in Python 3.6.5, I created a huge plot for a research paper with several subplots. The axes objects are stored in a dictionary called axes. This dictionary is passed to the function adjust_xticklabels(), which is supposed to align the first xticklabel slightly to the right and the last xticklabel slightly to the left in each subplot, such that the xticklabels of neighbouring plots dont get in the way of each other. The function is defined as:
def adjust_xticklabels(axes, rate = 0.1):
for ax in axes.values():
left, right = ax.get_xlim() # get boundaries
dist = right-left # get distance
xtl = ax.get_xticklabels()
if len(xtl) > 1:
xtl[0].set_position((left + rate*dist, 0.)) # (x, y), shift right
xtl[-1].set_position((right - rate*dist, 0.)) # shift left
Calling it has no effect. Of course I also tried it with ridiculously high values. However, is has an effect in y-direction, for instance in case of setting xtl[0].set_position((0.3, 0.3)).
A simple reproduction:
ax = plt.subplot(111)
ax.plot(np.arange(10))
xtl = ax.get_xticklabels()
xtl[4].set_position((0.3, 0.3)) # wlog, 4 corresponds to 6
I spent quite a while on trying to figure out if this is a feature or a bug. Did I miss something or is this a bug? Is there any other way to do the same thing?
This is a feature, no bug. The ticklabels are positionned at drawtime to sit at the correct locations according to the ticker in use. This ensures that the label always sits where the corresponding tick is located. If you change the limits, move or zoom the plot, the label always follows those changes.
You are usually not meant to change this location, but you may, by adding a custom transform to it. This is described in
Moving matplotlib xticklabels by pixel value. The general idea is to set a translating transformation on the label. E.g. to translate the second label by 20 pixels to the right,
import matplotlib.transforms as mtrans
# ...
trans = mtrans.Affine2D().translate(20, 0)
label = ax.get_xticklabels()[1]
label.set_transform(label.get_transform()+trans)
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 have a question, I am making a program that displays a zoomed area of Peru but the axis shown are in the range of the image (e.g. 7000x7500) but i want to be in UTM range (e.g. x-axis between 500000-600000 and y-axis 9500000-9700000)
I have tried using plt.set_xlim and plt.sety_lim but no success, I think I have to use plt.autoscale(False) but it also didn't work or I used it wrong.
I create the figure and axes out of the main program
f = plt.figure(figsize=(5,5))
axe = f.add_axes([0, 0, 1, 1])
this is the function I call everytime I want to plot
def plotear(self, mapa):
axe.clear()
axe.imshow(mapa, cmap='gray', interpolation='nearest')
axe.set_xlim(0,10000) #This is just for testing
axe.set_ylim(0,10000) #This is just for testing
plt.autoscale(False)
self.canvas.draw()
Edit: #ImportanceOfBeingErnest's answer worked as expected! Now I am having another problem, in the canvas I am showing the image, the x-axis and y-axis doesnt visualize correctly, here is an image example
how could I fix it? thanks.
From the imshow documentation you'd find that there is an argument extent which can be used to scale the image.
extent : scalars (left, right, bottom, top), optional, default: None
The location, in data-coordinates, of the lower-left and upper-right corners. If None, the image is positioned such that the pixel centers fall on zero-based (row, column) indices.
In this case you'd use it like
ax.imshow(mapa, extent=[5e5, 6e5, 9.5e6, 9.7e6])
Answer to the edited question:
In the case of the image being too large, this is probably caused by you setting axe = f.add_axes([0, 0, 1, 1]). You should rather use ax = fig.add_subplot(111) and if the margins are not as you want then, setting plt.subplots_adjust( ... ) with the respective spacings.
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!