Search code examples
pythonmatplotlibwxpython

How do I use matplotlib's event handing inside wxPython?


I am attempting to modify the "Draggable Rectangle Exercise" found in the matplotlib docs so that it works inside a wxPython window.

I have the following so far:

import wx

import matplotlib
matplotlib.interactive(True)
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas

import numpy as np

class DraggableRectangle:
    def __init__(self, rect):
        self.rect = rect
        self.press = None

    def connect(self):
        'connect to all the events we need'
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        'on button press we will see if the mouse is over us and store some data'
        if event.inaxes != self.rect.axes: return

        contains, attrd = self.rect.contains(event)
        if not contains: return
        print 'event contains', self.rect.xy
        x0, y0 = self.rect.xy
        self.press = x0, y0, event.xdata, event.ydata

    def on_motion(self, event):
        'on motion we will move the rect if the mouse is over us'
        if self.press is None: return
        if event.inaxes != self.rect.axes: return
        x0, y0, xpress, ypress = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        #print 'x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f'%(x0, xpress, event.xdata, dx, x0+dx)
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        #self.rect.figure.canvas.draw()
        self.rect.figure.canvas.draw_idle()

    def on_release(self, event):
        'on release we reset the press data'
        self.press = None
        #self.rect.figure.canvas.draw()
        self.rect.figure.canvas.draw_idle()

    def disconnect(self):
        'disconnect all the stored connection ids'
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)

class Frame(wx.Frame):
    def __init__(self, title):
        wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(800,600))

        self.panel = wx.Panel(self)

        self.figure = Figure(figsize=(6, 4), dpi=100)
        self.axes = self.figure.add_subplot(111)
        self.canvas = FigureCanvas(self.panel, wx.ID_ANY, self.figure)

        rects = self.axes.bar(range(10), 20*np.random.rand(10))
        drs = []
        for rect in rects:
            dr = DraggableRectangle(rect)
            dr.connect()
            drs.append(dr)

app = wx.App(redirect=False)
top = Frame("test")
top.Show()
app.MainLoop()

Unfortunately this doesn't work as advertised; the bars appear on the graph but they are not draggable at all. Copying and pasting the example as-is works fine, it's only when I try to use it with wxPython that I run into problems.


Solution

  • Don't ask me why, but changing drs to self.drs (lines 75 and 79) seems to work. As if you had to prevent drsand all dr from being garbage collected. (Related to Wx Matplotlib Event Handling ?)

    import wx
    
    import matplotlib
    matplotlib.interactive(True)
    matplotlib.use('WXAgg')
    from matplotlib.figure import Figure
    from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
    
    import numpy as np
    
    class DraggableRectangle:
        def __init__(self, rect):
            self.rect = rect
            self.press = None
    
        def connect(self):
            'connect to all the events we need'
            self.cidpress = self.rect.figure.canvas.mpl_connect(
                'button_press_event', self.on_press)
            self.cidrelease = self.rect.figure.canvas.mpl_connect(
                'button_release_event', self.on_release)
            self.cidmotion = self.rect.figure.canvas.mpl_connect(
                'motion_notify_event', self.on_motion)
    
        def on_press(self, event):
            print "on_press"
            'on button press we will see if the mouse is over us and store some data'
            if event.inaxes != self.rect.axes: return
    
            contains, attrd = self.rect.contains(event)
            if not contains: return
            print 'event contains', self.rect.xy
            x0, y0 = self.rect.xy
            self.press = x0, y0, event.xdata, event.ydata
    
        def on_motion(self, event):
            print "on_motion"
            'on motion we will move the rect if the mouse is over us'
            if self.press is None: return
            if event.inaxes != self.rect.axes: return
            x0, y0, xpress, ypress = self.press
            dx = event.xdata - xpress
            dy = event.ydata - ypress
            #print 'x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f'%(x0, xpress, event.xdata, dx, x0+dx)
            self.rect.set_x(x0+dx)
            self.rect.set_y(y0+dy)
    
            #self.rect.figure.canvas.draw()
            self.rect.figure.canvas.draw_idle()
    
        def on_release(self, event):
            print "on_release"
            'on release we reset the press data'
            self.press = None
            #self.rect.figure.canvas.draw()
            self.rect.figure.canvas.draw_idle()
    
        def disconnect(self):
            'disconnect all the stored connection ids'
            self.rect.figure.canvas.mpl_disconnect(self.cidpress)
            self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
            self.rect.figure.canvas.mpl_disconnect(self.cidmotion)
    
    class Frame(wx.Frame):
        def __init__(self, title):
            wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(800,600))
    
            self.panel = wx.Panel(self)
    
            self.figure = Figure(figsize=(6, 4), dpi=100)
            self.axes = self.figure.add_subplot(111)
            self.canvas = FigureCanvas(self.panel, wx.ID_ANY, self.figure)
    
            rects = self.axes.bar(range(10), 20*np.random.rand(10))
            self.drs = []
            for rect in rects:
                dr = DraggableRectangle(rect)
                dr.connect()
                self.drs.append(dr)
    
    print "test"
    app = wx.PySimpleApp()#redirect=False)
    top = Frame("test")
    top.Show()
    app.MainLoop()