source: sasview/guitools/PlotPanel.py @ bbec827

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

working

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