def __init__(self): wx.Frame.__init__(self, None, -1, self.title) # Set initial time axis length self.initial_xlen = 3 # Start data generator self.data = DataGen() # Create lists to store x and y data self.xdata = [0] self.ydata = [0] self.paused = False # Create visualiser UI print "Loading visualiser..." self.create_menu() self.create_status_bar() self.create_main_panel() # Delay for one second; prevents errors that occur when the # thread attempts to draw the graph before the UI is fully loaded time.sleep(1) print "Opening visualiser..." # Start timer for refreshing graph self.start_time = time.time() # Open thread to continuously redraw graph Thread(target=self.refresh_graph, args=()).start()
class GraphFrame(wx.Frame): """Creates the graph frame and continuously plots the data from the Arduino board""" title = 'Arduino Oscilloscope' def __init__(self): wx.Frame.__init__(self, None, -1, self.title) # Set initial time axis length self.initial_xlen = 3 # Start data generator self.data = DataGen() # Create lists to store x and y data self.xdata = [0] self.ydata = [0] self.paused = False # Create visualiser UI print "Loading visualiser..." self.create_menu() self.create_status_bar() self.create_main_panel() # Delay for one second; prevents errors that occur when the # thread attempts to draw the graph before the UI is fully loaded time.sleep(1) print "Opening visualiser..." # Start timer for refreshing graph self.start_time = time.time() # Open thread to continuously redraw graph Thread(target=self.refresh_graph, args=()).start() def create_menu(self): """Creates a menu with an option to save the plot""" self.menubar = wx.MenuBar() # Create save option menu_file = wx.Menu() m_expt = menu_file.Append(-1, "&Save plot\tCtrl-S", "Save plot to file") self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt) self.menubar.Append(menu_file, "&File") self.SetMenuBar(self.menubar) def create_main_panel(self): """Creates the aplication's main window""" self.panel = wx.Panel(self) # Create graph plot self.init_plot() self.canvas = FigCanvas(self.panel, -1, self.fig) # Create pause button self.pause_button = wx.Button(self.panel, -1, "Pause") self.Bind(wx.EVT_BUTTON, self.on_pause_button, self.pause_button) self.Bind(wx.EVT_UPDATE_UI, self.on_update_pause_button, self.pause_button) # Create show grid checkbox self.cb_grid = wx.CheckBox(self.panel, -1, "Show Grid", style=wx.ALIGN_RIGHT) self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid) self.cb_grid.SetValue(True) # Create box to control length of x axis self.xlen_control = ControlPanel(self.panel, -1, "x_len", self.initial_xlen) self.xlen_ctrl_text = wx.StaticText(self.panel, -1, "Set x axis length:") self.xlen_ctrl_text.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.BOLD)) self.xlen_ctrl_text.SetSize(self.xlen_ctrl_text.GetBestSize()) # Create boxes to control limits of y axis self.ymin_control = ControlPanel(self.panel, -1, "y_min", -0.1) self.ymax_control = ControlPanel(self.panel, -1, "y_max", 5.1) self.ylim_ctrl_text = wx.StaticText(self.panel, -1, "Set y axis limits:") self.ylim_ctrl_text.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.BOLD)) self.ylim_ctrl_text.SetSize(self.ylim_ctrl_text.GetBestSize()) # Set position of UI elements self.hbox1 = wx.BoxSizer(wx.HORIZONTAL) self.hbox1.Add(self.pause_button, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.hbox1.AddSpacer(20) self.hbox1.Add(self.cb_grid, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.hbox1.AddSpacer(20) self.hbox1.Add(self.xlen_ctrl_text,0,wx.ALL,10) self.hbox1.Add(self.xlen_control, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.hbox1.AddSpacer(20) self.hbox1.Add(self.ylim_ctrl_text,0,wx.ALL,10) self.hbox1.Add(self.ymin_control, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.hbox1.Add(self.ymax_control, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.vbox = wx.BoxSizer(wx.VERTICAL) self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW) self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP) self.panel.SetSizer(self.vbox) self.vbox.Fit(self) def create_status_bar(self): self.statusbar = self.CreateStatusBar() def init_plot(self): """Initialises the graph plot""" self.dpi = 100 self.fig = Figure((10.0, 5.0), dpi=self.dpi) self.axes = self.fig.add_subplot(111) self.axes.set_axis_bgcolor('black') self.axes.set_title('Arduino Oscilloscope Data', size=12) self.axes.set_xlabel('Time /s', size=10) self.axes.set_ylabel('Voltage /V', size=10) pylab.setp(self.axes.get_xticklabels(), fontsize=8) pylab.setp(self.axes.get_yticklabels(), fontsize=8) # Plot data as a line self.plot_data = self.axes.plot( self.xdata,self.ydata, linewidth=1, color=(1, 1, 0), )[0] def draw_plot(self): """Re-draws the graph plot.""" # Obtain manually set value for x axis length try: x_len = float(self.xlen_control.manual_value()) except ValueError: x_len = self.initial_xlen # Set maximum x axis value equal to maximum x point, # with x axis length equal to x_len t_max = self.xdata[-1] if t_max > x_len: xmax = t_max xmin = t_max - x_len else: xmax = x_len xmin = 0 # Obtain manually set values for y axis limits try: ymax = float(self.ymax_control.manual_value()) except ValueError: ymax = 5.5 try: ymin = float(self.ymin_control.manual_value()) except ValueError: ymin = -0.5 # Set graph boundaries self.axes.set_xbound(lower=xmin, upper=xmax) self.axes.set_ybound(lower=ymin, upper=ymax) # Draws grid if "Show grid" is checked if self.cb_grid.IsChecked(): self.axes.grid(True, color='gray') else: self.axes.grid(False) # Set x and y data self.plot_data.set_xdata(self.xdata) self.plot_data.set_ydata(self.ydata) # Redraw the plot self.canvas.draw() def on_pause_button(self, event): self.paused = not self.paused def on_update_pause_button(self, event): label = "Resume" if self.paused else "Pause" self.pause_button.SetLabel(label) def on_cb_grid(self, event): self.draw_plot() def on_cb_xlab(self, event): self.draw_plot() def on_save_plot(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 refresh_graph(self): """Pulls new data from the interfacer and re-draws the plot.""" while True: # if paused do not add data, but still redraw the plot # (to respond to manual control changes) if not self.paused: # Pull the data from the interfacer data buffer y_buffer_draw = self.data.get_data() # Calculate length of data buffer y_len = len(y_buffer_draw) # Only attempt to update data if there is new data if not y_len == 0: # Calculate the time difference from last redraw, # and estimate time values for each y data point current_time = time.time() - self.start_time last_time = self.xdata[-1] t_int = (current_time - last_time) / y_len # Add new data values to data lists for y in y_buffer_draw: self.ydata.append(y) x_buffer_draw = np.arange(last_time + t_int, current_time + t_int, t_int) for x in x_buffer_draw: self.xdata.append(x) # Correct length of data lists if there is a difference if len(self.ydata) > len(self.xdata): lendiff = len(self.ydata) - len(self.xdata) del self.ydata[0:lendiff] if len(self.xdata) > len(self.ydata): lendiff = len(self.xdata) - len(self.ydata) del self.xdata[0:lendiff] # Limit length of data lists to maintain performance if len(self.xdata) > 10000: self.xdata = self.xdata[-10000:-1] self.ydata = self.ydata[-10000:-1] # Redraw the plot self.draw_plot() 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('')