source: sasview/guitools/PlotPanel.py @ 3d3a0e5

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

CHANGE PLOTPANEL PLOTTABLE

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