source: sasview/guitools/PlotPanel.py @ 416223d

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

making change on fitdialog ..still some bugs

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