Search code examples
pythonmatplotliblegend

How to make a legend with line and markers with different fillstyles?


I am unsure of how to reproduce the following lines/marker combination in matplotlib:

Figure with lines of best fits and markers on them. Some markers are not filled. The legend has both the line and the marker

Note that the legend has both the line and the marker correctly. The best I could find in the matplotlib documentation is the following example:

import matplotlib.pyplot as plt
import matplotlib.lines as mlines

fig, ax = plt.subplots()
blue_line = mlines.Line2D([], [], color='blue', marker="*",
                          markersize=15, label='Blue stars')
ax.legend(handles=[blue_line])
plt.show() 

But I could not produce other kinds of unfilled markers. Alternatively, I know how to make unfilled markers on a graph and have it display in the legend, but not combined with the line. Is there a way to achieve the above result (which should be extremely common, for example, line of best fit and the data used to generate the line of best fit?)


Solution

  • Here is a recreation of that graph, with the various marker and line styles indicated, all taken more or less directly from matplotlib's documentation.

    graph with matching marker and line styles

    And the code to generate it:

    import matplotlib.pyplot as plt
    
    def _main():
        data = [
            [x / 3 for x in range(20)],
            [x ** 2 / 50 for x in range(20)],
            [x ** 3 / 1000 for x in range(20)],
            [x ** 0.5 / 0.5 for x in range(20)],
            [x ** 0.25 / 0.25 for x in range(20)],
            [round(x / 3) for x in range(20)],
            [(x - 10) ** 2 / 12 for x in range(20)],
            [8 - (x - 10) ** 2 / 12 for x in range(20)],
        ]
    
        plot_type = [
            {'marker': 'o', 'linestyle': 'solid', 'markerfacecolor': 'none'},
            {'marker': 'o', 'linestyle': 'dotted', 'markerfacecolor': 'none'},
            {'marker': 'o', 'linestyle': 'dashed', 'markerfacecolor': 'none'},
            {'marker': 'o', 'linestyle': 'dashdot', 'markerfacecolor': 'none'},
            {'marker': '^', 'linestyle': 'solid'},
            {'marker': '^', 'linestyle': 'dotted'},
            {'marker': '^', 'linestyle': 'dashed'},
            {'marker': 6, 'linestyle': 'dashdot'},
        ]
    
        fig, ax = plt.subplots()
        for datax, plot_typex in zip(data, plot_type):
            plot_typex['label'] = str(plot_typex['marker']) + '   ' + str(plot_typex['linestyle'])
            ax.plot(datax, **plot_typex)
        ax.legend()
        plt.show()
    
    
    if __name__ == '__main__':
        _main()
    
    

    Here is another answer that showed me the facecolors trick: How to do a scatter plot with empty circles in Python?

    Here are the relevant matplotlib docs:

    Let me know if you have any questions.