- 📊 Using a shared legend in Matplotlib makes things clearer and uses subplot space well.
- 🔍
fig.legend()creates a legend spanning multiple subplots, unlikeax.legend(). - 🛠️
constrained_layout=Truehelps avoid overlaps between legends, labels, and titles. - 🎯
bbox_to_anchorgives exact control over legend placement outside the plot area. - 🧩 Manual legend placement via
add_axeslets you adjust it precisely.
Matplotlib is a strong library for making static, interactive, and animated plots in Python. But handling legends in multi-panel plots, also called subplots, can get messy. This article shows you good ways to use a shared legend in Matplotlib subplots. This helps you make neat and clear plots that work well at any size and look professional.
Default Legend Behavior in Subplots
When you make subplots with matplotlib.pyplot.subplots(), each Axes object acts on its own. This includes its legend. If you call ax.legend() on each subplot, you get many separate legends. This happens even if your plots have the same labels.
Example of Default Behavior
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 2)
axs[0].plot([1, 2], [3, 4], label='Line A')
axs[0].legend()
axs[1].plot([1, 2], [4, 3], label='Line B')
axs[1].legend()
plt.show()
Each subplot will now have its own legend. As your plots get bigger or more complex, for example in 2×2 grids, a separate legend for each subplot makes them harder to read. And it takes up too much space.
Consequences of Individual Legends
- Subplot legends might overlap data.
- Space, up and down or side to side, gets limited.
- Labels are not consistent if text is repeated.
- Keeping legends right across many subplots gets hard.
And so, using one legend that covers all subplots is a much better way to go in most multi-panel plots.
Why You Want a Shared Legend
Using a shared legend, especially with similar data or repeated groups, has many upsides:
- 🚀 Makes it easier to read: People can understand labels from one legend instead of looking at many.
- 🖼️ Uses plot space well: You get more room for your data.
- 📐 Shows what is important: It sets aside space for understanding the data, not just showing it.
- 🔁 Cuts down on repeated info: You have one legend, but it refers to many things.
This is very helpful when working with data grouped by type over time, or with computer models that share settings, or with plots like those on a dashboard.
ax.legend() vs fig.legend()
This is a key difference for making shared legends in Matplotlib:
ax.legend(): Puts a legend on a single axes object, meaning one subplot.fig.legend(): Makes a legend for the whole figure. It can bring together labels from many axes.
Example Comparison for Shared Legend
fig, axs = plt.subplots(1, 2)
line1, = axs[0].plot([1, 2], [3, 4], label='Line A')
line2, = axs[1].plot([1, 2], [4, 3], label='Line B')
fig.legend(handles=[line1, line2], loc='upper center', ncol=2)
plt.show()
✅ Result: You get one legend above both subplots. It clearly shows Line A and Line B.
Making Subplots the Right Way
How you set up your subplots matters for how they line up, how they are arranged, and how easy it is to place shared legends.
fig, axs = plt.subplots(2, 2, constrained_layout=True)
for ax in axs.flat:
ax.plot([1, 2, 3], [1, 4, 9], label='Parabola')
plt.show()
Key Point:
- Use
.flatto go through 2D axes grids in a standard way. - Setting
constrained_layout=Truemakes sure labels, axes, and legends do not overlap.
And for many subplots, this method works well and keeps the spacing correct.
Keep Plot Handles and Labels Straight
To use fig.legend() well, you need to say exactly which handles (lines or patches) and labels show up in the legend.
fig, axs = plt.subplots(1, 2)
line1, = axs[0].plot([1, 2, 3], [1, 2, 3], label='Linear')
line2, = axs[1].plot([1, 2, 3], [1, 4, 9], label='Quadratic')
handles = [line1, line2]
labels = [h.get_label() for h in handles]
fig.legend(handles=handles, labels=labels, loc='lower center', bbox_to_anchor=(0.5, -0.05), ncol=2)
💡 Tip:
Hold onto references (like line1, =) so you don't lose the lines you plotted. Using .get_label() is good if you want to get labels with code or change them before you make the legend.
How to Use fig.legend() for Shared Legends
The fig.legend() method is very flexible. It lets you fully control:
- Where it is on the plot (
loc) - Its anchor points (
bbox_to_anchor) - How it looks (for example, the number of columns with
ncol) - Its style (
fontsize,frameon, spacing)
Example: Placing Legend Below All Subplots
fig.legend(handles=handles, loc='lower center', bbox_to_anchor=(0.5, -0.05), ncol=2)
bbox_to_anchor=(0.5, -0.05)moves the legend below the plots.ncol=2puts the legend items in a row.- Change these values if you need different spacing.
And use this spot if you like legends at the bottom, like in papers or slide shows.
Adjusting with constrained_layout
The constrained_layout tool in Matplotlib moves subplot positions on its own to stop things from overlapping. It is a good idea to use this when you place shared legends.
fig, axs = plt.subplots(2, 2, constrained_layout=True)
# plotting code...
fig.legend(handles=handles, loc='upper center', ncol=2)
plt.show()
Main Good Points:
- It changes the space between subplots as needed.
- It makes sure titles, x/y labels, and legends have enough white space.
- It also makes sure shared legends, whether above or below, do not cut off or overlap content.
This is important: Always call fig.legend() after you have finished all your plotting. Layout tools need to know everything about the figure to adjust it right.
tight_layout() vs constrained_layout=True
| Feature | tight_layout() | constrained_layout=True |
|---|---|---|
| Scope | One-time adjustment | Moves things as needed, knows the layout |
| Handles Legends | Not well | Well |
| Nesting Subplots | Not much | Yes |
| Ease of Use | Simple but stiff | A bit more complex but strong |
Do not use both together unless you have tried each one alone and are sure it does not mess up the layout.
Custom Legend Areas Using add_axes
For very specific plots, or when you need more complex places for your legend, like on busy dashboards or where grids break, you can make another "hidden" axes with add_axes(). Then, put your legend there.
legend_ax = fig.add_axes([0.3, 0.92, 0.4, 0.05])
legend_ax.axis('off')
legend_ax.legend(handles=handles, loc='center', ncol=2)
How the Axes Work Together:
[0.3, 0.92, 0.4, 0.05]means[left, bottom, width, height]in figure units, from 0 to 1.- This box holds the legend, but you cannot see it because
axis('off')is set.
This is the most hands-on, but also the most flexible, way to place legends. It gives you exact control for dashboard-like designs or figures for papers.
How bbox_to_anchor Places Things
The bbox_to_anchor setting works with loc to put a legend anywhere on the plot.
fig.legend(handles=handles, loc='upper center', bbox_to_anchor=(0.5, 1.05), ncol=2)
loc='upper center': Sets where it aligns.bbox_to_anchor=(0.5, 1.05): Puts the legend above the figure. This means x is at the center (0.5), and y is just above (1.05).ncol=2: The labels go across, not down.
Try and See Strategy
You often need to move the bbox_to_anchor values a little up or down, or left or right, to find the best spot. Here is a quick guide:
| Placement Goal | loc |
bbox_to_anchor |
|---|---|---|
| Top center (outside) | upper center |
(0.5, 1.05) |
| Bottom center (outside) | lower center |
(0.5, -0.05) |
| Right side (outside) | center left |
(1.02, 0.5) |
Fixing Misplaced Legends
Even if you use fig.legend(), legends can sometimes be in the wrong place or hidden. Here is how to fix them:
✅ Checklist for Shared Legend Problems
- Make sure all subplots are drawn before you call
fig.legend()for the legend. - Use
constrained_layout=Trueto make text that overlaps adjust on its own. - Do not mix
tight_layout()unless you really need to. - Make the figure bigger if the legend overlaps:
fig, axs = plt.subplots(..., figsize=(12, 8)). - Put the legend outside plots using
bbox_to_anchorin a smart way.
fig.tight_layout()
fig.legend(...)
Sometimes tight_layout() can cause problems with spacing. But if you call the legend after it, it gets the last needed adjustment.
Before/After Comparison
Let us show how much clearer a shared legend makes things.
🔴 Before:
- Many legends on each subplot
- Not lined up well, too many labels
✅ After:
- One legend in the middle, below the whole figure
- Uses space better
- Groupings are clear for people looking at the plot
(Pictures or examples could be made in a live notebook)
Summary and Good Ways to Work
Making a shared legend in Matplotlib can greatly improve how plots with many subplots look.
✅ Shared Legend Checklist:
- Use
fig.legend()instead ofax.legend() - Get plot handles with
line1, = ax.plot(...) - Control how the layout looks with
constrained_layout=True - Put legends in clear spots with
bbox_to_anchor - Use
add_axes()for your own placement if needed
By setting up your figures on purpose and using layout tools, your plots will be easier to understand and look more professional, no matter their size.
Bonus: Style Your Shared Legend
Make your shared legend look better by changing its style:
fig.legend(handles=handles, fontsize='medium', labelspacing=0.5, frameon=True)
fontsize='medium': Makes text the same sizelabelspacing=0.5: Changes the space up and down between legend itemsframeon=True: Puts a box you can see around the legend
These small changes can help a lot when you show work to others or write papers.
Final Thoughts
Working with a matplotlib legend across subplots means you need to think about layout tools, plot handles, and how you space things. Whether you are making interactive dashboards or static plots for reports, using a shared legend matplotlib method makes sure your data groups are shown clearly and briefly. Getting good at designing subplot legends is a skill that makes your plotting tools better.
And keep looking at Matplotlib to find more layout choices and ways to change things, like grid layouts, colorbars, and notes. Your data should be told in the best way. Give it a plot that fits.
References
Hunter, J. D. (2007). Matplotlib: A 2D graphics environment. Computing in Science & Engineering, 9(3), 90–95. https://doi.org/10.1109/MCSE.2007.55
Waskom, M. L., et al. (2020). Seaborn: Statistical data visualization. Journal of Open Source Software, 5(51), 3021. https://doi.org/10.21105/joss.03021
Van Rossum, G., & Drake, F. (2009). Python 3 Reference Manual. Scotts Valley, CA: CreateSpace.