source: sasview/guitools/PlotPanel.py @ 2da23bc

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

problem with the scale fixed.

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