Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two unique marker symbols for one legend

I would like to add a "red filled square" symbol beside the "red filled circle" symbol under legend. How do I achieve this? I prefer to stick with pyplot rather than pylab.

Below is the code I've been using:

fig = plt.figure()
ax1 = fig.add_axes([0.1,0.29,0.86,0.68])
plt.ylabel('Radial Velocity (km s$^{-1}$)')
plt.plot(time_model, rv_model_primary, 'k-', label = 'Primary')
plt.plot(time_model_sec, rv_model_secondary, 'k--', label = 'Secondary')
plt.plot(time_obs, rv_obs_primary, 'bo', label='XYZ')

plt.plot(time_obs_apg, rv_obs_primary_apg, 'ro', label='This Work')
plt.plot(time_obs_apg_sec, rv_obs_secondary_apg, 'rs')
plt.plot((0.0, 1.0),(0.0,0.0), 'k-.')
plt.legend(loc='upper left', numpoints=1) 

Here's what I tried:

p1=plt.plot(time_model, rv_model_primary, 'k-')
p2=plt.plot(time_model_sec, rv_model_secondary, 'k--')
p3=plt.plot(time_obs, rv_obs_primary, 'bo')
p4=plt.plot(time_obs_apg, rv_obs_primary_apg, 'ro')
p5=plt.plot(time_obs_apg_sec, rv_obs_secondary_apg, 'rs')

plt.legend([p1,p2,p3,(p4,p5)],["Primary", "Secondary", "XYZ", "This Work"])

enter image description here

After making changes to the code using tcaswell's suggestions I get the following. The looks good but I would like to have just one symbol for blue while keeping the two for red. Currently there are two.

enter image description here

The final solution by adding numpoints=1 to the general legend() worked. Here's how I wanted it. Thanks tcaswell!

enter image description here

like image 439
Rohit Avatar asked Aug 02 '13 00:08

Rohit


People also ask

What is a legend marker?

Legend Markers can be displayed within the legend to help end-users visually identify different chart elements such as the series, constant lines and strips indicated by the legend. The content of each legend marker depends upon the type of the corresponding chart element.

How do I mark a legend in Matplotlib?

In the matplotlib library, there's a function called legend() which is used to Place a legend on the axes. The attribute Loc in legend() is used to specify the location of the legend. Default value of loc is loc=”best” (upper left).


1 Answers

solve exception issue

Suspect you need to do:

p1, = plt.plot(time_model, rv_model_primary, 'k-')
p2, = plt.plot(time_model_sec, rv_model_secondary, 'k--')
p3, = plt.plot(time_obs, rv_obs_primary, 'bo')
p4, = plt.plot(time_obs_apg, rv_obs_primary_apg, 'ro')
p5, = plt.plot(time_obs_apg_sec, rv_obs_secondary_apg, 'rs')

plot returns a list of Line2D objects (the extra , unpacks it) and I think that the expected types in are getting mucked up. This fixes your exceptions issue, but does not actually solve your problem.

hacky solution

A some what hacky way to solve this is:

plt.legend([p1,p2,p3,(p5,p4)],["Primary", "Secondary", "XYZ", "This Work"],
           handler_map={p4:HandlerLine2D(numpoints=2), p5:HandlerLine2D(numpoints=1)})

which gives you three points, two of one and one of the other.

cleaner solution

from matplotlib.legend_handler import HandlerLine2D

class HandlerXoffset(HandlerLine2D):
    def __init__(self, marker_pad=0.3, numpoints=1, x_offset=0,  **kw):
        HandlerLine2D.__init__(self, marker_pad=marker_pad, numpoints=numpoints, **kw)
        self._xoffset = x_offset
    def get_xdata(self, legend, xdescent, ydescent, width, height, fontsize):
        numpoints = self.get_numpoints(legend)

        if numpoints > 1:
            # we put some pad here to compensate the size of the
            # marker
            xdata = np.linspace(-xdescent + self._marker_pad * fontsize,
                                width - self._marker_pad * fontsize,
                                numpoints) - self._xoffset
            xdata_marker = xdata
        elif numpoints == 1:
            xdata = np.linspace(-xdescent, width, 2) - self._xoffset
            xdata_marker = [0.5 * width - 0.5 * xdescent - self._xoffset]

        print xdata, self._xoffset
        print xdata_marker

        return xdata, xdata_marker

time_model = time_model_sec = time_obs = time_obs_apg = time_obs_apg_sec = range(5)

rv_model_primary = np.random.rand(5)
rv_model_secondary = np.random.rand(5)
rv_obs_primary = np.random.rand(5)
rv_obs_primary_apg =  np.random.rand(5)
rv_obs_secondary_apg =  np.random.rand(5)

p1,=plt.plot(time_model, rv_model_primary, 'k-')
p2,=plt.plot(time_model_sec, rv_model_secondary, 'k--')
p3,=plt.plot(time_obs, rv_obs_primary, 'bo')
p4,=plt.plot(time_obs_apg, rv_obs_primary_apg, 'ro')
p5,=plt.plot(time_obs_apg_sec, rv_obs_secondary_apg, 'rs')

plt.legend([p1,p2,p3,(p5,p4)], 
           ["Primary", "Secondary", "XYZ", "This Work"],
            handler_map={p4:HandlerXoffset(x_offset=10),   
                         p5:HandlerXoffset(x_offset=-10)})

gist

You will probably have to play with x_offset a bit to make it look right, and there is probably a better way to automatically figure out what it's value should be, but this should be enough to get you started.

enter image description here

like image 70
tacaswell Avatar answered Sep 21 '22 03:09

tacaswell