Search code examples
wxpython

wxpython figure.canvas.mpl_connect() not work for interactive plot


import wx
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import rand
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure


class CanvasPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.figure, self.ax = plt.subplots()
        self.x, self.y, self.c, self.s = rand(4, 100)
        self.ax.scatter(self.x, self.y, 100 * self.s, self.c, picker=True)
        self.figure.canvas.mpl_connect('pick_event', self.onpick3)
        # plt.show()


        self.canvas = FigureCanvas(self, -1, self.figure)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.RIGHT | wx.GROW)
        self.SetSizerAndFit(self.sizer)

    def onpick3(self, event):
        ind = event.ind
        print('onpick3 scatter:', ind, np.take(self.x, ind), np.take(self.y, ind))


class BetaFrame(wx.Frame):

    def __init__(self, parent, title):
        super().__init__(parent, -1, title=title, size=(1000, 1000),
                         style=wx.MINIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU |
                               wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN)

        self.initpos = 300
        self.sp = wx.SplitterWindow(self)
        self.p1 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER)
        self.p2 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER, name='Plot')
        self.p1.SetBackgroundColour('light')
        self.p2.SetBackgroundColour('light')
        self.sp.SplitVertically(self.p1, self.p2, self.initpos)
        self.Centre()

        CanvasPanel(self.p2)

if __name__ == '__main__':
    app = wx.App()
    frame = BetaFrame(None, 'Beta Version')
    frame.Show()
    app.SetTopWindow(frame)
    app.MainLoop()

enter image description here I tried to use the figure.canvas.mpl_connect() in matplotlib to build an interactive plot in a panel, but failed. The code below works well without plotting on a panel. The difference is on a panle, the plt.show() cannot be used. Does figure.canvas.mpl_connect() work on wxpython? Or there are other ways to make interactive plot? Thanks!

import numpy as np
from numpy.random import rand
import matplotlib.pyplot as plt

class CanvasPanel:
    def __init__(self):
        # self.figure = Figure()
        self.figure, self.ax = plt.subplots()
        self.x, self.y, self.c, self.s = rand(4, 100)
        self.ax.scatter(self.x, self.y, 100 * self.s, self.c, picker=True)
        self.figure.canvas.mpl_connect('pick_event', self.onpick3)
        plt.show()

    def onpick3(self, event):
        ind = event.ind
        print('onpick3 scatter:', ind, np.take(self.x, ind), np.take(self.y, ind))
        
CanvasPanel()

Solution

  • I run a mile when faced with plotting with matplotlib but I'm pretty sure you are meant to connect to the FigureCanvas not the figure thrown out by plt.subplots, although I'm happy to be corrected, as I said, I'm an unhappy camper in the forest of matplotlib.

    The result would be:

    import wx
    import matplotlib.pyplot as plt
    import numpy as np
    from numpy.random import rand
    from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
    from matplotlib.backends.backend_wx import NavigationToolbar2Wx
    from matplotlib.figure import Figure
    
    
    class CanvasPanel(wx.Panel):
        def __init__(self, parent):
            wx.Panel.__init__(self, parent)
            self.figure, self.ax = plt.subplots()
            self.x, self.y, self.c, self.s = rand(4, 100)
            self.ax.scatter(self.x, self.y, 100 * self.s, self.c, picker=True)
            # plt.show()
    
    
            self.canvas = FigureCanvas(self, -1, self.figure)
            self.canvas.mpl_connect('pick_event', self.onpick3)
            self.sizer = wx.BoxSizer(wx.VERTICAL)
            self.sizer.Add(self.canvas, 1, wx.RIGHT | wx.GROW)
            self.SetSizerAndFit(self.sizer)
    
        def onpick3(self, event):
            ind = event.ind
            print('onpick3 scatter:', ind, np.take(self.x, ind), np.take(self.y, ind))
    
    
    class BetaFrame(wx.Frame):
    
        def __init__(self, parent, title):
            super().__init__(parent, -1, title=title, size=(1000, 1000),
                             style=wx.MINIMIZE_BOX | wx.RESIZE_BORDER | wx.SYSTEM_MENU |
                                   wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN)
    
            self.initpos = 300
            self.sp = wx.SplitterWindow(self)
            self.p1 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER)
            self.p2 = wx.Panel(self.sp, style=wx.SUNKEN_BORDER, name='Plot')
            self.p1.SetBackgroundColour('light')
            self.p2.SetBackgroundColour('light')
            self.sp.SplitVertically(self.p1, self.p2, self.initpos)
            self.Centre()
    
            CanvasPanel(self.p2)
    
    if __name__ == '__main__':
        app = wx.App()
        frame = BetaFrame(None, 'Beta Version')
        frame.Show()
        app.SetTopWindow(frame)
        app.MainLoop()
    

    Now click on a coloured dot.

    onpick3 scatter: [7] [0.18704031] [0.43573093]
    onpick3 scatter: [11] [0.19506781] [0.79896204]
    onpick3 scatter: [71] [0.80362554] [0.9426998]
    onpick3 scatter: [40 41] [0.78295576 0.76609123] [0.92129472 0.94352157]
    onpick3 scatter: [40] [0.78295576] [0.92129472]
    onpick3 scatter: [94] [0.94643354] [0.0547319]
    onpick3 scatter: [26 37] [0.73187773 0.73352872] [0.01974885 0.05060254]