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)
Related
I need to precisely control the position of my ylabel independently of my yticklabels with matplotlib. This is because I have a matplotlib animation that currently has the ylabel jumping around as I change yticklabels. This is undesirable.
The docs seem to only allow me to specify distance from the leftmost part of my yticklabels. (which does not solve the problem, and indeed is causing it)
One solution would be to manually put the label. But is there a simpler way?
You can emulate the behavior of a normal y-label by adding text explicitly to the axes. If the y-limits are changing quite a bit, this is best done by placing the text in axes coordinates, rather than data coordinates. This is done with the transform keyword-argument, like so:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
t = ax.text(-0.1, 0.5, 'Y label', rotation=90,
verticalalignment='center', horizontalalignment='right',
transform=ax.transAxes)
ax.set_ylim(-10, 10) # Change y-limits, label position won't change.
This places the text halfway up the axes, and slightly to the left. Changes to the data limits of the axes have no effect on the text, as it is always defined in axes coordinates. Similarly, scaling the plot or axes (resizing the window with the mouse, using fig.set_size_inches, etc) will keep the y-label in position relative to the axes box itself, exactly what you want for a label.
You may have to play with the x-position of the label, to make sure it doesn't overlap the tickmarks as they change during animation.
Im trying to scatter a single (square) marker such that it fills the whole figure (no more, no less).
As for simplification, I'm creating a figure such that x- and y- axes both go from -0.5 to 0.5. That is, the plotting area is the unit square, centred at the origin.
The marker now shall be scattered at the origin. What size should it be so that it occupies exactly the unit square?
I looked at this Finding the right marker size for a scatter plot and this pyplot scatter plot marker size but couldn't get it right so far.
This is what I tried:
fig, ax = plt.subplots(figsize=(4,4));
ax.set_aspect('equal');
ax.set_xlim(-0.5, 0.5);
ax.set_ylim(-0.5, 0.5);
figsize = fig.get_size_inches()[0]
dpi = fig.dpi
print(f'figsize = {int(figsize)}')
print(f'dpi = {int(dpi)}')
print(f'figure is {int(figsize*dpi)} x {int(figsize*dpi)} pixels\n')
print(f'setting the marker size to be {int(figsize*dpi)}**2 = {int((figsize*dpi)**2)}')
ax.scatter(0, 0, s=(figsize*dpi)**2, marker='s');
It turns out that the marker (blue area) does fill the unit square but it is actually filling way more than that. After manually trying different sizes, the right value seems to be around 46000 (opposed to the 82944 suggested at the second post).
You will need to apply the aspect, then get the axes width and transform it to display space (or transform the axes position first, then get its width). This can be used to calculate the width of the axes in units of points.
The square of that number is the markersize of the scatter if it shall be as large as the axes.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(4,4))
ax.set_xlim(-0.5, 0.5)
ax.set_ylim(-0.5, 0.5)
ax.set_aspect('equal')
ax.apply_aspect()
s = ax.get_position().transformed(fig.transFigure).width*72/fig.dpi
ax.scatter(0, 0, s=s**2, marker='s');
plt.show()
I'm trying to place a legend just above the ax in matplotlib using ax.legend(loc=(0, 1.1)); however, if I change the figure size from (5,5) to (5,10) the legend shows up at a different distance from the top edge of the plot.
Is there any way to reference the top edge of the plot and offset it a set distance from it?
Thanks
There is a constant distance between the legend bounding box and the axes by default. This is set via the borderaxespad parameter. This defaults to the rc value of rcParams["legend.borderaxespad"], which is usually set to 0.5 (in units of the fontsize).
So essentially you get the behaviour you're asking for for free. Mind however that you should specify the loc to the corner of the legend from which that padding is to be taken. I.e.
import numpy as np
import matplotlib.pyplot as plt
for figsize in [(5,4), (5,9)]:
fig, ax = plt.subplots(figsize=figsize)
ax.plot([1,2,3], label="label")
ax.legend(loc="lower left", bbox_to_anchor=(0,1))
plt.show()
For more detailed explanations on how to position legend outside the axes, see How to put the legend out of the plot. Also relevant: How to specify legend position in matplotlib in graph coordinates
I'm trying to add in some annotations on polar projected matplotlib ax objects. Most of the annotations are pretty long (40 +/- 3 characters).
Is there a way to have more control of these longer annotations on a polar projection? By more control, I mean closer to the line.
# This annotation works alright
fig = plt.figure()
ax = plt.subplot(111, projection="polar")
ax.axvline(x=np.pi/3, color="black", alpha=0.618)
ax.annotate("this ok", xy=[np.pi/3,0.75], rotation=np.rad2deg(np.pi/3))
# This one doesn't work
fig = plt.figure()
ax = plt.subplot(111, projection="polar")
ax.axvline(x=np.pi/3, color="black", alpha=0.618)
ax.annotate("this doesn't work b/c it's pretty long", xy=[np.pi/3,0.75], rotation=np.rad2deg(np.pi/3))
The problem is that the annotating text is rotated. Thereby the upper left corner of the rectangle that surrounds the complete text (called bounding box) is far away from the text. This upper left corner however is the point which sits by default at the coordinates that are specified with the xy argument to ax.annotate(). This can be seen in the left plot below.
The solution is to use the lower left point of the surrounding rectangle instead and move it somewhere close to the origin. Using the lower left corner can be done by specifying ha="left" (horizontal alignment), va="bottom".
ax2.annotate("this does work b/c we specifiy alignment",
xy=[0,0.07], rotation=np.rad2deg(np.pi/3),
ha="left", va="bottom")
try setting the font sizes smaller so it fits
SIZE = 8
MED_SIZE = 10
BIG_SIZE = 12
plt.rc('axes',titlesize=SIZE) #fontsize of the axes titles is set to 8
I want to use ax.axis('equal') to force even spacing on X & Y, but I also want to prescribe specific ranges for the X and Y axes. If the margins are also fixed, the problem is over constrained and the result is shown on the left side of the Figure 1. If instead, the margins were allowed to automatically increase themselves to take up the slack, then xlim and ylim could stay as I set them while still satisfying axis('equal'). An example of what I'm after is shown on the right side of Figure 1. How can I allow the plot margins to "float"?
f,ax=plt.subplots(1) #open a figure
ax.axis('equal') #make the axes have equal spacing
ax.plot([0,20],[0,20]) #test data set
#change the plot axis limits
ax.set_xlim([2,18])
ax.set_ylim([5,15])
#read the plot axis limits
xlim2=array(ax.get_xlim())
ylim2=array(ax.get_ylim())
#define indices for drawing a rectangle with xlim2, ylim2
sqx=array([0,1,1,0,0])
sqy=array([0,0,1,1,0])
#plot a thick rectangle marking the xlim2, ylim2
ax.plot(xlim2[sqx],ylim2[sqy],lw=3) #this does not go all the way around the edge
Figure 1: output from the above code snippet.
ax.set_aspect('equal',adjustable='box')