Issue
Say I'm making a plot with five items, and only have room to create a legend with 3 columns (more columns than this would be too wide), e.g.
import matplotlib.pyplot as plt
f, a = plt.subplots()
for i in range(5):
a.plot(np.arange(10),np.random.rand(10),label='Item #%d'%i)
a.legend(ncol=3)
The trailing two entries in the bottom row are left-aligned, leaving a big empty space on the right that isn't very aesthetically pleasing. This becomes especially problematic when you have to label very large numbers of lines.
Is there any way to center the entries within the unfilled row?
Solution
The matplotlib legend is column-based. You cannot have legend entries spanning multiple columns. That said, it is of course possible to "center" an odd number of entries in a legend with an odd number of rows and similarly "center" an even number of entries in a legend with an even number of rows. This would be done by using empty artists at the outer positions.
import matplotlib.pyplot as plt
import numpy as np
ncols = 3 # or 4
nlines = 7 # or 10
x = np.linspace(0,19)
f = lambda x,p: np.sin(x*p)
h = [plt.plot(x,f(x,i/10.), label="Line {}".format(i))[0] for i in range(nlines)]
h.insert(nlines//ncols, plt.plot([],[],color=(0,0,0,0), label=" ")[0])
plt.legend(handles=h, ncol=ncols, framealpha=1)
plt.show()
If the number of columns is odd and the number of legend entries is even or vice versa the above is not possible. An option may be to use two different legends and position them below one another such that it looks like the entries of the last line are centered.
import matplotlib.pyplot as plt
import numpy as np
ncols = 3
nlines = 8
x = np.linspace(0,19)
f = lambda x,p: np.sin(x*p)
h = [plt.plot(x,f(x,i/10.), label="Line {}".format(i))[0] for i in range(nlines)]
kw = dict(framealpha=1, borderaxespad=0, bbox_to_anchor=(0.5,0.2), edgecolor="w")
leg1 = plt.legend(handles=h[:nlines//ncols*ncols], ncol=ncols, loc="lower center", **kw)
plt.gca().add_artist(leg1)
leg2 = plt.legend(handles=h[nlines//ncols*ncols:], ncol=nlines-nlines//ncols*ncols,
loc="upper center", **kw)
plt.show()
The drawback here is that the legend has no border and both legends need to be positionned individually. To overcome this one would need to dig a little deeper and use some private attributed of the legend (caution, those may change from version to version without notice).
The idea can then be to remove the second legend once created and get its _legend_handle_box
to place into the first legend.
import matplotlib.pyplot as plt
import numpy as np
ncols = 3
nlines = 8
x = np.linspace(0,19)
f = lambda x,p: np.sin(x*p)
h = [plt.plot(x,f(x,i/10.), label="Line {}".format(i))[0] for i in range(nlines)]
kw = dict(framealpha=1, bbox_to_anchor=(0.5,0.2))
leg1 = plt.legend(handles=h[:nlines//ncols*ncols], ncol=ncols, loc="lower center", **kw)
plt.gca().add_artist(leg1)
leg2 = plt.legend(handles=h[nlines//ncols*ncols:], ncol=nlines-nlines//ncols*ncols)
leg2.remove()
leg1._legend_box._children.append(leg2._legend_handle_box)
leg1._legend_box.stale = True
plt.show()
Answered By - ImportanceOfBeingErnest
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.