class PlotPanel(wx.Panel): ''' Base class for the plotting in GenX - all the basic functionallity should be implemented in this class. The plots should be derived from this class. These classes should implement an update method to update the plots. ''' def __init__(self, parent, id = -1, color = None, dpi = None , style = wx.NO_FULL_REPAINT_ON_RESIZE|wx.EXPAND|wx.ALL , config = None, config_name = '', **kwargs): wx.Panel.__init__(self,parent, id = id, style = style, **kwargs) self.parent = parent self.callback_window = self self.config = config self.config_name = config_name self.figure = Figure(None,dpi) self.canvas = FigureCanvasWxAgg(self, -1, self.figure) self.canvas.SetExtraStyle(wx.EXPAND) self.SetColor(color) self.Bind(wx.EVT_IDLE, self._onIdle) self.Bind(wx.EVT_SIZE, self._onSize) self._resizeflag = True self.print_size = (15./2.54, 12./2.54) #self._SetSize() # Flags and bindings for zooming self.zoom = False self.zooming = False self.scale = 'linear' self.autoscale = True self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnLeftMouseButtonDown) self.canvas.Bind(wx.EVT_LEFT_UP, self.OnLeftMouseButtonUp) self.canvas.Bind(wx.EVT_MOTION, self.OnMouseMove) self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDblClick) self.canvas.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) cursor = wx.StockCursor(wx.CURSOR_CROSS) self.canvas.SetCursor(cursor) self.old_scale_state = True self.ax = None # Init printout stuff self.fig_printer = FigurePrinter(self) # Create the drawing bitmap self.bitmap =wx.EmptyBitmap(1, 1) # DEBUG_MSG("__init__() - bitmap w:%d h:%d" % (w,h), 2, self) self._isDrawn = False def SetColor(self, rgbtuple=None): ''' Set the figure and canvas color to be the same ''' if not rgbtuple: rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() col = [c/255. for c in rgbtuple] self.figure.set_facecolor(col) self.figure.set_edgecolor(col) self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) def _onSize(self, evt): self._resizeflag = True self._SetSize() #self.canvas.draw(repaint = False) def _onIdle(self, evt): if self._resizeflag: self._resizeflag = False self._SetSize() #self.canvas.gui_repaint(drawDC = wx.PaintDC(self)) def _SetSize(self, pixels = None): ''' This method can be called to force the Plot to be a desired size which defaults to the ClientSize of the Panel. ''' if not pixels: pixels = self.GetClientSize() self.canvas.SetSize(pixels) #self.figure.set_size_inches(pixels[0]/self.figure.get_dpi() #, pixels[1]/self.figure.get_dpi()) def ReadConfig(self): '''ReadConfig(self) --> None Reads in the config file ''' bool_items = ['zoom', 'autoscale'] bool_func = [self.SetZoom, self.SetAutoScale] if not self.config: return vals = [] for index in range(len(bool_items)): try: val = self.config.get_boolean(self.config_name,\ bool_items[index]) except io.OptionError as e: print('Could not locate option %s.%s'\ %(self.config_name, bool_items[index])) vals.append(None) else: vals.append(val) try: scale = self.config.get(self.config_name, 'y scale') string_sucess = True except io.OptionError as e: string_sucess = False print('Could not locate option %s.%s'\ %(self.config_name, 'scale')) else: self.SetYScale(scale) # This is done due to that the zoom and autoscale has to read # before any commands are issued in order not to overwrite # the config [bool_func[i](vals[i]) for i in range(len(vals)) if vals[i]] def WriteConfig(self): '''WriteConfig(self) --> None Writes the current settings to the config file ''' if self.config: self.config.set(self.config_name, 'zoom', self.GetZoom()) self.config.set(self.config_name, 'autoscale', self.GetAutoScale()) self.config.set(self.config_name, 'y scale', self.GetYScale()) def SetZoom(self, active = False): ''' set the zoomstate ''' #if not self.zoom_sel: #self.zoom_sel = RectangleSelector(self.ax,\ # self.box_select_callback, drawtype='box',useblit=False) #print help(self.zoom_sel.ignore) if active: #self.zoom_sel.ignore = lambda x: False self.zoom = True cursor = wx.StockCursor(wx.CURSOR_MAGNIFIER) self.canvas.SetCursor(cursor) if self.callback_window: evt = state_changed(zoomstate = True,\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) if self.ax: #self.ax.set_autoscale_on(False) self.old_scale_state = self.GetAutoScale() self.SetAutoScale(False) else: #self.zoom_sel.ignore = lambda x: True self.zoom = False cursor = wx.StockCursor(wx.CURSOR_CROSS) self.canvas.SetCursor(cursor) if self.callback_window: evt = state_changed(zoomstate = False,\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) if self.ax: #self.ax.set_autoscale_on(self.autoscale) self.SetAutoScale(self.old_scale_state) self.WriteConfig() def GetZoom(self): '''GetZoom(self) --> state [bool] Returns the zoom state of the plot panel. True = zoom active False = zoom inactive ''' return self.zoom def SetAutoScale(self, state): '''SetAutoScale(self, state) --> None Sets autoscale of the main axes wheter or not it should autoscale when plotting ''' #self.ax.set_autoscale_on(state) self.autoscale = state self.WriteConfig() evt = state_changed(zoomstate = self.GetZoom(),\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) def GetAutoScale(self): '''GetAutoScale(self) --> state [bool] Returns the autoscale state, true if the plots is automatically scaled for each plot command. ''' return self.autoscale def AutoScale(self, force = False): '''AutoScale(self) --> None A log safe way to autoscale the plots - the ordinary axis tight does not work for negative log data. This works! ''' if not (self.autoscale or force): return # If nothing is plotted no autoscale use defaults... if sum([len(line.get_ydata()) > 0 for line in self.ax.lines]) == 0: self.ax.set_xlim(0, 1) self.ax.set_ylim(1e-3, 1.0) return if self.scale == 'log': #print 'log scaling' # Find the lowest possible value of all the y-values that are #greater than zero. check so that y data contain data before min # is applied tmp = [line.get_ydata().compress(line.get_ydata() > 0.0).min()\ for line in self.ax.lines if array(line.get_ydata() > 0.0).sum() > 0] if len(tmp) > 0: ymin = min(tmp) else: ymin = 1e-3 tmp = [line.get_ydata().compress(line.get_ydata() > 0.0).max()\ for line in self.ax.lines if array(line.get_ydata() > 0.0).sum() > 0] if len(tmp) > 0: ymax = max(tmp) else: ymax = 1 else: ymin = min([array(line.get_ydata()).min()\ for line in self.ax.lines if len(line.get_ydata()) > 0]) ymax = max([array(line.get_ydata()).max()\ for line in self.ax.lines if len(line.get_ydata()) > 0]) tmp = [array(line.get_xdata()).min()\ for line in self.ax.lines if len(line.get_ydata()) > 0] if len(tmp) > 0: xmin = min(tmp) else: xmin = 0 tmp = [array(line.get_xdata()).max()\ for line in self.ax.lines if len(line.get_ydata()) > 0] if len(tmp) > 0: xmax = max(tmp) else: xmax = 1 # Set the limits #print 'Autoscaling to: ', ymin, ymax self.ax.set_xlim(xmin, xmax) self.ax.set_ylim(ymin*(1-sign(ymin)*0.05), ymax*(1+sign(ymax)*0.05)) #self.ax.set_yscale(self.scale) self.flush_plot() def SetYScale(self, scalestring): ''' SetYScale(self, scalestring) --> None Sets the y-scale of the main plotting axes. Currently accepts 'log' or 'lin'. ''' if self.ax: if scalestring == 'log': self.scale = 'log' self.AutoScale(force = True) try: self.ax.set_yscale('log') except OverflowError: self.AutoScale(force = True) elif scalestring == 'linear' or scalestring == 'lin': self.scale = 'linear' self.ax.set_yscale('linear') self.AutoScale(force = True) else: raise ValueError('Not allowed scaling') self.flush_plot() evt = state_changed(zoomstate = self.GetZoom(),\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) self.WriteConfig() def GetYScale(self): '''GetYScale(self) --> String Returns the current y-scale in use. Currently the string 'log' or 'linear'. If the axes does not exist it returns None. ''' if self.ax: return self.ax.get_yscale() else: return None def CopyToClipboard(self, event = None): '''CopyToClipboard(self, event) --> None Copy the plot to the clipboard. ''' self.canvas.Copy_to_Clipboard(event = event) def PrintSetup(self, event = None): '''PrintSetup(self) --> None Sets up the printer. Creates a dialog box ''' self.fig_printer.pageSetup() def PrintPreview(self, event = None): '''PrintPreview(self) --> None Prints a preview on screen. ''' self.fig_printer.previewFigure(self.figure) def Print(self, event= None): '''Print(self) --> None Print the figure. ''' self.fig_printer.printFigure(self.figure) def SetCallbackWindow(self, window): '''SetCallbackWindow(self, window) --> None Sets the callback window that should recieve the events from picking. ''' self.callback_window = window def OnLeftDblClick(self, event): if self.ax and self.zoom: tmp = self.GetAutoScale() self.SetAutoScale(True) self.AutoScale() self.SetAutoScale(tmp) #self.AutoScale() #self.flush_plot() #self.ax.set_autoscale_on(False) def OnLeftMouseButtonDown(self, event): self.start_pos = event.GetPosition() #print 'Left Mouse button pressed ', self.ax.transData.inverse_xy_tup(self.start_pos) class Point: pass p = Point() p.x, p.y = self.start_pos size = self.canvas.GetClientSize() p.y = (size.height - p.y) if self.zoom and self.ax: if mat_ver > zoom_ver: in_axes = self.ax.in_axes(p) else: in_axes = self.ax.in_axes(*self.start_pos) if in_axes: self.zooming = True self.cur_rect = None self.canvas.CaptureMouse() else: self.zooming = False elif self.ax: size = self.canvas.GetClientSize() if mat_ver > zoom_ver: xy = self.ax.transData.inverted().transform(\ array([self.start_pos[0], size.height-self.start_pos[1]])\ [newaxis,:]) x, y = xy[0,0], xy[0,1] else: x, y = self.ax.transData.inverse_xy_tup(\ (self.start_pos[0], size.height - self.start_pos[1])) if self.callback_window: evt = plot_position(text = '(%.3e, %.3e)'%(x, y)) wx.PostEvent(self.callback_window, evt) #print x,y def OnMouseMove(self, event): if self.zooming and event.Dragging() and event.LeftIsDown(): self.cur_pos = event.GetPosition() #print 'Mouse Move ', self.ax.transData.inverse_xy_tup(self.cur_pos) class Point: pass p = Point() p.x, p.y = self.cur_pos size = self.canvas.GetClientSize() p.y = (size.height - p.y) if mat_ver > zoom_ver: in_axes = self.ax.in_axes(p) else: in_axes = self.ax.in_axes(*self.start_pos) if in_axes: new_rect = (min(self.start_pos[0], self.cur_pos[0]), min(self.start_pos[1], self.cur_pos[1]), abs(self.cur_pos[0] - self.start_pos[0]), abs(self.cur_pos[1] - self.start_pos[1])) self._DrawAndErase(new_rect, self.cur_rect) self.cur_rect = new_rect #event.Skip() def OnLeftMouseButtonUp(self, event): if self.canvas.HasCapture(): #print 'Left Mouse button up' self.canvas.ReleaseMouse() if self.zooming and self.cur_rect: # Note: The coordinte system for matplotlib have a different # direction of the y-axis and a different origin! size = self.canvas.GetClientSize() if mat_ver > zoom_ver: start = self.ax.transData.inverted().transform(\ array([self.start_pos[0], size.height-self.start_pos[1]])[newaxis,:]) end = self.ax.transData.inverted().transform(\ array([self.cur_pos[0], size.height-self.cur_pos[1]])[newaxis, :]) xend, yend = end[0,0], end[0,1] xstart, ystart = start[0,0], start[0,1] else: xstart, ystart = self.ax.transData.inverse_xy_tup(\ (self.start_pos[0], size.height-self.start_pos[1])) xend, yend = self.ax.transData.inverse_xy_tup(\ (self.cur_pos[0], size.height-self.cur_pos[1])) #print xstart, xend #print ystart, yend self.ax.set_xlim(min(xstart,xend), max(xstart,xend)) self.ax.set_ylim(min(ystart,yend), max(ystart,yend)) self.flush_plot() self.zooming = False def _DrawAndErase(self, box_to_draw, box_to_erase = None): '''_DrawAndErase(self, box_to_draw, box_to_erase = None) --> None ''' dc = wx.ClientDC(self.canvas) # dc.BeginDrawing() dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT)) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetLogicalFunction(wx.XOR) if box_to_erase: dc.DrawRectangle(*box_to_erase) dc.DrawRectangle(*box_to_draw) # dc.EndDrawing() def OnContextMenu(self, event): '''OnContextMenu(self, event) --> None Callback to show the popmenu for the plot which allows various settings to be made. ''' menu = wx.Menu() zoomID = wx.NewId() menu.AppendCheckItem(zoomID, "Zoom") menu.Check(zoomID, self.GetZoom()) def OnZoom(event): self.SetZoom(not self.GetZoom()) self.Bind(wx.EVT_MENU, OnZoom, id = zoomID) zoomallID = wx.NewId() menu.Append(zoomallID, 'Zoom All') def zoomall(event): tmp = self.GetAutoScale() self.SetAutoScale(True) self.AutoScale() self.SetAutoScale(tmp) #self.flush_plot() self.Bind(wx.EVT_MENU, zoomall, id = zoomallID) copyID = wx.NewId() menu.Append(copyID, "Copy") def copy(event): self.CopyToClipboard() self.Bind(wx.EVT_MENU, copy, id = copyID) yscalemenu = wx.Menu() logID = wx.NewId() linID = wx.NewId() yscalemenu.AppendRadioItem(logID, "log") yscalemenu.AppendRadioItem(linID, "linear") menu.AppendMenu(-1, "y-scale", yscalemenu) if self.GetYScale() == 'log': yscalemenu.Check(logID, True) else: yscalemenu.Check(linID, True) def yscale_log(event): if self.ax: self.SetYScale('log') self.AutoScale() self.flush_plot() def yscale_lin(event): if self.ax: self.SetYScale('lin') self.AutoScale() self.flush_plot() self.Bind(wx.EVT_MENU, yscale_log, id = logID) self.Bind(wx.EVT_MENU, yscale_lin, id = linID) autoscaleID = wx.NewId() menu.AppendCheckItem(autoscaleID, "Autoscale") menu.Check(autoscaleID, self.GetAutoScale()) def OnAutoScale(event): self.SetAutoScale(not self.GetAutoScale()) self.Bind(wx.EVT_MENU, OnAutoScale, id = autoscaleID) # Time to show the menu self.PopupMenu(menu) menu.Destroy() def flush_plot(self): #self._SetSize() #self.canvas.gui_repaint(drawDC = wx.PaintDC(self)) #self.ax.set_yscale(self.scale) self.canvas.draw() def update(self, data): pass