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()
Related
I am using matplotlib to draw vertical and horizontal grid lines over an image. The resulting image will have numbered squares over it. To do this, I used this code:
def drawGridOverImage(image):
"""Draw a numbered grid with 1cm2 boxes over an image."""
import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
my_dpi=300.
# Set up figure
fig=plt.figure(figsize=(float(image.size[0])/my_dpi,float(image.size[1])/my_dpi),dpi=my_dpi)
ax=fig.add_subplot(111)
# Remove whitespace from around the image
fig.subplots_adjust(left=0,right=1,bottom=0,top=1)
# Set the gridding interval
# my_dpi is dots per inch, so each square is 1 inch2
myInterval=my_dpi
loc = plticker.MultipleLocator(base=myInterval)
ax.xaxis.set_major_locator(loc)
ax.yaxis.set_major_locator(loc)
# Add the grid
ax.grid(True, which='major', axis='both', linestyle='-')
# Add the image
ax.imshow(image)
# Find number of gridsquares in x and y direction
nx=abs(int(float(ax.get_xlim()[1]-ax.get_xlim()[0])/float(myInterval)))
ny=abs(int(float(ax.get_ylim()[1]-ax.get_ylim()[0])/float(myInterval)))
# Add some labels to the gridsquares
for j in range(ny):
y=myInterval/2+j*myInterval
for i in range(nx):
x=myInterval/2.+float(i)*myInterval
ax.text(x,y,'{:d}'.format(i+j*nx),color='r',ha='center',va='center')
# Save the figure
#fig.savefig('myImageGrid.tiff',dpi=my_dpi)
newImageName = nameImageWithGrid(image.filename)
fig.savefig(newImageName,dpi=my_dpi)
return fig
However, when I run this code, the vertical grid lines stop appearing at a certain point. I have attached a screenshot of part of this image (the whole image is very large) to demonstrate the problem.
After some searching, I did find this previous issue so I am not sure if this is the same issue. It is worth noting that I am working with very large images, the one in the picture above has dimensions of 11648 × 8736.
I would appreciate some help on resolving this problem and making vertical grid lines appear on whole image.
EDIT:
I tried the code on another large image that can be found in this link here and had the same problem, here is a screenshot of part of the image where the vertical grid lines stop:
I believe that the locator is the cause of the issues that are occurring now. The grid lines are drawn for the ticks created by the interval values. For example, if you change the tick marks on the x-axis like this, the grid lines are drawn correctly.
# Set the gridding interval
# my_dpi is dots per inch, so each square is 1 inch2
myInterval=my_dpi
# loc = plticker.MultipleLocator(base=myInterval)
# ax.xaxis.set_major_locator(loc)
# ax.yaxis.set_major_locator(loc)
ax.set_xticks(np.arange(0,9900,myInterval))
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.
I was trying to put a summary of the data under its figure. However, since the length of the summary text varies, it is hard to keep the textbox and the figure vertically aligned (more specifically, the textbox and the figure should have the same width). Is there any way to do this?
You can try to do a subplot right below the figure. This guarantees that the width will be the same:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(1, 10, 50)
y = np.sin(x)
plt.subplot(2,1,1)
plt.plot(x,y)
plt.subplot(2,1,2)
description = '''
Sin Function:
In the figure avobe we see how the sin function oscillates and behaves
between 0 and 10
'''
plt.text(0.5, 0.5, description, va='top', ha='center')
plt.axis('off')
plt.show()
However, I am afraid you'll have to insert the line breaks yourself as matplotlib doesn't support text wrapping. Here there's something you can try though.
Yes. You can put a long summary of the plot under the plot. Technically the caption doesn't go into the axes object, it goes into the figure object that the axes was created in, but the reader can't tell that.
Here I estimate the length of a long caption and then add the needed vertical space to the figure in which the axes object plots. With some rough calculations of text size, this makes room for matplotlib's text wrapping to fit very long captions below a figure:
import matplotlib.pyplot as plt
t = "A point is that which has no part. A line is breadthless length. The ends of a line are points. A straight line is a line which lies evenly with the points on itself. A surface is that which has length and breadth only. The edges of a surface are lines. A plane surface is a surface which lies evenly with the straight lines on itself. A plane angle is the inclination to one another of two lines in a plane which meet one another and do not lie in a straight line. "
#t = 3*t
AestheticFigWidth = 6.4
AestheticPlotHeight = 2.4
# 12pt fonts should be 6 lines per vertical inch.
# Very rough estimate of 10 12pt characters per horizontal inch -- it varies!
# Calculate how many more inches of fig you need for your caption,
# add extra for whitespace and labels:
CaptionHeight = (len(t)/(6 * (AestheticFigWidth * 10))) + 0.5
fig = plt.figure(figsize=(AestheticFigWidth,
AestheticPlotHeight + CaptionHeight))
CaptionProportion = CaptionHeight / (AestheticPlotHeight + CaptionHeight)
ax = fig.add_axes((.1, #location proportional to figure
CaptionProportion + .03,
.85, # size proportional to figure
.85 - CaptionProportion))
fig.suptitle("Make space for a big caption")
ax.plot([1,2,3,4,5], [0,5,1,8,0], 'o-')
ax.set_ylabel('Angle')
ax.set_xlabel("Let's not overlap this")
fig.text(.05,.03, t, ha='left', rotation=0, wrap=True, fontsize=12)
plt.show()
With a very long caption:
With a middling caption:
If you need something more elegant or automatic, I recommend generating the plots in matplotlib and then generating a template in LaTeX to pick up the plots and their captions.
Created a legend and formatted the text as needed, but can't figure out how to remove the line "dashes" so that only text appears. Here's what I'm getting now (notice how the line is going through the text that is being right aligned):
#Add legend
leg = ax1.legend(bbox_to_anchor=(0.03, 1.05), prop={'size':8})
leg.get_frame().set_alpha(0)
legText = pylab.gca().get_legend().get_texts()
#Format legend text
legText[0].set_color('#5998ff')
legText[1].set_color('#ffbb82')
legText[2].set_color('#d689c4')
for text in legText:
text.set_ha('right')
As far as I know you can't remove the dashes (this is referred to as the legend handle I think) but you could replace it with something invisible. For example a common problem is to define the legend handle as a coloured rectangle.
The basic idea is to create the handle directly then pass all the items to be including in the legend as two lists. The first list being the handle and the second the text of the label.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle
x = np.linspace(0, 1)
p1, = plt.plot(x, np.cos(x))
leg1 = Rectangle((0, 0), 0, 0, alpha=0.0)
plt.legend([leg1], ['label'], handlelength=0)
plt.show()
I suspect you will need to play with this a bit to get the exact look your looking for. If you don't need the frame I might suggest using the frameon=False argument when calling plt.legend() this way you don't need to worry about the alignment with respect to the box.
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)