When legend is outside the axes, pick events get handled twice #1962

Closed
dharadave opened this Issue Apr 30, 2013 · 3 comments

Projects

None yet

3 participants

@dharadave

Pick events on legends work perfectly unless the legend is moved out of the axes using bbox_to_anchor. After that, the pick event gets called twice for a single mouse click.

The following code demonstrates the error. The bug appears when the legend line is clicked: the line toggles twice on a mouse click instead of once.

''' 
Toggle plot line-styles between ':' and '-' when line is clicked or 
when the corresponding legend line is clicked.
'''
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))

# Define event function
def onpick(event):
    thisline = event.artist

    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()

(See also: http://stackoverflow.com/questions/16278358/double-event-registered-on-mouse-click-if-legend-is-outside-axes)

@cimarronm
Contributor

PR #2755 partially fixes this issue.

The main issue is that the picker gets called twice as the legend.get_children() method returns more than just children. Note that this happens regardless of whether the legend is placed in the axes or outside with the bbox_to_anchor. Why doesn't the picker get called twice when inside the axis? Due to the following code

        # Pick children
        for a in self.get_children():
            # make sure the event happened in the same axes
            ax = getattr(a, 'axes', None)
            if mouseevent.inaxes is None or mouseevent.inaxes == ax:
                # we need to check if mouseevent.inaxes is None
                # because some objects associated with an axes (e.g., a
                # tick label) can be outside the bounding box of the
                # axes and inaxes will be None
                a.pick(mouseevent)

The offsetbox that legend use as a child does not contain an axes property (perhaps incorrectly so) which means mouseevent.inaxes == ax is not satisfied on the actual legend offsetbox itself resulting in only one call. When a user clicks outside of the axes when the legend is bound outside, mouseevent.inaxes is None holds true so mouseevent.inaxes does not even get evaluated and both get called.

Bottom line, PR #2755 fixes the issue with incorrect children traversal tree and fixes the problem you see here while clicking on the legend outside of the axes but exposes an issue or bug with the picking logic while within the axes.

@cimarronm
Contributor

With both PR #2755 and PR #2756 applied I believe this issue is resolved.

@tacaswell
Member

This should be fixed, please re-open if that is not true.

@tacaswell tacaswell closed this Feb 1, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment