import matplotlib.pyplot as plt
My solution to https://twitter.com/zh1zh4n6/status/1759823358340825149
A legend is a collection of OffsetBox
instances. You may collect multiple legends and rearange them to your need.
Example of multiple legends
We will start with a modified version of the multiple legend example from multiple-legends-on-the-same-axes
Note that multiple calls of ax.legend
will replace an exising legend. To work around this, we manually add the legend artist to the axes using ax.add_artist
.
#! echo: false
def plot(ax):
= ax.plot([1, 2, 3], label="Line 1", linestyle='--')
line1, = ax.plot([3, 2, 1], label="Line 2", linewidth=4)
line2,
= ax.scatter([0, 1, 2], [1.5, 4, 2.], label="Sct 1", marker="s")
sct1 = ax.scatter([0, 1, 2], [4, 3, 0.5], label="Sct 2", marker="^")
sct2
return [line1, line2], [sct1, sct2]
= dict(alignment="left",
title_props =dict(weight="bold"))
title_fontproperties
= plt.subplots(num=1, clear=True)
fig, ax
= plot(ax)
[line1, line2], [sct1, sct2]
# Create a legend for the first set.
= ax.legend(handles=[line1, sct1], loc='upper right',
first_legend ="Data 1", **title_props)
title
# Add the legend manually to the Axes.
ax.add_artist(first_legend)
# Create another legend for the second.
=[line2, sct2], loc='lower right',
ax.legend(handles="Data 2", **title_props) title
<matplotlib.legend.Legend at 0x7fe8cdde6170>
Assembling multiple legends
In the modified version below, We will create multiple legend boxes, which will be assemebed into a single offset box. Note that we don’t want individual legends be displayed.
= plt.subplots(num=2, clear=True)
fig, ax
= plot(ax)
[line1, line2], [sct1, sct2]
# Legend is a collection of offset_box instances, wrapped around with
# AnchoredOffsetbox. We will create legends and collect the offset_box
# instances, but without showing them on the screen for now.
= []
offsetboxes for title, handles in [("Data 1", [line1, sct1]),
"Data 2", [line2, sct2])]:
(= ax.legend(handles=handles,
leg =title, **title_props)
title
offsetboxes.append(leg._legend_box)
# We don't want the legend to be displayed. ax.legend_.remove()
We now have a list of offset-boxes. One can vertically pack boxes into a single box and place it on the corner of the axes, similar to a legend.
# We will add the collected offsetbox. They first need to be packed in a box.
# We use `VPacker` which will pack its childrent vertically.
from matplotlib.offsetbox import VPacker, AnchoredOffsetbox
= VPacker(children=offsetboxes, sep=15)
vp
# Then this will be added to the axes using `AnchoredOffsetbox`, which can be
# placed just like legend.
= AnchoredOffsetbox(loc="upper right", bbox_to_anchor=ax.bbox, child=vp)
ob ax.add_artist(ob)
Of course it is possible to place the box outside the axes. There are several ways to do this. I will use axes_grid1
toolkit, which I am most familiar with.
We will start with a sample example.
= plt.subplots(num=3, clear=True)
fig, ax
= plot(ax)
[line1, line2], [sct1, sct2]
# Legend is a collection of offset_box instances, wrapped around with
# AnchoredOffsetbox. We will create legends and collect the offset_box
# instances, but without showing them on the screen for now.
= []
offsetboxes for title, handles in [("Data 1", [line1, sct1]),
"Data 2", [line2, sct2])]:
(= ax.legend(handles=handles,
leg =title, **title_props)
title
offsetboxes.append(leg._legend_box)
# We don't want the legend to be displayed. ax.legend_.remove()
Placing the box outside of the main axes
We will create a new axes on the right side of the main axes. In addtion, we want the width of this axes set to the width of the offset box.
from mpl_toolkits.axes_grid1 import make_axes_locatable
from mpl_toolkits.axes_grid1.axes_size import MaxWidth
= VPacker(children=offsetboxes, sep=15)
vp
= make_axes_locatable(ax)
divider = [] # we start with an empty artist list.
artist_list = divider.append_axes("right", size=MaxWidth(artist_list), pad=0.1)
ax_right
= AnchoredOffsetbox(loc="upper left", bbox_to_anchor=ax_right.bbox, child=vp,
ob =0, borderpad=0)
pad
ax_right.add_artist(ob)
# We add `ob` to the artist_list, so that the width of `ax_right` is adjusted.
artist_list.append(ob)
ax_right.set_axis_off()