I use the matplotlib library for plotting data in python. In my figure I also have some text to distinguish the data. The problem is that the text goes over the border in the figure window. Is it possible to make the border of the plot cut off the text at the corresponding position and only when I pan inside the plot the the rest of the text gets visible (but only when inside plot area). I use the text() function to display the text
[EDIT:]
The code looks like this:
fig = plt.figure()
ax = fig.add_subplot(111)
# ...
txt = ax.text(x, y, n, fontsize=10)
txt.set_clip_on(False) # I added this due to the answer from tcaswell
I think that your text goes over the border because you didn't set the limits of your plot.
Why don't you try this?
fig=figure()
ax=fig.add_subplot(1,1,1)
text(0.1, 0.85,'dummy text',horizontalalignment='left',verticalalignment='center',transform = ax.transAxes)
This way your text will always be inside the plot and its left corner will be at point (0.1,0.85) in units of your plot.
You just need to tell the text artists to not clip:
txt = ax.text(...)
txt.set_clip_on(False) # this will turn clipping off (always visible)
# txt.set_clip_on(True) # this will turn clipping on (only visible when text in data range)
However, there is a bug matplotlib (https://github.com/matplotlib/matplotlib/pull/1885 now fixed) which makes this not work. The other way to do this (as mentioned in the comments) is
to use
txt = ax.text(..., clip_on=True)
Related
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.
I have to plot some data and some vertical lines to delimit interesting intervals and then I would like to add some labels to that using text. I can not entirely avoid the labels overlapping with the data or the vertical lines, so I decided to put a bbox around the text to keep it readable. My problem is that I am not able to align it centrally within this box and this is clearly visible and quite annoying in my opinion.
I'm doing something like this:
import numpy
import matplotlib
import matplotlib.pyplot as plt
fig=plt.figure()
plot=fig.add_subplot(111)
x=numpy.linspace(1,10,50)
y=numpy.random.random(50)
plot.plot(x,y)
plot.text(4.5,.5,'TEST TEST',\
bbox={'facecolor':'white','alpha':1,'edgecolor':'none','pad':1})
plot.axvline(5,color='k',linestyle='solid')
plt.show()
Which creates the following plot:
It is quite apparent, that the text is not centered in its bbox. How can I change this? I've spent quite some time on Google but I could not find anything.
EDIT:
Thanks for the suggestions so far.
This suggests that what I see is actually desired behavior. Apparently the bbox in new versions of matplotlib is chosen taking into account the possible maximum descent of the text it contains (the descent of 'g').
When a 'g' appears in the text, this does indeed look good:
Unfortunately in my case there is no 'g' or anything with a similar descent. Does anyone have any further ideas?
Use the text properties ha and va:
plot.text(5.5,.5,'TEST TEST TEST TEST',
bbox={'facecolor':'white','alpha':1,'edgecolor':'none','pad':1},
ha='center', va='center')
To check, draw lines in the center of your plot:
plot.axvline(5.5,color='k',linestyle='solid')
plot.axhline(0.5,color='k',linestyle='solid')
It seems that there are now options to properly position the text in the coordinate system (in particular the new va = 'baseline'). However, as pointed out by user35915, this does not change the alignment of the box relative to the text. The misalignment is particularly obvious in single digit numbers, in particular number '1' (see also this bug). Until this is fixed, my workaround is to place the rectangle by hand, not via the bbox parameter:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
# define the rectangle size and the offset correction
rect_w = 0.2
rect_h = 0.2
rect_x_offset = 0.004
rect_y_offset =0.006
# text coordinates and content
x_text = 0.5
y_text = 0.5
text = '1'
# create the canvas
fig,ax = plt.subplots(figsize=(1,1),dpi=120)
ax.set_xlim((0,1))
ax.set_ylim((0,1))
# place the text
ax.text(x_text, y_text, text, ha="center", va="center", zorder=10)
# compare: vertical alignment with bbox-command: box is too low.
ax.text(x_text+0.3, y_text, text, ha="center", va="center",
bbox=dict(facecolor='wheat',boxstyle='square',edgecolor='black',pad=0.1), zorder=10)
# compare: horizontal alignment with bbox-command: box is too much to the left.
ax.text(x_text, y_text+0.3, text, ha="center", va="center",
bbox=dict(facecolor='wheat',boxstyle='square',edgecolor='black',pad=0.2), zorder=10)
# create the rectangle (below the text, hence the smaller zorder)
rect = patches.Rectangle((x_text-rect_w/2+rect_x_offset, y_text-rect_h/2+rect_y_offset),
rect_w,rect_h,linewidth=1,edgecolor='black',facecolor='white',zorder=9)
# add rectangle to plot
ax.add_patch(rect)
# show figure
fig.show()
I have a figure with some fairly delicate features that are sensitive to linewidth. I want to save this figure as a PDF that can be easily printed (i.e. no scaling on the receiver's side, just Command+P and go). Unfortunately, when I set figsize=(8.5,11) in order to correctly size the PDF for printing, matplotlib picks a very thick default linewidth and text size that mess up the plot (the legend is too large and the lines in the bar chart overlap). If I set figsize=(17,22) I get a very workable default linewidth and textsize after scaling the PDF to 50% for printing. This is what I have been using, but that solution has become unworkable due to politics and I really don't want to scale the PDF in illustrator every time I make a change.
If I could work with bitmaps I could achieve the desired result by setting figsize=(17,22) and setting dpi to half of the target dpi, but this does not work for PDFs since the dpi parameter seems to be ignored. I would like a PDF that
looks like boxes_good.png (the size-tricked bitmap with thin lines, small text)
has dimensions 8.5x11in (or prints like it does)
can be edited in illustrator (is not a bitmap wrapped in a pdf)
I can't help but suspect that there is an easy way to pull the "double size, half dpi" trick when saving as a PDF, but I gave up on getting that to work and started trying to directly manipulate the linewidths and textsizes. I succeeded in modifying textsize but not linewidth. Here is a record of the things I tried:
# Tried:
# fig.set_size_inches(17,22)
# fig.savefig('boxes.pdf', dpi=100,150,300)
# dpi parameter has no effect on linewidth, text size, or the PDF's dimensions
# 'markerscale=.5' on plt.legend and pax.legend
# no effect
# mp.rcParams['font.size']=8
# worked! made text smaller, now for linewidth...
# mp.rcParams['lines.linewidth']=5
# no effect
# fig.set_linewidth(5)
# no effect
# pax.axhline(linewidth=5)
# only changes x axis not box surrounding subplot
# fig.set_size_inches(8.5,11) immediately before plt.savefig('boxes.pdf')
# identical results to calling plt.figure(figsize=(8.5,11)) in the first place
# I tried passing plt.figure(figsize=(17,22)) and swapping it to 8.5x11 using
# fig.set_size_inches right before saving, but the lines were thick and the text
# was large in the PDF, exactly as if I had set figsize=(8.5,11) to begin with
Here is the sourcefile (I have reduced the plot to essentials, so obvious styling workarounds probably aren't workable solutions)
import numpy as np
import matplotlib as mp
import matplotlib.pyplot as plt
x = np.arange(200)
bottom_red_bar = -np.random.random(200)
bottom_black_bar = np.random.random(200) * bottom_red_bar
fig = plt.figure()
for subplotnum in [1,2,3]:
pax = plt.subplot(310+subplotnum)
pax.set_ylim([-1,1])
bot_rb = pax.bar(x, bottom_red_bar,1,color='r')
bot_bb = pax.bar(x+(1-.3)/2,bottom_black_bar,.3,color='k')
pax.legend([bot_rb,bot_bb],['Filler Text 1','Filler Text 2'],loc=4)
fig.set_size_inches(8.5,11)
fig.savefig('boxes_bad.png',dpi=300) # Lines are too thick
fig.set_size_inches(17,22)
fig.savefig('boxes_good.png',dpi=150) # Lines are just right
fig.set_size_inches(8.5,11)
plt.savefig('boxes.pdf') # Lines are too thick
fig.set_size_inches(17,22) # Lines are just right
plt.savefig('boxes.pdf') # but the PDF needs scaling before printing
So I'm after a way to either adjust the linewidth of an entire figure or a way to have matplotlib save a pdf with dimension metadata different from figsize. Any suggestions?
Thanks Marius, I'll upvote as soon as I get 15 reputation required to do so. While your rcParams didn't quite match what I wanted to do, rcParams itself was the correct place to look so I listed rcParams containing 'linewidth' via rcParams.keys():
>>> [s for s in mp.rcParams.keys() if 'linewidth' in s]
['axes.linewidth', 'grid.linewidth', 'lines.linewidth', 'patch.linewidth']
After some experimentation, I matched up what each param controlled:
mp.rcParams['axes.linewidth']: the square surrounding the entire plot (not ticks, not the y=0 line)
mp.rcParams['grid.linewidth']: didn't test, presumably grid width
mp.rcParams['lines.linewidth']: the width of line plots made using pyplot.plot
mp.rcParams['patch.linewidth']: the width of rectangle strokes including the bars of a pyplot.bar plot, legends, and legend labels of bar plots
mp.rcParams['xtick.minor.width']: the linewidth of small xticks (yticks similar)
mp.rcParams['xtick.major.width']: the linewidth of large xticks (yticks similar)
The specific solution I wound up using was
mp.rcParams['axes.linewidth'] = .5
mp.rcParams['lines.linewidth'] = .5
mp.rcParams['patch.linewidth'] = .5
I would suggest to adjust parameters like linewidth via the rcParams (or your matplotlibrc file):
# mp.rcParams['figure.figsize'] = fig_size # set figure size
mp.rcParams['font.size'] = font_size
mp.rcParams['axes.labelsize'] = font_size
mp.rcParams['axes.linewidth'] = font_size / 12.
mp.rcParams['axes.titlesize'] = font_size
mp.rcParams['legend.fontsize'] = font_size
mp.rcParams['xtick.labelsize'] = font_size
mp.rcParams['ytick.labelsize'] = font_size
I normally use the standard figure.figsize which is (8,6) and a linewidth in the axes object that is 1/12 of the font size (eg. font_size = 16 when I include the plots in twocolumn papers).
Remember that vector graphics doesn't mean, that all lines and letters always have the same size when scaling. It means that you can scale without loosing quality or sharpness (roughly speaking).
So far i have placed my suptitles above the frame, like this:
How can i get the suptitles from above the frame into the frame?
So far i have a solution that just prints a text and sets it on the right position with computing xlim and ylim. However this is errorprone and if the text is different it just looks aweful. Is there a way to set the suplabel into the frame? Or just place text below the frame and centered?
it would be really convenient, if i did not need to know about the data that is displayed inside the frame.
Your solution using text is also my go-to solution. However, you don't need to compute the position based on xlim and ylim. If you set transform=ax.transAxes the coordinates for positioning the text are taken as being relative to the axes bounding box (0,0 being the lower left corner). Like so:
data = range(1,10);
fig = figure()
for i in range(6):
ax = fig.add_subplot(2,3,i)
ax.text(.5,.9,'centered title',
horizontalalignment='center',
transform=ax.transAxes)
ax.plot(data)
show()
Hope that helps!
Have you considered axes.set_title? You can also pass x and y coordinates as keyword arguments ax.set_title("my title", x=0.5, y=0.6).
Hope this helps.
I am plotting things using matplotlib and Basemap (within a wxpython gui). Currently, my plot code look something like this:
self.map = Basemap(llcrnrlon=lon_L, llcrnrlat=lat_D, urcrnrlon=lon_R,
urcrnrlat=lat_U, projection='lcc', lat_0=map_lat1, lon_0=map_lon1,
resolution='i', area_thresh=10000,ax=self.axes, fix_aspect=False)
m = Basemap(llcrnrlon=lon_L, llcrnrlat=lat_D, urcrnrlon=lon_R,
urcrnrlat=lat_U, projection='lcc', lat_0=map_lat1, lon_0=map_lon1,
resolution='i', area_thresh=10000,ax=self.axes)
x,y=m(some_x_data,some_y_data)
plot_handle, = self.map.plot(x,y,'bo')
plot_handle.set_xdata(x)
plot_handle.set_ydata(y)
self.figure.canvas.draw()
This plots it just fine. Now what I want to do is take a single point (single x and single y within my data) and color it a different color. I still want to use the plot_handle because I am constantly updating the map/plot -- so i don't want to just reset my data. Any help?
Thanks!
If you use scatter (doc) you can set and update the color of each point.
import matplotlib.pylab as plt
x,y = m(some_x,some_y)
c = iterator_of_colors
plt_handle, = self.map.scatter(x,y,c=c)
# some code
c[j] = new_color # update you color list
plt_handle.set_array(c) # update the colors in the graph
plt.draw()
It looks a little strange to use set_array but that is how matplotlib deals with scatter plots internally (it looks like they use the same class that is used for displaying images, only just color in markers instead of squares in the grid).
Do a new plot_handle for the specific plot with a different marker:
plot_handle1, = self.map.plot([x[i]], [y[i]], 'ro')
You'll then have to update this every time you want to change that point's position. It's not possible to use only one plot_handle and have points showing with different markers.