I draw a graph but unfortunately my legend falls out of the figure. How can I correct it?
I put a dummy code to illustrate it:
from matplotlib import pyplot as plt
from bisect import bisect_left,bisect_right
import numpy as np
global ranOnce
ranOnce=False
def threeLines(x):
"""Draws a function which is a combination of two lines intersecting in
a point one with a large slope and one with a small slope.
"""
start=0
mid=5
end=20
global ranOnce,slopes,intervals,intercepts;
if(not ranOnce):
slopes=np.array([5,0.2,1]);
intervals=[start,mid,end]
intercepts=[start,(mid-start)*slopes[0]+start,(end-mid)*slopes[1]+(mid-start)*slopes[0]+start]
ranOnce=True;
place=bisect_left(intervals,x)
if place==0:
y=(x-intervals[place])*slopes[place]+intercepts[place];
else:
y=(x-intervals[place-1])*slopes[place-1]+intercepts[place-1];
return y;
def threeLinesDrawer(minimum,maximum):
t=np.arange(minimum,maximum,1)
fig=plt.subplot(111)
markerSize=400;
fig.scatter([minimum,maximum],[threeLines(minimum),threeLines(maximum)],marker='+',s=markerSize)
y=np.zeros(len(t));
for i in range(len(t)):
y[i]=int(threeLines(t[i]))
fig.scatter(t,y)
fig.grid(True)
fig.set_xlabel('Y')
fig.set_ylabel('X')
legend1 = plt.Circle((0, 0), 1, fc="r")
legend2 = plt.Circle((0, 0), 1, fc="b")
legend3 = plt.Circle((0, 0), 1, fc="g")
fig.legend([legend1,legend2,legend3], ["p(y|x) likelihood","Max{p(y|x)} for a specific x","Y distribution"],
bbox_to_anchor=(0., 1.02, 1., .102), loc=3,ncol=2, mode="expand", borderaxespad=0.)
threeLinesDrawer(0,20)
plt.show()
You can adjust the space that a set of axes takes within a figure by modifying the subplot parameters. For example, add the following line just before plt.show():
plt.subplots_adjust(top=.9, bottom=.1, hspace=.1, left=.1, right=.9, wspace=.1)
You should tweak the above values as you see fit in the range of [0, 1]. Feel free to get rid of the parameters you're not interested in tweaking (e.g. since you only have one axis in your figure, you won't care about the hspace and wspace parameters, which modify the spacing between subplots). These settings can also be modified through the plt.show() GUI, but you'd have to do it every time you run the script. A set of good settings for your case is the following:
plt.subplots_adjust(top=.83, bottom=.08, left=.08, right=.98)
For doing this adjustment automatically, you can try using tight_layout(). Add the following line just before plt.show():
plt.tight_layout()
This won't necessarily give the intended results in every case, though.
Related
I'm working on a plot with translucent 'x' markers (20% alpha). How do I make the marker appear at 100% opacity in the legend?
import matplotlib.pyplot as plt
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
plt.legend(loc=3, mode="expand", numpoints=1, scatterpoints=1 )
UPDATED:
There is an easier way! First, assign your legend to a variable when you create it:
leg = plt.legend()
Then:
for lh in leg.legendHandles:
lh.set_alpha(1)
OR if the above doesn't work (you may be using an older version of matplotlib):
for lh in leg.legendHandles:
lh._legmarker.set_alpha(1)
to make your markers opaque for a plt.plot or a plt.scatter, respectively.
Note that using simply lh.set_alpha(1) on a plt.plot will make the lines in your legend opaque rather than the markers. You should be able to adapt these two possibilities for the other plot types.
Sources:
Synthesized from some good advice by DrV about marker sizes. Update was inspired by useful comment from Owen.
Following up on cosmosis's answer, to make the "fake" lines for the legend invisible on the plot, you can use NaNs, and they will still work for generating legend entries:
import numpy as np
import matplotlib.pyplot as plt
# Plot data with alpha=0.2
plt.plot((0,1), (0,1), marker = 'x', color=[1, 0, 0, .2])
# Plot non-displayed NaN line for legend, leave alpha at default of 1.0
legend_line_1 = plt.plot( np.NaN, np.NaN, marker = 'x', color=[1, 0, 0], label='Data Series' )
plt.legend()
Other answers here give good practical solutions by either changing the alpha value in the legend after creation, or changing the alpha of the line after legend creation.
A solution to achieve a different opacity in the legend without manipulating anything afterwards would be the following. It uses a handler_map and an updating function.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(43)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D
plt.plot(np.linspace(0,1,8), np.random.rand(8), marker="o", markersize=12, label="A line", alpha=0.2)
plt.scatter(np.random.rand(8),np.random.rand(8), s=144,
c="red", marker=r"$\clubsuit$", label="A scatter", alpha=0.2)
def update(handle, orig):
handle.update_from(orig)
handle.set_alpha(1)
plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func= update),
plt.Line2D : HandlerLine2D(update_func = update)})
plt.show()
If you want to have something specific in your legend, it's easier to define objects that you place in the legend with appropriate text. For example:
import matplotlib.pyplot as plt
import pylab
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
line1 = pylab.Line2D(range(1),range(1),color='white',marker='x',markersize=10, markerfacecolor="red",alpha=1.0)
line2 = pylab.Line2D(range(10),range(10),marker="_",linewidth=3.0,color="dodgerblue",alpha=1.0)
plt.legend((line1,line2),('Text','Other Text'),numpoints=1,loc=1)
Here, line1 defines a short, white line (so essentially invisible) with the marker 'x' in red and full opacity. As an example, line2 gives you a longer blue line with no markers visible. By creating this "lines," you are able to more easily control their properties within the legend.
It looks like matplotlib draws the plot lines after it copies the alpha level to the legend. That means that you can create the plot lines with the alpha level that you want in the legend, create the legend to copy that alpha level, then change the alpha level on the plot lines.
Here's a complete example:
import matplotlib.pyplot as plt
x = (0, 1, 2)
y = (0, 2, 1)
line, = plt.plot(x, y, 'ro', label='label') # Default alpha is 1.0.
plt.legend() # Copy alpha to legend.
line.set_alpha(0.2) # Change alpha for data points.
plt.show()
That plot looks like this when I run it with matplotlib 2.2.3 on Python 2.7.15:
I've found that the .set_alpha() function works on many legend objects, but unfortunately, many legend objects have several pieces (such as the output of errorbar()) and the .set_alpha() call will only affect one of them.
One can use .get_legend_handles_labels() and then loop through parts of the handles and .set_alpha(), but unfortunately, copy.deepcopy() does not seem to work on the list of handles, so the plot itself will be affected. The best workaround I could find was to save the original alphas, .set_alpha() to what I wanted, create the legend, then reset the plot alphas back to their original values. It would be much cleaner if I could deepcopy handles (I wouldn't have to save alpha values or reset them), but I could not do this in python2.7 (maybe this depends on what objects are in the legend).
f,ax=plt.subplots(1)
ax.plot( ... )
def legend_alpha(ax,newalpha=1.0):
#sets alpha of legends to some value
#this would be easier if deepcopy worked on handles, but it doesn't
handles,labels=ax.get_legend_handles_labels()
alphass=[None]*len(handles) #make a list to hold lists of saved alpha values
for k,handle in enumerate(handles): #loop through the legend entries
alphas=[None]*len(handle) #make a list to hold the alphas of the pieces of this legend entry
for i,h in enumerate(handle): #loop through the pieces of this legend entry (there could be a line and a marker, for example)
try: #if handle was a simple list of parts, then this will work
alphas[i]=h.get_alpha()
h.set_alpha(newalpha)
except: #if handle was a list of parts which themselves were made up of smaller subcomponents, then we must go one level deeper still.
#this was needed for the output of errorbar() and may not be needed for simpler plot objects
alph=[None]*len(h)
for j,hh in enumerate(h):
alph[j]=hh.get_alpha() #read the alpha values of the sub-components of the piece of this legend entry
hh.set_alpha(newalpha)
alphas[i]=alph #save the list of alpha values for the subcomponents of this piece of this legend entry
alphass[k]=alphas #save the list of alpha values for the pieces of this legend entry
leg=ax.legend(handles,labels) #create the legend while handles has updated alpha values
for k,handle in enumerate(handles): #loop through legend items to restore origina alphas on the plot
for i,h in enumerate(handle): #loop through pieces of this legend item to restore alpha values on the plot
try:
h.set_alpha(alphass[k][i])
except:
for j,hh in enumerate(h): #loop through sub-components of this piece of this legend item to restore alpha values
hh.set_alpha(alphass[k][i][j])
return leg
leg=legend_alpha(ax)
leg.draggable()
In my case, set_alpha(1) also modified the edgecolors, which I didn't want: I had "invisible" edges, and setting alpha to opaque made them visible in the legend. The following snippet (OOP) changes the opacity of the face without changing the border color:
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1 # set opacity here
lh.set_fc(fc_arr)
Note the call to .copy(), if we don't do this it will modify the opacity for the whole plot. Calling copy means we are only modifying the facecolor inside the legend box.
Alternatively, you can add this function to your library:
def opaque_legend(ax):
"""
Calls legend, and sets all the legend colors opacity to 100%.
Returns the legend handle.
"""
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1
lh.set_fc(fc_arr)
return leg
And then simply replace leg = ax.legend() with leg = opaque_legend(ax). Hope this helps!
Andres
Instead of messing up with the opacity of the legend, I found another way. Firstly, I create a plot line with the style I want the legend to be. Then I change the plot line style, and, miraculously, the legend style remains intact. MWE:
plt.plot(x, y, 'ro', label='label')
for lh in plt.gca().get_legend_handles_labels():
lh[0].set_alpha(new_alpha)
I'd like to explain, why it works, but I can't. Neither I'm sure that it works for all backends.
And yes, I know that the question is old. As it still appears in Google, I'll find it later and help my future self.
What I'm trying to achieve: a plot with two axhline horizontal lines, with the area between them shaded.
The best so far:
ax.hline(y1, color=c)
ax.hline(y2, color=c)
ax.fill_between(ax.get_xlim(), y1, y2, color=c, alpha=0.5)
The problem is that this leaves a small amount of blank space to the left and right of the shaded area.
I understand that this is likely due to the plot creating a margin around the used/data area of the plot. So, how do I get the fill_between to actually cover the entire plot without matplotlib rescaling the x-axis after drawing? Is there an alternative to get_xlim that would give me appropriate limits of the plot, or an alternative to fill_between?
This is the current result:
Note that this is part of a larger grid layout with several plots, but they all leave a similar margin around these shaded areas.
Not strictly speaking an answer to the question of getting the outer limits, but it does solve the problem. Instead of using fill_between, I should have used:
ax.axhspan(y1, y2, facecolor=c, alpha=0.5)
Result:
ax.get_xlim() does return the limits of the axis, not that of the data:
Axes.get_xlim()
Returns the current x-axis limits as the tuple (left, right).
But Matplotlib simply rescales the x-axis after drawing the fill_between:
import matplotlib.pylab as pl
import numpy as np
pl.figure()
ax=pl.subplot(111)
pl.plot(np.random.random(10))
print(ax.get_xlim())
pl.fill_between(ax.get_xlim(), 0.5, 1)
print(ax.get_xlim())
This results in:
(-0.45000000000000001, 9.4499999999999993)
(-0.94499999999999995, 9.9449999999999985)
If you don't want to manually set the x-limits, you could use something like:
import matplotlib.pylab as pl
import numpy as np
pl.figure()
ax=pl.subplot(111)
pl.plot(np.random.random(10))
xlim = ax.get_xlim()
pl.fill_between(xlim, 0.5, 1)
ax.set_xlim(xlim)
I am building a bar chart using matplotlib using the code below. When my first or last column of data is 0, my first column is wedged against the Y-axis.
An example of this. Note that the first column is ON the x=0 point.
If I have data in this column, I get a huge padding between the Y-Axis and the first column as seen here. Note the additional bar, now at X=0. This effect is repeated if I have data in my last column as well.
My code is as follows:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator
binVals = [0,5531608,6475325,1311915,223000,609638,291151,449434,1398731,2516755,3035532,2976924,2695079,1822865,1347155,304911,3562,157,5,0,0,0,0,0,0,0,0]
binTot = sum(binVals)
binNorm = []
for v in range(len(binVals)):
binNorm.append(float(binVals[v])/binTot)
fig = plt.figure(figsize=(6,4))
ax1 = fig.add_subplot(1,1,1)
ax1.bar(range(len(binNorm)),binNorm,align='center', label='Values')
plt.legend(loc=1)
plt.title("Demo Histogram")
plt.xlabel("Value")
plt.xticks(range(len(binLabels)),binLabels,rotation='vertical')
plt.grid(b=True, which='major', color='grey', linestyle='--', alpha=0.35)
ax1.xaxis.grid(False)
plt.ylabel("% of Count")
plt.subplots_adjust(bottom=0.15)
plt.tight_layout()
plt.show()
How can I set a constant margin between the Y-axis and my first/last bar?
Additionally, I realize it's labeled "Demo Histogram", that is a because I missed it when correcting problems discussed here.
I can't run the code snippet you gave, and even with some modification I couldn't replicate the big space. Aside from that, if you need to enforce a border to matplotlib, you ca do somthing like this:
ax.set_xlim( min(your_data) - 10, None )
The first term tells the axis to put the border at 10 units of distance from the minimum of your data, the None parameter teels it to keep the present value.
to put it into contest:
from collections import Counter
from pylab import *
data = randint(20,size=1000)
res = Counter(data)
vals = arange(20)
ax = gca()
ax.bar(vals-0.4, [ res[i] for i in vals ], width=0.8)
ax.set_xlim( min(data)-1, None )
show()
searching around stackoverflow I just learned a new trick: you can call
ax.margins( margin_you_desire )
to let automatically let matplotlib put that amount of space around your plot. It can also be configured differently between x and y.
In your case the best solution would be something like
ax.margins(0.01, None)
The little catch is that the unit is in axes unit, referred to the size of you plot, so a margin of 1 will put space around your plot at both sizes big as your present plot
The problem is align='center'. Remove it.
I'm working on a plot with translucent 'x' markers (20% alpha). How do I make the marker appear at 100% opacity in the legend?
import matplotlib.pyplot as plt
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
plt.legend(loc=3, mode="expand", numpoints=1, scatterpoints=1 )
UPDATED:
There is an easier way! First, assign your legend to a variable when you create it:
leg = plt.legend()
Then:
for lh in leg.legendHandles:
lh.set_alpha(1)
OR if the above doesn't work (you may be using an older version of matplotlib):
for lh in leg.legendHandles:
lh._legmarker.set_alpha(1)
to make your markers opaque for a plt.plot or a plt.scatter, respectively.
Note that using simply lh.set_alpha(1) on a plt.plot will make the lines in your legend opaque rather than the markers. You should be able to adapt these two possibilities for the other plot types.
Sources:
Synthesized from some good advice by DrV about marker sizes. Update was inspired by useful comment from Owen.
Following up on cosmosis's answer, to make the "fake" lines for the legend invisible on the plot, you can use NaNs, and they will still work for generating legend entries:
import numpy as np
import matplotlib.pyplot as plt
# Plot data with alpha=0.2
plt.plot((0,1), (0,1), marker = 'x', color=[1, 0, 0, .2])
# Plot non-displayed NaN line for legend, leave alpha at default of 1.0
legend_line_1 = plt.plot( np.NaN, np.NaN, marker = 'x', color=[1, 0, 0], label='Data Series' )
plt.legend()
Other answers here give good practical solutions by either changing the alpha value in the legend after creation, or changing the alpha of the line after legend creation.
A solution to achieve a different opacity in the legend without manipulating anything afterwards would be the following. It uses a handler_map and an updating function.
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(43)
from matplotlib.collections import PathCollection
from matplotlib.legend_handler import HandlerPathCollection, HandlerLine2D
plt.plot(np.linspace(0,1,8), np.random.rand(8), marker="o", markersize=12, label="A line", alpha=0.2)
plt.scatter(np.random.rand(8),np.random.rand(8), s=144,
c="red", marker=r"$\clubsuit$", label="A scatter", alpha=0.2)
def update(handle, orig):
handle.update_from(orig)
handle.set_alpha(1)
plt.legend(handler_map={PathCollection : HandlerPathCollection(update_func= update),
plt.Line2D : HandlerLine2D(update_func = update)})
plt.show()
If you want to have something specific in your legend, it's easier to define objects that you place in the legend with appropriate text. For example:
import matplotlib.pyplot as plt
import pylab
plt.plot_date( x = xaxis, y = yaxis, marker = 'x', color=[1, 0, 0, .2], label='Data Series' )
line1 = pylab.Line2D(range(1),range(1),color='white',marker='x',markersize=10, markerfacecolor="red",alpha=1.0)
line2 = pylab.Line2D(range(10),range(10),marker="_",linewidth=3.0,color="dodgerblue",alpha=1.0)
plt.legend((line1,line2),('Text','Other Text'),numpoints=1,loc=1)
Here, line1 defines a short, white line (so essentially invisible) with the marker 'x' in red and full opacity. As an example, line2 gives you a longer blue line with no markers visible. By creating this "lines," you are able to more easily control their properties within the legend.
It looks like matplotlib draws the plot lines after it copies the alpha level to the legend. That means that you can create the plot lines with the alpha level that you want in the legend, create the legend to copy that alpha level, then change the alpha level on the plot lines.
Here's a complete example:
import matplotlib.pyplot as plt
x = (0, 1, 2)
y = (0, 2, 1)
line, = plt.plot(x, y, 'ro', label='label') # Default alpha is 1.0.
plt.legend() # Copy alpha to legend.
line.set_alpha(0.2) # Change alpha for data points.
plt.show()
That plot looks like this when I run it with matplotlib 2.2.3 on Python 2.7.15:
I've found that the .set_alpha() function works on many legend objects, but unfortunately, many legend objects have several pieces (such as the output of errorbar()) and the .set_alpha() call will only affect one of them.
One can use .get_legend_handles_labels() and then loop through parts of the handles and .set_alpha(), but unfortunately, copy.deepcopy() does not seem to work on the list of handles, so the plot itself will be affected. The best workaround I could find was to save the original alphas, .set_alpha() to what I wanted, create the legend, then reset the plot alphas back to their original values. It would be much cleaner if I could deepcopy handles (I wouldn't have to save alpha values or reset them), but I could not do this in python2.7 (maybe this depends on what objects are in the legend).
f,ax=plt.subplots(1)
ax.plot( ... )
def legend_alpha(ax,newalpha=1.0):
#sets alpha of legends to some value
#this would be easier if deepcopy worked on handles, but it doesn't
handles,labels=ax.get_legend_handles_labels()
alphass=[None]*len(handles) #make a list to hold lists of saved alpha values
for k,handle in enumerate(handles): #loop through the legend entries
alphas=[None]*len(handle) #make a list to hold the alphas of the pieces of this legend entry
for i,h in enumerate(handle): #loop through the pieces of this legend entry (there could be a line and a marker, for example)
try: #if handle was a simple list of parts, then this will work
alphas[i]=h.get_alpha()
h.set_alpha(newalpha)
except: #if handle was a list of parts which themselves were made up of smaller subcomponents, then we must go one level deeper still.
#this was needed for the output of errorbar() and may not be needed for simpler plot objects
alph=[None]*len(h)
for j,hh in enumerate(h):
alph[j]=hh.get_alpha() #read the alpha values of the sub-components of the piece of this legend entry
hh.set_alpha(newalpha)
alphas[i]=alph #save the list of alpha values for the subcomponents of this piece of this legend entry
alphass[k]=alphas #save the list of alpha values for the pieces of this legend entry
leg=ax.legend(handles,labels) #create the legend while handles has updated alpha values
for k,handle in enumerate(handles): #loop through legend items to restore origina alphas on the plot
for i,h in enumerate(handle): #loop through pieces of this legend item to restore alpha values on the plot
try:
h.set_alpha(alphass[k][i])
except:
for j,hh in enumerate(h): #loop through sub-components of this piece of this legend item to restore alpha values
hh.set_alpha(alphass[k][i][j])
return leg
leg=legend_alpha(ax)
leg.draggable()
In my case, set_alpha(1) also modified the edgecolors, which I didn't want: I had "invisible" edges, and setting alpha to opaque made them visible in the legend. The following snippet (OOP) changes the opacity of the face without changing the border color:
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1 # set opacity here
lh.set_fc(fc_arr)
Note the call to .copy(), if we don't do this it will modify the opacity for the whole plot. Calling copy means we are only modifying the facecolor inside the legend box.
Alternatively, you can add this function to your library:
def opaque_legend(ax):
"""
Calls legend, and sets all the legend colors opacity to 100%.
Returns the legend handle.
"""
leg = ax.legend()
for lh in leg.legendHandles:
fc_arr = lh.get_fc().copy()
fc_arr[:, -1] = 1
lh.set_fc(fc_arr)
return leg
And then simply replace leg = ax.legend() with leg = opaque_legend(ax). Hope this helps!
Andres
Instead of messing up with the opacity of the legend, I found another way. Firstly, I create a plot line with the style I want the legend to be. Then I change the plot line style, and, miraculously, the legend style remains intact. MWE:
plt.plot(x, y, 'ro', label='label')
for lh in plt.gca().get_legend_handles_labels():
lh[0].set_alpha(new_alpha)
I'd like to explain, why it works, but I can't. Neither I'm sure that it works for all backends.
And yes, I know that the question is old. As it still appears in Google, I'll find it later and help my future self.
Is there a way of telling pyplot.text() a location like you can with pyplot.legend()?
Something like the legend argument would be excellent:
plt.legend(loc="upper left")
I am trying to label subplots with different axes using letters (e.g. "A","B"). I figure there's got to be a better way than manually estimating the position.
Thanks
Just use annotate and specify axis coordinates. For example, "upper left" would be:
plt.annotate('Something', xy=(0.05, 0.95), xycoords='axes fraction')
You could also get fancier and specify a constant offset in points:
plt.annotate('Something', xy=(0, 1), xytext=(12, -12), va='top'
xycoords='axes fraction', textcoords='offset points')
For more explanation see the examples here and the more detailed examples here.
I'm not sure if this was available when I originally posted the question but using the loc parameter can now actually be used. Below is an example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.offsetbox import AnchoredText
# make some data
x = np.arange(10)
y = x
# set up figure and axes
f, ax = plt.subplots(1,1)
# loc works the same as it does with figures (though best doesn't work)
# pad=5 will increase the size of padding between the border and text
# borderpad=5 will increase the distance between the border and the axes
# frameon=False will remove the box around the text
anchored_text = AnchoredText("Test", loc=2)
ax.plot(x,y)
ax.add_artist(anchored_text)
plt.show()
The question is quite old but as there is no general solution to the problem till now (2019) according to Add loc=best kwarg to pyplot.text(), I'm using legend() and the following workaround to obtain auto-placement for simple text boxes:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpl_patches
x = np.linspace(-1,1)
fig, ax = plt.subplots()
ax.plot(x, x*x)
# create a list with two empty handles (or more if needed)
handles = [mpl_patches.Rectangle((0, 0), 1, 1, fc="white", ec="white",
lw=0, alpha=0)] * 2
# create the corresponding number of labels (= the text you want to display)
labels = []
labels.append("pi = {0:.4g}".format(np.pi))
labels.append("root(2) = {0:.4g}".format(np.sqrt(2)))
# create the legend, supressing the blank space of the empty line symbol and the
# padding between symbol and label by setting handlelenght and handletextpad
ax.legend(handles, labels, loc='best', fontsize='small',
fancybox=True, framealpha=0.7,
handlelength=0, handletextpad=0)
plt.show()
The general idea is to create a legend with a blank line symbol and to remove the resulting empty space afterwards. How to adjust the size of matplotlib legend box? helped me with the legend formatting.