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
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.
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)
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)
I want to create a set of axes to form an inset at a specific location in the parent set of axes. It is therefore not appropriate to just use the parameter loc=1,2,3 in the inset_axes as shown here:
inset_axes = inset_axes(parent_axes,
width="30%", # width = 30% of parent_bbox
height=1., # height : 1 inch
loc=3)
However, I would like something close to this. And the answers here and here seem to be answers to questions slightly more complicated than mine.
So, the question is is there a parameter that I can replace in the above code that will allow custom locations of the inset axes within the parent axes? I've tried to use the bbox_to_anchor but do not understand it's specification or behavior from the documentation. Specifically I've tried:
inset_axes = inset_axes(parent_axes,
width="30%", # width = 30% of parent_bbox
height=1., # height : 1 inch
bbox_to_anchor=(0.4,0.1))
to try to get the anchor for the left and bottom of the inset to be at 40% and 10% of the x and y axis respectively. Or, I tried to put it in absolute coordinates:
inset_axes = inset_axes(parent_axes,
width="30%", # width = 30% of parent_bbox
height=1., # height : 1 inch
bbox_to_anchor=(-4,-100))
Neither of these worked correctly and gave me a warning that I couldn't interpret.
More generally, it seems like loc is a pretty standard parameter in many functions belonging to matplotlib, so, is there a general solution to this problem that can be used anywhere? It seems like that's what bbox_to_anchor is but again, I can't figure out how to use it correctly.
The approach you took is in principle correct. However, just like when placing a legend with bbox_to_anchor, the location is determined as an interplay between bbox_to_anchor and loc. Most of the explanation in the above linked answer applies here as well.
The default loc for inset_axes is loc=1 ("upper right"). This means that if you you specify bbox_to_anchor=(0.4,0.1), those will be the coordinates of the upper right corner, not the lower left one.
You would therefore need to specify loc=3 to have the lower left corner of the inset positionned at (0.4,0.1).
However, specifying a bounding as a 2-tuple only makes sense if not specifying the width and height in relative units ("30%"). Or in other words, in order to use relative units you need to use a 4-tuple notation for the bbox_to_anchor.
In case of specifying the bbox_to_anchor in axes units one needs to use the bbox_transform argument, again, just as with legends explained here, and set it to ax.transAxes.
plt.figure(figsize=(6,3))
ax = plt.subplot(221)
ax.set_title("100%, (0.5,1-0.3,.3,.3)")
ax.plot(xdata, ydata)
axins = inset_axes(ax, width="100%", height="100%", loc='upper left',
bbox_to_anchor=(0.5,1-0.3,.3,.3), bbox_transform=ax.transAxes)
ax = plt.subplot(222)
ax.set_title("30%, (0.5,0,1,1)")
ax.plot(xdata, ydata)
axins = inset_axes(ax, width="30%", height="30%", loc='upper left',
bbox_to_anchor=(0.5,0,1,1), bbox_transform=ax.transAxes)
Find a complete example on the matplotlib page: Inset Locator Demo
Another option is to use InsetPosition instead of inset_axes and to give an existing axes a new position. InsetPosition takes the x and y coordinates of the lower left corner of the axes in normalized axes coordinates, as well as the width and height as input.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
fig, ax= plt.subplots()
iax = plt.axes([0, 0, 1, 1])
ip = InsetPosition(ax, [0.4, 0.1, 0.3, 0.7]) #posx, posy, width, height
iax.set_axes_locator(ip)
iax.plot([1,2,4])
plt.show()
Finally one should mention that from matplotlib 3.0 on, you can use matplotlib.axes.Axes.inset_axes
import matplotlib.pyplot as plt
plt.figure(figsize=(6,3))
ax = plt.subplot(221)
ax.set_title("ax.inset_axes, (0.5,1-0.3,.3,.3)")
ax.plot([0,4], [0,10])
axins = ax.inset_axes((0.5,1-0.3,.3,.3))
plt.show()
The result is roughly the same, except that mpl_toolkits.axes_grid1.inset_locator.inset_axes allows for a padding around the axes (and applies it by default), while Axes.inset_axes does not have this kind of padding.
Using the answer from ImportanceOfBeingErnest and several of the suggested links from the unreleased matplotlib documentation like the locator demo and the inset_axes docs, it still took me some time to figure out how all the parameters behaved. So, I will repeat my understanding here for clarity. I ended up using:
bbox_ll_x = 0.2
bbox_ll_y = 0
bbox_w = 1
bbox_h = 1
eps = 0.01
inset_axes = inset_axes(parent_axes,
height="30%", #height of inset axes as frac of bounding box
width="70%", #width of inset axes as frac of bounding box
bbox_to_anchor=(bbox_ll_x,bbox_ll_y,bbox_w-bbox_ll_x,bbox_h),
loc='upper left',
bbox_transform=parent_axes.transAxes)
parent_axes.add_patch(plt.Rectangle((bbox_ll_x, bbox_ll_y+eps),
bbox_w-eps-bbox_ll_x,
bbox_h-eps,
ls="--",
ec="c",
fc="None",
transform=parent_axes.transAxes))
bbox_ll_x is the x location of the lower left corner of the bounding box in the parent axis coordinates (that is the meaning of the bbox_transform input)
bbox_ll_y is the y location of the lower left corner of the bounding box in the parent axis coordinates
bbox_w is the width of the bounding box in parent axis coordinates
bbox_h is the height of the bounding box in parent axis coordinates
eps is a small number to get the rectangles to show up from under axes when drawing the rectangular bounding box.
I used the add_patch call in order to put a cyan dashed line that represents the inner edge of the bounding box that is drawn.
The trickiest part for me was realizing that the height and width inputs (when specified as percents) are relative to the bounding box size. That's why (as noted in the links and the answer below) you must specify a 4-tuple for the bbox_to_anchor parameter if you specify the size of the inset axes in percents. If you specify the size of the inset axes as percents and don't supply bbox_w or bbox_h how can matplotlib get the absolute size of the inset?
Another thing was that the loc parameter specifies where to anchor the inset axes within the bounding box. As far as I can tell that's the only function of that parameter.
I'm writing a pythonic script for a coastal engineering application which should output, amongst other things, a figure with two subplots.
The problem is that I would like to shade a section of both subplots using plt.axvspan() but for some reason it only shades one of them.
Please find below an excerpt of the section of the code where I set up the plots as well as the figure that it's currently outputting (link after code).
Thanks for your help, and sorry if this is a rookie question (but it just happens that I am indeed a rookie in Python... and programming in general) but I couldn't find an answer for this anywhere else.
Feel free to add any comments to the code.
# PLOTTING
# now we generate a figure with the bathymetry vs required m50 and another figure with bathy vs Hs
#1. Generate plots
fig = plt.figure() # Generate Figure
ax = fig.add_subplot(211) # add the first plot to the figure.
depth = ax.plot(results[:,0],results[:,1]*-1,label="Depth [mDMD]") #plot the first set of data onto the first set of axis.
ax2 = ax.twinx() # generate a secondary vertical axis with the same horizontal axis as the first
m50 = ax2.plot(results[:,0],results[:,6],"r",label="M50 [kg]") # plot the second set of data onto the second vertical axis
ax3 = fig.add_subplot(212) # generate the second subplot
hs = ax3.plot(results[:,0],results[:,2],"g",label="Hs(m)")
#Now we want to find where breaking starts to occur so we shade it on the plot.
xBreakingDistance = results[numpy.argmax(breakingIndex),0]
# and now we plot a box from the origin to the depth of breaking.
plt.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) # this box is called a span in matplotlib (also works for axhspan)
# and then we write BREAKING ZONE in the box we just created
yLimits = ax.get_ylim() # first we get the range of y being plotted
yMiddle = (float(yLimits[1])-float(yLimits[0])) / 2 + yLimits[0] # then we calculate the middle value in y (to center the text)
xMiddle = xBreakingDistance / 2 # and then the middle value in x (to center the text)
#now we write BREAKING ZONE in the center of the box.
ax.text(xMiddle,yMiddle,"BREAKING ZONE",fontweight="bold",rotation=90,verticalalignment="center",horizontalalignment="center")
#FIGURE FORMATTING
ax.set_xlabel("Distance [m]") # define x label
ax.set_ylabel("Depth [mDMD]") # define y label on the first vertical axis (ax)
ax2.set_ylabel("M50 [kg]") # define y label on the second vertical axis (ax2)
ax.grid() # show grid
ax3.set_xlabel("Distance[m]") #define x label
ax3.set_ylabel("Hs[m]") # define y label
ax3.grid()
plt.tight_layout() # minimize subplot labels overlapping
# generating a label on a plot with 2 vertical axis is not very intuitive. Normally we would just write ax.label(loc=0)
combined_plots = depth+m50 #first we need to combine the plots in a vector
combined_labels = [i.get_label() for i in combined_plots] # and then we combine the labels
ax.legend(combined_plots,combined_labels,loc=0) # and finally we plot the combined_labels of the combined_plots
plt.savefig("Required M50(kg) along the trench.png",dpi=1000)
plt.close(fig)
Output Figure:
By just calling plt.axvspan, you are telling matplotlib to create the axvspan on the currently active axes (i.e. in this case, the last one you created, ax3)
You need to plot the axvspan on both of the axes you would like for it to appear on. In this case, ax and ax3.
So, you could do:
ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
ax3.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1)
or in one line:
[this_ax.axvspan(0,xBreakingDistance,facecolor="b",alpha=0.1) for this_ax in [ax,ax3]]
It's difficult to analyze your code and not being able to reproduce it. I advise you to build a minimal example. In any case notice that you are calling "plt.axvspan(" which is general call to the library.
You need to specifically state that you want this in both "ax" and "ax2" (i think).
Also if you need more control consider using Patches (I don't know axvspan):
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig1 = plt.figure()
ax1 = fig1.add_subplot(111, aspect='equal')
ax1.add_patch(
patches.Rectangle(
(0.1, 0.1), # (x,y)
0.5, # width
0.5, # height
)
)
fig1.savefig('rect1.png', dpi=90, bbox_inches='tight')
See that call to "ax1" in the example? Just make something similar to yours. Or just add axvspan to each of your plots.