source: sasview/guitools/PlotPanel.py @ 831149e

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 831149e was 831149e, checked in by Gervaise Alina <gervyh@…>, 16 years ago

working better .Since have to think about the zoom

  • Property mode set to 100644
File size: 15.9 KB
Line 
1import wx.lib.newevent
2import matplotlib
3matplotlib.interactive(False)
4#Use the WxAgg back end. The Wx one takes too long to render
5matplotlib.use('WXAgg')
6from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
7from matplotlib.figure import Figure
8import os
9import fittings
10import transform
11from canvas import FigureCanvas
12#TODO: make the plottables interactive
13
14from plottables import Graph
15#(FuncFitEvent, EVT_FUNC_FIT) = wx.lib.newevent.NewEvent()
16import math,pylab
17def show_tree(obj,d=0):
18    """Handy function for displaying a tree of graph objects"""
19    print "%s%s" % ("-"*d,obj.__class__.__name__)
20    if 'get_children' in dir(obj):
21        for a in obj.get_children(): show_tree(a,d+1)
22
23
24
25class PlotPanel(wx.Panel):
26    """
27    The PlotPanel has a Figure and a Canvas. OnSize events simply set a
28    flag, and the actually redrawing of the
29    figure is triggered by an Idle event.
30    """
31    def __init__(self, parent, id = -1, color = None,\
32        dpi = None, **kwargs):
33        wx.Panel.__init__(self, parent, id = id, **kwargs)
34        self.parent = parent
35        self.figure = Figure(None, dpi)
36        #self.figure = pylab.Figure(None, dpi)
37        #self.canvas = NoRepaintCanvas(self, -1, self.figure)
38        self.canvas = FigureCanvas(self, -1, self.figure)
39        self.SetColor(color)
40        #self.Bind(wx.EVT_IDLE, self._onIdle)
41        #self.Bind(wx.EVT_SIZE, self._onSize)
42        self._resizeflag = True
43        self._SetSize()
44        self.subplot = self.figure.add_subplot(111)
45        self.figure.subplots_adjust(left=.2, bottom=.2)
46        self.yscale = 'linear'
47        self.xscale = 'linear'
48        sizer = wx.BoxSizer(wx.VERTICAL)
49        sizer.Add(self.canvas,1,wx.EXPAND)
50        self.SetSizer(sizer)
51
52        # Graph object to manage the plottables
53        self.graph = Graph()
54        #self.Bind(EVT_FUNC_FIT, self.onFitRange)
55        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
56        #self.Bind(EVT_PROPERTY, self._onEVT_FUNC_PROPERTY)
57        # Define some constants
58        self.colorlist = ['b','g','r','c','m','y']
59        self.symbollist = ['o','x','^','v','<','>','+','s','d','D','h','H','p']
60        #User scale
61        self.xscales ="x"
62        self.yscales ="log10(y)"
63        # keep track if the previous transformation of x and y in Property dialog
64        self.prevXtrans =" "
65        self.prevYtrans =" "
66       
67    def returnTrans(self):
68        return self.xscales,self.yscales
69       
70    def setTrans(self,xtrans,ytrans): 
71        """
72            @param xtrans: set x transformation on Property dialog
73            @param ytrans: set y transformation on Property dialog
74        """
75        self.prevXtrans =xtrans
76        self.prevYtrans =ytrans
77       
78    def onFitting(self, event): 
79        """
80            when clicking on linear Fit on context menu , display Fitting Dialog
81        """
82        list =[]
83        list = self.graph.returnPlottable()
84        from fitDialog import LinearFit
85       
86        if len(list.keys())>0:
87            first_item = list.keys()[0]
88            #print first_item, list[first_item].__class__.__name__
89            dlg = LinearFit( None, first_item, self.onFitDisplay,self.returnTrans, -1, 'Fitting')
90            dlg.ShowModal() 
91
92    def _onProperties(self, event):
93        """
94            when clicking on Properties on context menu ,The Property dialog is displayed
95            The user selects a transformation for x or y value and a new plot is displayed
96        """
97        from PropertyDialog import Properties
98        dial = Properties(self, -1, 'Properties')
99        dial.setValues( self.prevXtrans, self.prevYtrans )
100        if dial.ShowModal() == wx.ID_OK:
101            self.xscales, self.yscales = dial.getValues()
102            self._onEVT_FUNC_PROPERTY()
103        dial.Destroy()
104 
105    def set_yscale(self, scale='linear'):
106        """
107            Set the scale on Y-axis
108            @param scale: the scale of y-axis
109        """
110        self.subplot.set_yscale(scale)
111        self.yscale = scale
112       
113    def get_yscale(self):
114        """
115             @return: Y-axis scale
116        """
117        return self.yscale
118   
119    def set_xscale(self, scale='linear'):
120        """
121            Set the scale on x-axis
122            @param scale: the scale of x-axis
123        """
124        self.subplot.set_xscale(scale)
125        self.xscale = scale
126       
127    def get_xscale(self):
128        """
129             @return: x-axis scale
130        """
131        return self.xscale
132
133    def SetColor(self, rgbtuple):
134        """Set figure and canvas colours to be the same"""
135        if not rgbtuple:
136            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
137        col = [c/255.0 for c in rgbtuple]
138        self.figure.set_facecolor(col)
139        self.figure.set_edgecolor(col)
140        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))
141
142    def _onSize(self, event):
143        self._resizeflag = True
144
145    def _onIdle(self, evt):
146        if self._resizeflag:
147            self._resizeflag = False
148            self._SetSize()
149            self.draw()
150
151    def _SetSize(self, pixels = None):
152        """
153        This method can be called to force the Plot to be a desired size, which defaults to
154        the ClientSize of the panel
155        """
156        if not pixels:
157            pixels = self.GetClientSize()
158        self.canvas.SetSize(pixels)
159        self.figure.set_size_inches(pixels[0]/self.figure.get_dpi(),
160        pixels[1]/self.figure.get_dpi())
161
162    def draw(self):
163        """Where the actual drawing happens"""
164        self.figure.canvas.draw_idle()
165       
166
167
168    def onSaveImage(self, evt):
169        #figure.savefig
170        #print "Save image not implemented"
171        path = None
172        dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.png", wx.SAVE)
173        if dlg.ShowModal() == wx.ID_OK:
174            path = dlg.GetPath()
175            mypath = os.path.basename(path)
176            print path
177        dlg.Destroy()
178        if not path == None:
179            self.subplot.figure.savefig(path,dpi=300, facecolor='w', edgecolor='w',
180                                        orentation='portrait', papertype=None, format='png')
181       
182    def onContextMenu(self, event):
183        """
184            Default context menu for a plot panel
185        """
186        # Slicer plot popup menu
187        slicerpop = wx.Menu()
188        slicerpop.Append(313,'&Save image', 'Save image as PNG')
189        wx.EVT_MENU(self, 313, self.onSaveImage)
190       
191        slicerpop.Append(316, '&Load 1D data file')
192        wx.EVT_MENU(self, 316, self._onLoad1DData)
193       
194        slicerpop.AppendSeparator()
195        slicerpop.Append(315, '&Properties')
196        wx.EVT_MENU(self, 315, self._onProperties)
197       
198        slicerpop.AppendSeparator()
199        slicerpop.Append(317, '&Linear Fit')
200        wx.EVT_MENU(self, 317, self.onFitting)
201       
202        pos = event.GetPosition()
203        pos = self.ScreenToClient(pos)
204        self.PopupMenu(slicerpop, pos)
205   
206    ## The following is plottable functionality
207
208
209    def properties(self,prop):
210        """Set some properties of the graph.
211       
212        The set of properties is not yet determined.
213        """
214        # The particulars of how they are stored and manipulated (e.g., do
215        # we want an inventory internally) is not settled.  I've used a
216        # property dictionary for now.
217        #
218        # How these properties interact with a user defined style file is
219        # even less clear.
220
221        # Properties defined by plot
222        self.subplot.set_xlabel(r"$%s$" % prop["xlabel"])
223        self.subplot.set_ylabel(r"$%s$" % prop["ylabel"])
224        self.subplot.set_title(prop["title"])
225
226        # Properties defined by user
227        #self.axes.grid(True)
228
229    def clear(self):
230        """Reset the plot"""
231       
232        # TODO: Redraw is brutal.  Render to a backing store and swap in
233        # TODO: rather than redrawing on the fly.
234        self.subplot.clear()
235        self.subplot.hold(True)
236
237
238    def render(self):
239        """Commit the plot after all objects are drawn"""
240        # TODO: this is when the backing store should be swapped in.
241        from matplotlib.font_manager import FontProperties
242        self.subplot.legend(prop=FontProperties(size=10))
243        #self.subplot.legend()
244        pass
245
246    def xaxis(self,label,units):
247        """xaxis label and units.
248       
249        Axis labels know about units.
250       
251        We need to do this so that we can detect when axes are not
252        commesurate.  Currently this is ignored other than for formatting
253        purposes.
254        """
255        if units != "": label = label + " (" + units + ")"
256        self.subplot.set_xlabel(label)
257        pass
258   
259    def yaxis(self,label,units):
260        """yaxis label and units."""
261        if units != "": label = label + " (" + units + ")"
262        self.subplot.set_ylabel(label)
263        pass
264
265    def _connect_to_xlim(self,callback):
266        """Bind the xlim change notification to the callback"""
267        def process_xlim(axes):
268            lo,hi = subplot.get_xlim()
269            callback(lo,hi)
270        self.subplot.callbacks.connect('xlim_changed',process_xlim)
271   
272    #def connect(self,trigger,callback):
273    #    print "PlotPanel.connect???"
274    #    if trigger == 'xlim': self._connect_to_xlim(callback)
275
276    def points(self,x,y,dx=None,dy=None,color=0,symbol=0,label=None):
277        """Draw markers with error bars"""
278        self.subplot.set_yscale('linear')
279        self.subplot.set_xscale('linear')
280        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
281        if dx != None and type(dx) == type(()):
282            dx = nx.vstack((x-dx[0],dx[1]-x)).transpose()
283        if dy != None and type(dy) == type(()):
284            dy = nx.vstack((y-dy[0],dy[1]-y)).transpose()
285
286        if dx==None and dy==None:
287            h = self.subplot.plot(x,y,color=self._color(color),
288                                   marker=self._symbol(symbol),linestyle='',label=label)
289        else:
290            self.subplot.errorbar(x, y, yerr=dy, xerr=None,
291             ecolor=self._color(color), capsize=2,linestyle='', barsabove=False,
292             marker=self._symbol(symbol),
293             lolims=False, uplims=False,
294             xlolims=False, xuplims=False,label=label)
295           
296        self.subplot.set_yscale(self.yscale)
297        self.subplot.set_xscale(self.xscale)
298
299    def curve(self,x,y,dy=None,color=0,symbol=0,label=None):
300        """Draw a line on a graph, possibly with confidence intervals."""
301        c = self._color(color)
302        self.subplot.set_yscale('linear')
303        self.subplot.set_xscale('linear')
304       
305        hlist = self.subplot.plot(x,y,color=c,marker='',linestyle='-',label=label)
306       
307        self.subplot.set_yscale(self.yscale)
308        self.subplot.set_xscale(self.xscale)
309
310    def _color(self,c):
311        """Return a particular colour"""
312        return self.colorlist[c%len(self.colorlist)]
313
314    def _symbol(self,s):
315        """Return a particular symbol"""
316        return self.symbollist[s%len(self.symbollist)]
317   
318    def _onEVT_FUNC_PROPERTY(self):
319        """
320             Receive the x and y transformation from myDialog,Transforms x and y in View
321              and set the scale   
322        """ 
323        list =[]
324        list = self.graph.returnPlottable()
325        for item in list:
326            if ( self.xscales=="x" ):
327                item.transform_x( transform.toX,transform.errToX )
328                self.set_xscale("linear")
329                name, units = item.get_xaxis()
330                self.graph.xaxis("%s" % name,  "%s^{-1}" % units)
331               
332            if ( self.xscales=="x^(2)" ):
333                item.transform_x( transform.toX2,transform.errToX2 )
334                self.set_xscale('linear')
335                name, units = item.get_xaxis()
336                self.graph.xaxis("%s^{2}" % name,  "%s^{-2}" % units)
337               
338            if (self.xscales=="log10(x)" ):
339                item.transform_x( transform.toX, transform.errToLogX )
340                self.set_xscale("log")
341                name, units = item.get_xaxis()
342                self.graph.xaxis("%s" % name,  "%s^{-1}" % units)
343               
344            if ( self.yscales=="ln(y)" ):
345                item.transform_y( transform.toLogX, transform.errToLogX )
346                self.set_yscale("linear")
347                name, units = item.get_yaxis()
348                self.graph.yaxis("%s" % name,  "%s^{-1}" % units)
349               
350            if ( self.yscales=="y" ):
351                item.transform_y( transform.toX, transform.errToX )
352                self.set_yscale("linear")
353                name, units = item.get_yaxis()
354                self.graph.yaxis("%s" % name,  "%s^{-1}" % units)
355               
356            if ( self.yscales=="log10(y)" ): 
357                item.transform_y( transform.toX, transform.errToLogX)
358                self.set_yscale("log") 
359                name, units = item.get_yaxis()
360                self.graph.yaxis("%s" % name,  "%s^{-1}" % units)
361               
362            if ( self.yscales=="y^(2)" ):
363                item.transform_y( transform.toX2, transform.errToX2 )   
364                self.set_yscale("linear")
365                name, units = item.get_yaxis()
366                self.graph.yaxis("%s^2" % name,  "%s^{-2}" % units)
367            if ( self.yscales =="1/y"):
368                item.transform_y( transform.toOneOverX , transform.errOneOverX )
369                self.set_yscale("linear")
370                name, units = item.get_yaxis()
371                self.graph.yaxis("%s" % name,  "%s" % units)
372            if ( self.yscales =="1/sqrt(y)" ):
373                item.transform_y( transform.toOneOverSqrtX ,transform.errOneOverSqrtX )
374                self.set_yscale("linear")
375                name, units = item.get_yaxis()
376                self.graph.yaxis("%s" %name,  "%s" % units)
377               
378            if ( self.yscales =="ln(y*x)"):
379                item.transform_y( transform.toLogXY ,transform.errToLogXY )
380                self.set_yscale("linear")
381                yname, yunits = item.get_yaxis()
382                xname, xunits = item.get_xaxis()
383                self.graph.yaxis("%s%s" % (yname,xname),  "%s^{-1}%s^{-1}" % (yunits,xunits))
384               
385            if ( self.yscales =="ln(y*x^(2)"):
386                item.transform_y( transform.toYX2 ,transform.ToYX2 )
387                self.set_yscale("linear")
388                yname, yunits = item.get_yaxis()
389                xname, xunits = item.get_xaxis()
390                self.graph.yaxis("%s%s^{2}" % (yname,xname),  "%s^{-1}%s^{-2}" % (yunits,xunits))
391           
392            if ( self.yscales =="ln(y*x^(4))"):
393                item.transform_y( transform.toLogYX4 ,transform.errToLogYX4 )
394                self.set_yscale("linear")
395                yname, yunits = item.get_yaxis()
396                xname, xunits = item.get_xaxis()
397                self.graph.yaxis("%s%s^{4}" % (yname,xname),  "%s^{-1}%s^{-4}" % (yunits,xunits))
398   
399        self.prevXtrans = self.xscales
400        self.prevYtrans = self.yscales 
401       
402        self.graph.render(self)
403        self.subplot.figure.canvas.draw_idle()
404    def onFitDisplay(self, plottable):
405        """
406            Add a new plottable into the graph .In this case this plottable will be used
407            to fit some data
408            @param plottable: the plottable to plot
409        """
410        plottable.reset_view()
411        self.graph.add(plottable)
412        self.graph.render(self)
413       
414        self.subplot.figure.canvas.draw_idle()
415        self.graph.delete(plottable)
416     
417   
418   
419class NoRepaintCanvas(FigureCanvasWxAgg):
420    """We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that
421    the draw method is only called for the first two paint events. After that,
422    the canvas will only be redrawn when it is resized.
423    """
424    def __init__(self, *args, **kwargs):
425        FigureCanvasWxAgg.__init__(self, *args, **kwargs)
426        self._drawn = 0
427
428    def _onPaint(self, evt):
429        """
430        Called when wxPaintEvt is generated
431        """
432        if not self._isRealized:
433            self.realize()
434        if self._drawn < 2:
435            self.draw(repaint = False)
436            self._drawn += 1
437        self.gui_repaint(drawDC=wx.PaintDC(self))
438           
Note: See TracBrowser for help on using the repository browser.