Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
410 views
in Technique[技术] by (71.8m points)

python - Centering matplotlib legend entries within incomplete/unfilled rows?

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.

enter image description hereenter image description here

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.

enter image description here

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()

enter image description here


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...