source: sasview/guitools/PlotPanel.py @ bceddd6

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 bceddd6 was bceddd6, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Got rid of Save Data item and unused calls.

  • Property mode set to 100644
File size: 21.1 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       
278        slicerpop.Append(316, '&Load 1D data file')
279        wx.EVT_MENU(self, 316, self._onLoad1DData)
280       
281        slicerpop.AppendSeparator()
282        slicerpop.Append(315, '&Properties')
283        wx.EVT_MENU(self, 315, self._onProperties)
284       
285        slicerpop.AppendSeparator()
286        slicerpop.Append(317, '&Linear Fit')
287        wx.EVT_MENU(self, 317, self.onFitting)
288       
289        pos = event.GetPosition()
290        pos = self.ScreenToClient(pos)
291        self.PopupMenu(slicerpop, pos)
292   
293    ## The following is plottable functionality
294
295
296    def properties(self,prop):
297        """Set some properties of the graph.
298       
299        The set of properties is not yet determined.
300        """
301        # The particulars of how they are stored and manipulated (e.g., do
302        # we want an inventory internally) is not settled.  I've used a
303        # property dictionary for now.
304        #
305        # How these properties interact with a user defined style file is
306        # even less clear.
307
308        # Properties defined by plot
309        self.subplot.set_xlabel(r"$%s$" % prop["xlabel"])
310        self.subplot.set_ylabel(r"$%s$" % prop["ylabel"])
311        self.subplot.set_title(prop["title"])
312
313        # Properties defined by user
314        #self.axes.grid(True)
315
316    def clear(self):
317        """Reset the plot"""
318       
319        # TODO: Redraw is brutal.  Render to a backing store and swap in
320        # TODO: rather than redrawing on the fly.
321        self.subplot.clear()
322        self.subplot.hold(True)
323
324
325    def render(self):
326        """Commit the plot after all objects are drawn"""
327        # TODO: this is when the backing store should be swapped in.
328        from matplotlib.font_manager import FontProperties
329        self.subplot.legend(prop=FontProperties(size=10))
330        #self.subplot.legend()
331        pass
332
333    def xaxis(self,label,units):
334        """xaxis label and units.
335       
336        Axis labels know about units.
337       
338        We need to do this so that we can detect when axes are not
339        commesurate.  Currently this is ignored other than for formatting
340        purposes.
341        """
342        if units != "": label = label + " (" + units + ")"
343        self.subplot.set_xlabel(label)
344        pass
345   
346    def yaxis(self,label,units):
347        """yaxis label and units."""
348        if units != "": label = label + " (" + units + ")"
349        self.subplot.set_ylabel(label)
350        pass
351
352    def _connect_to_xlim(self,callback):
353        """Bind the xlim change notification to the callback"""
354        def process_xlim(axes):
355            lo,hi = subplot.get_xlim()
356            callback(lo,hi)
357        self.subplot.callbacks.connect('xlim_changed',process_xlim)
358   
359    #def connect(self,trigger,callback):
360    #    print "PlotPanel.connect???"
361    #    if trigger == 'xlim': self._connect_to_xlim(callback)
362
363    def points(self,x,y,dx=None,dy=None,color=0,symbol=0,label=None):
364        """Draw markers with error bars"""
365        self.subplot.set_yscale('linear')
366        self.subplot.set_xscale('linear')
367        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
368        if dx != None and type(dx) == type(()):
369            dx = nx.vstack((x-dx[0],dx[1]-x)).transpose()
370        if dy != None and type(dy) == type(()):
371            dy = nx.vstack((y-dy[0],dy[1]-y)).transpose()
372
373        if dx==None and dy==None:
374            h = self.subplot.plot(x,y,color=self._color(color),
375                                   marker=self._symbol(symbol),linestyle='',label=label)
376        else:
377            self.subplot.errorbar(x, y, yerr=dy, xerr=None,
378             ecolor=self._color(color), capsize=2,linestyle='', barsabove=False,
379             marker=self._symbol(symbol),
380             lolims=False, uplims=False,
381             xlolims=False, xuplims=False,label=label)
382           
383        self.subplot.set_yscale(self.yscale)
384        self.subplot.set_xscale(self.xscale)
385
386    def curve(self,x,y,dy=None,color=0,symbol=0,label=None):
387        """Draw a line on a graph, possibly with confidence intervals."""
388        c = self._color(color)
389        self.subplot.set_yscale('linear')
390        self.subplot.set_xscale('linear')
391       
392        hlist = self.subplot.plot(x,y,color=c,marker='',linestyle='-',label=label)
393       
394        self.subplot.set_yscale(self.yscale)
395        self.subplot.set_xscale(self.xscale)
396
397    def _color(self,c):
398        """Return a particular colour"""
399        return self.colorlist[c%len(self.colorlist)]
400
401    def _symbol(self,s):
402        """Return a particular symbol"""
403        return self.symbollist[s%len(self.symbollist)]
404   
405    def _onEVT_FUNC_PROPERTY(self):
406        """
407             Receive the x and y transformation from myDialog,Transforms x and y in View
408              and set the scale   
409        """ 
410        list =[]
411        list = self.graph.returnPlottable()
412        for item in list:
413            if ( self.xscales=="x" ):
414                item.transform_x(  self.toX, self.errToX )
415                self.set_xscale("linear")
416                name, units = item.get_xaxis()
417                self.graph.xaxis("%s" % name,  "%s^{-1}" % units)
418               
419            if ( self.xscales=="x^(2)" ):
420                item.transform_x(  self.toX2, self.errToX2 )
421                self.set_xscale('linear')
422                name, units = item.get_xaxis()
423                self.graph.xaxis("%s^{2}" % name,  "%s^{-2}" % units)
424               
425            if (self.xscales=="Log(x)" ):
426                item.transform_x(  self.toX, self.errToLogX )
427                self.set_xscale("log")
428                name, units = item.get_xaxis()
429                self.graph.xaxis("%s" % name,  "%s^{-1}" % units)
430               
431            if ( self.yscales=="y" ):
432                item.transform_y(  self.toX, self.errToX )
433                self.set_yscale("linear")
434                name, units = item.get_yaxis()
435                self.graph.yaxis("%s" % name,  "%s^{-1}" % units)
436               
437            if ( self.yscales=="Log(y)" ): 
438                item.transform_y(  self.toX, self.errToLogX)
439                self.set_yscale("log") 
440                name, units = item.get_yaxis()
441                self.graph.yaxis("%s" % name,  "%s^{-1}" % units)
442               
443            if ( self.yscales=="y^(2)" ):
444                item.transform_y(  self.toX2, self.errToX2 )   
445                self.set_yscale("linear")
446                name, units = item.get_yaxis()
447                self.graph.yaxis("%s^2" % name,  "%s^{-2}" % units)
448            if ( self.yscales =="1/y"):
449                item.transform_y( self.toOneOverX ,self.errOneOverX )
450                self.set_yscale("linear")
451                name, units = item.get_yaxis()
452                self.graph.yaxis("%s" % name,  "%s" % units)
453            if ( self.yscales =="1/sqrt(y)" ):
454                item.transform_y( self.toOneOverSqrtX ,self.errOneOverSqrtX )
455                self.set_yscale("linear")
456                name, units = item.get_yaxis()
457                self.graph.yaxis("%s" %name,  "%s" % units)
458               
459            if ( self.yscales =="Log(y*x)"):
460                item.transform_y( self.toLogXY ,self.errToLogXY )
461                self.set_yscale("linear")
462                yname, yunits = item.get_yaxis()
463                xname, xunits = item.get_xaxis()
464                self.graph.yaxis("%s%s" % (yname,xname),  "%s^{-1}%s^{-1}" % (yunits,xunits))
465            if ( self.yscales =="Log(y*x^(2)"):
466                item.transform_y( self.toYX2 ,self.errToYX2 )
467                self.set_yscale("linear")
468                yname, yunits = item.get_yaxis()
469                xname, xunits = item.get_xaxis()
470                self.graph.yaxis("%s%s^{2}" % (yname,xname),  "%s^{-1}%s^{-2}" % (yunits,xunits))
471   
472        self.prevXtrans = self.xscales
473        self.prevYtrans = self.yscales 
474       
475        self.graph.render(self)
476        self.subplot.figure.canvas.draw_idle()
477       
478    def errToX(self,x,y=None,dx=None,dy=None):
479        """
480            calculate error of x**2
481            @param x: float value
482            @param dx: float value
483        """
484        return dx
485   
486   
487    def errToX2(self,x,y=None,dx=None,dy=None):
488        """
489            calculate error of x**2
490            @param x: float value
491            @param dx: float value
492        """
493        if  dx != None:
494            err = 2*x*dx
495            if math.fabs(err) >= math.fabs(x):
496                err = 0.9*x
497            return math.fabs(err)
498        else:
499            return 0.0
500    def errFromX2(self,x,y=None,dx=None,dy=None):
501        """
502            calculate error of sqrt(x)
503            @param x: float value
504            @param dx: float value
505        """
506        if (x > 0):
507            if(dx != None):
508                err = dx/(2*math.sqrt(x))
509            else:
510                err = 0
511            if math.fabs(err) >= math.fabs(x):
512                err = 0.9*x   
513        else:
514            err = 0.9*x
515           
516            return math.fabs(err)
517       
518    def errToLogX(self,x,y=None,dx=None,dy=None):
519        """
520            calculate error of Log(x)
521            @param x: float value
522            @param dx: float value
523        """
524        if math.fabs(dx) >= math.fabs(x):
525            return 0.9*x
526        return dx
527   
528    def errToXY(self, x, y, dx=None, dy=None):
529        if dx==None:
530            dx=0
531        if dy==None:
532            dy=0
533        err =math.sqrt((y*dx)**2 +(x*dy)**2)
534        if err >= math.fabs(x):
535            err =0.9*x
536        return err
537   
538    def errToYX2(self, x, y, dx=None, dy=None):
539        if dx==None:
540            dx=0
541        if dy==None:
542            dy=0
543        err =math.sqrt((2*x*y*dx)**2 +((x**2)*dy)**2)
544        if err >= math.fabs(x):
545            err =0.9*x
546        return err
547       
548    def errToLogXY(self,x,y,dx=None, dy=None):
549        """
550            calculate error of Log(xy)
551        """
552        if (x!=0) and (y!=0):
553            if dx == None:
554                dx = 0
555            if dy == None:
556                dy = 0
557            err = (dx/x)**2 + (dy/y)**2
558            if  math.sqrt(math.fabs(err)) >= math.fabs(x):
559                err= 0.9*x
560        else:
561            raise ValueError, "cannot compute this error"
562       
563        return math.sqrt(math.fabs(err))
564       
565    def errToLogYX2(self,x,y,dx=None, dy=None):
566        """
567            calculate error of Log(yx**2)
568        """
569        if (x > 0) and (y > 0):
570            if (dx == None):
571                dx = 0
572            if (dy == None):
573                dy = 0
574            err = 4*(dx**2)/(x**2) + (dy**2)/(y**2)
575            if math.fabs(err) >= math.fabs(x):
576                err =0.9*x
577        else:
578             raise ValueError, "cannot compute this error"
579         
580        return math.sqrt(math.fabs(err)) 
581           
582    def errOneOverX(self,x,y=None,dx=None, dy=None):
583        """
584             calculate error on 1/x
585        """
586        if (x != 0):
587            if dx ==None:
588                dx= 0
589            err = -(dx)**2/x**2
590        else:
591            raise ValueError,"Cannot compute this error"
592       
593        if math.fabs(err)>= math.fabs(x):
594            err= 0.9*x
595        return math.fabs(err)
596   
597    def errOneOverSqrtX(self,x,y=None, dx=None,dy=None):
598        """
599            Calculate error on 1/sqrt(x)
600        """
601        if (x >0):
602            if dx==None:
603                dx =0
604            err= -1/2*math.pow(x, -3/2)* dx
605            if math.fabs(err)>= math.fabs(x):
606                err=0.9*x
607        else:
608            raise ValueError, "Cannot compute this error"
609       
610        return math.fabs(err)
611   
612                     
613    def onFitDisplay(self, plottable):
614        """
615            Add a new plottable into the graph .In this case this plottable will be used
616            to fit some data
617            @param plottable: the plottable to plot
618        """
619        plottable.reset_view()
620        self.graph.add(plottable)
621        self.graph.render(self)
622       
623        self.subplot.figure.canvas.draw_idle()
624        self.graph.delete(plottable)
625     
626       
627     
628       
629   
630class NoRepaintCanvas(FigureCanvasWxAgg):
631    """We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that
632    the draw method is only called for the first two paint events. After that,
633    the canvas will only be redrawn when it is resized.
634    """
635    def __init__(self, *args, **kwargs):
636        FigureCanvasWxAgg.__init__(self, *args, **kwargs)
637        self._drawn = 0
638
639    def _onPaint(self, evt):
640        """
641        Called when wxPaintEvt is generated
642        """
643        if not self._isRealized:
644            self.realize()
645        if self._drawn < 2:
646            self.draw(repaint = False)
647            self._drawn += 1
648        self.gui_repaint(drawDC=wx.PaintDC(self))
649           
Note: See TracBrowser for help on using the repository browser.