78

I'm attempting to create a plot with a legend to the side of it using matplotlib. I can see that the plot is being created, but the image bounds do not allow the entire legend to be displayed.

lines = []
ax = plt.subplot(111)
for filename in args:
    lines.append(plt.plot(y_axis, x_axis, colors[colorcycle], linestyle='steps-pre', label=filename))
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)

This produces: enter image description here

Richard
  • 56,349
  • 34
  • 180
  • 251
user1005909
  • 1,825
  • 2
  • 17
  • 27
  • [This answer](https://stackoverflow.com/a/43439132/4124317) provides an overview over several techniques that can be used to get the legend appear inside the figure boundaries. – ImportanceOfBeingErnest Oct 11 '17 at 22:03

6 Answers6

142

Eventhough that it is late, I want to refer to a nice recently introduced alternative:

New matplotlib feature: The tight bounding box

If you are interested in the output file of plt.savefig: in this case the flag bbox_inches='tight' is your friend!

import matplotlib.pyplot as plt

fig = plt.figure(1)
plt.plot([1, 2, 3], [1, 0, 1], label='A')
plt.plot([1, 2, 3], [1, 2, 2], label='B')
plt.legend(loc='center left', bbox_to_anchor=(1, 0))

fig.savefig('samplefigure', bbox_inches='tight')

Output file: samplefigure.png

I want to refer also to a more detailed answer: Moving matplotlib legend outside of the axis makes it cutoff by the figure box

Advantages

  • There is no need to adjust the actual data/picture.
  • It is compatible with plt.subplots as-well where as the others are not!
  • It applies at least to the mostly used output files, e.g. png, pdf.
strpeter
  • 2,562
  • 3
  • 27
  • 48
  • Thanks! This worked very well w/ little modifications necessary. –  Aug 08 '17 at 23:27
  • 1
    @CarlodelMundo: What modifications were necessary in your case? Thanks for sharing them with us. – strpeter Aug 10 '17 at 07:47
  • 6
    this should be the default for plt.savefig() – MaxS Apr 25 '19 at 09:13
  • 2
    worked, thank you! Just for reference, here's the documentation from matplotlib: > Bbox in inches. Only the given portion of the figure is saved. If 'tight', try to figure out the tight bbox of the figure. If None, use savefig.bbox ^^^ wtf does that even mean? whoever wrote this should get kicked in the face. – Jonathan Jul 08 '19 at 21:59
  • This should be the answer. Simple, to the point and gets the job done. Thanks! – Sameen Dec 22 '20 at 13:16
  • 1
    Thank you, I've been looking for this for ages! – Marthe Veldhuis Jan 18 '21 at 16:35
  • Is there no equivalent for this for just plotting? `fig.tight_layout` doesn't do it. – Princy Apr 30 '21 at 20:37
  • What if we want to just do fig.show(), without saving? Even when I save the figure then show it, the legend is still cropped. – Faraz Masroor Feb 01 '23 at 22:23
  • Thanks. tight works the best fig.savefig(buffer, format='png', bbox_inches='tight') – shailesh gavathe Apr 20 '23 at 01:31
31

As pointed by Adam, you need to make space on the side of your graph. If you want to fine tune the needed space, you may want to look at the add_axes method of matplotlib.pyplot.artist.

Below is a rapid example:

import matplotlib.pyplot as plt
import numpy as np

# some data
x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)

# plot of the data
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.6, 0.75])
ax.plot(x, y1,'-k', lw=2, label='black sin(x)')
ax.plot(x, y2,'-r', lw=2, label='red cos(x)')
ax.set_xlabel('x', size=22)
ax.set_ylabel('y', size=22)
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)

plt.show()

and the resulting image: image

gcalmettes
  • 8,474
  • 1
  • 32
  • 28
  • 135
    I know matplotlib likes to tout that everything is under the control of the user, but this entire thing with the legends is too much of a good thing. If I put the legend outside, I _obviously_ want it to still be visible. The window should just scale itself to fit instead of creating this huge rescaling hassle. At the very least there should be a default True option to control this autoscaling behavior. Forcing users to go through a ridiculous number of re-renders to try and get the scale numbers right in the name of control accomplishes the opposite. – Elliot Jan 02 '13 at 21:43
  • 2
    @strpeter has provided an automatic solution in his answer that worked perfectly fine for me. – Tim Tröndle Feb 27 '17 at 12:21
  • 5
    i am looking at this code for minutes and still do not get which line fixes the problem – Jemshit Mar 22 '19 at 20:28
  • Did you read the text with my link to `add_axes`? https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure.add_axes I think the proposed solution is sufficiently well documented. – strpeter Jul 28 '23 at 12:51
11

Just use plt.tight_layout()

import matplotlib.pyplot as plt
    
fig = plt.figure(1)
plt.plot([1, 2, 3], [1, 0, 1], label='A')
plt.plot([1, 2, 3], [1, 2, 2], label='B')
plt.legend(loc='center left', bbox_to_anchor=(1, 0))

plt.tight_layout()

This is probably introduced in the newer matplotlib version and neatly does the job.

M.Reza
  • 263
  • 2
  • 11
  • Right, this is how one would do it today with the more recent versions of `matplotlib`. For custom fine-tuning, the other options are still more appropriate. – strpeter Jul 28 '23 at 12:56
9

Here is another way of making space (shrinking an axis):

# get the current axis
ax = plt.gca()
# Shink current axis by 20%
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])

where 0.8 scales the width of the axis by 20%. On my win7 64 machine, using a factor greater than 1 will make room for the legend if it's outside the plot.

This code was referenced from: How to put the legend out of the plot

ecoe
  • 4,994
  • 7
  • 54
  • 72
3

Edit: @gcalmettes posted a better answer.
His solution should probably be used instead of the method shown below.
Nonetheless I'll leave this since it sometimes helps to see different ways of doing things.


As shown in the legend plotting guide, you can make room for another subplot and place the legend there.

import matplotlib.pyplot as plt
ax = plt.subplot(121) # <- with 2 we tell mpl to make room for an extra subplot
ax.plot([1,2,3], color='red', label='thin red line')
ax.plot([1.5,2.5,3.5], color='blue', label='thin blue line')
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.show()

Produces:

enter image description here

Community
  • 1
  • 1
mechanical_meat
  • 163,903
  • 24
  • 228
  • 223
  • I dislike this option because it is not clean and does not work generally. The second "hidden" subplot adds some padding that does not match the width of the legend. Long legend text could still be truncated. – strpeter Jul 28 '23 at 12:59
-2

Store your legend call instance to a variable. e.g:

rr = sine_curve_plot.legend(loc=(0.0,1.1))

Then, include the bbox_extra_artists, bbox_inches keyword argument to plt.savefig. i.e:

plt.savefig('output.pdf', bbox_inches='tight', bbox_extra_artists=(rr)) 

bbox_extra_artists accepts an iterable, so you can include as many legend instances into it. The bbox_extra_artists automatically tells plt to cover every extra info passed into bbox_extra_artists.

DISCLAIMER: The loc variable simply defines the position of the legend, you can tweak the values for better flexibility in positioning. Of course, strings like upper left, upper right, etc. are also valid.

Joker
  • 119
  • 6