Combine key and mouse button events in wxpython panel using matplotlib
Question: In a `wxPython` panel I want to use `matplotlib's`
[Lasso](http://matplotlib.org/api/widgets_api.html?highlight=lasso#matplotlib.widgets.Lasso)
widget. In my implementation `Lasso` is used in three different
functionalities. Nevertheless, in order to accomplish these functionalities I
must combine key events with mouse button events.

By default, the initial `Lasso` function can be used by pressing the left
mouse button. So, for my first functionality I press the left mouse button,
select a region of interest and do something with the points included. For the
second functionality, I would like to select another region of interest and do
something else with these points.I am trying to do that by pressing
`shift`+`LMB`. Finally, in my third functionality I would like to do something
else with the selected points by pressing `Ctrl`+`LMB`.

Schematically, I would like to do the following:

    
    
     if left mouse button is pressed:
        use Lasso and with the included points do the 1st functionality
    
     if shift is press and left mouse button is pressed:
    
       use Lasso and with the included points do the 2nd functionality
    
     if ctrl is press and lmb is also pressed :
        call Lasso and with the included points do the 3rd functionality
    

Unfortunately, I can not achieve my goal and I get the following error: `if
self.shift_is_held == True: AttributeError: object has no attribute
'shift_is_held'`. It seems that it can not recognize the button events, while
in other cases the `widgetlock` command seems not to make the axes available.

Here are parts of my code:

    
    
    def scatter(self,data):
    #create scatter plot
        fig = self._view_frame.figure
        fig.clf()
    
        Data = []
        self.lmans = []
        self.points_colors = []
    
        Data2 = []
        self.lmans_2 = []
    
        Data3 = []
        self.lmans_2 = []
    
    
        ax = fig.add_subplot('111')
    
        datum = []
        datum_2 = []
        datum_3 = []
    
        for x,y in zip(self.data[:,0],self.data[:,1]):
            datum.append(Datum(x,y))
            datum_2.append(Datum2(x,y))
            datum_3.append(Datum3(x,y))
    
    
        ax.scatter(self.data[:, 0], self.data[:, 1], s=150, marker='d')
        ax.set_xlim((min(self.data[:,0]), max(self.data[:,0])))
        ax.set_ylim((min(self.data[:,1]), max(self.data[:,1])))
        ax.set_aspect('auto')
    
        Data.append(datum)
        Data2.append(datum_2)
        Data3.append(datum_3)
        lman = self.LassoManager(ax, datum, Data)
        lman_2 = self.LassoManager2(ax, datum_2, Data2)
        lman_3 = self.LassoManager3(ax, datum_3, Data3)
        fig.canvas.draw()
        self.lmans.append(lman)
        self.lmans_2.append(lman_2)
        self.lmans_3.append(lman_3)
        fig.canvas.mpl_connect('axes_enter_event', self.enter_axes)
    
    
    class Lasso(AxesWidget):
    
    
    """Selection curve of an arbitrary shape.
    
    The selected path can be used in conjunction with
    :func:`~matplotlib.path.Path.contains_point` to select data points
    from an image.
    
    Unlike :class:`LassoSelector`, this must be initialized with a starting
    point `xy`, and the `Lasso` events are destroyed upon release.
    
    Parameters:
    
    *ax* : :class:`~matplotlib.axes.Axes`
        The parent axes for the widget.
    *xy* : array
        Coordinates of the start of the lasso.
    *callback* : function
        Whenever the lasso is released, the `callback` function is called and
        passed the vertices of the selected path.
    
    """
    
    def __init__(self, ax, xy, callback=None, useblit=True):
        AxesWidget.__init__(self, ax)
    
        self.useblit = useblit and self.canvas.supports_blit
        if self.useblit:
            self.background = self.canvas.copy_from_bbox(self.ax.bbox)
    
        x, y = xy
        self.verts = [(x, y)]
        self.line = Line2D([x], [y], linestyle='-', color='black', lw=2)
        self.ax.add_line(self.line)
        self.callback = callback
        self.connect_event('button_release_event', self.onrelease)
        self.connect_event('motion_notify_event', self.onmove)
    
    def onrelease(self, event):
        if self.ignore(event):
            return
        if self.verts is not None:
            self.verts.append((event.xdata, event.ydata))
            if len(self.verts) > 2:
                self.callback(self.verts)
            self.ax.lines.remove(self.line)
        self.verts = None
        self.disconnect_events()
    
    def onmove(self, event):
        if self.ignore(event):
            return
        if self.verts is None:
            return
        if event.inaxes != self.ax:
            return
        if event.button != 1:
            return
        self.verts.append((event.xdata, event.ydata))
    
        self.line.set_data(list(zip(*self.verts)))
    
        if self.useblit:
            self.canvas.restore_region(self.background)
            self.ax.draw_artist(self.line)
            self.canvas.blit(self.ax.bbox)
        else:
            self.canvas.draw_idle()
    
    def enter_axes(self, event):
        self.idfig = event.inaxes.colNum
        self._view_frame.figure.canvas.mpl_connect('button_press_event', self.onpress_2)
        self._view_frame.figure.canvas.mpl_connect('key_press_event', self.onkey_press_2)
        self._view_frame.figure.canvas.mpl_connect('key_release_event', self.onkey_release_2)
        self._view_frame.figure.canvas.mpl_connect('button_press_event', self.onpress_3)
        self._view_frame.figure.canvas.mpl_connect('key_press_event', self.onkey_press_3)
        self._view_frame.figure.canvas.mpl_connect('key_release_event', self.onkey_release_3)
    
    def LassoManager(self, ax, data, Data):
        self.axes = ax
        self.canvas = ax.figure.canvas
        self.data = data
        self.Data = Data
        self.Nxy = len(data)
    
        # self.facecolors = [d.color for d in data]
        self.xys = [(d.x, d.y) for d in data]
        fig = ax.figure
        #self.cid = self.canvas.mpl_connect('button_press_event', self.onpress)
    
    
    
    def callback(self, verts):
        #facecolors = self.facecolors#collection.get_facecolors()
        #colorin = colorConverter.to_rgba('red')
        #colorout = colorConverter.to_rgba('blue')
        p = path.Path(verts)
        self.ind = p.contains_points([(d.x, d.y) for d in self.Data[self.where.colNum]])
        self._view_frame.figure.canvas.mpl_connect('button_press_event', self.onpress)
    
        #Functionality 1
    
        self.canvas.draw_idle()
        self.canvas.widgetlock.release(self.lasso)
        del self.lasso
    
    def onpress(self,event):
         if self.canvas.widgetlock.locked(): return
            if event.inaxes is None: return
            self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.callback)
            self.where = event.inaxes
            # acquire a lock on the widget drawing
            self.canvas.widgetlock(self.lasso)
    
       def onkey_press_2(self,event):
        if event.key =='shift':
            self.merge_is_held = True
    
    
    def onkey_press_3(self,event):
        if event.key == 'control':
            self.split_is_held = True
    
    def onkey_release_2(self, event):
           if event.key == 'shift':
               self.merge_is_held = False
    
    
    def onkey_release_3(self, event):
       if event.key == 'control':
            self.split_is_held = False
    
    def LassoManagerMerge(self, ax, data2, Data2):
        self.axes = ax
        self.canvas = ax.figure.canvas
        self.data2 = data2
        self.Data2 = Data2
        self.Nxy = len(dataMerge)
    
        self.facecolors_2 = [d.color for d in data2]
        # print "facecolors",self.facecolors
        self.xys = [(d.x, d.y) for d in data2]
        # print "xys",self.xys
        fig = ax.figure
        self.collection_2 = RegularPolyCollection(
            fig.dpi, 6, sizes=(0,),
            facecolors=self.facecolors_2,
            offsets = self.xys,
            transOffset = ax.transData)
    
        ax.add_collection(self.collection_2)
    
    
    
    
    
    def callback_2(self, verts):
    
        self.facecolors_2 = self.collection_2.get_facecolors()
        #colorin = colorConverter.to_rgba('red')
        #colorout = colorConverter.to_rgba('blue')
        p = path.Path(verts)
        self.ind = p.contains_points([(d.x, d.y) for d in self.Data2[self.where.colNum]])   
        #Functionality 2             
    
        self.canvas.draw_idle()
        self.canvas.widgetlock.release(self.lasso_2)
    
        del self.lasso_2
    
     def onpress_2(self, event):
    
    
                if self.canvas.widgetlock.locked():  return
                if event.inaxes is None: return
                if event.button == 1:
                   if self.shift_is_held == True:
                        print "Shift pressed"
                        self.lasso_2 = Lasso(event.inaxes, (event.xdata, event.ydata), self.callback_2)
                        print "Shift pressed"
                        self.where = event.inaxes
                        self.canvas.widgetlock(self.lasso_2)
    
                  def LassoManager3(self, ax, data3, Data3):
        self.axes = ax
        self.canvas = ax.figure.canvas
        self.data3 = data3
        self.Data3 = Data3
        self.Nxy = len(data3)
    
        self.facecolors_3= [d.color for d in data3]
        # print "facecolors",self.facecolors
        self.xys = [(d.x, d.y) for d in data3]
    
        # print "xys",self.xys
        fig = ax.figure
        self.collection_3 = RegularPolyCollection(
            fig.dpi, 6, sizes=(0,),
            facecolors=self.facecolors_3,
            offsets = self.xys,
            transOffset = ax.transData)
    
        ax.add_collection(self.collection_3)
    
    
    
    def callback_3(self, verts):
        self.facecolors_3 = self.collection_3.get_facecolors()
        #colorin = colorConverter.to_rgba('red')
        #colorout = colorConverter.to_rgba('blue')
        p = path.Path(verts)
        self.ind = p.contains_points([(d.x, d.y) for d in self.Data3[self.where.colNum]])
    
        #Functionality 3
        self.canvas.draw_idle()
        self.canvas.widgetlock.release(self.lasso_3)
    
        del self.lasso_3
    
    def onpress_3(self, event):
    
                if self.canvas.widgetlock.locked(): return
                if event.inaxes is None: return
                if event.button == 1:
                    if self.split_is_held == True:
                        print " Split pressed"
                        self.lasso_3 = Lasso(event.inaxes, (event.xdata, event.ydata), self.callback_3)
                        print "Split pressed"
                        self.where = event.inaxes
                        # acquire a lock on the widget drawing
                        self.canvas.widgetlock(self.lasso_3)
    

