source: sasview/guitools/PlotPanel.py @ 7a03e65

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 7a03e65 was 7a03e65, checked in by Gervaise Alina <gervyh@…>, 17 years ago

plotpanel modified plottables and fitdialog

  • Property mode set to 100644
File size: 21.2 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
10from canvas import FigureCanvas
11#TODO: make the plottables interactive
12
13from plottables import Graph
14#(FuncFitEvent, EVT_FUNC_FIT) = wx.lib.newevent.NewEvent()
15import math,pylab
16def show_tree(obj,d=0):
17    """Handy function for displaying a tree of graph objects"""
18    print "%s%s" % ("-"*d,obj.__class__.__name__)
19    if 'get_children' in dir(obj):
20        for a in obj.get_children(): show_tree(a,d+1)
21
22
23
24class PlotPanel(wx.Panel):
25    """
26    The PlotPanel has a Figure and a Canvas. OnSize events simply set a
27    flag, and the actually redrawing of the
28    figure is triggered by an Idle event.
29    """
30    def __init__(self, parent, id = -1, color = None,\
31        dpi = None, **kwargs):
32        wx.Panel.__init__(self, parent, id = id, **kwargs)
33        self.parent = parent
34        self.figure = Figure(None, dpi)
35        #self.figure = pylab.Figure(None, dpi)
36        #self.canvas = NoRepaintCanvas(self, -1, self.figure)
37        self.canvas = FigureCanvas(self, -1, self.figure)
38        self.SetColor(color)
39        #self.Bind(wx.EVT_IDLE, self._onIdle)
40        #self.Bind(wx.EVT_SIZE, self._onSize)
41        self._resizeflag = True
42        self._SetSize()
43        self.subplot = self.figure.add_subplot(111)
44        self.figure.subplots_adjust(left=.2, bottom=.2)
45        self.yscale = 'linear'
46        self.xscale = 'linear'
47        sizer = wx.BoxSizer(wx.VERTICAL)
48        sizer.Add(self.canvas,1,wx.EXPAND)
49        self.SetSizer(sizer)
50
51        # Graph object to manage the plottables
52        self.graph = Graph()
53        #self.Bind(EVT_FUNC_FIT, self.onFitRange)
54        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
55        #self.Bind(EVT_PROPERTY, self._onEVT_FUNC_PROPERTY)
56        # Define some constants
57        self.colorlist = ['b','g','r','c','m','y']
58        self.symbollist = ['o','x','^','v','<','>','+','s','d','D','h','H','p']
59        #User scale
60        self.xscales ="x"
61        self.yscales ="Log(y)"
62        # keep track if the previous transformation of x and y in Property dialog
63        self.prevXtrans =" "
64        self.prevYtrans =" "
65       
66    def returnTrans(self):
67        return self.xscales,self.yscales
68       
69    def setTrans(self,xtrans,ytrans): 
70        """
71            @param xtrans: set x transformation on Property dialog
72            @param ytrans: set y transformation on Property dialog
73        """
74        self.prevXtrans =xtrans
75        self.prevYtrans =ytrans
76       
77    def onFitting(self, event): 
78        """
79            when clicking on linear Fit on context menu , display Fitting Dialog
80        """
81        list =[]
82        list = self.graph.returnPlottable()
83        from fitDialog import LinearFit
84       
85        if len(list.keys())>0:
86            first_item = list.keys()[0]
87            #print first_item, list[first_item].__class__.__name__
88            dlg = LinearFit( None, first_item, self.onFitDisplay,self.returnTrans, -1, 'Fitting')
89            dlg.ShowModal() 
90
91    def _onProperties(self, event):
92        """
93            when clicking on Properties on context menu ,The Property dialog is displayed
94            The user selects a transformation for x or y value and a new plot is displayed
95        """
96        from PropertyDialog import Properties
97        dial = Properties(self, -1, 'Properties')
98        dial.setValues( self.prevXtrans, self.prevYtrans )
99        if dial.ShowModal() == wx.ID_OK:
100            self.xscales, self.yscales = dial.getValues()
101            self._onEVT_FUNC_PROPERTY()
102        dial.Destroy()
103           
104    def toX(self,x,y=None):
105        """
106            This function is used to load value on Plottable.View
107            @param x: Float value
108            @return x,
109        """
110        return x
111   
112    def toX2(self,x,y=None):
113        """
114            This function is used to load value on Plottable.View
115            Calculate x^(2)
116            @param x: float value
117        """
118        return x*x
119   
120    def fromX2(self,x,y=None):
121         """
122             This function is used to load value on Plottable.View
123            Calculate square root of x
124            @param x: float value
125         """
126         if not x >=0 :
127             raise ValueError, "square root of a negative value "
128         else:
129             return math.sqrt(x)
130    def toLogX(self,x,y=None):
131        """
132            This function is used to load value on Plottable.View
133            calculate log x
134            @param x: float value
135        """
136        if not x > 0:
137            raise ValueError, "Log(X)of a negative value "
138        else:
139            return math.log(x)
140       
141    def toOneOverX(self,x,y=None):
142        if x !=0:
143            return 1/x
144        else:
145            raise ValueError,"cannot divide by zero"
146    def toOneOverSqrtX(self,x=None,y=None):
147        if y!=None:
148            if y > 0:
149                return 1/math.sqrt(y)
150            else:
151                raise ValueError,"cannot be computed"
152        if x!= None:
153            if x > 0:
154                return 1/math.sqrt(x)
155            else:
156                raise ValueError,"cannot be computed"
157       
158    def toLogYX2(self,x,y):
159        if y*(x**2) >0:
160            return math.log(y*(x**2))
161        else:
162             raise ValueError,"cannot be computed"
163         
164         
165    def toYX2(self,x,y):
166        return (x**2)*y
167   
168   
169    def toXY(self,x,y):
170        return x*y
171   
172   
173    def toLogXY(self,x,y):
174        """
175            This function is used to load value on Plottable.View
176            calculate log x
177            @param x: float value
178        """
179        if not x*y > 0:
180            raise ValueError, "Log(X*Y)of a negative value "
181        else:
182            return math.log(x*y)
183       
184    def fromLogXY(self,x):
185        """
186            This function is used to load value on Plottable.View
187            Calculate e^(x)
188            @param x: float value
189        """
190        return math.exp(x*y)
191   
192    def set_yscale(self, scale='linear'):
193        """
194            Set the scale on Y-axis
195            @param scale: the scale of y-axis
196        """
197        self.subplot.set_yscale(scale)
198        self.yscale = scale
199       
200    def get_yscale(self):
201        """
202             @return: Y-axis scale
203        """
204        return self.yscale
205   
206    def set_xscale(self, scale='linear'):
207        """
208            Set the scale on x-axis
209            @param scale: the scale of x-axis
210        """
211        self.subplot.set_xscale(scale)
212        self.xscale = scale
213       
214    def get_xscale(self):
215        """
216             @return: x-axis scale
217        """
218        return self.xscale
219
220    def SetColor(self, rgbtuple):
221        """Set figure and canvas colours to be the same"""
222        if not rgbtuple:
223            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
224        col = [c/255.0 for c in rgbtuple]
225        self.figure.set_facecolor(col)
226        self.figure.set_edgecolor(col)
227        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))
228
229    def _onSize(self, event):
230        self._resizeflag = True
231
232    def _onIdle(self, evt):
233        if self._resizeflag:
234            self._resizeflag = False
235            self._SetSize()
236            self.draw()
237
238    def _SetSize(self, pixels = None):
239        """
240        This method can be called to force the Plot to be a desired size, which defaults to
241        the ClientSize of the panel
242        """
243        if not pixels:
244            pixels = self.GetClientSize()
245        self.canvas.SetSize(pixels)
246        self.figure.set_size_inches(pixels[0]/self.figure.get_dpi(),
247        pixels[1]/self.figure.get_dpi())
248
249    def draw(self):
250        """Where the actual drawing happens"""
251        self.figure.canvas.draw_idle()
252       
253
254
255    def onSaveImage(self, evt):
256        #figure.savefig
257        #print "Save image not implemented"
258        path = None
259        dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.png", wx.SAVE)
260        if dlg.ShowModal() == wx.ID_OK:
261            path = dlg.GetPath()
262            mypath = os.path.basename(path)
263            print path
264        dlg.Destroy()
265        if not path == None:
266            self.subplot.figure.savefig(path,dpi=300, facecolor='w', edgecolor='w',
267                                        orentation='portrait', papertype=None, format='png')
268       
269    def onContextMenu(self, event):
270        """
271            Default context menu for a plot panel
272        """
273        # Slicer plot popup menu
274        slicerpop = wx.Menu()
275        slicerpop.Append(313,'&Save image', 'Save image as PNG')
276        wx.EVT_MENU(self, 313, self.onSaveImage)
277        slicerpop.Append(316, '&Load 1D data file')
278       
279        wx.EVT_MENU(self, 314, self.onSave1DData)
280        wx.EVT_MENU(self, 316, self._onLoad1DData)
281        slicerpop.AppendSeparator()
282        slicerpop.Append(315, '&Properties')
283       
284        slicerpop.AppendSeparator()
285        slicerpop.Append(317, '&Linear Fit')
286       
287        wx.EVT_MENU(self, 314, self.onSave1DData)
288        wx.EVT_MENU(self, 316, self._onLoad1DData)
289        wx.EVT_MENU(self, 315, self._onProperties)
290        wx.EVT_MENU(self, 317, self.onFitting)
291       
292        pos = event.GetPosition()
293        pos = self.ScreenToClient(pos)
294        self.PopupMenu(slicerpop, pos)
295   
296    ## The following is plottable functionality
297
298
299    def properties(self,prop):
300        """Set some properties of the graph.
301       
302        The set of properties is not yet determined.
303        """
304        # The particulars of how they are stored and manipulated (e.g., do
305        # we want an inventory internally) is not settled.  I've used a
306        # property dictionary for now.
307        #
308        # How these properties interact with a user defined style file is
309        # even less clear.
310
311        # Properties defined by plot
312        self.subplot.set_xlabel(r"$%s$" % prop["xlabel"])
313        self.subplot.set_ylabel(r"$%s$" % prop["ylabel"])
314        self.subplot.set_title(prop["title"])
315
316        # Properties defined by user
317        #self.axes.grid(True)
318
319    def clear(self):
320        """Reset the plot"""
321       
322        # TODO: Redraw is brutal.  Render to a backing store and swap in
323        # TODO: rather than redrawing on the fly.
324        self.subplot.clear()
325        self.subplot.hold(True)
326
327
328    def render(self):
329        """Commit the plot after all objects are drawn"""
330        # TODO: this is when the backing store should be swapped in.
331        from matplotlib.font_manager import FontProperties
332        self.subplot.legend(prop=FontProperties(size=10))
333        #self.subplot.legend()
334        pass
335
336    def xaxis(self,label,units):
337        """xaxis label and units.
338       
339        Axis labels know about units.
340       
341        We need to do this so that we can detect when axes are not
342        commesurate.  Currently this is ignored other than for formatting
343        purposes.
344        """
345        if units != "": label = label + " (" + units + ")"
346        self.subplot.set_xlabel(label)
347        pass
348   
349    def yaxis(self,label,units):
350        """yaxis label and units."""
351        if units != "": label = label + " (" + units + ")"
352        self.subplot.set_ylabel(label)
353        pass
354
355    def _connect_to_xlim(self,callback):
356        """Bind the xlim change notification to the callback"""
357        def process_xlim(axes):
358            lo,hi = subplot.get_xlim()
359            callback(lo,hi)
360        self.subplot.callbacks.connect('xlim_changed',process_xlim)
361   
362    #def connect(self,trigger,callback):
363    #    print "PlotPanel.connect???"
364    #    if trigger == 'xlim': self._connect_to_xlim(callback)
365
366    def points(self,x,y,dx=None,dy=None,color=0,symbol=0,label=None):
367        """Draw markers with error bars"""
368        self.subplot.set_yscale('linear')
369        self.subplot.set_xscale('linear')
370        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
371        if dx != None and type(dx) == type(()):
372            dx = nx.vstack((x-dx[0],dx[1]-x)).transpose()
373        if dy != None and type(dy) == type(()):
374            dy = nx.vstack((y-dy[0],dy[1]-y)).transpose()
375
376        if dx==None and dy==None:
377            h = self.subplot.plot(x,y,color=self._color(color),
378                                   marker=self._symbol(symbol),linestyle='',label=label)
379        else:
380            self.subplot.errorbar(x, y, yerr=dy, xerr=None,
381             ecolor=self._color(color), capsize=2,linestyle='', barsabove=False,
382             marker=self._symbol(symbol),
383             lolims=False, uplims=False,
384             xlolims=False, xuplims=False,label=label)
385           
386        self.subplot.set_yscale(self.yscale)
387        self.subplot.set_xscale(self.xscale)
388
389    def curve(self,x,y,dy=None,color=0,symbol=0,label=None):
390        """Draw a line on a graph, possibly with confidence intervals."""
391        c = self._color(color)
392        self.subplot.set_yscale('linear')
393        self.subplot.set_xscale('linear')
394       
395        hlist = self.subplot.plot(x,y,color=c,marker='',linestyle='-',label=label)
396       
397        self.subplot.set_yscale(self.yscale)
398        self.subplot.set_xscale(self.xscale)
399
400    def _color(self,c):
401        """Return a particular colour"""
402        return self.colorlist[c%len(self.colorlist)]
403
404    def _symbol(self,s):
405        """Return a particular symbol"""
406        return self.symbollist[s%len(self.symbollist)]
407   
408    def _onEVT_FUNC_PROPERTY(self):
409        """
410             Receive the x and y transformation from myDialog,Transforms x and y in View
411              and set the scale   
412        """ 
413        list =[]
414        list = self.graph.returnPlottable()
415        for item in list:
416            if ( self.xscales=="x" ):
417                item.transform_x(  self.toX, self.errToX )
418                self.set_xscale("linear")
419                name, units = item.get_xaxis()
420                self.graph.xaxis("%s" % name,  "%s^{-1}" % units)
421               
422            if ( self.xscales=="x^(2)" ):
423                item.transform_x(  self.toX2, self.errToX2 )
424                self.set_xscale('linear')
425                name, units = item.get_xaxis()
426                self.graph.xaxis("%s^{2}" % name,  "%s^{-2}" % units)
427               
428            if (self.xscales=="Log(x)" ):
429                item.transform_x(  self.toX, self.errToLogX )
430                self.set_xscale("log")
431                name, units = item.get_xaxis()
432                self.graph.xaxis("%s" % name,  "%s^{-1}" % units)
433               
434            if ( self.yscales=="y" ):
435                item.transform_y(  self.toX, self.errToX )
436                self.set_yscale("linear")
437                name, units = item.get_yaxis()
438                self.graph.yaxis("%s" % name,  "%s^{-1}" % units)
439               
440            if ( self.yscales=="Log(y)" ): 
441                item.transform_y(  self.toX, self.errToLogX)
442                self.set_yscale("log") 
443                name, units = item.get_yaxis()
444                self.graph.yaxis("%s" % name,  "%s^{-1}" % units)
445               
446            if ( self.yscales=="y^(2)" ):
447                item.transform_y(  self.toX2, self.errToX2 )   
448                self.set_yscale("linear")
449                name, units = item.get_yaxis()
450                self.graph.yaxis("%s^2" % name,  "%s^{-2}" % units)
451            if ( self.yscales =="1/y"):
452                item.transform_y( self.toOneOverX ,self.errOneOverX )
453                self.set_yscale("linear")
454                name, units = item.get_yaxis()
455                self.graph.yaxis("%s" % name,  "%s" % units)
456            if ( self.yscales =="1/sqrt(y)" ):
457                item.transform_y( self.toOneOverSqrtX ,self.errOneOverSqrtX )
458                self.set_yscale("linear")
459                name, units = item.get_yaxis()
460                self.graph.yaxis("%s" %name,  "%s" % units)
461               
462            if ( self.yscales =="Log(y*x)"):
463                item.transform_y( self.toLogXY ,self.errToLogXY )
464                self.set_yscale("linear")
465                yname, yunits = item.get_yaxis()
466                xname, xunits = item.get_xaxis()
467                self.graph.yaxis("%s%s" % (yname,xname),  "%s^{-1}%s^{-1}" % (yunits,xunits))
468            if ( self.yscales =="Log(y*x^(2)"):
469                item.transform_y( self.toYX2 ,self.errToYX2 )
470                self.set_yscale("linear")
471                yname, yunits = item.get_yaxis()
472                xname, xunits = item.get_xaxis()
473                self.graph.yaxis("%s%s^{2}" % (yname,xname),  "%s^{-1}%s^{-2}" % (yunits,xunits))
474   
475        self.prevXtrans = self.xscales
476        self.prevYtrans = self.yscales 
477       
478        self.graph.render(self)
479        self.subplot.figure.canvas.draw_idle()
480       
481    def errToX(self,x,y=None,dx=None,dy=None):
482        """
483            calculate error of x**2
484            @param x: float value
485            @param dx: float value
486        """
487        return dx
488   
489   
490    def errToX2(self,x,y=None,dx=None,dy=None):
491        """
492            calculate error of x**2
493            @param x: float value
494            @param dx: float value
495        """
496        if  dx != None:
497            err = 2*x*dx
498            if math.fabs(err) >= math.fabs(x):
499                err = 0.9*x
500            return math.fabs(err)
501        else:
502            return 0.0
503    def errFromX2(self,x,y=None,dx=None,dy=None):
504        """
505            calculate error of sqrt(x)
506            @param x: float value
507            @param dx: float value
508        """
509        if (x > 0):
510            if(dx != None):
511                err = dx/(2*math.sqrt(x))
512            else:
513                err = 0
514            if math.fabs(err) >= math.fabs(x):
515                err = 0.9*x   
516        else:
517            err = 0.9*x
518           
519            return math.fabs(err)
520       
521    def errToLogX(self,x,y=None,dx=None,dy=None):
522        """
523            calculate error of Log(x)
524            @param x: float value
525            @param dx: float value
526        """
527        if math.fabs(dx) >= math.fabs(x):
528            return 0.9*x
529        return dx
530   
531    def errToXY(self, x, y, dx=None, dy=None):
532        if dx==None:
533            dx=0
534        if dy==None:
535            dy=0
536        err =math.sqrt((y*dx)**2 +(x*dy)**2)
537        if err >= math.fabs(x):
538            err =0.9*x
539        return err
540   
541    def errToYX2(self, x, y, dx=None, dy=None):
542        if dx==None:
543            dx=0
544        if dy==None:
545            dy=0
546        err =math.sqrt((2*x*y*dx)**2 +((x**2)*dy)**2)
547        if err >= math.fabs(x):
548            err =0.9*x
549        return err
550       
551    def errToLogXY(self,x,y,dx=None, dy=None):
552        """
553            calculate error of Log(xy)
554        """
555        if (x!=0) and (y!=0):
556            if dx == None:
557                dx = 0
558            if dy == None:
559                dy = 0
560            err = (dx/x)**2 + (dy/y)**2
561            if  math.sqrt(math.fabs(err)) >= math.fabs(x):
562                err= 0.9*x
563        else:
564            raise ValueError, "cannot compute this error"
565       
566        return math.sqrt(math.fabs(err))
567       
568    def errToLogYX2(self,x,y,dx=None, dy=None):
569        """
570            calculate error of Log(yx**2)
571        """
572        if (x > 0) and (y > 0):
573            if (dx == None):
574                dx = 0
575            if (dy == None):
576                dy = 0
577            err = 4*(dx**2)/(x**2) + (dy**2)/(y**2)
578            if math.fabs(err) >= math.fabs(x):
579                err =0.9*x
580        else:
581             raise ValueError, "cannot compute this error"
582         
583        return math.sqrt(math.fabs(err)) 
584           
585    def errOneOverX(self,x,y=None,dx=None, dy=None):
586        """
587             calculate error on 1/x
588        """
589        if (x != 0):
590            if dx ==None:
591                dx= 0
592            err = -(dx)**2/x**2
593        else:
594            raise ValueError,"Cannot compute this error"
595       
596        if math.fabs(err)>= math.fabs(x):
597            err= 0.9*x
598        return math.fabs(err)
599   
600    def errOneOverSqrtX(self,x,y=None, dx=None,dy=None):
601        """
602            Calculate error on 1/sqrt(x)
603        """
604        if (x >0):
605            if dx==None:
606                dx =0
607            err= -1/2*math.pow(x, -3/2)* dx
608            if math.fabs(err)>= math.fabs(x):
609                err=0.9*x
610        else:
611            raise ValueError, "Cannot compute this error"
612       
613        return math.fabs(err)
614   
615                     
616    def onFitDisplay(self, plottable):
617        """
618            Add a new plottable into the graph .In this case this plottable will be used
619            to fit some data
620            @param plottable: the plottable to plot
621        """
622        plottable.reset_view()
623        self.graph.add(plottable)
624        self.graph.render(self)
625        self.subplot.figure.canvas.draw_idle()
626        self.graph.delete(plottable)
627       
628     
629       
630   
631class NoRepaintCanvas(FigureCanvasWxAgg):
632    """We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that
633    the draw method is only called for the first two paint events. After that,
634    the canvas will only be redrawn when it is resized.
635    """
636    def __init__(self, *args, **kwargs):
637        FigureCanvasWxAgg.__init__(self, *args, **kwargs)
638        self._drawn = 0
639
640    def _onPaint(self, evt):
641        """
642        Called when wxPaintEvt is generated
643        """
644        if not self._isRealized:
645            self.realize()
646        if self._drawn < 2:
647            self.draw(repaint = False)
648            self._drawn += 1
649        self.gui_repaint(drawDC=wx.PaintDC(self))
650           
Note: See TracBrowser for help on using the repository browser.