75

how is possible to make a box around text in matplotlib? I have text on three different lines and in three different colors:

 ax.text(2,1, 'alpha', color='red')
 ax.text(2,2, 'beta', color='cyan')
 ax.text(2,3, 'epsilon', color='black')

I saw the tutorial http://matplotlib.org/users/recipes.html (last example) but I can't solve the problem.

starball
  • 20,030
  • 7
  • 43
  • 238
Marika Blum
  • 907
  • 2
  • 8
  • 7
  • 1
    What is your problem with the last example of the recipes? – David Zwicker Jun 13 '13 at 12:43
  • @David In that example there is only 1 line of text. I'm looking for how it is possible to have the same box around the text I've written. Following the recipe I can write text in one line as in the example (textstr = '$\mu=%.2f$\n$\mathrm{median}=%.2f$\n$\sigma=%.2f$') but then how is it possible to change the color of the text? – Marika Blum Jun 13 '13 at 13:31

3 Answers3

157

As the example you linked to mentions, you can use the bbox kwarg to add a box.

I assume you're confused on how to set the color, etc, of the box? As a quick example:

import matplotlib.pyplot as plt
fig, ax = plt.subplots()

ax.text(0.5, 0.8, 'Test', color='red', 
        bbox=dict(facecolor='none', edgecolor='red'))

ax.text(0.5, 0.6, 'Test', color='blue', 
        bbox=dict(facecolor='none', edgecolor='blue', pad=10.0))

ax.text(0.5, 0.4, 'Test', color='green', 
        bbox=dict(facecolor='none', edgecolor='green', boxstyle='round'))

ax.text(0.5, 0.2, 'Test', color='black', 
        bbox=dict(facecolor='none', edgecolor='black', boxstyle='round,pad=1'))

plt.show()

enter image description here

The last two are "Fancy" bbox patches, so the padding, etc is set in a different manner. (Which is rather annoying for simple things like padding, though it makes the implementation simpler behind-the-scenes.)

Also, if you're labeling things in your plot, you'll probably find that annotate is a better choice. Among other things, it allows you to place your text at an offsent in points from a particular data position.

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Thanks for your reply, but probably I did not explain well the problem I have. What I need is to put the three text lines in one box.. – Marika Blum Jun 13 '13 at 13:27
  • Ah! And with different colors, I'd assume? This is a long-standing limitation of matplotlib. (There's been discussion of wholesale changes to the text rendering backend that would allow it.) You basically have two options. 1) Use latex (you'll need "full" latex, not just matplotlib's mathtext), or 2) Plot them separately and draw a box around the result. Both options are show here, incidentally: http://stackoverflow.com/questions/9169052/partial-coloring-of-text-in-matplotlib – Joe Kington Jun 13 '13 at 13:39
22

enter image description here There is some documentation online somewhere (the best I can find quickly is http://matplotlib.org/users/annotations_guide.html) for using VPacker and an AnnotationBbox to put together several texts of varying font properties.

from matplotlib.offsetbox import TextArea, VPacker, AnnotationBbox
from pylab import *
fig = figure(1)
ax = gca()
texts = ['alpha','beta','epsilon']
colors = ['red','cyan','black']
Texts = []
for t,c in zip(texts,colors):
    Texts.append(TextArea(t,textprops=dict(color=c)))
texts_vbox = VPacker(children=Texts,pad=0,sep=0)
ann = AnnotationBbox(texts_vbox,(.02,.5),xycoords=ax.transAxes,
                            box_alignment=(0,.5),bboxprops = 
                            dict(facecolor='wheat',boxstyle='round',color='black'))
ann.set_figure(fig)
fig.artists.append(ann)

I'm not sure why both of the last two lines are needed. I would think the second to last would suffice.

esmit
  • 1,748
  • 14
  • 27
13

A solution could be to explore the boundingbox from the text objects and generate a box yourself. Its not very convenient. Perhaps my example can be improved, transformations always confuse me a bit.

import matplotlib.patches as patches
import matplotlib.pyplot as plt

fig, axs = plt.subplots(1,1)

t1 = axs.text(0.4,0.6, 'Hello world line 1', ha='center', color='red', weight='bold', transform=axs.transAxes)
t2 = axs.text(0.5,0.5, 'Hello world line 2', ha='center', color='green', weight='bold', transform=axs.transAxes)
t3 = axs.text(0.6,0.4, 'Hello world line 3', ha='center', color='blue', weight='bold', transform=axs.transAxes)

fig.canvas.draw()

textobjs = [t1,t2,t3]

xmin = min([t.get_window_extent().xmin for t in textobjs])
xmax = max([t.get_window_extent().xmax for t in textobjs])
ymin = min([t.get_window_extent().ymin for t in textobjs])
ymax = max([t.get_window_extent().ymax for t in textobjs])

xmin, ymin = fig.transFigure.inverted().transform((xmin, ymin))
xmax, ymax = fig.transFigure.inverted().transform((xmax, ymax))

rect = patches.Rectangle((xmin,ymin),xmax-xmin,ymax-ymin, facecolor='grey', alpha=0.2, transform=fig.transFigure)

axs.add_patch(rect)

You might want to add a small buffer etc, but the idea would stay the same.

enter image description here

Rutger Kassies
  • 61,630
  • 17
  • 112
  • 97
  • This solution is the best if you want the box to be in a precise location, for example when you want a clickable box for interactive plotting using e.g. matplotlib's ginput – ClimateUnboxed Mar 13 '21 at 09:43