Search code examples
pythonmatplotlibplotlegend

Two line styles in legend


I have a plot with two line styles (solid and dashed). I would like them to be used for the same legend entry. The code below produces the typical legend, with two entries.

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

What I would like is something similar to this:

I have tried playing with proxy artists, but can't seem to get two lines, offset from each other to appear for one entry.


Solution

  • I made a custom legend handler based off of the HandlerLineCollection class. It figures out how many lines there are in the collection and spreads them out vertically.

    Example image:Image with multi-line legend

    Here's the handler:

    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
    

    And here's an example of how to use it:

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