Any suggestions?


Answer: I'm not entirely sure what you're doing wrong because your code looks
incomplete. I think your general idea was correct, but you seem to have been
mixing up classes and trying to access the property `shift_is_held` from the
wrong class or something.

I wrote this simple example using the `lasso_example.py` code from matplotlib
examples. I did run into some complications trying to use the `control` key.
When I try to drag with the mouse using the control key, the Lasso manager
becomes unresponsive (including in the original code from matplotlib). I could
not figure out why, so I used the `shift` and `alt` keys as modifiers in the
present code.

You'll see that the logic of what action to perform depending on which key is
held down at the moment when you release the lasso is performed in
`LassoManager.callback()`

    
    
    import logging
    import matplotlib
    from matplotlib.widgets import Lasso
    from matplotlib.colors import colorConverter
    from matplotlib.collections import RegularPolyCollection
    from matplotlib import path
    
    import matplotlib.pyplot as plt
    from numpy.random import rand
    
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    
    
    class Datum(object):
        colorin = colorConverter.to_rgba('red')
        colorShift = colorConverter.to_rgba('cyan')
        colorCtrl = colorConverter.to_rgba('pink')
        colorout = colorConverter.to_rgba('blue')
    
        def __init__(self, x, y, include=False):
            self.x = x
            self.y = y
            if include:
                self.color = self.colorin
            else:
                self.color = self.colorout
    
    
    class LassoManager(object):
        def __init__(self, ax, data):
            self.axes = ax
            self.canvas = ax.figure.canvas
            self.data = data
    
            self.Nxy = len(data)
    
            facecolors = [d.color for d in data]
            self.xys = [(d.x, d.y) for d in data]
            fig = ax.figure
            self.collection = RegularPolyCollection(
                fig.dpi, 6, sizes=(100,),
                facecolors=facecolors,
                offsets = self.xys,
                transOffset = ax.transData)
    
            ax.add_collection(self.collection)
    
            self.cid = self.canvas.mpl_connect('button_press_event', self.onpress)
            self.keyPress = self.canvas.mpl_connect('key_press_event', self.onKeyPress)
            self.keyRelease = self.canvas.mpl_connect('key_release_event', self.onKeyRelease)
            self.lasso = None
            self.shiftKey = False
            self.ctrlKey = False
    
        def callback(self, verts):
            logging.debug('in LassoManager.callback(). Shift: %s, Ctrl: %s' % (self.shiftKey, self.ctrlKey))
            facecolors = self.collection.get_facecolors()
            p = path.Path(verts)
            ind = p.contains_points(self.xys)
            for i in range(len(self.xys)):
                if ind[i]:
                    if self.shiftKey:
                        facecolors[i] = Datum.colorShift
                    elif self.ctrlKey:
                        facecolors[i] = Datum.colorCtrl
                    else:
                        facecolors[i] = Datum.colorin
                else:
                    facecolors[i] = Datum.colorout
    
            self.canvas.draw_idle()
            self.canvas.widgetlock.release(self.lasso)
            del self.lasso
    
        def onpress(self, event):
            if self.canvas.widgetlock.locked():
                return
            if event.inaxes is None:
                return
            self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.callback)
            # acquire a lock on the widget drawing
            self.canvas.widgetlock(self.lasso)
    
        def onKeyPress(self, event):
            logging.debug('in LassoManager.onKeyPress(). Event received: %s (key: %s)' % (event, event.key))
            if event.key == 'alt+alt':
                self.ctrlKey = True
            if event.key == 'shift':
                self.shiftKey = True
    
        def onKeyRelease(self, event):
            logging.debug('in LassoManager.onKeyRelease(). Event received: %s (key: %s)' % (event, event.key))
            if event.key == 'alt':
                self.ctrlKey = False
            if event.key == 'shift':
                self.shiftKey = False
    
    if __name__ == '__main__':
    
        data = [Datum(*xy) for xy in rand(100, 2)]
    
        ax = plt.axes(xlim=(0,1), ylim=(0,1), autoscale_on=False)
        lman = LassoManager(ax, data)
    
        plt.show()
    

