source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ 2170dee2

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 2170dee2 was 88ca6db, checked in by Gervaise Alina <gervyh@…>, 15 years ago

remove approprite button in the toolbar for 2 d

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