source: sasview/guiframe/local_perspectives/plotting/Plotter1D.py @ 095ab1b

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 095ab1b was 21d99c2, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

guiframe: revised logic for removing empty plots

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