class MatplotPanel(wx.Panel): clsFrame = None clsID_new_figure = wx.NOT_FOUND isInitialized = False kwargs = {} clsID_delete_datatip = wx.NewId() clsID_clear_datatip = wx.NewId() def __init__(self, parent, title=None, num=-1, thisFig=None): # set the size to positive value, otherwise the toolbar will assert # wxpython/ext/wxWidgets/src/gtk/bitmap.cpp(539): assert ""width > 0 && # height > 0"" failed in Create(): invalid bitmap size wx.Panel.__init__(self, parent, size=(100, 100)) # initialize matplotlib stuff self.figure = thisFig if not self.figure: self.figure = Figure(None, None) self.canvas = FigureCanvas(self, -1, self.figure) # since matplotlib 3.2, it does not allow canvas size to become smaller # than MinSize in wx backend. So the canvas size (e.g., (640, 480))may # be large than the window size. self.canvas.SetMinSize((1, 1)) #self.canvas.manager = self self.num = num if title is None: title = 'Figure %d' % self.num self.title = title self.isdestory = False szAll = wx.BoxSizer(wx.VERTICAL) self.figure.set_label(title) self.toolbar = Toolbar(self.canvas, self.figure) szAll.Add(self.toolbar, 0, wx.EXPAND) szAll.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.toolbar.update() self.SetSizer(szAll) self.figmgr = FigureManagerWx(self.canvas, num, self) self.Bind(wx.EVT_CLOSE, self._onClose) self.canvas.mpl_connect('button_press_event', self._onClick) dp.connect(self.simLoad, 'sim.loaded') self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu) self.Bind(wx.EVT_MENU, self.OnProcessCommand, id=self.clsID_delete_datatip) self.Bind(wx.EVT_MENU, self.OnProcessCommand, id=self.clsID_clear_datatip) self.Bind(wx.EVT_MENU, self.OnProcessCommand, id=wx.ID_NEW) def GetToolBar(self): """Override wxFrame::GetToolBar as we don't have managed toolbar""" return self.toolbar def simLoad(self, num): for l in self.figure.gca().lines: if hasattr(l, 'trace'): sz = len(l.get_ydata()) for s in l.trace: if (not s) or (not s.startswith(str(num) + '.')): continue #dispatcher.send(signal='sim.trace_buf', objects=s, size=sz) def _onClick(self, event): if event.dblclick: self.toolbar.home() def OnProcessCommand(self, evt): if self.toolbar.datacursor.ProcessCommand(evt.GetId()): self.canvas.draw() def _show_context_menu(self): if self.toolbar.mode != 'datatip': return menu = wx.Menu() delMenu = menu.Append(self.clsID_delete_datatip, "Delete current datatip") note = self.toolbar.datacursor.active delMenu.Enable((note is not None) and note.get_visible()) menu.Append(self.clsID_clear_datatip, "Delete all datatips") self.PopupMenu(menu) menu.Destroy() def OnContextMenu(self, event): # Show menu after the current and pending event handlers have been # completed, otherwise it causes the following error in some system # (e.g., xubuntu, matplotlib 3.2.2, wx 4.1.0), and the menu doesn't show. # GLib-GObject-CRITICAL **: g_object_set_data: assertion 'G_IS_OBJECT # (object)' failed wx.CallAfter(self._show_context_menu) def _onClose(self, evt): self.canvas.close_event() self.canvas.stop_event_loop() Gcf.destroy(self.num) def destroy(self, *args): if self.isdestory is False: dp.send('frame.delete_panel', panel=self) wx.WakeUpIdle() def Destroy(self, *args, **kwargs): self.isdestory = True self.canvas.close_event() self.canvas.stop_event_loop() Gcf.destroy(self.num) return super(MatplotPanel, self).Destroy(*args, **kwargs) def GetTitle(self): """return the figure title""" return self.title def SetTitle(self, title): """set the figure title""" if title == self.title: return self.title = title dp.send('frame.update_panel_title', pane=self, title=self.title) def show(self): """show figure""" if self.IsShown() is False: self.canvas.draw() dp.send('frame.show_panel', panel=self) def update_buffer(self, bufs): """update the data used in plot_trace""" for l in self.figure.gca().lines: if hasattr(l, 'trace'): x = l.trace[0] y = l.trace[1] if x is None: if y in bufs: l.set_data(numpy.arange(len(bufs[y])), bufs[y]) elif x in bufs or y in bufs: xd = l.get_xdata() yd = l.get_ydata() if y in bufs: yd = bufs[y] if x in bufs: xd = bufs[x] if len(xd) != len(yd): sz = min(len(xd), len(yd)) xd = xd[0:sz] yd = yd[0:sz] l.set_data(xd, yd) if hasattr(l, 'autorelim') and l.autorelim: #Need both of these in order to rescale self.figure.gca().relim() self.figure.gca().autoscale_view() self.canvas.draw() def plot_trace(self, x, y, autorelim, *args, **kwargs): """plot and trace""" if y is None: return if x is None: l, = self.figure.gca().plot(list(y.values())[0], *args, **kwargs) l.trace = [None, list(y.keys())[0]] else: xd = list(x.values())[0] yd = list(y.values())[0] if len(xd) != len(yd): sz = min(len(xd), len(yd)) if sz > 0: xd = xd[0:sz] yd = yd[0:sz] else: xd = 0 yd = 0 l, = self.figure.gca().plot(xd, yd, *args, **kwargs) l.trace = [list(x.keys())[0], list(y.keys())[0]] l.autorelim = autorelim self.canvas.draw() def set_window_title(self, label): pass @classmethod def setactive(cls, pane): """set the active figure""" if pane and isinstance(pane, MatplotPanel): Gcf.set_active(pane) @classmethod def addFigure(cls, title=None, num=None, thisFig=None): direction = cls.kwargs.get('direction', 'top') fig = cls(cls.clsFrame, title=title, num=num, thisFig=thisFig) # set the minsize to be large enough to avoid some following assert; it # will not eliminate all as if a page is added to a notebook, the # minsize of notebook is not the max of all its children pages (check # frameplus.py). # wxpython/ext/wxWidgets/src/gtk/bitmap.cpp(539): assert ""width > 0 && # height > 0"" failed in Create(): invalid bitmap size dp.send('frame.add_panel', panel=fig, direction=direction, title=fig.GetTitle(), target=Gcf.get_active(), minsize=(75, 75)) return fig @classmethod def Initialize(cls, frame, **kwargs): if cls.isInitialized: return cls.isInitialized = True cls.clsFrame = frame cls.kwargs = kwargs resp = dp.send('frame.add_menu', path='File:New:Figure', rxsignal='bsm.figure') if resp: cls.clsID_new_figure = resp[0][1] if cls.clsID_new_figure is not wx.NOT_FOUND: dp.connect(cls.ProcessCommand, 'bsm.figure') dp.connect(cls.Uninitialize, 'frame.exit') dp.connect(cls.Initialized, 'frame.initialized') dp.connect(cls.setactive, 'frame.activate_panel') dp.connect(cls.OnBufferChanged, 'sim.buffer_changed') @classmethod def Initialized(cls): dp.send('shell.run', command='from matplotlib.pyplot import *', prompt=False, verbose=False, history=False) @classmethod def OnBufferChanged(cls, bufs): """the buffer has be changes, update the plot_trace""" for p in Gcf.get_all_fig_managers(): p.update_buffer(bufs) @classmethod def Uninitialize(cls): """destroy the module""" Gcf.destroy_all() @classmethod def ProcessCommand(cls, command): """process the menu command""" if command == cls.clsID_new_figure: plt.figure()