Related Documentation:
Enhanced version of plotVm that wraps Matplotlib plots within a wxPython GUI
#!/usr/bin/env python # A prototype of a stand-alone application for plotting the output of # GENESIS 3 models. This uses a basic wxPython frame to hold a matplotlib # figure for plotting. It defines some basic menu items, and a control # panel of buttons and toggles, each with bindings to a function to execute # on a mouse click. It is based on the example program wx_mpl_bars.py and # other wxPython and matplotlib examples. import sys, os, glob # import needed wxPython modules import wx import wx.html import wx.lib.dialogs import matplotlib matplotlib.use(’WXAgg’) from matplotlib.figure import Figure from matplotlib.backends.backend_wxagg import \ FigureCanvasWxAgg as FigCanvas, \ NavigationToolbar2WxAgg as NavigationToolbar import matplotlib.font_manager as font_manager import numpy as np class MainApp(wx.App): def OnInit(self): frame = PlotFrame("GENESIS 3 Plot Demo", (50, 60), (640, 640)) frame.Show() self.SetTopWindow(frame) return True class PlotFrame(wx.Frame): """ PlotFrame is a custom wxPython frame to hold the panel with a Figure and WxAgg backend canvas for matplotlib plots or other figures. In this frame: self is an instance of a wxFrame; axes is an instance of MPL Axes; fig is an instance of MPL Figure; panel is an instance of wxPanel, used for the main panel, to hold canvas, an instance of MPL FigureCanvasWxAgg. """ def __init__(self, title, pos, size): wx.Frame.__init__(self, None, wx.ID_ANY, title, pos, size) # define some variables to be shared among functions below # default filename list will be either empty or from args self.filenames = sys.argv[1:] # python 2.5 and later hack to remove duplicates: list to set to list self.filenames = list(set(self.filenames)) self.plot_type = ’generic’ # format string for plot - default is color black self.plot_format = ’k’ # Make the main Matplotlib panel for plots self.create_main_panel() # creates canvas # # Layout with box sizers # self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND) self.sizer.AddSpacer(10) self.sizer.Add(self.toolbar, 0, wx.EXPAND) self.sizer.AddSpacer(10) # Make the control panel with a row of buttons self.create_button_bar() self.sizer.Add(self.button_bar_sizer, 0, flag = wx.ALIGN_CENTER | wx.TOP) # Make a Status Bar self.statusbar = self.CreateStatusBar() self.sizer.Add(self.statusbar, 0, wx.EXPAND) self.SetStatusText("Frame created ...") # ------------------------------------------------------- # set up the Menu Bar # ------------------------------------------------------- menuBar = wx.MenuBar() menuFile = wx.Menu() # File menu menuFile.Append(1, "&Open", "Filename(s) or wildcard list to plot") menuFile.Append(3, "Save", "Save plot as a PNG image") menuFile.AppendSeparator() menuFile.Append(10, "E&xit") menuBar.Append(menuFile, "&File") menuHelp = wx.Menu() # Help menu menuHelp.Append(11, "&About Basic Plot") menuHelp.Append(12, "&Usage and Help") menuHelp.Append(13, "Program &Info") menuBar.Append(menuHelp, "&Help") self.SetMenuBar(menuBar) self.panel.SetSizer(self.sizer) self.sizer.Fit(self) # ------------------------------------------------------- # Bind the menu items to functions # ------------------------------------------------------- self.Bind(wx.EVT_MENU, self.OnOpen, id=1) self.Bind(wx.EVT_MENU, self.OnSave, id=3) self.Bind(wx.EVT_MENU, self.OnQuit, id=10) self.Bind(wx.EVT_MENU, self.OnAbout, id=11) self.Bind(wx.EVT_MENU, self.OnUsage, id=12) self.Bind(wx.EVT_MENU, self.OnInfo, id=13) self.set_plot_type() # ---------- end of __init__ ---------------------------- # ------------------------------------------------------- # Function to make the main Matplotlib panel for plots # ------------------------------------------------------- def create_main_panel(self): """ create_main_panel creates the main panel with: * mathplotlib canvas * mathplotlib navigation toolbar """ self.panel = wx.Panel(self) # Create the mpl Figure and FigCanvas objects. # 5x4 inches, 100 dots-per-inch # self.dpi = 100 self.fig = Figure((5.0, 4.0), dpi=self.dpi) self.canvas = FigCanvas(self.panel, wx.ID_ANY, self.fig) # Since we have only one plot, we could use add_axes # instead of add_subplot, but then the subplot # configuration tool in the navigation toolbar wouldn’t work. # # (111) == (1,1,1) --> row 1, col 1, Figure 1) self.axes = self.fig.add_subplot(111) # # Create the navigation toolbar, tied to the canvas # self.toolbar = NavigationToolbar(self.canvas) def create_button_bar(self): """ create_button_bar makes a control panel bar with buttons and toggles for Clear - Plot - Overlay ON/OFF - generic/Vm plot - Legend OFF/On It does not create a Panel container, but simply creates Button objects with bindings, and adds them to a horizontal BoxSizer self.button_bar_sizer. This is added to the PlotFrame vertical BoxSizer during initialization of the frame. """ clear_button = wx.Button(self.panel, -1, "Clear") self.Bind(wx.EVT_BUTTON, self.OnClear, clear_button) replot_button = wx.Button(self.panel, -1, "Plot") self.Bind(wx.EVT_BUTTON, self.OnReplot, replot_button) # The toggle buttons need to be globally accessible self.overlay_button = wx.ToggleButton(self.panel, -1, " Overlay ON ") self.overlay_button.SetValue(True) # the default is self.axes.hold(True) self.overlay_button.SetLabel(" Overlay ON ") self.Bind(wx.EVT_TOGGLEBUTTON, self.OnOverlay, self.overlay_button) self.autoscale_button = wx.ToggleButton(self.panel, -1, " generic plot ") self.autoscale_button.SetValue(False) self.autoscale_button.SetLabel(" generic plot ") self.Bind(wx.EVT_TOGGLEBUTTON, self.OnAutoscale, self.autoscale_button) self.legend_button = wx.ToggleButton(self.panel, -1, " Legend OFF ") self.legend_button.SetValue(False) self.legend_button.SetLabel(" Legend OFF ") self.Bind(wx.EVT_TOGGLEBUTTON, self.OnLegend, self.legend_button) # info_button = wx.Button(self.panel, -1, "Info") # self.Bind(wx.EVT_BUTTON, self.OnInfo, info_button) # Set button colors to match G2 "colorize function" defaults # This is highly dependent on X11 color definitions clear_button.SetBackgroundColour(’rosybrown1’) replot_button.SetBackgroundColour(’rosybrown1’) self.overlay_button.SetForegroundColour(’red’) self.overlay_button.SetBackgroundColour(’cadetblue1’) self.autoscale_button.SetForegroundColour(’blue’) self.autoscale_button.SetBackgroundColour(’cadetblue1’) self.legend_button.SetForegroundColour(’blue’) self.legend_button.SetBackgroundColour(’cadetblue1’) self.button_bar_sizer = wx.BoxSizer(wx.HORIZONTAL) flags = wx.ALIGN_CENTER | wx.ALL self.button_bar_sizer.Add(clear_button, 0, border=3, flag=flags) self.button_bar_sizer.Add(replot_button, 0, border=3, flag=flags) self.button_bar_sizer.Add(self.overlay_button, 0, border=3, flag=flags) self.button_bar_sizer.Add(self.autoscale_button, 0, border=3, flag=flags) self.button_bar_sizer.Add(self.legend_button, 0, border=3, flag=flags) # ------------------------------------------------------- # Functions to generate or read (x,y) data and plot it # ------------------------------------------------------- def plot_file(self): # print ’Plotting %s’ % self.file x = []; y = [] fp = open(self.file, ’r’) # print ’Opened %s’ % self.file for line in fp.readlines(): data = line.split(" ") x.append(data[0]); y.append(data[1]) self.axes.plot(x, y, self.plot_format) def plot_data_files(self): formats = [’k’, ’r’, ’b’, ’g’, ’m’, ’c’] plotnum = 0 if len(self.filenames) > 0: for self.file in self.filenames: self.plot_format = formats[plotnum % len(formats)] try: if os.path.exists(self.file): self.plot_file() plotnum += 1 else: print ’*** Error: Incorrect file name or path specified’ # I need to do better error handling! except: print ’An error ocurred’ # sys.exit() else: # bring up a warning dialog msg = """ No existing files were specified for plotting! Please enter one or more files to plot with File/Open. """ wx.MessageBox(msg, "Plot Warning", wx.OK | wx.ICON_ERROR,self) # --------------------------------------------------------------- # Define the functions executed on menu choices # --------------------------------------------------------------- def OnQuit(self, event): self.Close() def OnSave(self, event): file_choices = "PNG (*.png)|*.png" dlg = wx.FileDialog( self, message="Save plot as...", defaultDir=os.getcwd(), defaultFile="plot.png", wildcard=file_choices, style=wx.SAVE) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.canvas.print_figure(path, dpi=self.dpi) self.flash_status_message("Saved to %s" % path) def OnOpen(self, event): dlg = wx.TextEntryDialog(self, "File(s) with x,y data to plot", "File Open", "Vm.out", style=wx.OK|wx.CANCEL) if dlg.ShowModal() == wx.ID_OK: filename_string = dlg.GetValue() # print "You entered: %s" % filename_string # expand wildcarded file list in filename_string # For now, replace and not add to filenames - RadioButtons later? self.filenames = [] if filename_string != "": for name in filename_string.split(): self.filenames += glob.glob(name) files_plotted = "" # python 2.5 and later remove duplicates: list to set to list self.filenames = list(set(self.filenames)) for name in self.filenames: files_plotted += " " + name self.SetStatusText("Plotting " + files_plotted) # print "filenames = ", self.filenames dlg.Destroy() def OnAbout(self, event): msg = """ G3Plot ver. 0.5 G3Plot is a protoype of a stand-alone application for plotting the output of GENESIS 3 simulations. It defines a custom wxPython frame class PlotFrame to embed a matplotlib figure for plotting. PlotFrame defines some basic menu items on the menu bar, and a control panel of colored buttons and toggles, with bindings to functions executed on a mouse click. The functions for the Help menu choices provide examples of providing documentation through the wxPython classes MessageDialog, ScrolledMessageDialog, Dialog, and HtmlWindow, for HTML-formatted documentation. Help/Usage gives HTML help for using G3Plot to plot data files. This is the main Help page. Help/Program Info provides some information about the objects and functions, and the wxPython and matplotlib classes used here. Dave Beeman, April 2010 """ dlg = wx.MessageDialog(self, msg, "About G3 Plot and PlotFrame", wx.OK | wx.ICON_QUESTION) dlg.ShowModal() dlg.Destroy() class UsageFrame(wx.Frame): text = """ <HTML> <HEAD></HEAD> <BODY BGCOLOR="#D6E7F7"> <CENTER><H1>Using G3 Plot and PlotFrame</H1></CENTER> <H2>Introduction and Quick Start</H2> <P> PlotFrame is a prototype of a Python class for use in an application for plotting the output of GENESIS 3 simulations. It uses a basic wxPython frame to embed a matplotlib figure for plotting. It defines some basic menu items and a control panel of buttons and toggles, each with bindings to a function to execute on a mouse click, and can aso be used as an example of embedding Matplotlib plots within a wx Widgets environment. <P> G3Plot is used to plot multiple files, with a number of features designed to simplify the process of comparing multiple plots of membrane potential. You may specify a list of one or more files that each contain multiple lines of (x,y) data pairs separated by white space. The file list may contain wildards, and can be specified either on the command line, e.g.: <PRE> G3Plot Vm.out Vm_data/pyr*.out </PRE> or in the dialog brought up from the File/Open menu. The wildcards will be expanded and paths to the matching files will go into a <i>filenames</i> list, which has any duplicate wildcard matches removed. Then, click on the Plot button in the Control Button Bar to plot the data in the file or files in the <i>filenames</i> list. <H2>Menu Bar Choices</H2> <UL> <LI><B>File</B> <UL> <LI><B>Open</B> - Enter the filename(s) or wildcarded list of files to plot. <LI><B>Save</B> - Set filename, browse for directory, and save the current plot in a PNG format file. <LI><B>Exit</B> - Exit the program </UL> <LI><B>Help</B> <UL> <LI><B>About</B> - Brief description of this application <LI><B>Usage</B> - Main Help and Usage Information <LI><B>Program Info</B> - Some details of the implementation, obtained from internal documentation strings </UL> </UL> <H2>Control Button Bar</H2> <UL> <LI><B>[Clear]</B> - Clear the plot <LI><B>[Plot]</B> - Plot the data in the file(s) that are in the <i>filenames</i> list <LI><B>[Overlay ON/OFF]</B> - When ON (the default), multiple files in the <i>filenames</i> list will be plotted on the same axes without clearing the plot. When OFF, the plot is cleared after each file is plotted. <LI><B>[generic/Vm plot]</B> - When set to ’generic plot’ , the title and axis labels are set for a generic (x,y) plot. When set to ’Vm plot’, the title and axis labels are set to values appropriate for membrane potential (volts) vs time (seconds). <LI><B>[Legend ON/OFF]</B> - Toggle the plot legend on and off. The legend is made from the <i>filenames</i> list, using the colors of the corresponding line plot. The legend will not be displayed if the currently displayed plot does not have the same number of line plots as there are files in the list, e.g. it will not attempt to show the legend after clearing the plot, and will pop up a warning dialog. </UL> <H2>Matplotlib Navigation Panel</H2> <P> Navigation within the current plot is performed using the seven icons displayed in the panel below the plot. <P> The Home, Back, and Forward buttons are used to navigate back and forth between previously defined views. You can create a new view of your data by using the Pan or Zoom buttons. <UL> <LI><B>[ |ˆ| ] Home</B> - Go to the first, default view <LI><B>[ <-- ] Back</B> - Go to previous view, if one exists <LI><B>[ --> ] Forward</B> - Go to next view, if one exists <LI><B>[ + ] Pan</B> - When this button is activated, click left mouse button and drag to pan the figure, dragging it to a new position. <LI><B>[ O ] Zoom</B> - When this button is activated, click left mouse button and drag to define a zoom region. <LI><B>[ # ] Configure Subplots</B> - configure the parameters of the plot: the left, right, top, bottom, space between the rows and space between the columns <LI><B>[ disk ] Save</B> - Launch a file save dialog for the plot </UL> <HR> </BODY> </HTML> """ def __init__(self, parent): wx.Frame.__init__(self, parent, -1, "Usage and Help", size=(640,600), pos=(400,100)) html = wx.html.HtmlWindow(self) html.SetPage(self.text) panel = wx.Panel(self, -1) button = wx.Button(panel, wx.ID_OK, "Close") self.Bind(wx.EVT_BUTTON, self.OnCloseMe, button) sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(html, 1, wx.EXPAND|wx.ALL, 5) sizer.Add(panel, 0, wx.ALIGN_CENTER|wx.ALL, 5) self.SetSizer(sizer) self.Layout() def OnCloseMe(self, event): self.Close(True) def OnUsage(self,event): usagewin = self.UsageFrame(self) usagewin.Show(True) def OnInfo(self,event): msg = "Program information for PlotFrame obtained from docstrings:" msg += "\n" + self.__doc__ + "\n" + self.create_main_panel.__doc__ msg += self.create_button_bar.__doc__ dlg = wx.lib.dialogs.ScrolledMessageDialog(self, msg, "PlotFrame Documentation") dlg.ShowModal() # --------------------------------------------------------------- # Define the functions executed on control button click # --------------------------------------------------------------- def OnClear(self,event): self.axes.clear() self.canvas.draw() # self.Refresh() def OnReplot(self,event): self.plot_data_files() self.canvas.draw() def OnOverlay(self,event): state = self.overlay_button.GetValue() if state: # print state self.overlay_button.SetLabel("Overlay ON") self.overlay_button.SetForegroundColour(’red’) self.axes.hold(True) else: # print state self.overlay_button.SetLabel("Overlay OFF") self.overlay_button.SetForegroundColour(’blue’) self.axes.hold(False) def OnAutoscale(self,event): state = self.autoscale_button.GetValue() if state: self.autoscale_button.SetLabel(" Vm plot ") self.autoscale_button.SetForegroundColour(’red’) self.plot_type = ’Vm’ else: self.autoscale_button.SetLabel("generic plot") self.autoscale_button.SetForegroundColour(’blue’) self.plot_type = ’generic’ self.set_plot_type() def set_plot_type(self): # print self.plot_type if self.plot_type == ’generic’: self.axes.set_autoscale_on(True) self.axes.set_title(’(x,y) data’) self.axes.set_xlabel(’X’) self.axes.set_ylabel(’Y’) self.canvas.draw() # self.Refresh() else: self.axes.set_autoscale_on(False) self.axes.set_title(’Membrane Potential’) self.axes.set_xlabel(’seconds’) self.axes.set_ylabel(’Volts’) self.axes.axis(ymin=-0.1, ymax=0.05) self.canvas.draw() # self.Refresh() def OnLegend(self,event): state = self.legend_button.GetValue() if state: self.legend_button.SetLabel("Legend ON") self.legend_button.SetForegroundColour(’red’) # Need to check that number in list == number of lines on plot # Could still be problems if plot is cleared and no filenames nlines = len(self.axes.get_lines()) if (len(self.filenames) == nlines) and (nlines != 0): # use filenames list for legend and reduce font size self.axes.legend(self.filenames, prop=font_manager.FontProperties(size=10)) else: msg = "The plot has " + str(len(self.axes.get_lines())) + \ " lines, but the filenames list has " + \ str(len(self.filenames)) + " entries.\n\n" + \ "Please enter one or more files to plot with File/Open, " + \ " click on Clear, and then Plot. Then toggle to Legend ON." wx.MessageBox(msg, "Legend Warning", wx.OK | wx.ICON_ERROR, self) self.canvas.draw() else: self.legend_button.SetLabel("Legend OFF") self.legend_button.SetForegroundColour(’blue’) self.autoscale_button.SetForegroundColour(’blue’) ax = self.fig.gca() ax.legend_ = None self.canvas.draw() # --------------------------------------------------------------- # Define some auxillary functions # --------------------------------------------------------------- def flash_status_message(self, msg, flash_len_ms=1500): self.statusbar.SetStatusText(msg) self.timeroff = wx.Timer(self) self.Bind( wx.EVT_TIMER, self.on_flash_status_off, self.timeroff) self.timeroff.Start(flash_len_ms, oneShot=True) def on_flash_status_off(self, event): self.statusbar.SetStatusText(’’) if __name__ == ’__main__’: app = MainApp(False) app.MainLoop() \ |