Moving matplotlib legend outside of the axis makes it cutoff by the figure box
Sorry EMS, but I actually just got another response from the matplotlib mailling list (Thanks goes out to Benjamin Root).
The code I am looking for is adjusting the savefig call to:
fig.savefig('samplefigure', bbox_extra_artists=(lgd,), bbox_inches='tight')
#Note that the bbox_extra_artists must be an iterable
This is apparently similar to calling tight_layout, but instead you allow savefig to consider extra artists in the calculation. This did in fact resize the figure box as desired.
import matplotlib.pyplot as plt
import numpy as np
plt.gcf().clear()
x = np.arange(-2*np.pi, 2*np.pi, 0.1)
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(x, np.sin(x), label='Sine')
ax.plot(x, np.cos(x), label='Cosine')
ax.plot(x, np.arctan(x), label='Inverse tan')
handles, labels = ax.get_legend_handles_labels()
lgd = ax.legend(handles, labels, loc='upper center', bbox_to_anchor=(0.5,-0.1))
text = ax.text(-0.2,1.05, "Aribitrary text", transform=ax.transAxes)
ax.set_title("Trigonometry")
ax.grid('on')
fig.savefig('samplefigure', bbox_extra_artists=(lgd,text), bbox_inches='tight')
This produces:
[edit] The intent of this question was to completely avoid the use of arbitrary coordinate placements of arbitrary text as was the traditional solution to these problems. Despite this, numerous edits recently have insisted on putting these in, often in ways that led to the code raising an error. I have now fixed the issues and tidied the arbitrary text to show how these are also considered within the bbox_extra_artists algorithm.
My matplotlib.pyplot legend is being cut off
As pointed by Adam, you need to make space on the side of your graph.
If you want to fine tune the needed space, you may want to look at the add_axes method of matplotlib.pyplot.artist.
Below is a rapid example:
import matplotlib.pyplot as plt
import numpy as np
# some data
x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)
# plot of the data
fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.6, 0.75])
ax.plot(x, y1,'-k', lw=2, label='black sin(x)')
ax.plot(x, y2,'-r', lw=2, label='red cos(x)')
ax.set_xlabel('x', size=22)
ax.set_ylabel('y', size=22)
ax.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.show()
and the resulting image:
Legend of the graph was cut out while exporting via Matplotlib
Use tight_layout
:
# plt.legend(bbox_to_anchor=(1.20, 0.6),loc='best')
# plt.savefig('Figure.jpg',dpi=300)
plt.legend(bbox_to_anchor=(1.04,0.5), loc="center left", borderaxespad=0)
plt.tight_layout(rect=[0,0,0.95,1])
plt.savefig("output.png", bbox_inches="tight")
How to put the legend outside the plot
- You can make the legend text smaller by specifying
set_size
ofFontProperties
. - Resources:
- Legend guide
matplotlib.legend
matplotlib.pyplot.legend
matplotlib.font_manager
set_size(self, size)
- Valid font size are xx-small, x-small, small, medium, large, x-large, xx-large, larger, smaller, and None.
- Real Python: Python Plotting With Matplotlib (Guide)
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
fontP = FontProperties()
fontP.set_size('xx-small')
p1, = plt.plot([1, 2, 3], label='Line 1')
p2, = plt.plot([3, 2, 1], label='Line 2')
plt.legend(handles=[p1, p2], title='title', bbox_to_anchor=(1.05, 1), loc='upper left', prop=fontP)
fontsize='xx-small'
also works, without importingFontProperties
.
plt.legend(handles=[p1, p2], title='title', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize='xx-small')
Matplotlib: plotting two legends outside of the axis makes it cutoff by the figure box
This issue is come with pie chart: https://github.com/matplotlib/matplotlib/issues/4251
And it is not fixed.
Matplotlib savefig with a legend outside the plot
The problem is that when you plot dynamically, matplotlib
determines the borders automatically to fit all your objects.
When you save a file, things are not being done automatically, so you need to specify
the size of your figure, and then the bounding box of your axes object.
Here is how to correct your code:
import matplotlib.pyplot as pyplot
x = [0, 1, 2, 3, 4]
y = [xx*xx for xx in x]
fig = pyplot.figure(figsize=(3,3))
ax = fig.add_subplot(111)
#box = ax.get_position()
#ax.set_position([0.3, 0.4, box.width*0.3, box.height])
# you can set the position manually, with setting left,buttom, witdh, hight of the axis
# object
ax.set_position([0.1,0.1,0.5,0.8])
ax.plot(x, y)
leg = ax.legend(['abc'], loc = 'center left', bbox_to_anchor = (1.0, 0.5))
fig.savefig('aaa.png')
Legend outside the plot in Python - matplotlib
After trying around a lot, this is the best I can come up with:
from matplotlib.lines import Line2D
from matplotlib.gridspec import GridSpec
from enum import Enum
class Location(Enum):
EastOutside = 1
WestOutside = 2
NorthOutside = 3
SouthOutside = 4
class Legend:
def __init__(self, figure, plotAxes, location: Location):
self.figure = figure
self.plotAxes = plotAxes
self.location = location
# Create a separate subplot for the legend. Actual location doesn't matter - will be modified anyway.
self.legendAxes = figure.add_subplot(1, 2, 1)
self.legendAxes.clear() # remove old lines
self.legendAxes.set_axis_off()
# Add all lines from the plot to the legend subplot
for line in plotAxes.get_lines():
legendLine = Line2D([], [])
legendLine.update_from(line)
self.legendAxes.add_line(legendLine)
if self.location == Location.EastOutside:
self.legend = self.legendAxes.legend(loc = "center left")
elif self.location == Location.WestOutside:
self.legend = self.legendAxes.legend(loc = "center right")
elif self.location == Location.NorthOutside:
self.legend = self.legendAxes.legend(loc = "lower center")
elif self.location == Location.SouthOutside:
self.legend = self.legendAxes.legend(loc = "upper center")
else:
raise Exception("Unknown legend location.")
self.UpdateSize()
# Recalculate legend size if the size changes
figure.canvas.mpl_connect('resize_event', lambda event: self.UpdateSize())
def UpdateSize(self):
self.figure.canvas.draw() # draw everything once in order to get correct legend size
# Extract legend size in percentage of the figure width
legendSize = self.legend.get_window_extent().inverse_transformed(self.figure.transFigure)
legendWidth = legendSize.width
legendHeight = legendSize.height
# Update subplot such that it is only as large as the legend
if self.location == Location.EastOutside:
gridspec = GridSpec(1, 2, width_ratios = [1 - legendWidth, legendWidth])
legendLocation = 1
plotLocation = 0
elif self.location == Location.WestOutside:
gridspec = GridSpec(1, 2, width_ratios = [legendWidth, 1 - legendWidth])
legendLocation = 0
plotLocation = 1
elif self.location == Location.NorthOutside:
gridspec = GridSpec(2, 1, height_ratios = [legendHeight, 1 - legendHeight])
legendLocation = 0
plotLocation = 1
elif self.location == Location.SouthOutside:
gridspec = GridSpec(2, 1, height_ratios = [1 - legendHeight, legendHeight])
legendLocation = 1
plotLocation = 0
else:
raise Exception("Unknown legend location.")
self.legendAxes.set_position(gridspec[legendLocation].get_position(self.figure))
self.legendAxes.set_subplotspec(gridspec[legendLocation]) # to make figure.tight_layout() work if that's desired
self.plotAxes.set_position(gridspec[plotLocation].get_position(self.figure))
self.plotAxes.set_subplotspec(gridspec[plotLocation]) # to make figure.tight_layout() work if that's desired
This places the legend more or less alright in the cases I have tested so far. Usage is e.g.
import matplotlib.pyplot as plt
plt.ion()
figure = plt.figure()
plotAxes = figure.gca()
plotAxes.plot([1, 2, 3], [4, 5, 6], "b-", label = "TestMoving Matplotlib Legend Outside of the Axis Makes It Cutoff by the Figure Boxaaaaaa 1")
plotAxes.plot([1, 2, 3], [6, 5, 4], "r-", label = "Test 2")
legend = Legend(figure, plotAxes, Location.EastOutside)
Let me ask the question I posted in a comment already... how would I go about suggesting this as an additional feature to the matplotlib developers? (Not my hack, but a native way of having the legend outside the figure)
matplotlib legend goes outside of the window area
either use
plt.tight_layout()
to automatically adjust the dimensions of the Axes to accomodate the legend box
or, if you prefer more fine control:
plt.subplots_adjust(right=xxxx)
to shrink the width of the Axes to accomodate the legend box
Related Topics
How to Check If a Python Module Exists Without Importing It
Scraping Dynamic Content Using Python-Scrapy
Extract Images from PDF Without Resampling, in Python
What Does 'Valueerror: Cannot Reindex from a Duplicate Axis' Mean
Dynamically Add Field to a Form
Scatter Plot and Color Mapping in Python
How to Export Keras .H5 to Tensorflow .Pb
Reading from a Frequently Updated File
Python: Importing a Sub‑Package or Sub‑Module
How to Sort Unicode Strings Alphabetically in Python
Python Numpy Valueerror: Operands Could Not Be Broadcast Together with Shapes
Check If a Number Is a Perfect Square
Round to 5 (Or Other Number) in Python
Python Multithreading Wait Till All Threads Finished
How to Access Outer Class from an Inner Class