How to include the outside legend into the generated file?

Question:

I am plotting many lines on several axes, so I have a several fairly busy plots, thus I need to place the legend outside of the figure:

import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
    for n in names:
        ax.plot(np.arange(10),np.random.normal(size=10),label=n)
fig.tight_layout()
axes[0].legend(loc="upper left", bbox_to_anchor=(1,0,1,1))

which produces something like

sample chart

However, when I save the figure using fig.savefig("test.png"), I get this:

enter image description here

note the missing legend.

How do I save the figure so that the legend is included?

Asked By: sds

||

Answers:

One option is to tell tight_layout() not to use the full width. That leaves enough room for your legend. I’m not sure if there’s a way measure the width of your legend in code, but I experimentally found this fits your legend:

import matplotlib.pyplot as plt
import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
    for n in names:
        ax.plot(np.arange(10),np.random.normal(size=10),label=n)
fig.tight_layout(rect=(0, 0, 0.84, 1))
axes[0].legend(loc="upper left", bbox_to_anchor=(1,0,1,1))

fig.savefig("test.png")

After some experimentation, though, it seems like simplifying the call to legend() tells tight_layout() about the legend and to leave room for it. Now, making the names longer automatically makes the plots smaller so that everything fits.

There was a problem with tight_layout() leaving gaps between subplots, because the legend was taller than the subplot. We put a single entry in the legend, call tight_layout(), then put all the entries in the legend. The legend extends below the bottom of the first subplot, but that’s what we want.

If the names are different lengths, you’d have to do some more trickery to use the longest name instead of the first name or split all the entries across all the subplots before calling tight_layout().

import matplotlib.pyplot as plt
import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
    for n in names:
        ax.plot(np.arange(10),np.random.normal(size=10),label=n)

# Create a legend with only one entry, so tight_layout doesn't stretch down.
handles, labels = axes[0].get_legend_handles_labels()
axes[0].legend(handles[:1], labels[:1], bbox_to_anchor=(1, 1))
fig.tight_layout()

# Use all the entries without worrying about expanding below the subplot.
axes[0].legend(handles, labels, bbox_to_anchor=(1, 1))

fig.savefig("test.png")
Answered By: Don Kirkby

Use plt.subplots_adjust and you can customize the space around the figure. Here I just used plt.subplots_adjust(right=0.8) but you can adjust all the settings (including top, bottom, left, hspace, wspace)

import numpy as np
nrows = 4
fig = plt.figure(figsize=(6, 2*nrows))
axes = fig.subplots(nrows=nrows, ncols=1)
names = [f"name-{n}" for n in range(10)]
for ax in axes:
    for n in names:
        ax.plot(np.arange(10),np.random.normal(size=10),label=n)
fig.tight_layout()
axes[0].legend(loc="upper left", bbox_to_anchor=(1,0,1,1))

fig.subplots_adjust(right=0.80)
fig.savefig("test.png")

Saved image:

enter image description here

Answered By: Michael S.
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.