How to position and align a matplotlib figure legend?

In this case, you can either use axes for figure legend methods. In either case, bbox_to_anchor is the key. As you've already noticed bbox_to_anchor specifies a tuple of coordinates (or a box) to place the legend at. When you're using bbox_to_anchor think of the location kwarg as controlling the horizontal and vertical alignment.

The difference is just whether the tuple of coordinates is interpreted as axes or figure coordinates.

As an example of using a figure legend:

import numpy as np
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)

x = np.linspace(0, np.pi, 100)

line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')

# The key to the position is bbox_to_anchor: Place it at x=0.5, y=0.5
# in figure coordinates.
# "center" is basically saying center horizontal alignment and 
# center vertical alignment in this case
fig.legend([line1, line2], ['yep', 'nope'], bbox_to_anchor=[0.5, 0.5], 
           loc='center', ncol=2)

plt.show()

enter image description here

As an example of using the axes method, try something like this:

import numpy as np
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)

x = np.linspace(0, np.pi, 100)

line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')

# The key to the position is bbox_to_anchor: Place it at x=0.5, y=0
# in axes coordinates.
# "upper center" is basically saying center horizontal alignment and 
# top vertical alignment in this case
ax1.legend([line1, line2], ['yep', 'nope'], bbox_to_anchor=[0.5, 0], 
           loc='upper center', ncol=2, borderaxespad=0.25)

plt.show()

enter image description here


This is a very good question and the accepted answer indicates the key (i.e. loc denotes alignment and bbox_to_anchor denotes position). I have also tried some codes and would like to stress the importance of bbox_transform property that may sometimes needs to be explicitly specified to achieve desired effects. Below I will show you my findings on fig.legend. ax.legend should be very similar as loc and bbox_to_anchor works the same way.

When using the default setting, we will have the following.

%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,4), sharex=True)

x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')

fig.legend([line1, line2], ['yep', 'nope'], loc='lower center', ncol=2)

enter image description here

This is basically satisfactory. But it could be easily found that the legend overlays with the x-axis ticklabels of ax2. This is the problem that will become even severe when figsize and/or dpi of the figure changes, see the following.

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})

x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')

fig.legend([line1, line2], ['yep', 'nope'], loc='lower center', ncol=2)

enter image description here

So you see there are big gaps between ax2 and the legend. That's not what we want. Like the questioner, we would like to manually control the location of the legend. First, I will use the 2-number style of bbox_to_anchor like the answer did.

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})

x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')

axbox = ax2.get_position()

# to place center point of the legend specified by loc at the position specified by bbox_to_anchor.
fig.legend([line1, line2], ['yep', 'nope'], loc='center', ncol=2,
           bbox_to_anchor=[axbox.x0+0.5*axbox.width, axbox.y0-0.05])

enter image description here

Almost there! But it is totally wrong as the center of the legend is not at the center of what we really mean! The key to solving this is that we need to explicitly inform the bbox_transform as fig.transFigure. By default None, the Axes' transAxes transform will be used. This is understandable as most of the time we will use ax.legend().

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})

x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')

axbox = ax2.get_position()

# to place center point of the legend specified by loc at the position specified by bbox_to_anchor!
fig.legend([line1, line2], ['yep', 'nope'], loc='center', ncol=2,
           bbox_to_anchor=[axbox.x0+0.5*axbox.width, axbox.y0-0.05], bbox_transform=fig.transFigure)

enter image description here

As an alternative, we can also use a 4-number style bbox_to_anchor for loc. This is essentially specify a real box for the legend and loc really denotes alignment! The default bbox_to_anchor should just be [0,0,1,1], meaning the entire figure box! The four numbers represent x0,y0,width,height, respectively. It is very similar to specifying a cax for a shared colorbar! Hence you can easily change the y0 just a little bit lower than axbox.y0 and adjust loc accordingly.

fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(6,12), sharex=True, facecolor='w', gridspec_kw={'hspace':0.01})

x = np.linspace(0, np.pi, 100)
line1, = ax1.plot(x, np.cos(3*x), color='red')
line2, = ax2.plot(x, np.sin(4*x), color='green')

axbox = ax2.get_position()

# to place center point specified by loc at the position specified by bbox_to_anchor!
fig.legend([line1, line2], ['yep', 'nope'], loc='lower center', ncol=2,
           bbox_to_anchor=[0, axbox.y0-0.05,1,1], bbox_transform=fig.transFigure)

enter image description here