I have to plot several data sets in one plot. It is useful to be able to highlight one or more of the plots in order to compare them. For this, I toggle the line style of th
My diving into the legend artist has found that a legend line is in the children tree of a legend twice when the legend has the bbox_to_anchor set.
I asked about this here with my solution where I watched for a NEW mouseevent and kept track of the artists that had already been handled by my callback.
I've asked for comments if anyone thinks there's a more elegant way to handle this "feature"
I'm not sure this is a bug. But, it seems unique to legends where the children lines are held in the .lines attribute and deep in the packing boxes data structure - the get_children method finds both of these. Luckily they are the same object rather than a copy so I could check for a line already been handled.
Another approach similar to tcaswell's solution counting events. Only 3 lines added to the original legend picking example. It uses python's function attributes.
def onpick(event):
if onpick.count % 2 == 0: #### LINE ADDED ####
# on the pick event, find the orig line corresponding to the
# legend proxy line, and toggle the visibility
legline = event.artist
origline = lined[legline]
vis = not origline.get_visible()
origline.set_visible(vis)
# Change the alpha on the line in the legend so we can see what lines
# have been toggled
if vis:
legline.set_alpha(1.0)
else:
legline.set_alpha(0.2)
fig.canvas.draw()
onpick.count += 1 #### LINE ADDED ####
onpick.count = 0 #### LINE ADDED ####
If you monkey patch Artist.pick with the following:
matplotlib.artist.Artist.orig_pick = matplotlib.artist.Artist.pick
def nu_pick(self, me):
print self
matplotlib.artist.Artist.orig_pick(self, me)
matplotlib.artist.Artist.pick = nu_pick
You can look at how the artists recurse on a pick event. (Each Artist object calls pick on it's self and then on all of it's children). For reasons I don't understand, there are two copies of each line in the drawing area of the legend (and it behaves differently when it is inside and outside).
A way-hacky solution is to just count how many times the leglines have been hit, and only toggle on the odd ones:
import pylab
import numpy
# Create data for plotting
t = numpy.linspace(0, 1.0, 100)
a = numpy.sin(2*numpy.pi*t)
# Set up figure
fig = pylab.figure()
ax = pylab.subplot(111)
# Plot figures
lines = []
for i in range(5):
line = ax.plot(t, (i+1)*a, linestyle=':', picker=5, label='line%d'%(i+1))
lines.append(line[0]) # Save plot lines
# Create legend
leg = ax.legend(bbox_to_anchor=(1.01, 1), loc=2) # Does not work as expected
#leg = ax.legend() # Works!!
# Get legend lines
leglines = leg.get_lines()
# Set event for legend lines
for line in leglines:
line.set_picker(5)
# Create a 2 way mapping between legend lines <-> plot lines
line2leg = dict(zip(lines+leglines, leglines+lines))
count_dict = dict((l, 0) for l in lines )
# Define event function
def onpick(event):
thisline = event.artist
print event
print thisline
if thisline in lines:
print 'lines'
count_dict[thisline] = 0
elif thisline in leglines:
print 'leglines'
thisline = line2leg[thisline]
count_dict[thisline] += 1
print 'added'
if (count_dict[thisline] % 2) == 1:
print count_dict[thisline]
return
print 'tested'
if thisline.get_linestyle()==':':
print ": -> -" # For debugging
thisline.set_linestyle('-')
line2leg[thisline].set_linestyle('-')
else:
print "- -> :" # For debugging
thisline.set_linestyle(':')
line2leg[thisline].set_linestyle(':')
fig.canvas.draw()
# connect event function
fig.canvas.mpl_connect('pick_event', onpick)
pylab.show()
(I left all my de-bugging statements in).
Pretty sure this is a bug, if you don't want to create an issue on github I will.