Specify axes position in pixels (or inches) in matplotlib - python

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?

Related

Adjusting the position of an xticklabel in matplotlib has no effect in x-direction

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)

bounds for matplotlib contourf plot not making sense

I'm generating the following contour plot + colorbar in matplotlib:
I extract the relative bounds of the resulting axes using the following loop:
fig = plt.gcf()
for ax in fig.get_axes():
print(ax.get_position().bounds)
and obtain
(0.125, 0.10999999999999999, 0.62, 0.77)
(0.78375, 0.10999999999999999, 0.11624999999999996, 0.77)
According to a previous question of mine there denote the [left, bottom, width, height] bounds in relative coordinates for each axes. I actually measured the relative bounds and found them to be incorrect. One easy way to spot it is the last values of the height for each axes object. How can they both be 0.77 when the color bar clearly has a greater height than the contourplot?
I would like to have full control of the size of the contour plot and axes with respect to the figure.
The position of the axes is determined at draw time. That means that before actually drawing the figure the axes' position is the would-be position or the boundary of the space the axes is free to take.
Usually this would be the same position the axes will take in the final plot, in case you let it expand freely. However, here it seems there is some constraint about the aspect in the game.
To obtain the axes position as it will appear in the drawn figure one should draw the figure manually; then ask for its position.
fig = plt.gcf()
fig.canvas.draw()
for ax in fig.get_axes():
print(ax.get_position().bounds)

matplotlib.pyplot.axes() arguments confusion

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.

Change range withouth scaling in matplot

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.

how to use 'extent' in matplotlib.pyplot.imshow

I managed to plot my data and would like to add a background image (map) to it.
Data is plotted by the long/lat values and I have the long/lat values for the image's three corners (top left, top right and bottom left) too.
I am trying to figure out how to use 'extent' option with imshow. However, the examples I found don't explain how to assign x and y for each corner ( in my case I have the information for three corners).
How can I assign the location of three corners for the image when adding it to the plot?
Thanks
Specify, in the coordinates of your current axis, the corners of the rectangle that you want the image to be pasted over
Extent defines the left and right limits, and the bottom and top limits. It takes four values like so: extent=[horizontal_min,horizontal_max,vertical_min,vertical_max].
Assuming you have longitude along the horizontal axis, then use extent=[longitude_top_left,longitude_top_right,latitude_bottom_left,latitude_top_left]. longitude_top_left and longitude_bottom_left should be the same, latitude_top_left and latitude_top_right should be the same, and the values within these pairs are interchangeable.
If your first element of your image should be plotted in the lower left, then use the origin='lower' imshow option as well, otherwise the 'upper' default is what you want.
Here's an example based on http://matplotlib.org/examples/pylab_examples/image_demo3.html showing use of extent.
#!/usr/bin/env python
from pylab import *
try:
from PIL import Image
except ImportError, exc:
raise SystemExit("PIL must be installed to run this example")
import matplotlib.cbook as cbook
datafile = cbook.get_sample_data('ada.png')
h = Image.open(datafile)
dpi = rcParams['figure.dpi']
figsize = h.size[0]/dpi, h.size[1]/dpi
figure(figsize=figsize)
ax = axes([0,0,1,1], frameon=False)
ax.set_axis_off()
ax.set_xlim(0,2)
ax.set_ylim(0,2)
im = imshow(h, origin='upper',extent=[-2,4,-2,4]) # axes zoom in on portion of image
im2 = imshow(h, origin='upper',extent=[0,.5,0,.5]) # image is a small inset on axes
show()
If you don't set your axis limits, they become your extents & then don't seem to have any effect.

Categories

Resources