source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 3215d32

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

make plotter1D and plotter2D context menu in the same order

  • Property mode set to 100644
File size: 19.6 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        ## Plottables
63        self.plots = {}
64        self.data2D= data2d
65        self.data =data2d.data
66        ## Unique ID (from gui_manager)
67        self.uid = None
68       
69        ## Action IDs for internal call-backs
70        self.action_ids = {}
71        self.connect = BindArtist(self.subplot.figure)
72       
73        # Beam stop
74        self.beamstop_radius = DEFAULT_BEAM
75       
76        self.slicer_z = 5
77        self.slicer = None
78        #self.parent.Bind(EVT_INTERNAL, self._onEVT_INTERNAL)
79        self.Bind(EVT_INTERNAL, self._onEVT_INTERNAL)
80        self.axes_frozen = False
81       
82        self.panel_slicer=None
83        #self.parent.Bind(EVT_SLICER_PARS, self.onParamChange)
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        #self.Bind(boxSum.EVT_SLICER_PARS_UPDATE, self._onEVT_SLICER_PARS)
90        #self.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
91       
92       
93       
94    def _onEVT_SLICER_PARS(self, event):
95        print "paramaters entered on slicer panel", event.type, event.params
96        self.slicer.set_params(event.params)
97        #from sans.guicomm.events import SlicerPanelEvent
98        #wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer))
99       
100    def _onEVT_1DREPLOT(self, event):
101        """
102            Data is ready to be displayed
103           
104            #TODO: this name should be changed to something more appropriate
105            # Don't forget that changing this name will mean changing code
106            # in plotting.py
107             
108            @param event: data event
109        """
110             
111        self.data2D= event.plot
112        self.data =event.plot.data
113        #TODO: Check for existence of plot attribute
114
115        # Check whether this is a replot. If we ask for a replot
116        # and the plottable no longer exists, ignore the event.
117        if hasattr(event, "update") and event.update==True \
118            and event.plot.name not in self.plots.keys():
119            return
120       
121        if hasattr(event, "reset"):
122            self._reset()
123        is_new = True
124        if event.plot.name in self.plots.keys():
125            # Check whether the class of plottable changed
126            if not event.plot.__class__==self.plots[event.plot.name].__class__:
127                #overwrite a plottable using the same name
128                self.graph.delete(self.plots[event.plot.name])
129            else:
130                # plottable is already draw on the panel
131                is_new = False
132           
133        if is_new:
134            # a new plottable overwrites a plotted one  using the same id
135            for plottable in self.plots.itervalues():
136                if hasattr(event.plot,"id"):
137                    if event.plot.id==plottable.id :
138                        self.graph.delete(plottable)
139           
140            self.plots[event.plot.name] = event.plot
141            self.graph.add(self.plots[event.plot.name])
142        else:
143            # Update the plottable with the new data
144           
145            #TODO: we should have a method to do this,
146            #      something along the lines of:
147            #      plottable1.update_data_from_plottable(plottable2)
148           
149            self.plots[event.plot.name].xmin = event.plot.xmin
150            self.plots[event.plot.name].xmax = event.plot.xmax
151            self.plots[event.plot.name].ymin = event.plot.ymin
152            self.plots[event.plot.name].ymax = event.plot.ymax
153            self.plots[event.plot.name].data = event.plot.data
154            self.plots[event.plot.name].err_data = event.plot.err_data
155            # update qmax with the new xmax of data plotted
156            self.qmax= event.plot.xmax
157           
158        self.slicer= None
159       
160        # Check axis labels
161        #TODO: Should re-factor this
162        #if event.plot._xunit != self.graph.prop["xunit"]:
163        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
164           
165        #if event.plot._yunit != self.graph.prop["yunit"]:
166        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
167        self.graph.title(self.data2D.name)
168        self.graph.render(self)
169        self.subplot.figure.canvas.draw_idle()
170
171
172    def onContextMenu(self, event):
173        """
174            2D plot context menu
175            @param event: wx context event
176        """
177       
178        #slicerpop = wx.Menu()
179        slicerpop = PanelMenu()
180        slicerpop.set_plots(self.plots)
181        slicerpop.set_graph(self.graph)
182       
183             
184        id = wx.NewId()
185        slicerpop.Append(id, '&Save image')
186        wx.EVT_MENU(self, id, self.onSaveImage)
187       
188        id = wx.NewId()
189        slicerpop.Append(id,'&Print image', 'Print image ')
190        wx.EVT_MENU(self, id, self.onPrint)
191       
192        slicerpop.AppendSeparator()
193        item_list = self.parent.get_context_menu(self.graph)
194        if (not item_list==None) and (not len(item_list)==0):
195               
196                for item in item_list:
197                    try:
198                        id = wx.NewId()
199                        slicerpop.Append(id, item[0], item[1])
200                        wx.EVT_MENU(self, id, item[2])
201                    except:
202                        pass
203                        #print sys.exc_value
204                        #print RuntimeError, "View1DPanel2D.onContextMenu: bad menu item"
205       
206                slicerpop.AppendSeparator()
207        id = wx.NewId()
208        slicerpop.Append(id, '&Edit Detector Parameters')
209        wx.EVT_MENU(self, id, self._onEditDetector) 
210       
211        id = wx.NewId()
212        slicerpop.Append(id, '&Perform circular average')
213        wx.EVT_MENU(self, id, self.onCircular) 
214       
215        id = wx.NewId()
216        slicerpop.Append(id, '&Sector [Q view]')
217        wx.EVT_MENU(self, id, self.onSectorQ) 
218       
219        id = wx.NewId()
220        slicerpop.Append(id, '&Annulus [Phi view ]')
221        wx.EVT_MENU(self, id, self.onSectorPhi) 
222       
223        id = wx.NewId()
224        slicerpop.Append(id, '&Box Sum')
225        wx.EVT_MENU(self, id, self.onBoxSum) 
226       
227        id = wx.NewId()
228        slicerpop.Append(id, '&Box averaging in Qx')
229        wx.EVT_MENU(self, id, self.onBoxavgX) 
230       
231        id = wx.NewId()
232        slicerpop.Append(id, '&Box averaging in Qy')
233        wx.EVT_MENU(self, id, self.onBoxavgY) 
234        if self.slicer !=None:
235            id = wx.NewId()
236            slicerpop.Append(id, '&Clear slicer')
237            wx.EVT_MENU(self, id,  self.onClearSlicer) 
238       
239            id = wx.NewId()
240            slicerpop.Append(id, '&Edit Slicer Parameters')
241            wx.EVT_MENU(self, id, self._onEditSlicer) 
242        slicerpop.AppendSeparator() 
243       
244        """
245        # Option to save the data displayed
246        id = wx.NewId()
247        slicerpop.Append(id,'&Printer setup', 'Set image size')
248        wx.EVT_MENU(self, id, self.onPrinterSetup)
249       
250        id = wx.NewId()
251        slicerpop.Append(id,'&Printer Preview', 'Set image size')
252        wx.EVT_MENU(self, id, self.onPrinterPreview)
253        """
254        id = wx.NewId()
255        slicerpop.Append(id, '&Toggle Linear/Log scale')
256        wx.EVT_MENU(self, id, self._onToggleScale) 
257                 
258     
259        pos = event.GetPosition()
260        pos = self.ScreenToClient(pos)
261        self.PopupMenu(slicerpop, pos)
262       
263    def _onEditDetector(self, event):
264        print "edit detortor param",self.zmin_2D, self.zmax_2D
265        import detector_dialog
266        dialog = detector_dialog.DetectorDialog(self, -1,base=self.parent)
267        xnpts = len(self.data2D.x_bins)
268        ynpts = len(self.data2D.y_bins)
269        xmax = max(self.data2D.xmin, self.data2D.xmax)
270        ymax = max(self.data2D.ymin, self.data2D.ymax)
271        qmax = math.sqrt(math.pow(xmax,2)+math.pow(ymax,2))
272        beam = self.data2D.xmin
273        zmin = self.zmin_2D
274        zmax = self.zmax_2D
275        dialog.setContent(xnpts=xnpts,ynpts=ynpts,qmax=qmax,
276                           beam=self.data2D.xmin,
277                           zmin = self.zmin_2D,
278                          zmax = self.zmax_2D)
279        if dialog.ShowModal() == wx.ID_OK:
280            evt = dialog.getContent()
281            self.zmin_2D = evt.zmin
282            self.zmax_2D = evt.zmax
283       
284        dialog.Destroy()
285        print "zmn ,zmax", self.zmin_2D, self.zmax_2D
286        self.image(data= self.data2D.data,
287                   xmin= self.data2D.xmin,
288                   xmax= self.data2D.xmax,
289                   ymin= self.data2D.ymin,
290                   ymax= self.data2D.ymax,
291                   zmin= self.zmin_2D,
292                   zmax= self.zmax_2D,
293                   color=0,symbol=0,label='data2D')
294        #self.graph.render(self)
295        self.subplot.figure.canvas.draw_idle()
296       
297    def get_corrected_data(self):
298        # Protect against empty data set
299        if self.data == None:
300            return None
301        import copy
302        output = copy.deepcopy(self.data)
303        return output
304    def freeze_axes(self):
305        self.axes_frozen = True
306       
307    def thaw_axes(self):
308        self.axes_frozen = False
309       
310    def onMouseMotion(self,event):
311        pass
312    def onWheel(self, event):
313        pass   
314    def update(self, draw=True):
315        """
316            Respond to changes in the model by recalculating the
317            profiles and resetting the widgets.
318        """
319        #self.slicer.update()
320        self.draw()
321       
322       
323    def _getEmptySlicerEvent(self):
324        return SlicerEvent(type=None,
325                           params=None,
326                           obj_class=None)
327    def _onEVT_INTERNAL(self, event):
328        """
329            I don't understand why Unbind followed by a Bind
330            using a modified self.slicer doesn't work.
331            For now, I post a clear event followed by
332            a new slicer event...
333        """
334        self._setSlicer(event.slicer)
335           
336    def _setSlicer(self, slicer):
337        # Clear current slicer
338        #printEVT("Plotter2D._setSlicer %s" % slicer)
339       
340        if not self.slicer == None: 
341            self.slicer.clear()           
342           
343        self.slicer_z += 1
344        self.slicer = slicer(self, self.subplot, zorder=self.slicer_z)
345        #print "come here"
346        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
347        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
348       
349        self.update()
350        self.slicer.update()
351       
352        # Post slicer event
353        event = self._getEmptySlicerEvent()
354        event.type = self.slicer.__class__.__name__
355       
356        event.obj_class = self.slicer.__class__
357        event.params = self.slicer.get_params()
358        #print "Plotter2D: event.type",event.type,event.params, self.parent
359       
360        #wx.PostEvent(self.parent, event)
361        wx.PostEvent(self, event)
362
363    def onCircular(self, event):
364        """
365            perform circular averaging on Data2D
366        """
367       
368        from DataLoader.manipulations import CircularAverage
369        import math
370        self.qmax= max(math.fabs(self.data2D.xmax),math.fabs(self.data2D.xmin))
371        self.ymax=max(math.fabs(self.data2D.ymax),math.fabs(self.data2D.ymin))
372        self.radius= math.sqrt( math.pow(self.qmax,2)+math.pow(self.ymax,2)) 
373        #print "radius?",self.radius
374        # bin_width = self.qmax -self.qmin/nbins
375        #nbins= 30
376        bin_width = (self.qmax +self.qmax)/100
377       
378        Circle = CircularAverage( r_min=0, r_max=self.radius, bin_width=bin_width)
379       
380        circ = Circle(self.data2D)
381        from sans.guiframe.dataFitting import Data1D
382        if hasattr(circ,"dxl"):
383            dxl= circ.dxl
384        else:
385            dxl= None
386        if hasattr(circ,"dxw"):
387            dxw= circ.dxw
388        else:
389            dxw= None
390       
391        new_plot = Data1D(x=circ.x,y=circ.y,dy=circ.dy,dxl=dxl,dxw=dxw)
392        new_plot.name = "Circ avg "+ self.data2D.name
393        new_plot.source=self.data2D.source
394        #new_plot.info=self.data2D.info
395        new_plot.interactive = True
396        #print "loader output.detector",output.source
397        new_plot.detector =self.data2D.detector
398       
399        # If the data file does not tell us what the axes are, just assume...
400        new_plot.xaxis("\\rm{Q}","A^{-1}")
401        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
402        new_plot.group_id = "Circ avg "+ self.data2D.name
403        new_plot.id = "Circ avg "+ self.data2D.name
404        self.scale = 'log'
405       
406        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))
407       
408    def _onEditSlicer(self, event):
409        if self.slicer !=None:
410            from SlicerParameters import SlicerParameterPanel
411            dialog = SlicerParameterPanel(self.parent, -1, "Slicer Parameters")
412            dialog.set_slicer(self.slicer.__class__.__name__,
413                            self.slicer.get_params())
414            if dialog.ShowModal() == wx.ID_OK:
415                dialog.Destroy() 
416       
417    def onSectorQ(self, event):
418        """
419            Perform sector averaging on Q
420        """
421        #print "onsector self.data2Dxmax",self.data2D.xmax, self.parent
422        from SectorSlicer import SectorInteractor
423        self.onClearSlicer(event)
424        #wx.PostEvent(self.parent, InternalEvent(slicer= SectorInteractor))
425        wx.PostEvent(self, InternalEvent(slicer= SectorInteractor))
426       
427    def onSectorPhi(self, event):
428        """
429            Perform sector averaging on Phi
430        """
431        from AnnulusSlicer import AnnulusInteractor
432        self.onClearSlicer(event)
433        #wx.PostEvent(self.parent, InternalEvent(slicer= AnnulusInteractor))
434        wx.PostEvent(self, InternalEvent(slicer= AnnulusInteractor))
435       
436    def onBoxSum(self,event):
437        from boxSum import BoxSum
438        self.onClearSlicer(event)
439        #wx.PostEvent(self.parent, InternalEvent(slicer= BoxSum))
440        if not self.slicer == None: 
441            self.slicer.clear()             
442        self.slicer_z += 1
443        self.slicer =  BoxSum(self, self.subplot, zorder=self.slicer_z)
444        #print "come here"
445        self.subplot.set_ylim(self.data2D.ymin, self.data2D.ymax)
446        self.subplot.set_xlim(self.data2D.xmin, self.data2D.xmax)
447       
448        self.update()
449        self.slicer.update()
450       
451        # Post slicer event
452        event = self._getEmptySlicerEvent()
453        event.type = self.slicer.__class__.__name__
454       
455       
456        event.obj_class = self.slicer.__class__
457        event.params = self.slicer.get_params()
458        #print "Plotter2D: event.type",event.type,event.params, self.parent
459       
460        from slicerpanel import SlicerPanel
461        new_panel = SlicerPanel(parent= self.parent,id= -1,base= self,type=event.type,
462                                 params=event.params, style=wx.RAISED_BORDER)
463        #new_panel.set_slicer(self.slicer.__class__.__name__,
464        new_panel.window_caption=str(self.slicer_z)+self.slicer.__class__.__name__+" "+ str(self.data2D.name)
465        new_panel.window_name = str(self.slicer_z)+self.slicer.__class__.__name__+" "+ str(self.data2D.name)
466        self.panel_slicer= new_panel
467        self.slicer.set_panel_name( name= new_panel.window_caption)
468        #wx.PostEvent(self.panel_slicer, event)
469        from sans.guicomm.events import SlicerPanelEvent
470        wx.PostEvent(self.parent, SlicerPanelEvent (panel= self.panel_slicer))
471        #print "finish box sum"
472       
473    def onBoxavgX(self,event):
474        from boxSlicer import BoxInteractorX
475        self.onClearSlicer(event)
476        #wx.PostEvent(self.parent, InternalEvent(slicer= BoxInteractorX))
477        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorX))
478       
479       
480    def onBoxavgY(self,event):
481        from boxSlicer import BoxInteractorY
482        self.onClearSlicer(event)
483        wx.PostEvent(self, InternalEvent(slicer= BoxInteractorY))
484        #wx.PostEvent(self.parent, InternalEvent(slicer= BoxInteractorY))
485       
486    def onClearSlicer(self, event):
487        """
488            Clear the slicer on the plot
489        """
490        if not self.slicer==None:
491            self.slicer.clear()
492            self.subplot.figure.canvas.draw()
493            self.slicer = None
494       
495            # Post slicer None event
496            event = self._getEmptySlicerEvent()
497            #wx.PostEvent(self.parent, event)
498            wx.PostEvent(self, event)
499         
500 
501       
502    def _onToggleScale(self, event):
503        """
504            toggle pixel scale and replot image
505        """
506        if self.scale == 'log':
507            self.scale = 'linear'
508        else:
509            self.scale = 'log'
510        self.image(self.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
511                   self.ymax_2D,self.zmin_2D ,self.zmax_2D )
512        wx.PostEvent(self.parent, StatusEvent(status="Image is in %s scale"%self.scale))
513       
514        """     
515            #TODO: this name should be changed to something more appropriate
516            # Don't forget that changing this name will mean changing code
517            # in plotting.py
518             
519            # Update the plottable with the new data
520           
521            #TODO: we should have a method to do this,
522            #      something along the lines of:
523            #      plottable1.update_data_from_plottable(plottable2)
524           
525            self.plots[event.plot.name].xmin = event.plot.xmin
526            self.plots[event.plot.name].xmax = event.plot.xmax
527            self.plots[event.plot.name].ymin = event.plot.ymin
528            self.plots[event.plot.name].ymax = event.plot.ymax
529            self.plots[event.plot.name].data = event.plot.data
530            self.plots[event.plot.name].err_data = event.plot.err_data
531        """
Note: See TracBrowser for help on using the repository browser.