Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: position text box fixed in corner and correctly aligned

I'm trying to mimic the legend method in matplotlib.pyplot where one can use loc='lower right' to position the legend box fixed and properly aligned no matter the axis and the content of the box.

Using text is out since this requires the manual input of the coordinates and I'm after something automatic.

I've tried using annotate and it gets me half the way there, but it still won't work right.

This is what I have so far:

import matplotlib.pyplot as plt

# Define some names and variables to go in the text box.
xn, yn, cod = 'r', 'p', 'abc'
prec = 2
ccl = [546.35642, 6785.35416]
ect = [12.5235, 13.643241]

fig = plt.figure()
ax = fig.add_subplot(111)
plt.xlim(-1., 10.)
plt.ylim(-1., 1.)

# Generate text to write.
text1 = "${}_{{t}} = {:.{p}f} \pm {:.{p}f}\; {c}$".format(xn, ccl[0],
    ect[0], c=cod, p=prec)
text2 = "${}_{{t}} = {:.{p}f} \pm {:.{p}f}\; {c}$".format(yn, ccl[1],
    ect[1], c=cod, p=prec)
text = text1 + '\n' + text2

ax.annotate(text, xy=(0.75, 0.9), xycoords='axes fraction', fontsize=10,
    bbox=dict(facecolor='white', alpha=0.8),
    horizontalalignment='left', verticalalignment='bottom')

plt.savefig('annotate_test.png', dpi=150)

which results in:

enter image description here

This will correctly scale for changing axis limits, but the problem is that: 1- it will fail if the axis are set to ax.set_aspect('equal'):

enter image description here

and 2- it will fail if the text is too long (here I set prec=5 in the MWE above):

enter image description here

How can I tell matplotlib to position the text box always in the top right corner and align it properly so it doesn't fall outside of the image (ie: what loc does in legend)?

like image 680
Gabriel Avatar asked Sep 10 '14 17:09

Gabriel


1 Answers

The quick-and-dirty way is to use right and top aligned text and place it at a fixed offset in points from the axes corner:

import matplotlib.pyplot as plt

# Define some names and variables to go in the text box.
xn, yn, cod = 'r', 'p', 'abc'
prec = 2
ccl = [546.35642, 6785.35416]
ect = [12.5235, 13.643241]

fig = plt.figure()
ax = fig.add_subplot(111)
ax.axis([-1, 10, -1, 1])

# Generate text to write.
text1 = "${}_{{t}} = {:.{p}f} \pm {:.{p}f}\; {c}$".format(xn, ccl[0],
    ect[0], c=cod, p=prec)
text2 = "${}_{{t}} = {:.{p}f} \pm {:.{p}f}\; {c}$".format(yn, ccl[1],
    ect[1], c=cod, p=prec)
text = text1 + '\n' + text2

ax.annotate(text, xy=(1, 1), xytext=(-15, -15), fontsize=10,
    xycoords='axes fraction', textcoords='offset points',
    bbox=dict(facecolor='white', alpha=0.8),
    horizontalalignment='right', verticalalignment='top')

plt.show()

enter image description here

Because we've specified top and right alignment, it works with your two edge cases:

enter image description here

enter image description here


The downside of this is that the text is right-aligned. Ideally, you'd want the text alignment to be separate from the box alignment. The matplotlib.offsetbox module has a number of methods to handle things like this.

If you want to mimic a legend box (down to the location codes), have a look at matplotlib.offsetbox.AnchoredText. (Note that you can adjust the padding, etc though the pad and borderpad kwargs: http://matplotlib.org/api/offsetbox_api.html#matplotlib.offsetbox.AnchoredOffsetbox )

import matplotlib.pyplot as plt
import matplotlib.offsetbox as offsetbox

# Define some names and variables to go in the text box.
xn, yn, cod = 'r', 'p', 'abc'
prec = 5
ccl = [546.35642, 6785.35416]
ect = [12.5235, 13.643241]

fig = plt.figure()
ax = fig.add_subplot(111)
ax.axis([-1, 10, -1, 1])

# Generate text to write.
text1 = "${}_{{t}} = {:.{p}f} \pm {:.{p}f}\; {c}$".format(xn, ccl[0],
    ect[0], c=cod, p=prec)
text2 = "${}_{{t}} = {:.{p}f} \pm {:.{p}f}\; {c}$".format(yn, ccl[1],
    ect[1], c=cod, p=prec)
text = text1 + '\n' + text2

ob = offsetbox.AnchoredText(text, loc=1)
ax.add_artist(ob)

plt.show()

enter image description here

One downside to this is that adjusting the font and box parameters for the result is a bit counter-intuitive. AnchoredText accepts a dictionary of font parameters as the prop kwarg. The box can be adjusted after initialization through the patch attribute. As a quick example:

ob = offsetbox.AnchoredText(text, loc=1,
                    prop=dict(color='white', size=20))
ob.patch.set(boxstyle='round', color='blue', alpha=0.5)
ax.add_artist(ob)

enter image description here

like image 95
Joe Kington Avatar answered Oct 20 '22 12:10

Joe Kington