Combine two Pyplot patches for legend

Question:

I am trying to plot some data with confidence bands. I am doing this with two plots for each data stream: plot, and fill_between. I would like the legend to look similar to the plots, where each entry has a box (the color of the confidence region) with a darker, solid line passing through the center. So far I have been able to use patches to create the rectangle legend key, but I don’t know how to achieve the centerline. I tried using hatch, but there is no control over the placement, thickness, or color.

My original idea was to try and combine two patches (Patch and 2DLine); however, it hasn’t worked yet. Is there a better approach? My MWE and current figure are shown below.

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,1,11)
y = np.linspace(0,1,11)

plt.plot(x, y, c='r')
plt.fill_between(x, y-0.2, y+0.2, color='r', alpha=0.5)
p = mpatches.Patch(color='r', alpha=0.5, linewidth=0)

plt.legend((p,), ('Entry',))

Figure

Asked By: Blink

||

Answers:

starting with your code,

this is the closest i can get you. there may be a way to create the patch in a way you want but i am a bit new to this as well and all i can do is build the proper legend:

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,1,11)
y = np.linspace(0,1,11)

fig = plt.figure()
ax = fig.add_subplot(111)
plt.plot(x, y, c='r',label='Entry')
plt.fill_between(x, y-0.2, y+0.2, color='r', alpha=0.5)
p_handle = [mpatches.Patch(color='r', alpha=0.5, linewidth=0)]
p_label = [u'Entry Confidence Interval']
handle, label = ax.get_legend_handles_labels()
handles=handle+p_handle
labels=label+p_label
plt.legend(handles,labels,bbox_to_anchor=(0. ,1.02 ,1.,0.3),loc=8,
           ncol=5,mode='expand',borderaxespad=0,prop={'size':9},numpoints=1)

plt.show()

enter image description here

From what i can tell you would have to create ans “artist” object that fits the design you are looking for which i cannot find a method of doing. some examples of something similar can be found in this thread:
Custom Legend Thread

Hope that helps good luck, I am interested if there are more in depth ways as well.

Answered By: Robert McMurry

The solution is borrowed from the comment by CrazyArm, found here: Matplotlib, legend with multiple different markers with one label. Apparently you can make a list of handles and assign only one label and it magically combines the two handles/artists.

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0,1,11)
y = np.linspace(0,1,11)

p1, = plt.plot(x, y, c='r')  # notice the comma!
plt.fill_between(x, y-0.2, y+0.2, color='r', alpha=0.5)
p2 = mpatches.Patch(color='r', alpha=0.5, linewidth=0)

plt.legend(((p1,p2),), ('Entry',))

Figure

Answered By: Blink

I am having ‘similar’ issues. I was able to achieve the following thanks to this question.

fig = pylab.figure()
figlegend = pylab.figure(figsize=(3,2))
ax = fig.add_subplot(111)
point1 = ax.scatter(range(3), range(1,4), 250, marker=ur'$u2640$', label = 'S', edgecolor = 'green')
point2 = ax.scatter(range(3), range(2,5), 250, marker=ur'$u2640$', label = 'I', edgecolor = 'red')
point3 = ax.scatter(range(1,4), range(3),  250, marker=ur'$u2642$', label = 'S', edgecolor = 'green')
point4 = ax.scatter(range(2,5), range(3), 250, marker=ur'$u2642$', label = 'I', edgecolor = 'red')
figlegend.legend(((point1, point3), (point2, point4)), ('S','I'), 'center',  scatterpoints = 1, handlelength = 1)
figlegend.show()
pylab.show()

However, my two (venus and mars) markers overlap in the legend. I tried playing with handlelength, but that doesn’t seem to help. Any suggestions or comments would be helpful.

Answered By: Mischief_Monkey

For those who want to push this topic further

I had a very similar problem, difference being that I wanted the legend to represent a custom shape (also created with plot and fill_between) by a rectangle with dashed line and filled (since the shape had this pattern). The example is shown in the following image:

example

The solution I found is to implement a custom legend handler following the Matplotlib Legend guide.

In particular, I implemented a class that defines a legend_artist method that allows you to customize the handler as much as you want by adding patches to a handlebox (or that is what I understood).

I post a MWE with a rectangle instead of the blob shape (that takes a lot of space and will blurry the information).

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt


class DashedFilledRectangleHandler:
    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
        x0, y0 = handlebox.xdescent, handlebox.ydescent
        width, height = handlebox.width, handlebox.height
        patch_fill = mpatches.Rectangle((x0, y0), width, height, facecolor='tab:blue', linestyle='', alpha=0.1,
                                        transform=handlebox.get_transform())
        patch_line = mpatches.Rectangle((x0, y0), width, height, fill=False, edgecolor='tab:blue', linestyle='--',
                                        transform=handlebox.get_transform())
        handlebox.add_artist(patch_line)
        handlebox.add_artist(patch_fill)
        return patch_line


fig, ax = plt.subplots()

rectangle_fill = ax.add_patch(mpatches.Rectangle((0, 0), 2, 1, facecolor='tab:blue', edgecolor='tab:blue', linestyle='',
                                                 alpha=0.1))
rectangle_line = ax.add_patch(mpatches.Rectangle((0, 0), 2, 1, fill=False, edgecolor='tab:blue', linestyle='--',
                                                 lw=3))

ax.legend([rectangle_fill], ['Dashed filled rectangle handler'],
          handler_map={rectangle_fill: DashedFilledRectangleHandler()})
ax.set_xlim([-1, 3])
ax.set_ylim([-1, 2])
plt.show()
Answered By: Manza