source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 4e0806e8

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

comment class

  • Property mode set to 100644
File size: 18.9 KB
Line 
1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2008, University of Tennessee
9"""
10
11
12import wx
13import sys, math
14import pylab
15
16import danse.common.plottools
17from danse.common.plottools.PlotPanel import PlotPanel
18from danse.common.plottools.plottables import Graph,Data1D
19from sans.guicomm.events import EVT_NEW_PLOT
20from sans.guicomm.events import EVT_SLICER_PARS
21from sans.guicomm.events import StatusEvent ,NewPlotEvent,SlicerEvent
22from sans.guiframe.utils import PanelMenu
23from binder import BindArtist
24from Plotter1D import ModelPanel1D
25(InternalEvent, EVT_INTERNAL)   = wx.lib.newevent.NewEvent()
26
27
28
29DEFAULT_QMAX = 0.05
30DEFAULT_QSTEP = 0.001
31DEFAULT_BEAM = 0.005
32BIN_WIDTH = 1.0
33
34
35
36
37class ModelPanel2D( ModelPanel1D):
38    """
39        Plot panel for use with the GUI manager
40    """
41   
42    ## Internal name for the AUI manager
43    window_name = "plotpanel"
44    ## Title to appear on top of the window
45    window_caption = "Plot Panel"
46    ## Flag to tell the GUI manager that this panel is not
47    #  tied to any perspective
48    ALWAYS_ON = True
49    ## Group ID
50    group_id = None
51   
52   
53    def __init__(self, parent, id = -1,data2d=None, color = None,\
54        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
55        """
56            Initialize the panel
57        """
58        ModelPanel1D.__init__(self, parent, id = id, style = style, **kwargs)
59       
60        ## Reference to the parent window
61        self.parent = parent
62        ## Dictionary containing Plottables
63        self.plots = {}
64        ## Save reference of the current plotted
65        self.data2D = data2d
66        ## Unique ID (from gui_manager)
67        self.uid = None
68        ## Action IDs for internal call-backs
69        self.action_ids = {}
70        ## Create Artist and bind it
71        self.connect = BindArtist(self.subplot.figure)
72        ## Beam stop
73        self.beamstop_radius = DEFAULT_BEAM
74        ## to set the order of lines drawn first.
75        self.slicer_z = 5
76        ## Reference to the current slicer
77        self.slicer = None
78        ## event to send slicer info
79        self.Bind(EVT_INTERNAL, self._onEVT_INTERNAL)
80       
81        self.axes_frozen = False
82        ## panel that contains result from slicer motion (ex: Boxsum info)
83        self.panel_slicer=None
84        ## Graph       
85        self.graph = Graph()
86        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
87        self.graph.yaxis("\\rm{Intensity} ","cm^{-1}")
88        self.graph.render(self)
89       
90       
91    def _onEVT_1DREPLOT(self, event):
92        """
93            Data is ready to be displayed
94           
95            #TODO: this name should be changed to something more appropriate
96            # Don't forget that changing this name will mean changing code
97            # in plotting.py
98             
99            @param event: data event
100        """
101        ## Update self.data2d with the current plot
102        self.data2D = event.plot
103        #TODO: Check for existence of plot attribute
104       
105        # Check whether this is a replot. If we ask for a replot
106        # and the plottable no longer exists, ignore the event.
107        if hasattr(event, "update") and event.update==True \
108            and event.plot.name not in self.plots.keys():
109            return
110       
111        if hasattr(event, "reset"):
112            self._reset()
113        is_new = True
114        if event.plot.name in self.plots.keys():
115            # Check whether the class of plottable changed
116            if not event.plot.__class__==self.plots[event.plot.name].__class__:
117                #overwrite a plottable using the same name
118                self.graph.delete(self.plots[event.plot.name])
119            else:
120                # plottable is already draw on the panel
121                is_new = False
122           
123        if is_new:
124            # a new plottable overwrites a plotted one  using the same id
125            for plottable in self.plots.itervalues():
126                if hasattr(event.plot,"id"):
127                    if event.plot.id==plottable.id :
128                        self.graph.delete(plottable)
129           
130            self.plots[event.plot.name] = event.plot
131            self.graph.add(self.plots[event.plot.name])
132        else:
133            # Update the plottable with the new data
134           
135            #TODO: we should have a method to do this,
136            #      something along the lines of:
137            #      plottable1.update_data_from_plottable(plottable2)
138           
139            self.plots[event.plot.name].xmin = event.plot.xmin
140            self.plots[event.plot.name].xmax = event.plot.xmax
141            self.plots[event.plot.name].ymin = event.plot.ymin
142            self.plots[event.plot.name].ymax = event.plot.ymax
143            self.plots[event.plot.name].data = event.plot.data
144            self.plots[event.plot.name].err_data = event.plot.err_data
145            # update qmax with the new xmax of data plotted
146            self.qmax= event.plot.xmax
147           
148        self.slicer= None
149        # Check axis labels
150        #TODO: Should re-factor this
151        ## render the graph with its new content
152        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
153        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
154        self.graph.title(self.data2D.name)
155        self.graph.render(self)
156        self.subplot.figure.canvas.draw_idle()
157
158
159    def onContextMenu(self, event):
160        """
161            2D plot context menu
162            @param event: wx context event
163        """
164        slicerpop = PanelMenu()
165        slicerpop.set_plots(self.plots)
166        slicerpop.set_graph(self.graph)
167       
168             
169        id = wx.NewId()
170        slicerpop.Append(id, '&Save image')
171        wx.EVT_MENU(self, id, self.onSaveImage)
172       
173        id = wx.NewId()
174        slicerpop.Append(id,'&Print image', 'Print image ')
175        wx.EVT_MENU(self, id, self.onPrint)
176       
177        id = wx.NewId()
178        slicerpop.Append(id,'&Print Preview', 'image preview for print')
179        wx.EVT_MENU(self, id, self.onPrinterPreview)
180       
181        slicerpop.AppendSeparator()
182        item_list = self.parent.get_context_menu(self.graph)
183        if (not item_list==None) and (not len(item_list)==0):
184               
185                for item in item_list:
186                    try:
187                        id = wx.NewId()
188                        slicerpop.Append(id, item[0], item[1])
189                        wx.EVT_MENU(self, id, item[2])
190                    except:
191                        wx.PostEvent(self.parent, StatusEvent(status=\
192                        "ModelPanel1D.onContextMenu: bad menu item  %s"%sys.exc_value))
193                        pass
194                slicerpop.AppendSeparator()
195       
196        id = wx.NewId()
197        slicerpop.Append(id, '&Edit Detector Parameters')
198        wx.EVT_MENU(self, id, self._onEditDetector) 
199       
200        id = wx.NewId()
201        slicerpop.Append(id, '&Perform circular average')
202        wx.EVT_MENU(self, id, self.onCircular) 
203       
204        id = wx.NewId()
205        slicerpop.Append(id, '&Sector [Q view]')
206        wx.EVT_MENU(self, id, self.onSectorQ) 
207       
208        id = wx.NewId()
209        slicerpop.Append(id, '&Annulus [Phi view ]')
210        wx.EVT_MENU(self, id, self.onSectorPhi) 
211       
212        id = wx.NewId()
213        slicerpop.Append(id, '&Box Sum')
214        wx.EVT_MENU(self, id, self.onBoxSum) 
215       
216        id = wx.NewId()
217        slicerpop.Append(id, '&Box averaging in Qx')
218        wx.EVT_MENU(self, id, self.onBoxavgX) 
219       
220        id = wx.NewId()
221        slicerpop.Append(id, '&Box averaging in Qy')
222        wx.EVT_MENU(self, id, self.onBoxavgY) 
223       
224        if self.slicer !=None :
225            id = wx.NewId()
226            slicerpop.Append(id, '&Clear slicer')
227            wx.EVT_MENU(self, id,  self.onClearSlicer) 
228           
229            if self.slicer.__class__.__name__ !="BoxSum":
230                id = wx.NewId()
231                slicerpop.Append(id, '&Edit Slicer Parameters')
232                wx.EVT_MENU(self, id, self._onEditSlicer) 
233               
234        slicerpop.AppendSeparator() 
235       
236        id = wx.NewId()
237        slicerpop.Append(id, '&Toggle Linear/Log scale')
238        wx.EVT_MENU(self, id, self._onToggleScale) 
239                 
240        pos = event.GetPosition()
241        pos = self.ScreenToClient(pos)
242        self.PopupMenu(slicerpop, pos)
243   
244   
245    def _onEditDetector(self, event):
246        """
247            Allow to view and edits  detector parameters
248            @param event: wx.menu event
249        """
250       
251        import detector_dialog
252        dialog = detector_dialog.DetectorDialog(self, -1,base=self.parent)
253        ## info of current detector and data2D
254        xnpts = len(self.data2D.x_bins)
255        ynpts = len(self.data2D.y_bins)
256        xmax = max(self.data2D.xmin, self.data2D.xmax)
257        ymax = max(self.data2D.ymin, self.data2D.ymax)
258        qmax = math.sqrt(math.pow(xmax,2)+math.pow(ymax,2))
259        beam = self.data2D.xmin
260        zmin = self.zmin_2D
261        zmax = self.zmax_2D
262        ## set dialog window content
263        dialog.setContent(xnpts=xnpts,ynpts=ynpts,qmax=qmax,
264                           beam=self.data2D.xmin,
265                           zmin = self.zmin_2D,
266                          zmax = self.zmax_2D)
267        if dialog.ShowModal() == wx.ID_OK:
268            evt = dialog.getContent()
269            self.zmin_2D = evt.zmin
270            self.zmax_2D = evt.zmax
271       
272        dialog.Destroy()
273        ## Redraw the current image
274        self.image(data= self.data2D.data,
275                   xmin= self.data2D.xmin,
276                   xmax= self.data2D.xmax,
277                   ymin= self.data2D.ymin,
278                   ymax= self.data2D.ymax,
279                   zmin= self.zmin_2D,
280                   zmax= self.zmax_2D,
281                   color=0,symbol=0,label='data2D')
282        self.subplot.figure.canvas.draw_idle()
283       
284       
285 
286    def freeze_axes(self):
287        self.axes_frozen = True
288       
289    def thaw_axes(self):
290        self.axes_frozen = False
291       
292    def onMouseMotion(self,event):
293        pass
294    def onWheel(self, event):
295        pass 
296     
297    def update(self, draw=True):
298        """
299            Respond to changes in the model by recalculating the
300            profiles and resetting the widgets.
301        """
302        self.draw()
303       
304       
305    def _getEmptySlicerEvent(self):
306        """
307            create an empty slicervent
308        """
309        return SlicerEvent(type=None,
310                           params=None,
311                           obj_class=None)
312    def _onEVT_INTERNAL(self, event):
313        """
314            Draw the slicer
315            @param event: wx.lib.newevent (SlicerEvent) containing slicer
316            parameter
317        """
318        self._setSlicer(event.slicer)
319           
320    def _setSlicer(self, slicer):
321        """
322            Clear the previous slicer and create a new one.Post an internal
323            event.
324            @param slicer: slicer class to create
325        """
326       
327        ## Clear current slicer
328        if not self.slicer == None: 
329            self.slicer.clear()           
330        ## Create a new slicer   
331        self.slicer_z += 1
332        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
333        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
334        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
335        ## Draw slicer
336        self.update()
337        self.slicer.update()
338        wx.PostEvent(self.parent, StatusEvent(status=\
339                        "Plotter2D._setSlicer  %s"%self.slicer.__class__.__name__))
340        # Post slicer event
341        event = self._getEmptySlicerEvent()
342        event.type = self.slicer.__class__.__name__
343       
344        event.obj_class = self.slicer.__class__
345        event.params = self.slicer.get_params()
346        wx.PostEvent(self, event)
347
348
349    def onCircular(self, event):
350        """
351            perform circular averaging on Data2D
352            @param event: wx.menu event
353        """
354       
355        from DataLoader.manipulations import CircularAverage
356        ## compute the maximum radius of data2D
357        self.qmax= max(math.fabs(self.data2D.xmax),math.fabs(self.data2D.xmin))
358        self.ymax=max(math.fabs(self.data2D.ymax),math.fabs(self.data2D.ymin))
359        self.radius= math.sqrt( math.pow(self.qmax,2)+math.pow(self.ymax,2)) 
360        ##Compute beam width
361        bin_width = (self.qmax +self.qmax)/100
362        ## Create data1D circular average of data2D
363        Circle = CircularAverage( r_min=0, r_max=self.radius, bin_width=bin_width)
364        circ = Circle(self.data2D)
365       
366        from sans.guiframe.dataFitting import Data1D
367        if hasattr(circ,"dxl"):
368            dxl= circ.dxl
369        else:
370            dxl= None
371        if hasattr(circ,"dxw"):
372            dxw= circ.dxw
373        else:
374            dxw= None
375       
376        new_plot = Data1D(x=circ.x,y=circ.y,dy=circ.dy,dxl=dxl,dxw=dxw)
377        new_plot.name = "Circ avg "+ self.data2D.name
378        new_plot.source=self.data2D.source
379        #new_plot.info=self.data2D.info
380        new_plot.interactive = True
381        new_plot.detector =self.data2D.detector
382        ## If the data file does not tell us what the axes are, just assume...
383        new_plot.xaxis("\\rm{Q}","A^{-1}")
384        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
385        new_plot.group_id = "Circ avg "+ self.data2D.name
386        new_plot.id = "Circ avg "+ self.data2D.name
387       
388        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))
389       
390       
391    def _onEditSlicer(self, event):
392        """
393            Is available only when a slicer is drawn.Create a dialog
394            window where the user can enter value to reset slicer
395            parameters.
396            @param event: wx.menu event
397        """
398        if self.slicer !=None:
399            from SlicerParameters import SlicerParameterPanel
400            dialog = SlicerParameterPanel(self, -1, "Slicer Parameters")
401            dialog.set_slicer(self.slicer.__class__.__name__,
402                            self.slicer.get_params())
403            if dialog.ShowModal() == wx.ID_OK:
404                dialog.Destroy() 
405       
406       
407    def onSectorQ(self, event):
408        """
409            Perform sector averaging on Q and draw sector slicer
410        """
411        from SectorSlicer import SectorInteractor
412        self.onClearSlicer(event)
413        wx.PostEvent(self, InternalEvent(slicer= SectorInteractor))
414       
415    def onSectorPhi(self, event):
416        """
417            Perform sector averaging on Phi and draw annulus slicer
418        """
419        from AnnulusSlicer import AnnulusInteractor
420        self.onClearSlicer(event)
421        wx.PostEvent(self, InternalEvent(slicer= AnnulusInteractor))
422       
423    def onBoxSum(self,event):
424        from boxSum import BoxSum
425        self.onClearSlicer(event)
426                   
427        self.slicer_z += 1
428        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
429       
430        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
431        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
432       
433        self.update()
434        self.slicer.update()
435        ## Value used to initially set the slicer panel
436        type = self.slicer.__class__.__name__
437        params = self.slicer.get_params()
438        ## Create a new panel to display results of summation of Data2D
439        from slicerpanel import SlicerPanel
440        new_panel = SlicerPanel(parent= self.parent, id= -1,
441                                    base= self, type= type,
442                                    params= params, style= wx.RAISED_BORDER)
443       
444        new_panel.window_caption=self.slicer.__class__.__name__+" "+ str(self.data2D.name)
445        new_panel.window_name = self.slicer.__class__.__name__+" "+ str(self.data2D.name)
446        ## Store a reference of the new created panel
447        self.panel_slicer= new_panel
448        ## save the window_caption of the new panel in the current slicer
449        self.slicer.set_panel_name( name= new_panel.window_caption)
450        ## post slicer panel to guiframe to display it
451        from sans.guicomm.events import SlicerPanelEvent
452        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer))
453
454       
455    def onBoxavgX(self,event):
456        """
457            Perform 2D data averaging on Qx
458            Create a new slicer .
459            @param event: wx.menu event
460        """
461        from boxSlicer import BoxInteractorX
462        self.onClearSlicer(event)
463        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorX))
464       
465       
466    def onBoxavgY(self,event):
467        """
468            Perform 2D data averaging on Qy
469            Create a new slicer .
470            @param event: wx.menu event
471        """
472        from boxSlicer import BoxInteractorY
473        self.onClearSlicer(event)
474        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorY))
475       
476       
477    def onClearSlicer(self, event):
478        """
479            Clear the slicer on the plot
480        """
481        if not self.slicer==None:
482            self.slicer.clear()
483            self.subplot.figure.canvas.draw()
484            self.slicer = None
485       
486            # Post slicer None event
487            event = self._getEmptySlicerEvent()
488            wx.PostEvent(self, event)
489         
490   
491    def _onToggleScale(self, event):
492        """
493            toggle pixel scale and replot image
494        """
495        if self.scale == 'log':
496            self.scale = 'linear'
497        else:
498            self.scale = 'log'
499        self.image(self.data2D.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
500                   self.ymax_2D,self.zmin_2D ,self.zmax_2D )
501        wx.PostEvent(self.parent, StatusEvent(status="Image is in %s scale"%self.scale))
502       
503        """     
504            #TODO: this name should be changed to something more appropriate
505            # Don't forget that changing this name will mean changing code
506            # in plotting.py
507             
508            # Update the plottable with the new data
509           
510            #TODO: we should have a method to do this,
511            #      something along the lines of:
512            #      plottable1.update_data_from_plottable(plottable2)
513           
514            self.plots[event.plot.name].xmin = event.plot.xmin
515            self.plots[event.plot.name].xmax = event.plot.xmax
516            self.plots[event.plot.name].ymin = event.plot.ymin
517            self.plots[event.plot.name].ymax = event.plot.ymax
518            self.plots[event.plot.name].data = event.plot.data
519            self.plots[event.plot.name].err_data = event.plot.err_data
520        """
Note: See TracBrowser for help on using the repository browser.