24

I'm creating plots using Matplotlib that I save as SVG, export to .pdf + .pdf_tex using Inkscape, and include the .pdf_tex-file in a LaTeX document.

This means that I can input LaTeX-commands in titles, legends etc., giving an image like this plot

which renders like this when I use it in my LaTeX document. Notice that the font for the numbers on the axes change, and the LaTeX-code in the legend is compiled:

plot rendered using LaTeX

Code for the plot (how to export to SVG not shown here, but can be shown on request):

import numpy as np
x = np.linspace(0,1,100)
y = x**2

import matplotlib.pyplot as plt
plt.plot(x, y, label = '{\\footnotesize \$y = x^2\$}')
plt.legend(loc = 'best')
plt.show()

The problem is, as you can see, that the alignment and size of the box around the legend is wrong. This is because the size of the text of the label changes when the image is passed through Inkscape + pdflatex (because \footnotesize etc. disappears, and the font size changes).

I have figured out that I can choose the placement of the label by either

plt.label(loc = 'upper right')

or if I want more control I can use

plt.label(bbox_to_anchor = [0.5, 0.2])

but I haven't found any way of making the box around the label smaller. Is this possible?

An alternative to making the box smaller is to remove the outline of the box using something like

legend = plt.legend()
legend.get_frame().set_edgecolor('1.0')

and then moving the label to where I want it. In that case I would like to be able to set the placement of the label by first letting python/matplotlib place it using

plt.label(loc = 'upper right')

and then for example moving it a bit to the right. Is this possible? I have tried using get_bbox_to_anchor() and set_bbox_to_anchor(), but can't seem to get it to work.

Filip S.
  • 1,514
  • 3
  • 19
  • 40

3 Answers3

28

You can move a legend after automatically placing it by drawing it, and then getting the bbox position. Here's an example:

import matplotlib.pyplot as plt
import numpy as np

# Plot data
x = np.linspace(0,1,100)
y = x**2
fig = plt.figure()
ax = fig.add_subplot(221) #small subplot to show how the legend has moved. 

# Create legend
plt.plot(x, y, label = '{\\footnotesize \$y = x^2\$}')
leg = plt.legend( loc = 'upper right')

plt.draw() # Draw the figure so you can find the positon of the legend. 

# Get the bounding box of the original legend
bb = leg.get_bbox_to_anchor().inverse_transformed(ax.transAxes)

# Change to location of the legend. 
xOffset = 1.5
bb.x0 += xOffset
bb.x1 += xOffset
leg.set_bbox_to_anchor(bb, transform = ax.transAxes)


# Update the plot
plt.show()

legend moved after first drawing

Filip S.
  • 1,514
  • 3
  • 19
  • 40
Molly
  • 13,240
  • 4
  • 44
  • 45
  • 1
    This looks great. I've never seen `legendPatch` before though, why isn't it mentioned in the [docs](http://matplotlib.org/api/legend_api.html#module-matplotlib.legend)? – Filip S. Apr 24 '14 at 09:33
  • I don't know. I learned this trick from the Matplotlib-users email list but I can't find the post. Hopefully some one else will chime in with a more complete answer! – Molly Apr 24 '14 at 15:40
  • this does not work with matplotlib version '1.5.1+1304.gc0728d2' `matplotlib/transforms.py in get_points(self) 1101 # same. 1102 points = self._transform.transform( -> 1103 [[p[0, 0], p[0, 1]], 1104 [p[1, 0], p[0, 1]], 1105 [p[0, 0], p[1, 1]], TypeError: list indices must be integers, not tuple` – Dima Lituiev Feb 22 '16 at 19:39
  • 1
    @DimaLituiev I have modified the answer, now it works as intended on matplotlib 1.5.1. – Filip S. Mar 27 '17 at 13:03
  • Worked great for me. You can also shift the legend vertically with: `bb.y0 += yOffset` and `bb.y1 += yOffset` after setting the variable yOffset to your desired magnitude. – jesseaam Feb 14 '20 at 18:39
  • I get the warning: `MatplotlibDeprecationWarning: The inverse_transformed function was deprecated in Matplotlib 3.3 and will be removed two minor releases later. Use transformed(transform.inverted()) instead.` when getting the bounding box of the legend – Manza Jan 28 '22 at 19:08
16

You may use the bbox_to_anchor and bbox_transform parameters to help you setting the anchor for your legend:

ax = plt.gca()
plt.legend(bbox_to_anchor=(1.1, 1.1), bbox_transform=ax.transAxes)

Note that (1.1, 1.1) are in the axes coordinates in this example. If you wish to use the data coordinates you have to use bbox_transform=ax.transData instead.

Saullo G. P. Castro
  • 56,802
  • 26
  • 179
  • 234
  • 1
    Thanks. This doesn't let me move the legends after placing it though. And btw, `transAxes` is actually default according to the [doc](http://matplotlib.org/api/legend_api.html#module-matplotlib.legend). – Filip S. Apr 24 '14 at 09:29
  • @FilipSund, yes, the `transAxes` is the default... you can perhaps pass to `bbox_to_achor` a tuple with 4 floats (x, y, width, height), which should give you more control over the bbox behavior... – Saullo G. P. Castro Apr 24 '14 at 09:37
1

Updating Molly's answer so that it is compatible with Matplotlib 3.3+. The old answer was to move the legend via:

import matplotlib.pyplot as plt
import numpy as np

# Plot data
x = np.linspace(0,1,100)
y = x**2
fig = plt.figure()
ax = fig.add_subplot(221) #small subplot to show how the legend has moved. 

# Create legend
plt.plot(x, y, label = '{\\footnotesize \$y = x^2\$}')
leg = plt.legend( loc = 'upper right')

plt.draw() # Draw the figure so you can find the positon of the legend. 

# Get the bounding box of the original legend
bb = leg.get_bbox_to_anchor().inverse_transformed(ax.transAxes)

# Change to location of the legend. 
xOffset = 1.5
bb.x0 += xOffset
bb.x1 += xOffset
leg.set_bbox_to_anchor(bb, transform = ax.transAxes)


# Update the plot
plt.show()

However this does not work in Matplotlib 3.3+. You can fix this by changing bb = leg.get_bbox_to_anchor().inverse_transformed(ax.transAxes) to bb = leg.get_bbox_to_anchor().transformed(ax.transAxes.inverted())

hallisky
  • 11
  • 1