Figure legend with constrained_layout

Hello, I’m trying to add a figure legend on the top of the figure that is using costrained_layout. However, matplotlib puts it inside the axis. The code:

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(constrained_layout=True)
ax = fig.subplots()

x = np.linspace(0, 2*np.pi, 200)
for n in [1, 2, 3, 4]:
    ax.plot(x, np.sin(n*x), label=f"sin({n}x)")

fig.legend(loc="upper center", ncol=4, mode="expand")
plt.savefig("four_plots.png", pad_inches=0.0, bbox_inches="tight")

I managed to move it out of the axis by using

fig.legend(bbox_to_anchor=(0., 1., 1., 0.01), loc="lower left", ncol=4, mode="expand")

instead. However, now the box around the legend is clipped of at the top:

How would I do this correctly?

Use ax.legend instead so the axes is resized to make room for the legend.

If I use ax.legend(), it also puts the legend inside the axes, which is not what I wanted.

Also, and I should have mentioned that, I have some plots with subplots that should share the legend, i.e.:

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(constrained_layout=True)
axs = fig.subplots(ncols=2)

x = np.linspace(0, 2*np.pi, 200)
for n in [1, 2, 3, 4]:
    axs[0].plot(x, np.sin(n*x), label=f"n = {n}")
    axs[1].plot(x, np.cos(n*x))

fig.legend(loc="upper center", ncol=4, mode="expand")
plt.savefig("legend_placement.png", pad_inches=0.0, bbox_inches="tight")

Right - that requires a bit of mucking around:


import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(constrained_layout=True)
axs = fig.subplots(ncols=2)

x = np.linspace(0, 2*np.pi, 200)
for n in [1, 2, 3, 4]:
    axs[0].plot(x, np.sin(n*x), label=f"n = {n}")
    axs[1].plot(x, np.cos(n*x))

axs[0].legend(loc="upper center", ncol=4, mode="expand", 
              bbox_to_anchor=(0, 0, 2, 1.1), 
             bbox_transform=axs[0].transAxes)
plt.show()

Thanks! It behaves a bit strangely when changing the width in bbox_to_anchor, i.e. pulls the two plots apart when I use for example 3 instead of 2, but I guess I’ll have to figure that out relative to my figure width.

The 2 is relative to the axes width, so if you make it 3, there had better be 3 subplots, or it will make extra room.

I’m not claiming this is particularly ideal. But its hard to think about other approaches that would work with how CL works.

You could make a third axes to hold the legend, and maybe that is the most automatic. Note that the natural height of this axis is essentially 0:

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(constrained_layout=True)
axs = fig.subplot_mosaic([['legend', 'legend'],['left', 'right']], 
                          gridspec_kw={'height_ratios':[0.001, 1]})

x = np.linspace(0, 2*np.pi, 200)
for n in [1, 2, 3, 4]:
    axs['left'].plot(x, np.sin(n*x), label=f"n = {n}")
    axs['right'].plot(x, np.cos(n*x))

axs['legend'].axis('off')
handles, labels = axs['left'].get_legend_handles_labels()
axs['legend'].legend(handles, labels, loc="upper center", ncol=4, mode="expand")

plt.show()

Legend

BTW there is a PR for this https://github.com/matplotlib/matplotlib/pull/13072 But I’ve not come back to it for quite a while.

Thanks again for the detailed response. Very helpful.

Thanks for the reminder about this. ENH: allow fig.legend in layout by jklymak · Pull Request #19743 · matplotlib/matplotlib · GitHub is a much simpler PR. Pretty sure it will be in for 3.5