source: sasview/guiframe/local_perspectives/plotting/Plotter2D.py @ ec02ddd

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 ec02ddd was 55a0dc1, checked in by Gervaise Alina <gervyh@…>, 14 years ago

remove reference to guicomm in guiframe

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