source: sasview/guitools/PlotPanel.py @ 6ed101a

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

more modification ..zoom is still bugging for logx logy

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