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

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 c8570c3 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
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
14import os
15import pylab
16import math
17import numpy
18import time
19
20import danse.common.plottools
21from danse.common.plottools.PlotPanel import PlotPanel
22from danse.common.plottools.plottables import Graph
23from sans.guiframe import dataFitting
24from sans.guicomm.events import EVT_NEW_PLOT
25from sans.guicomm.events import StatusEvent ,NewPlotEvent,SlicerEvent,ErrorDataEvent
26from sans.guicomm.events import RemoveDataEvent
27from sans.guiframe.utils import PanelMenu
28from sans.guiframe.dataFitting import Data1D
29from sans.guiframe.dataFitting import Theory1D
30from binder import BindArtist
31
32
33DEFAULT_QMAX = 0.05
34DEFAULT_QSTEP = 0.001
35DEFAULT_BEAM = 0.005
36BIN_WIDTH =1
37
38
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 = {}
65        ## save errors dy  for each data plotted
66        self.err_dy={}
67        ## flag to determine if the hide or show context menu item should
68        ## be displayed
69        self.errors_hide=False
70        ## Unique ID (from gui_manager)
71        self.uid = None
72        ## Action IDs for internal call-backs
73        self.action_ids = {}
74        ## Default locations
75        self._default_save_location = os.getcwd()       
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   
82   
83    def _reset(self):
84        """
85            Resets internal data and graph
86        """   
87        self.graph.reset()
88        self.plots      = {}
89        self.action_ids = {}
90   
91   
92    def _onEVT_1DREPLOT(self, event):
93        """
94            Data is ready to be displayed
95            @param event: data event
96        """
97       
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       
108        # Check whether the plottable is empty
109        is_empty = len(event.plot.x)==0
110               
111        is_new = True
112        if event.plot.name in self.plots.keys():
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])
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
125       
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   
143         
144        #TODO: Should re-factor this
145        ## for all added plot the option to hide error show be displayed first
146        #self.errors_hide = 0
147        ## Set axis labels
148        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
149        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
150        ## Set the view scale for all plots
151        self._onEVT_FUNC_PROPERTY()
152        ## render the graph
153        self.graph.render(self)
154        self.subplot.figure.canvas.draw_idle()
155        if self.errors_hide:
156            self._on_remove_errors(evt=None)
157        else:
158            self._on_add_errors( evt=None)
159        return
160   
161    def onLeftDown(self,event): 
162        """
163            left button down and ready to drag
164            Display the position of the mouse on the statusbar
165        """
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
172
173    def _onRemove(self, event):
174        """
175            Remove a plottable from the graph and render the graph
176            @param event: Menu event
177        """
178        ## Check if there is a selected graph to remove
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)
185            self.graph.delete(self.plots[self.graph.selected_plottable])
186            del self.plots[self.graph.selected_plottable]
187            ## increment graph color
188            self.graph.color += color
189            self.graph.render(self)
190            self.subplot.figure.canvas.draw_idle()   
191           
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               
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 ')
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)
215           
216        slicerpop.AppendSeparator()
217        item_list = self.parent.get_context_menu(self.graph)
218       
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:
226                    wx.PostEvent(self.parent, StatusEvent(status=\
227                        "ModelPanel1D.onContextMenu: bad menu item  %s"%sys.exc_value))
228                    pass
229            slicerpop.AppendSeparator()
230       
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
235           
236            slicerpop.Append(id, "&Save points" )
237            self.action_ids[str(id)] = plot
238            wx.EVT_MENU(self, id, self._onSave)
239         
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)
244            slicerpop.AppendSeparator()
245            # Option to hide
246            #TODO: implement functionality to hide a plottable (legend click)
247       
248        if self.graph.selected_plottable in self.plots:
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":
253                if numpy.all(selected_plot.dy==0):
254                    id = wx.NewId()
255                    slicerpop.Append(id, '&Show errors')
256                    wx.EVT_MENU(self, id, self._on_add_errors)
257                elif selected_plot.dy !=None and selected_plot.dy != []:
258                    id = wx.NewId()
259                    slicerpop.Append(id, '&Hide Error bars')
260                    wx.EVT_MENU(self, id, self._on_remove_errors)
261           
262            id = wx.NewId()
263            slicerpop.Append(id, '&Linear fit')
264            wx.EVT_MENU(self, id, self.onFitting)
265               
266            slicerpop.AppendSeparator()
267       
268        id = wx.NewId()
269        slicerpop.Append(id, '&Change scale')
270        wx.EVT_MENU(self, id, self._onProperties)
271       
272        id = wx.NewId()
273        slicerpop.Append(id, '&Reset Graph')
274        wx.EVT_MENU(self, id, self.onResetGraph) 
275       
276        pos = event.GetPosition()
277        pos = self.ScreenToClient(pos)
278        self.PopupMenu(slicerpop, pos)
279       
280       
281    def _on_remove_errors(self, evt):
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        """
290        if not self.graph.selected_plottable == None:
291            ## store existing dy
292            name =self.plots[self.graph.selected_plottable].name
293            dy = self.plots[self.graph.selected_plottable].dy
294            self.err_dy[name]= dy
295            ## Create a new dy for a new plottable
296            import numpy
297            dy= numpy.zeros(len(self.plots[self.graph.selected_plottable].y))
298            selected_plot= self.plots[self.graph.selected_plottable]
299           
300            if selected_plot.__class__.__name__=="Data1D":
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
308                new_plot = Data1D( x=selected_plot.x,
309                              y= selected_plot.y,
310                               dx=selected_plot.dx,
311                              dy=dy)
312                new_plot.dxl  = dxl
313                new_plot.dxw = dxw
314                             
315            else:
316                 new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
317            new_plot.interactive = True
318            self.errors_hide = True
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)
330            ## save the color of the selected plottable before it is deleted
331            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
332            self.graph.delete(self.plots[self.graph.selected_plottable])
333            ## add newly created plottable to the graph with the save color
334            self.graph.color += color
335            self.graph.add(new_plot,color)
336            ## transforming the view of the new data into the same of the previous data
337            self._onEVT_FUNC_PROPERTY()
338            ## save the plot
339            self.plots[self.graph.selected_plottable]=new_plot
340            ## Render the graph
341            self.graph.render(self)
342            self.subplot.figure.canvas.draw_idle() 
343           
344            event = ErrorDataEvent(err_dy=self.err_dy)
345            wx.PostEvent(self.parent, event)
346   
347   
348    def _on_add_errors(self, evt):
349        """
350            create a new data1D witht the errors saved in self.err_dy
351            to show errors of the plot.
352            Compute reasonable errors for a data set without
353            errors and transorm the plottable to a Data1D
354            @param evt: Menu event
355        """
356       
357       
358        if not self.graph.selected_plottable == None \
359            and self.graph.selected_plottable in self.plots.keys():
360            ##Reset the flag to display the hide option on the context menu
361            self.errors_hide = False
362            ## restore dy
363            length = len(self.plots[self.graph.selected_plottable].x)
364            dy = numpy.zeros(length)
365           
366            selected_plot= self.plots[self.graph.selected_plottable]
367           
368            try:
369                dy = self.err_dy[selected_plot.name]
370               
371            except:
372                #for i in range(length):
373                #dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])     
374                if hasattr(selected_plot,"dy"):
375                    dy= selected_plot.dy
376                else:
377                    dy = numpy.zeros(selected_plot.dy)
378                   
379            ## Create a new plottable data1D
380            if selected_plot.__class__.__name__=="Data1D":
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
388                new_plot = Data1D( x=selected_plot.x,
389                                               y= selected_plot.y,
390                                               dx=selected_plot.dx,
391                                               dy=dy)
392                new_plot.dxl = dxl
393                new_plot.dxw = dxw
394                                     
395            else:
396                ## Create a new plottable Theory1D
397                new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
398           
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
403                new_plot.id = self.plots[self.graph.selected_plottable].id
404            else:
405                new_plot.group_id = str(time.time())
406                new_plot.id = str(time.time())
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)
412            ## save the color of the selected plottable before it is deleted
413            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
414            self.graph.delete(self.plots[self.graph.selected_plottable])
415            self.graph.color += color
416            ## add newly created plottable to the graph with the save color
417            self.graph.add(new_plot, color)
418            ## transforming the view of the new data into the same of the previous data
419            self._onEVT_FUNC_PROPERTY()
420            ## save the plot
421            self.plots[self.graph.selected_plottable]=new_plot
422            ## render the graph with its new content
423            self.graph.render(self)
424            self.subplot.figure.canvas.draw_idle() 
425               
426               
427    def _onsaveTXT(self, path):
428        """
429            Save file as txt
430           
431            TODO: Refactor and remove this method. See TODO in _onSave.
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
449           
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()                 
465            try:
466                self._default_save_location = os.path.dirname(path)
467            except:
468                pass   
469               
470    def _onSave(self, evt):
471        """
472            Save a data set to a text file
473            @param evt: Menu event
474        """
475       
476        id = str(evt.GetId())
477        if id in self.action_ids:         
478           
479            path = None
480            wildcard = "Text files (*.txt)|*.txt|"\
481            "CanSAS 1D files(*.xml)|*.xml" 
482            dlg = wx.FileDialog(self, "Choose a file",
483                                self._default_save_location, "",wildcard , wx.SAVE)
484           
485            if dlg.ShowModal() == wx.ID_OK:
486                path = dlg.GetPath()
487                mypath = os.path.basename(path)
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
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   
507            dlg.Destroy()
508           
509           
510   
511           
512           
513   
514   
515   
516       
Note: See TracBrowser for help on using the repository browser.