图例中的两种线条样式

5

我有一个包含两种线型(实线和虚线)的图表。我希望它们出现在同一个图例条目中。下面的代码会生成传统的图例,包含两个条目。

import matplotlib.pyplot as plt
import numpy as np

xy = np.linspace(0,10,10)

plt.figure()
plt.plot(xy,xy, c='k', label='solid')
plt.plot(xy,xy+1, c='k', ls='dashed', label='dashed')
plt.plot(xy,xy-1, c='k', ls='dashed')
plt.legend()

plt.show()

我想要的类似于这个的东西: 我尝试过使用代理艺术家,但是似乎无法让两条相互偏移的线条出现在一个条目中。

1
请参考以下链接:https://dev59.com/0Hzaa4cB1Zd3GeqPNTkb#21630591 - tacaswell
这是一个关于编程的问题,链接指向Stack Overflow网站上的一个帖子。帖子讨论了如何为一个图例使用两个独特的标记符号。你可以点击链接查看详细内容,可能会对你有所帮助。 - tacaswell
@tcaswell 感谢您的建议。我使用这些建议来回答另一个我之前提出的问题:https://dev59.com/Z4jca4cB1Zd3GeqPtjp0。我只是无法通过偏移线条来得到任何有效解决方案。 - Blink
2个回答

14
我根据HandlerLineCollection类创建了一个自定义图例处理程序。它会计算出集合中有多少条线,并将它们垂直地分散开来。
示例图片:Image with multi-line legend 这是处理程序的代码:
from matplotlib.legend_handler import HandlerLineCollection
from matplotlib.collections import LineCollection
from matplotlib.lines import Line2D


class HandlerDashedLines(HandlerLineCollection):
"""
Custom Handler for LineCollection instances.
"""
def create_artists(self, legend, orig_handle,
                   xdescent, ydescent, width, height, fontsize, trans):
    # figure out how many lines there are
    numlines = len(orig_handle.get_segments())
    xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
                                         width, height, fontsize)
    leglines = []
    # divide the vertical space where the lines will go
    # into equal parts based on the number of lines
    ydata = ((height) / (numlines + 1)) * np.ones(xdata.shape, float)
    # for each line, create the line at the proper location
    # and set the dash pattern
    for i in range(numlines):
        legline = Line2D(xdata, ydata * (numlines - i) - ydescent)
        self.update_prop(legline, orig_handle, legend)
        # set color, dash pattern, and linewidth to that
        # of the lines in linecollection
        try:
            color = orig_handle.get_colors()[i]
        except IndexError:
            color = orig_handle.get_colors()[0]
        try:
            dashes = orig_handle.get_dashes()[i]
        except IndexError:
            dashes = orig_handle.get_dashes()[0]
        try:
            lw = orig_handle.get_linewidths()[i]
        except IndexError:
            lw = orig_handle.get_linewidths()[0]
        if dashes[0] != None:
            legline.set_dashes(dashes[1])
        legline.set_color(color)
        legline.set_transform(trans)
        legline.set_linewidth(lw)
        leglines.append(legline)
    return leglines

以下是使用它的示例:

#make proxy artists
#make list of one line -- doesn't matter what the coordinates are
line = [[(0, 0)]]
#set up the line collections
lc = LineCollection(2 * line, linestyles = ['solid', 'dashed'], colors = ['black', 'black'])
lc2 = LineCollection(2 * line, linestyles = ['solid', 'dashed'], colors = ['blue', 'blue'])
lc3 = LineCollection(3 * line, linestyles = ['solid', 'dashed', 'solid'], colors = ['blue', 'red', 'green'])
#create the legend
plt.legend([lc, lc2, lc3], ['text', 'more text', 'extra line'], handler_map = {type(lc) : HandlerDashedLines()}, handlelength = 2.5)

哇,谢谢Amy。我希望你不是只为了我写这个;看起来好像很费力。它确实非常有效,正是我所需要的。 - Blink
哦,没关系。如果我不觉得这很有趣,我就不会做了,而且我大部分是复制和修改了一个现有的图例处理程序。@tcaswell,我很乐意将其添加到示例中。我会查看开发人员指南,看看我能否弄清楚如何做到这一点。 :) - Amy Teegarden
@Blink,我稍微更新了一下代码。之前我没有正确计算行高,如果手柄高度发生变化,可能会出现问题。我已经修复了这个问题。 - Amy Teegarden
我无法运行这段代码,因为我收到了多个错误。有人能告诉我这是哪个Python版本吗? - MrT77
class HandlerDashedLines(HandlerLineCollection): 这一行开始缺少缩进。 - Emilio

8

根据@Amys的答案,我构建了另一个实现,它将HandlerTuple垂直堆叠,因此您不需要添加代理艺术家。

import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple

class HandlerTupleVertical(HandlerTuple):
    def __init__(self, **kwargs):
        HandlerTuple.__init__(self, **kwargs)

    def create_artists(self, legend, orig_handle,
                       xdescent, ydescent, width, height, fontsize, trans):
        # How many lines are there.
        numlines = len(orig_handle)
        handler_map = legend.get_legend_handler_map()

        # divide the vertical space where the lines will go
        # into equal parts based on the number of lines
        height_y = (height / numlines)

        leglines = []
        for i, handle in enumerate(orig_handle):
            handler = legend.get_legend_handler(handler_map, handle)

            legline = handler.create_artists(legend, handle,
                                             xdescent,
                                             (2*i + 1)*height_y,
                                             width,
                                             2*height,
                                             fontsize, trans)
            leglines.extend(legline)

        return leglines

然后它可以被使用,使用

line1 = plt.plot(xy,xy, c='k', label='solid')
line2 = plt.plot(xy,xy+1, c='k', ls='dashed', label='dashed')
line3 = plt.plot(xy,xy-1, c='k', ls='dashed', label='dashed')

plt.legend([(line1, line2), line3], ['text', 'more text', 'even more'],
           handler_map = {tuple : HandlerTupleVertical()})

简单并且有效。谢谢!(请注意for循环中的缩进少了一个空格) - m7thon
感谢您的修复。 :) - gyger
当我逐个绘制每条线时,是否可以实现这一点?所有这些答案似乎都依赖于紧密绘制线条。在我的应用程序中,plot 在一个 for 循环中... - kilojoules
1
我总是遇到一个错误:异常已发生:属性错误'NoneType'对象没有属性'create_artists',出错行为"legline = handler.create_artist..."。有人可以帮忙吗? - MrT77
它无法工作的原因是plt.plot返回了一系列的内容,所以应该使用类似于line1, = plt.plot(...)这样的东西。 - lericson
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接