How to make two markers share the same label in the legend using matplotlib?

前端 未结 5 1321
抹茶落季
抹茶落季 2020-12-28 18:41

What I want is like this: \"enter

What I get is this:

5条回答
  •  清酒与你
    2020-12-28 19:05

    Here is a new solution that will plot any collection of markers with the same label. I have not figured out how to make it work with markers from a line plot, but you can probably do a scatter plot on top of a line plot if you need to.

    from matplotlib import pyplot as plt
    import matplotlib.collections as mcol
    import matplotlib.transforms as mtransforms
    import numpy as np
    from matplotlib.legend_handler import HandlerPathCollection
    from matplotlib import cm
    
    
    class HandlerMultiPathCollection(HandlerPathCollection):
        """
        Handler for PathCollections, which are used by scatter
        """
        def create_collection(self, orig_handle, sizes, offsets, transOffset):
            p = type(orig_handle)(orig_handle.get_paths(), sizes=sizes,
                                  offsets=offsets,
                                  transOffset=transOffset,
                                  )
            return p
    
    fig, ax = plt.subplots()
    #make some data to plot
    x = np.arange(0, 100, 10)
    models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x]
    tests = [model + np.random.rand(len(model)) - .5 for model in models]
    #make colors and markers
    colors = cm.brg(np.linspace(0, 1, len(models)))
    markers = ['o', 'D', '*', 's']
    markersize = 50
    plots = []
    #plot points and lines
    for i in xrange(len(models)):
        line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model')
        plot = plt.scatter(x, tests[i], c = colors[i], s = markersize, marker = markers[i])
        plots.append(plot)
    
    #get attributes
    paths = []
    sizes = []
    facecolors = []
    edgecolors = []
    for plot in plots:
        paths.append(plot.get_paths()[0])
        sizes.append(plot.get_sizes()[0])
        edgecolors.append(plot.get_edgecolors()[0])
        facecolors.append(plot.get_facecolors()[0])
    
    #make proxy artist out of a collection of markers
    PC = mcol.PathCollection(paths, sizes, transOffset = ax.transData, facecolors = colors, edgecolors = edgecolors)
    PC.set_transform(mtransforms.IdentityTransform())
    plt.legend([PC, line], ['Test', 'Model'], handler_map = {type(PC) : HandlerMultiPathCollection()}, scatterpoints = len(paths), scatteryoffsets = [.5], handlelength = len(paths))
    plt.show()
    

    I have a solution for you if you're willing to use all circles for markers and differentiate by color only. You can use a circle collection to represent the markers, and then have a legend label for the collection as a whole.

    Example code:

    import matplotlib.pyplot as plt
    import matplotlib.collections as collections
    from matplotlib import cm
    import numpy as np
    
    #make some data to plot
    x = np.arange(0, 100, 10)
    models = [.05 * x, 8 * np.exp(- .1 * x), np.log(x + 1), .01 * x]
    tests = [model + np.random.rand(len(model)) - .5 for model in models]
    #make colors
    colors = cm.brg(np.linspace(0, 1, len(models)))
    markersize = 50
    #plot points and lines
    for i in xrange(len(models)):
        line, = plt.plot(x, models[i], linestyle = 'dashed', color = 'black', label = 'Model')
        plt.scatter(x, tests[i], c = colors[i], s = markersize)
    #create collection of circles corresponding to markers
    circles = collections.CircleCollection([markersize] * len(models), facecolor = colors)
    #make the legend -- scatterpoints needs to be the same as the number 
    #of markers so that all the markers show up in the legend
    plt.legend([circles, line], ['Test', 'Model'], scatterpoints = len(models), scatteryoffsets = [.5], handlelength = len(models))
    plt.show()
    

    Scatter and Line plot with merged legend labels

提交回复
热议问题