source: sasview/guiframe/local_perspectives/plotting/Plotter1D.py @ e5c6fff

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 e5c6fff was a7bd562, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

guiframe: fixed code that generated an exception when a plotted plottable didn't have an 'id' data member (a concept that exists only for sansview - I didn't try to fix the spaghetti… refactor needed).

  • Property mode set to 100644
File size: 20.1 KB
RevLine 
[1bf33c1]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
[6063b16]13import sys, os
[31482fc]14import pylab, time,numpy
[0d9dae8]15
[1bf33c1]16import danse.common.plottools
17from danse.common.plottools.PlotPanel import PlotPanel
[3b69ca6]18from danse.common.plottools.plottables import Graph,Theory1D
19from sans.guiframe import dataFitting
[1bf33c1]20from sans.guicomm.events import EVT_NEW_PLOT
[18eba35]21from sans.guicomm.events import StatusEvent ,NewPlotEvent,SlicerEvent,ErrorDataEvent
[1debb29]22from sans.guicomm.events import RemoveDataEvent
[0d9dae8]23from sans.guiframe.utils import PanelMenu
[1bf33c1]24
25from binder import BindArtist
26
[0d9dae8]27
28DEFAULT_QMAX = 0.05
[1bf33c1]29DEFAULT_QSTEP = 0.001
30DEFAULT_BEAM = 0.005
31BIN_WIDTH =1
32
[0d9dae8]33
[1bf33c1]34class ModelPanel1D(PlotPanel):
35    """
36        Plot panel for use with the GUI manager
37    """
38   
39    ## Internal name for the AUI manager
40    window_name = "plotpanel"
41    ## Title to appear on top of the window
42    window_caption = "Plot Panel"
43    ## Flag to tell the GUI manager that this panel is not
44    #  tied to any perspective
45    ALWAYS_ON = True
46    ## Group ID
47    group_id = None
48   
49    def __init__(self, parent, id = -1, color = None,\
50        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
51        """
52            Initialize the panel
53        """
54        PlotPanel.__init__(self, parent, id = id, style = style, **kwargs)
55       
56        ## Reference to the parent window
57        self.parent = parent
58        ## Plottables
59        self.plots = {}
[869c368]60        ## save errors dy  for each data plotted
61        self.err_dy={}
[6c0568b]62        ## flag to determine if the hide or show context menu item should
63        ## be displayed
[31482fc]64        self.errors_hide=False
[1bf33c1]65        ## Unique ID (from gui_manager)
66        self.uid = None
67        ## Action IDs for internal call-backs
68        self.action_ids = {}
[6063b16]69        ## Default locations
70        self._default_save_location = os.getcwd()       
[1bf33c1]71        ## Graph       
72        self.graph = Graph()
73        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
74        self.graph.yaxis("\\rm{Intensity} ","cm^{-1}")
75        self.graph.render(self)
76   
[6c0568b]77   
[1bf33c1]78    def _reset(self):
79        """
80            Resets internal data and graph
81        """   
82        self.graph.reset()
83        self.plots      = {}
84        self.action_ids = {}
85   
[6c0568b]86   
[1bf33c1]87    def _onEVT_1DREPLOT(self, event):
88        """
89            Data is ready to be displayed
90            @param event: data event
91        """
[ffd23b5]92       
[1bf33c1]93        #TODO: Check for existence of plot attribute
94        # Check whether this is a replot. If we ask for a replot
95        # and the plottable no longer exists, ignore the event.
96        if hasattr(event, "update") and event.update==True \
97            and event.plot.name not in self.plots.keys():
98            return
99       
100        if hasattr(event, "reset"):
101            self._reset()
102       
103        is_new = True
104        if event.plot.name in self.plots.keys():
105            # Check whether the class of plottable changed
106            if not event.plot.__class__==self.plots[event.plot.name].__class__:
[ab8f936]107                #overwrite a plottable using the same name
[1bf33c1]108                self.graph.delete(self.plots[event.plot.name])
109            else:
[ab8f936]110                # plottable is already draw on the panel
[1bf33c1]111                is_new = False
[ffd23b5]112       
[1bf33c1]113        if is_new:
[ab8f936]114            # a new plottable overwrites a plotted one  using the same id
115            for plottable in self.plots.itervalues():
[a7bd562]116                if hasattr(event.plot,"id") and hasattr(plottable, "id"):
[e48a62e]117                    if event.plot.id==plottable.id :
118                        self.graph.delete(plottable)
[ab8f936]119           
[1bf33c1]120            self.plots[event.plot.name] = event.plot
121            self.graph.add(self.plots[event.plot.name])
122        else:
[ab8f936]123            #replot the graph
[1bf33c1]124            self.plots[event.plot.name].x = event.plot.x   
125            self.plots[event.plot.name].y = event.plot.y   
126            self.plots[event.plot.name].dy = event.plot.dy 
127            if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
128                self.plots[event.plot.name].dx = event.plot.dx   
[31482fc]129         
[1bf33c1]130        #TODO: Should re-factor this
[6c0568b]131        ## for all added plot the option to hide error show be displayed first
[31482fc]132        #self.errors_hide = 0
[6c0568b]133        ## Set axis labels
[1bf33c1]134        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
135        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
[6c0568b]136        ## Set the view scale for all plots
[1bf33c1]137        self._onEVT_FUNC_PROPERTY()
[6c0568b]138        ## render the graph
[1bf33c1]139        self.graph.render(self)
140        self.subplot.figure.canvas.draw_idle()
[31482fc]141        if self.errors_hide:
142            self._on_remove_errors(evt=None)
143        else:
144            self._on_add_errors( evt=None)
145        return
146   
[1bf33c1]147    def onLeftDown(self,event): 
[6c0568b]148        """
149            left button down and ready to drag
150            Display the position of the mouse on the statusbar
151        """
[1bf33c1]152        PlotPanel.onLeftDown(self, event)
153        ax = event.inaxes
154        if ax != None:
155            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
156            wx.PostEvent(self.parent, StatusEvent(status=position))
157
[6c0568b]158
[1bf33c1]159    def _onRemove(self, event):
160        """
[6c0568b]161            Remove a plottable from the graph and render the graph
162            @param event: Menu event
[1bf33c1]163        """
[6c0568b]164        ## Check if there is a selected graph to remove
[1debb29]165        if not self.graph.selected_plottable == None and\
166            self.graph.selected_plottable in self.plots.keys():
167            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
168           
169            event = RemoveDataEvent(data =self.plots[self.graph.selected_plottable])
170            wx.PostEvent(self.parent, event)
[1bf33c1]171            self.graph.delete(self.plots[self.graph.selected_plottable])
172            del self.plots[self.graph.selected_plottable]
[1debb29]173            ## increment graph color
174            self.graph.color += color
[1bf33c1]175            self.graph.render(self)
176            self.subplot.figure.canvas.draw_idle()   
[1debb29]177           
[1bf33c1]178           
179
180    def onContextMenu(self, event):
181        """
182            1D plot context menu
183            @param event: wx context event
184        """
185        slicerpop = PanelMenu()
186        slicerpop.set_plots(self.plots)
187        slicerpop.set_graph(self.graph)
188               
[9a585d0]189        # Various plot options
190        id = wx.NewId()
191        slicerpop.Append(id,'&Save image', 'Save image as PNG')
192        wx.EVT_MENU(self, id, self.onSaveImage)
193       
194        id = wx.NewId()
195        slicerpop.Append(id,'&Print image', 'Print image ')
[18eba35]196        wx.EVT_MENU(self, id, self.onPrint)
197         
198        id = wx.NewId()
199        slicerpop.Append(id,'&Print Preview', 'image preview for print')
200        wx.EVT_MENU(self, id, self.onPrinterPreview)
[9a585d0]201           
202        slicerpop.AppendSeparator()
203        item_list = self.parent.get_context_menu(self.graph)
[6c0568b]204       
[9a585d0]205        if (not item_list==None) and (not len(item_list)==0):
206            for item in item_list:
207                try:
208                    id = wx.NewId()
209                    slicerpop.Append(id, item[0], item[1])
210                    wx.EVT_MENU(self, id, item[2])
211                except:
[6c0568b]212                    wx.PostEvent(self.parent, StatusEvent(status=\
213                        "ModelPanel1D.onContextMenu: bad menu item  %s"%sys.exc_value))
[9a585d0]214                    pass
215            slicerpop.AppendSeparator()
[6c0568b]216       
[1bf33c1]217        if self.graph.selected_plottable in self.plots:
218            plot = self.plots[self.graph.selected_plottable]
219            id = wx.NewId()
220            name = plot.name
[6c0568b]221           
[42d27f2]222            slicerpop.Append(id, "&Save points" )
[1bf33c1]223            self.action_ids[str(id)] = plot
224            wx.EVT_MENU(self, id, self._onSave)
[31482fc]225         
[1bf33c1]226            id = wx.NewId()
227            slicerpop.Append(id, "Remove %s curve" % name)
228            self.action_ids[str(id)] = plot
229            wx.EVT_MENU(self, id, self._onRemove)
[9a585d0]230            slicerpop.AppendSeparator()
[1bf33c1]231            # Option to hide
232            #TODO: implement functionality to hide a plottable (legend click)
[d468daa]233       
[1bf33c1]234        if self.graph.selected_plottable in self.plots:
[0b16ee3]235            selected_plot= self.plots[self.graph.selected_plottable]
236            #if self.plots[self.graph.selected_plottable].name in self.err_dy.iterkeys()\
237            #    and self.errors_hide:
238            if selected_plot.__class__.__name__=="Data1D":
239                if selected_plot.dy ==None or selected_plot.dy== []:
240                    id = wx.NewId()
241                    slicerpop.Append(id, '&Show errors to data')
242                    wx.EVT_MENU(self, id, self._on_add_errors)
243                elif numpy.all(selected_plot.dy==0):
244                    id = wx.NewId()
245                    slicerpop.Append(id, '&Show errors to data')
246                    wx.EVT_MENU(self, id, self._on_add_errors)
247                else:
[dd66fbd]248                    id = wx.NewId()
[18eba35]249                    slicerpop.Append(id, '&Hide Error bars')
[dd66fbd]250                    wx.EVT_MENU(self, id, self._on_remove_errors)
[3cc533e]251           
252            id = wx.NewId()
253            slicerpop.Append(id, '&Linear fit')
254            wx.EVT_MENU(self, id, self.onFitting)
[1bf33c1]255               
[9a585d0]256            slicerpop.AppendSeparator()
[6c0568b]257       
[1bf33c1]258        id = wx.NewId()
259        slicerpop.Append(id, '&Change scale')
260        wx.EVT_MENU(self, id, self._onProperties)
[6c0568b]261       
[1bf33c1]262        id = wx.NewId()
263        slicerpop.Append(id, '&Reset Graph')
[d468daa]264        wx.EVT_MENU(self, id, self.onResetGraph) 
[6d920cd]265       
[1bf33c1]266        pos = event.GetPosition()
267        pos = self.ScreenToClient(pos)
268        self.PopupMenu(slicerpop, pos)
[18eba35]269       
270       
[869c368]271    def _on_remove_errors(self, evt):
[6c0568b]272        """
273            Save name and dy of data in dictionary self.err_dy
274            Create a new data1D with the same x, y
275            vector and dy with zeros.
276            post self.err_dy as event (ErrorDataEvent) for any object
277            which wants to reconstruct the initial data.
278            @param evt: Menu event
279        """
[869c368]280        if not self.graph.selected_plottable == None:
[6c0568b]281            ## store existing dy
[869c368]282            name =self.plots[self.graph.selected_plottable].name
283            dy = self.plots[self.graph.selected_plottable].dy
284            self.err_dy[name]= dy
[6c0568b]285            ## Create a new dy for a new plottable
[18eba35]286            import numpy
287            dy= numpy.zeros(len(self.plots[self.graph.selected_plottable].y))
[0b16ee3]288            selected_plot= self.plots[self.graph.selected_plottable]
289           
290            if selected_plot.__class__.__name__=="Data1D":
[8068b52]291                # Make sure that we can pass a basic Data1D
292                dxl = None
293                dxw = None
294                if hasattr(selected_plot, "dxl"):
295                    dxl = selected_plot.dxl
296                if hasattr(selected_plot, "dxw"):
297                    dxw = selected_plot.dxw
[3b69ca6]298                new_plot = dataFitting.Data1D( x=selected_plot.x,
299                              y= selected_plot.y,
300                               dx=selected_plot.dx,
301                              dy=dy,
[8068b52]302                              dxl=dxl,
303                              dxw=dxw)
[3b69ca6]304                           
[0b16ee3]305            else:
[3b69ca6]306                 new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
[869c368]307            new_plot.interactive = True
[31482fc]308            self.errors_hide = True
[869c368]309            new_plot.name = self.plots[self.graph.selected_plottable].name
310            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
311                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
312                new_plot.id = self.plots[self.graph.selected_plottable].id
313            else:
314                new_plot.group_id = str(time.time())
315                new_plot.id = str(time.time())
316            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
317            new_plot.xaxis(label, unit)
318            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
319            new_plot.yaxis(label, unit)
[6c0568b]320            ## save the color of the selected plottable before it is deleted
[869c368]321            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
[18eba35]322            self.graph.delete(self.plots[self.graph.selected_plottable])
[6c0568b]323            ## add newly created plottable to the graph with the save color
[0b16ee3]324            self.graph.color += color
[dd66fbd]325            self.graph.add(new_plot,color)
[6c0568b]326            ## transforming the view of the new data into the same of the previous data
[869c368]327            self._onEVT_FUNC_PROPERTY()
[6c0568b]328            ## save the plot
[869c368]329            self.plots[self.graph.selected_plottable]=new_plot
[6c0568b]330            ## Render the graph
[869c368]331            self.graph.render(self)
332            self.subplot.figure.canvas.draw_idle() 
[18eba35]333           
334            event = ErrorDataEvent(err_dy=self.err_dy)
335            wx.PostEvent(self.parent, event)
[1bf33c1]336   
[6c0568b]337   
[1bf33c1]338    def _on_add_errors(self, evt):
339        """
[6c0568b]340            create a new data1D witht the errors saved in self.err_dy
341            to show errors of the plot.
[1bf33c1]342            Compute reasonable errors for a data set without
343            errors and transorm the plottable to a Data1D
[6c0568b]344            @param evt: Menu event
[1bf33c1]345        """
346        import math
347        import numpy
348        import time
349       
[1debb29]350        if not self.graph.selected_plottable == None \
351            and self.graph.selected_plottable in self.plots.keys():
[6c0568b]352            ##Reset the flag to display the hide option on the context menu
[31482fc]353            self.errors_hide = False
[6c0568b]354            ## restore dy
[1bf33c1]355            length = len(self.plots[self.graph.selected_plottable].x)
356            dy = numpy.zeros(length)
[3b69ca6]357           
[869c368]358            selected_plot= self.plots[self.graph.selected_plottable]
[0b16ee3]359           
[869c368]360            try:
361                dy = self.err_dy[selected_plot.name]
[0b16ee3]362               
[869c368]363            except:
[bb5c1c7]364                #for i in range(length):
365                #dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])     
[0b16ee3]366                if hasattr(selected_plot,"dy"):
367                    dy= selected_plot.dy
368                else:
369                    dy = numpy.zeros(selected_plot.dy)
370                   
[6c0568b]371            ## Create a new plottable data1D
[0b16ee3]372            if selected_plot.__class__.__name__=="Data1D":
[8068b52]373                # Make sure that we can pass a basic Data1D
374                dxl = None
375                dxw = None
376                if hasattr(selected_plot, "dxl"):
377                    dxl = selected_plot.dxl
378                if hasattr(selected_plot, "dxw"):
379                    dxw = selected_plot.dxw
[3b69ca6]380                new_plot = dataFitting.Data1D( x=selected_plot.x,
381                                               y= selected_plot.y,
382                                               dx=selected_plot.dx,
383                                               dy=dy,
[8068b52]384                                               dxl=dxl,
385                                               dxw=dxw)
[0b16ee3]386            else:
387                ## Create a new plottable Theory1D
[3b69ca6]388                new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
389           
[1bf33c1]390            new_plot.interactive = True
391            new_plot.name = self.plots[self.graph.selected_plottable].name
392            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
393                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
[d1dd9d4]394                new_plot.id = self.plots[self.graph.selected_plottable].id
[1bf33c1]395            else:
396                new_plot.group_id = str(time.time())
[d1dd9d4]397                new_plot.id = str(time.time())
[1bf33c1]398           
399            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
400            new_plot.xaxis(label, unit)
401            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
402            new_plot.yaxis(label, unit)
[6c0568b]403            ## save the color of the selected plottable before it is deleted
[18eba35]404            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
405            self.graph.delete(self.plots[self.graph.selected_plottable])
[0b16ee3]406            self.graph.color += color
[6c0568b]407            ## add newly created plottable to the graph with the save color
[18eba35]408            self.graph.add(new_plot, color)
[6c0568b]409            ## transforming the view of the new data into the same of the previous data
[ffd23b5]410            self._onEVT_FUNC_PROPERTY()
[6c0568b]411            ## save the plot
[1bf33c1]412            self.plots[self.graph.selected_plottable]=new_plot
[6c0568b]413            ## render the graph with its new content
[1bf33c1]414            self.graph.render(self)
[8bd764d]415            self.subplot.figure.canvas.draw_idle() 
416               
[6c0568b]417               
[42d27f2]418    def _onSaveXML(self, path):
[6c0568b]419        """
420            Save 1D  Data to  XML file
[1abcb04]421           
422            TODO: Refactor and remove this method. See TODO in _onSave.
423           
[6c0568b]424            @param evt: Menu event
425        """
[42d27f2]426        if not path == None:
427            out = open(path, 'w')
428            from DataLoader.readers import cansas_reader
429            reader = cansas_reader.Reader()
430            datainfo= self.plots[self.graph.selected_plottable].info
431            reader.write( path, datainfo)
[6063b16]432           
433            try:
434                self._default_save_location = os.path.dirname(path)
435            except:
436                pass
437       
[42d27f2]438        return 
439   
440   
441    def _onsaveTXT(self, path):
442        """
443            Save file as txt
[1abcb04]444           
445            TODO: Refactor and remove this method. See TODO in _onSave.
[42d27f2]446        """
447        data = self.plots[self.graph.selected_plottable]
448       
449        if not path == None:
450            out = open(path, 'w')
451            has_errors = True
452            if data.dy==None or data.dy==[]:
453                has_errors = False
454               
455            # Sanity check
456            if has_errors:
457                try:
458                    if len(data.y) != len(data.dy):
459
460                        has_errors = False
461                except:
462                    has_errors = False
[8bd764d]463           
[42d27f2]464            if has_errors:
465                out.write("<X>   <Y>   <dY>\n")
466            else:
467                out.write("<X>   <Y>\n")
468               
469            for i in range(len(data.x)):
470                if has_errors:
471                    out.write("%g  %g  %g\n" % (data.x[i], 
472                                                data.y[i],
473                                               data.dy[i]))
474                else:
475                    out.write("%g  %g\n" % (data.x[i], 
476                                            data.y[i]))
477                   
478            out.close()                 
[6063b16]479            try:
480                self._default_save_location = os.path.dirname(path)
481            except:
482                pass   
[8bd764d]483               
[1bf33c1]484    def _onSave(self, evt):
485        """
486            Save a data set to a text file
487            @param evt: Menu event
488        """
489        import os
490        id = str(evt.GetId())
491        if id in self.action_ids:         
492           
493            path = None
[5fe5871c]494            wildcard = "Text files (*.txt)|*.txt|"\
[7959f297]495            "CanSAS 1D files(*.xml)|*.xml" 
[6063b16]496            dlg = wx.FileDialog(self, "Choose a file",
497                                self._default_save_location, "",wildcard , wx.SAVE)
[42d27f2]498           
[1bf33c1]499            if dlg.ShowModal() == wx.ID_OK:
500                path = dlg.GetPath()
501                mypath = os.path.basename(path)
[1abcb04]502               
503                #TODO: This is bad design. The DataLoader is designed to recognize extensions.
504                # It should be a simple matter of calling the .save(file, data, '.xml') method
505                # of the DataLoader.loader.Loader class.
506               
[5fe5871c]507                if os.path.splitext(mypath)[1].lower() ==".txt":
508                    self._onsaveTXT(path)
509                if os.path.splitext(mypath)[1].lower() ==".xml":
510                    self._onSaveXML(path)
[6063b16]511           
[1bf33c1]512            dlg.Destroy()
[5fe5871c]513           
514           
[6c0568b]515   
[1bf33c1]516   
[6c0568b]517   
[1bf33c1]518       
Note: See TracBrowser for help on using the repository browser.