import wx import matplotlib matplotlib.interactive(False) #Use the WxAgg back end. The Wx one takes too long to render matplotlib.use('WXAgg') from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg from matplotlib.figure import Figure import os from canvas import FigureCanvas #TODO: make the plottables interactive from plottables import Graph def show_tree(obj,d=0): """Handy function for displaying a tree of graph objects""" print "%s%s" % ("-"*d,obj.__class__.__name__) if 'get_children' in dir(obj): for a in obj.get_children(): show_tree(a,d+1) class PlotPanel(wx.Panel): """ The PlotPanel has a Figure and a Canvas. OnSize events simply set a flag, and the actually redrawing of the figure is triggered by an Idle event. """ def __init__(self, parent, id = -1, color = None,\ dpi = None, **kwargs): wx.Panel.__init__(self, parent, id = id, **kwargs) self.figure = Figure(None, dpi) #self.figure = pylab.Figure(None, dpi) #self.canvas = NoRepaintCanvas(self, -1, self.figure) self.canvas = FigureCanvas(self, -1, self.figure) self.SetColor(color) #self.Bind(wx.EVT_IDLE, self._onIdle) #self.Bind(wx.EVT_SIZE, self._onSize) self._resizeflag = True self._SetSize() self.subplot = self.figure.add_subplot(111) self.figure.subplots_adjust(left=.2, bottom=.2) self.yscale = 'linear' sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.canvas,1,wx.EXPAND) self.SetSizer(sizer) # Graph object to manage the plottables self.graph = Graph() self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu) # Define some constants self.colorlist = ['b','g','r','c','m','y'] self.symbollist = ['o','x','^','v','<','>','+','s','d','D','h','H','p'] def set_yscale(self, scale='linear'): self.subplot.set_yscale(scale) self.yscale = scale def get_yscale(self): return self.yscale def SetColor(self, rgbtuple): """Set figure and canvas colours to be the same""" if not rgbtuple: rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() col = [c/255.0 for c in rgbtuple] self.figure.set_facecolor(col) self.figure.set_edgecolor(col) self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) def _onSize(self, event): self._resizeflag = True def _onIdle(self, evt): if self._resizeflag: self._resizeflag = False self._SetSize() self.draw() 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 draw(self): """Where the actual drawing happens""" self.figure.canvas.draw_idle() def onSaveImage(self, evt): #figure.savefig print "Save image not implemented" path = None dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.png", wx.SAVE) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() mypath = os.path.basename(path) print path dlg.Destroy() if not path == None: self.subplot.figure.savefig(path,dpi=300, facecolor='w', edgecolor='w', orentation='portrait', papertype=None, format='png') def onContextMenu(self, event): """ Default context menu for a plot panel """ # Slicer plot popup menu slicerpop = wx.Menu() slicerpop.Append(313,'&Save image', 'Save image as PNG') wx.EVT_MENU(self, 313, self.onSaveImage) pos = event.GetPosition() pos = self.ScreenToClient(pos) self.PopupMenu(slicerpop, pos) ## The following is plottable functionality def properties(self,prop): """Set some properties of the graph. The set of properties is not yet determined. """ # The particulars of how they are stored and manipulated (e.g., do # we want an inventory internally) is not settled. I've used a # property dictionary for now. # # How these properties interact with a user defined style file is # even less clear. # Properties defined by plot self.subplot.set_xlabel(r"$%s$" % prop["xlabel"]) self.subplot.set_ylabel(r"$%s$" % prop["ylabel"]) self.subplot.set_title(prop["title"]) # Properties defined by user #self.axes.grid(True) def clear(self): """Reset the plot""" # TODO: Redraw is brutal. Render to a backing store and swap in # TODO: rather than redrawing on the fly. self.subplot.clear() self.subplot.hold(True) def render(self): """Commit the plot after all objects are drawn""" # TODO: this is when the backing store should be swapped in. from matplotlib.font_manager import FontProperties self.subplot.legend(prop=FontProperties(size=10)) #self.subplot.legend() pass def xaxis(self,label,units): """xaxis label and units. Axis labels know about units. We need to do this so that we can detect when axes are not commesurate. Currently this is ignored other than for formatting purposes. """ if units != "": label = label + " (" + units + ")" self.subplot.set_xlabel(label) pass def yaxis(self,label,units): """yaxis label and units.""" if units != "": label = label + " (" + units + ")" self.subplot.set_ylabel(label) pass def _connect_to_xlim(self,callback): """Bind the xlim change notification to the callback""" def process_xlim(axes): lo,hi = subplot.get_xlim() callback(lo,hi) self.subplot.callbacks.connect('xlim_changed',process_xlim) #def connect(self,trigger,callback): # print "PlotPanel.connect???" # if trigger == 'xlim': self._connect_to_xlim(callback) def points(self,x,y,dx=None,dy=None,color=0,symbol=0,label=None): """Draw markers with error bars""" self.subplot.set_yscale('linear') # Convert tuple (lo,hi) to array [(x-lo),(hi-x)] if dx != None and type(dx) == type(()): dx = nx.vstack((x-dx[0],dx[1]-x)).transpose() if dy != None and type(dy) == type(()): dy = nx.vstack((y-dy[0],dy[1]-y)).transpose() if dx==None and dy==None: h = self.subplot.plot(x,y,color=self._color(color), marker=self._symbol(symbol),linestyle='',label=label) else: col = self._color(color) self.subplot.errorbar(x, y, yerr=dy, xerr=None, ecolor=col, capsize=2,linestyle='', barsabove=False, marker=self._symbol(symbol), lolims=False, uplims=False, xlolims=False, xuplims=False,label=label, mec = col, mfc = col) self.subplot.set_yscale(self.yscale) def curve(self,x,y,dy=None,color=0,symbol=0,label=None): """Draw a line on a graph, possibly with confidence intervals.""" c = self._color(color) self.subplot.set_yscale('linear') hlist = self.subplot.plot(x,y,color=c,marker='',linestyle='-',label=label) self.subplot.set_yscale(self.yscale) def _color(self,c): """Return a particular colour""" return self.colorlist[c%len(self.colorlist)] def _symbol(self,s): """Return a particular symbol""" return self.symbollist[s%len(self.symbollist)] class NoRepaintCanvas(FigureCanvasWxAgg): """We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that the draw method is only called for the first two paint events. After that, the canvas will only be redrawn when it is resized. """ def __init__(self, *args, **kwargs): FigureCanvasWxAgg.__init__(self, *args, **kwargs) self._drawn = 0 def _onPaint(self, evt): """ Called when wxPaintEvt is generated """ if not self._isRealized: self.realize() if self._drawn < 2: self.draw(repaint = False) self._drawn += 1 self.gui_repaint(drawDC=wx.PaintDC(self))