我有一个带有两个不同系列曲线的图,我将使用点和线来绘制它们。我想要一个图例,使线和点标记共享同一标签。
我尝试了this的建议,如果我的两个系列图都具有不同的点类型(而不是线和点),则该建议效果很好。 我当前使用的代码带有不正确的图例,
s
您是否有解决此问题的建议?
答案 0 :(得分:1)
在这种情况下,将我的答案应用于How to create two legend objects for a single plot instance?:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
Vs = np.array([0.5, 1, 1.5, 2])
Xs = np.array([[ 0.5, 0.2, 0.7], [ 0.5, 0.3, 0.9], [ 0.5, 0.5, 0.4],
[ 0.5, 0.7, 0.4],[ 0.5, 0.9, 0.7], [ 1, 0.15, 0.9],
[ 1, 0.35, 0.6], [ 1, 0.45, 0.6], [ 1, 0.67, 0.5],
[ 1, 0.85, 0.9], [ 1.5, 0.1, 0.9], [ 1.5, 0.3, 0.7],
[ 1.5, 0.76, 0.3], [ 1.5, 0.98, 0.4], [ 2, 0.21, 0.5],
[ 2, 0.66, 0.3], [ 2, 0.76, 0.5], [ 2, 0.88, 0.4],
[ 2, 0.99, 0.4]])
f, axs = plt.subplots(1, 1, figsize=(2.5,3))
axs.set_xlim(0.38,1.0)
axs.set_ylim(0.0,4.0)
colors = plt.cm.gist_ncar(np.linspace(0,1,max(Vs)+3))
for idx,Val in enumerate(Vs):
axs.plot(Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2],'s',label=r"$Y={}$".format(Val), ms=3, color=colors[idx])
axs.plot(Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2]*Val/0.3,'-', label=r"$Y={}$".format(Val), ms=3, color=colors[idx])
axs.set_ylim(0.0,4.0)
axs.set_ylabel(r"$Y$ ", labelpad=2)
axs.set_xlabel(r"$X$ ", labelpad=2)
axs.set_yticks([0,0.5,1.0,1.5,2.0, 2.5, 3.0, 3.5, 4.0])
axs.set_xticks([0,0.5,1.0])
h, l = axs.get_legend_handles_labels()
axs.legend(handles=zip(h[::2], h[1::2]), labels=l[::2],
handler_map = {tuple: matplotlib.legend_handler.HandlerTuple(None)})
plt.show()
答案 1 :(得分:0)
我会创建自定义行以显示在您的图例中。您可以通过保存每个绘图命令的输出来进行处理(折线返回一个matplotlib.lines.Line2D
对象,该对象存储线条样式,标记样式,颜色等)。然后,您可以遍历已保存的行并创建新的Line2D
对象,这些对象将具有相同颜色的两行属性组合在一起。将这些新的Line2D
对象保存在列表中,例如handles
,然后可以将该列表传递给ax.legend()
调用:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
Vs = np.array([0.5, 1, 1.5, 2])
Xs = np.array([[ 0.5, 0.2, 0.7],
[ 0.5, 0.3, 0.9],
[ 0.5, 0.5, 0.4],
[ 0.5, 0.7, 0.4],
[ 0.5, 0.9, 0.7],
[ 1, 0.15, 0.9],
[ 1, 0.35, 0.6],
[ 1, 0.45, 0.6],
[ 1, 0.67, 0.5],
[ 1, 0.85, 0.9],
[ 1.5, 0.1, 0.9],
[ 1.5, 0.3, 0.7],
[ 1.5, 0.76, 0.3],
[ 1.5, 0.98, 0.4],
[ 2, 0.21, 0.5],
[ 2, 0.46, 0.4],
[ 2, 0.66, 0.3],
[ 2, 0.76, 0.5],
[ 2, 0.88, 0.4],
[ 2, 0.99, 0.4]])
f, axs = plt.subplots(1, 1, figsize=(2.5,3))
#-------------------------------------
axs.set_xlim(0.38,1.0)
axs.set_ylim(0.0,4.0)
colors = plt.cm.gist_ncar(np.linspace(0,1,max(Vs)+3))
##saving the Line2D objects:
lines = []
points = []
for idx,Val in enumerate(Vs):
point, = axs.plot(
Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2],'s',
label=r"$Y={}$".format(Val), ms=3, color=colors[idx]
)
line, = axs.plot(
Xs[Xs[:,0] == Val ,1], Xs[Xs[:,0] == Val ,2]*Val/0.3,'-',
label=r"$Y={}$".format(Val), ms=3, color=colors[idx]
)
points.append(point)
lines.append(line)
axs.set_ylim(0.0,4.0)
axs.set_ylabel(r"$Y$ ", labelpad=2)
axs.set_xlabel(r"$X$ ", labelpad=2)
axs.set_yticks([0,0.5,1.0,1.5,2.0, 2.5, 3.0, 3.5, 4.0])
axs.set_xticks([0,0.5,1.0])
#axs.legend(fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,handletextpad=0.2, frameon=False)
#f.savefig("tmp.pdf")
##generating the legend handles, with linestyle, markerstyle, color, and label
##copied from the plotted lines:
handles = [
Line2D(
[],[], marker=point.get_marker(), linestyle=line.get_linestyle(),
color = line.get_color(),
label = line.get_label(),
) for line, point in zip(lines,points)
]
##passing handles as argument to the `legend()` call:
axs.legend(
handles=handles,
fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,
handletextpad=0.2, frameon=False,
)
plt.show()
生成的图片如下:
编辑:
按照问题中链接的示例,可以设计一个生成所需图例句柄的处理程序对象。用以下代码替换上面代码的最后一部分:
##a dedicated class that holds the lines to be included in the legend entry
class LineContainer:
def __init__(self, *args):
args = [line for line in args if isinstance(line,Line2D)]
if len(args) < 0:
raise ValueError('At least one line must be passed')
self._lines = list(args)
def get_lines(self):
return self._lines
def get_label(self):
##assuming here that all lines have the same label
return self._lines[0].get_label()
##adapted from https://stackoverflow.com/a/31530393/2454357
class data_handler(object):
def legend_artist(self, legend, orig_handle, fontsize, handlebox):
scale = fontsize / 22
x0, y0 = handlebox.xdescent, handlebox.ydescent
width, height = handlebox.width, handlebox.height
##use these two lines to control the lengths of the individual line
##segments and the spacing between them:
##width for individual artists
l = 0.7*width/len(orig_handle.get_lines())
##distance between individual artists
l0 = 0.3*width/len(orig_handle.get_lines())
result = []
for i, line in enumerate(orig_handle.get_lines()):
new_line = Line2D([],[])
new_line.update_from(line)
##if no linestyle is defined, plot only the marker:
if new_line.get_linestyle() in ['None', None]:
new_line.set_data(
[x0+l*(i+0.5)], [y0+height/2]
)
##else plot markers and lines:
else:
new_line.set_data(
[x0+l*i+l0/2, x0+l*(i+1)-l0/2],
[y0+height/2, y0+height/2]
)
new_line.set_transform(handlebox.get_transform())
handlebox.add_artist(new_line)
result.append(new_line)
return result
##generating the handles
handles = [
LineContainer(line, point) for line, point in zip(lines, points)
]
axs.legend(
handles = handles,
handler_map={LineContainer: data_handler()},
fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,
handletextpad=0.2, frameon=False,
)
plt.show()
给出以下图像: