简体   繁体   English

在图例中使用线和点标记共享两个图的相同标签

[英]Sharing the same label for two plots with line and point markers in legend

I have a plot with two different series of curves which I am going to plot them using points and lines. 我有一个包含两个不同系列曲线的图,我将使用点和线来绘制它们。 I would like to have a legend such that the line and point markers share the same label. 我想要一个图例,使线和点标记共享同一标签。

I have tried this suggestion which works well if my both series of plots have different point types, instead of line and points. 我已经尝试过这个建议,如果我的两个系列绘图都具有不同的点类型,而不是线和点,则建议会很好。 The code that I am currently using, with improper legend, is 我当前使用的代码带有不正确的图例,是

import numpy as np
import matplotlib.pyplot as plt

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

axs.legend(fontsize=6, loc=2, numpoints = 1, labelspacing=0.2,handletextpad=0.2, frameon=False)

f.savefig("tmp.pdf")
plt.show()

Do you any suggestions to resolve this issue? 您是否有解决此问题的建议?

Applying my answer to How to create two legend objects for a single plot instance? 将我的答案应用于如何为单个绘图实例创建两个图例对象? in this case: 在这种情况下:

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

在此处输入图片说明

I would go with creating custom lines to be shown in your legend. 我会创建自定义行以显示在您的图例中。 You can go about it by saving the output of each plot command (a line plot returns a matplotlib.lines.Line2D object which stores the line style, marker style, color, etc). 您可以通过保存每个plot命令的输出来进行处理(折线返回一个matplotlib.lines.Line2D对象,该对象存储线条样式,标记样式,颜色等)。 You can then loop over the saved lines and create new Line2D objects that combine the properties of the two lines with the same color. 然后,您可以遍历已保存的线并创建新的Line2D对象,该对象将具有相同颜色的两条线的属性组合在一起。 Saving these new Line2D objects in a list, say handles , you can then pass that list to the ax.legend() call: 将这些新的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()

The resulting picture looks like this: 结果图片如下:

以上代码的结果

EDIT : 编辑

Following the example that is linked in the question, one can design a handler object that generates the wanted legend handles. 按照问题中链接的示例,可以设计一个处理程序对象,该对象生成所需的图例句柄。 Replacing the last part of the above code with the following: 将以下代码的最后一部分替换为:

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

gives the following image: 给出以下图像:

新传奇

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM