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

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

guiframe: ensure that the legend (and the whole plottable) is removed from the graph when an empty plottable is sent in a plotting event

  • Property mode set to 100644
File size: 20.0 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               
117            # Check whether the class of plottable changed
118            if not event.plot.__class__==self.plots[event.plot.name].__class__:
119                #overwrite a plottable using the same name
120                self.graph.delete(self.plots[event.plot.name])
121            else:
122                # plottable is already draw on the panel
123                is_new = False
124       
125        if not is_empty:
126            if is_new:
127                # a new plottable overwrites a plotted one  using the same id
128                for plottable in self.plots.itervalues():
129                    if hasattr(event.plot,"id") and hasattr(plottable, "id"):
130                        if event.plot.id==plottable.id :
131                            self.graph.delete(plottable)
132               
133                self.plots[event.plot.name] = event.plot
134                self.graph.add(self.plots[event.plot.name])
135            else:
136                #replot the graph
137                self.plots[event.plot.name].x = event.plot.x   
138                self.plots[event.plot.name].y = event.plot.y   
139                self.plots[event.plot.name].dy = event.plot.dy 
140                if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
141                    self.plots[event.plot.name].dx = event.plot.dx   
142         
143        #TODO: Should re-factor this
144        ## for all added plot the option to hide error show be displayed first
145        #self.errors_hide = 0
146        ## Set axis labels
147        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
148        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
149        ## Set the view scale for all plots
150        self._onEVT_FUNC_PROPERTY()
151        ## render the graph
152        self.graph.render(self)
153        self.subplot.figure.canvas.draw_idle()
154        if self.errors_hide:
155            self._on_remove_errors(evt=None)
156        else:
157            self._on_add_errors( evt=None)
158        return
159   
160    def onLeftDown(self,event): 
161        """
162            left button down and ready to drag
163            Display the position of the mouse on the statusbar
164        """
165        PlotPanel.onLeftDown(self, event)
166        ax = event.inaxes
167        if ax != None:
168            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
169            wx.PostEvent(self.parent, StatusEvent(status=position))
170
171
172    def _onRemove(self, event):
173        """
174            Remove a plottable from the graph and render the graph
175            @param event: Menu event
176        """
177        ## Check if there is a selected graph to remove
178        if not self.graph.selected_plottable == None and\
179            self.graph.selected_plottable in self.plots.keys():
180            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
181           
182            event = RemoveDataEvent(data =self.plots[self.graph.selected_plottable])
183            wx.PostEvent(self.parent, event)
184            self.graph.delete(self.plots[self.graph.selected_plottable])
185            del self.plots[self.graph.selected_plottable]
186            ## increment graph color
187            self.graph.color += color
188            self.graph.render(self)
189            self.subplot.figure.canvas.draw_idle()   
190           
191           
192
193    def onContextMenu(self, event):
194        """
195            1D plot context menu
196            @param event: wx context event
197        """
198        slicerpop = PanelMenu()
199        slicerpop.set_plots(self.plots)
200        slicerpop.set_graph(self.graph)
201               
202        # Various plot options
203        id = wx.NewId()
204        slicerpop.Append(id,'&Save image', 'Save image as PNG')
205        wx.EVT_MENU(self, id, self.onSaveImage)
206       
207        id = wx.NewId()
208        slicerpop.Append(id,'&Print image', 'Print image ')
209        wx.EVT_MENU(self, id, self.onPrint)
210         
211        id = wx.NewId()
212        slicerpop.Append(id,'&Print Preview', 'image preview for print')
213        wx.EVT_MENU(self, id, self.onPrinterPreview)
214           
215        slicerpop.AppendSeparator()
216        item_list = self.parent.get_context_menu(self.graph)
217       
218        if (not item_list==None) and (not len(item_list)==0):
219            for item in item_list:
220                try:
221                    id = wx.NewId()
222                    slicerpop.Append(id, item[0], item[1])
223                    wx.EVT_MENU(self, id, item[2])
224                except:
225                    wx.PostEvent(self.parent, StatusEvent(status=\
226                        "ModelPanel1D.onContextMenu: bad menu item  %s"%sys.exc_value))
227                    pass
228            slicerpop.AppendSeparator()
229       
230        if self.graph.selected_plottable in self.plots:
231            plot = self.plots[self.graph.selected_plottable]
232            id = wx.NewId()
233            name = plot.name
234           
235            slicerpop.Append(id, "&Save points" )
236            self.action_ids[str(id)] = plot
237            wx.EVT_MENU(self, id, self._onSave)
238         
239            id = wx.NewId()
240            slicerpop.Append(id, "Remove %s curve" % name)
241            self.action_ids[str(id)] = plot
242            wx.EVT_MENU(self, id, self._onRemove)
243            slicerpop.AppendSeparator()
244            # Option to hide
245            #TODO: implement functionality to hide a plottable (legend click)
246       
247        if self.graph.selected_plottable in self.plots:
248            selected_plot= self.plots[self.graph.selected_plottable]
249            #if self.plots[self.graph.selected_plottable].name in self.err_dy.iterkeys()\
250            #    and self.errors_hide:
251            if selected_plot.__class__.__name__=="Data1D":
252                if numpy.all(selected_plot.dy==0):
253                    id = wx.NewId()
254                    slicerpop.Append(id, '&Show errors')
255                    wx.EVT_MENU(self, id, self._on_add_errors)
256                elif selected_plot.dy !=None and selected_plot.dy != []:
257                    id = wx.NewId()
258                    slicerpop.Append(id, '&Hide Error bars')
259                    wx.EVT_MENU(self, id, self._on_remove_errors)
260           
261            id = wx.NewId()
262            slicerpop.Append(id, '&Linear fit')
263            wx.EVT_MENU(self, id, self.onFitting)
264               
265            slicerpop.AppendSeparator()
266       
267        id = wx.NewId()
268        slicerpop.Append(id, '&Change scale')
269        wx.EVT_MENU(self, id, self._onProperties)
270       
271        id = wx.NewId()
272        slicerpop.Append(id, '&Reset Graph')
273        wx.EVT_MENU(self, id, self.onResetGraph) 
274       
275        pos = event.GetPosition()
276        pos = self.ScreenToClient(pos)
277        self.PopupMenu(slicerpop, pos)
278       
279       
280    def _on_remove_errors(self, evt):
281        """
282            Save name and dy of data in dictionary self.err_dy
283            Create a new data1D with the same x, y
284            vector and dy with zeros.
285            post self.err_dy as event (ErrorDataEvent) for any object
286            which wants to reconstruct the initial data.
287            @param evt: Menu event
288        """
289        if not self.graph.selected_plottable == None:
290            ## store existing dy
291            name =self.plots[self.graph.selected_plottable].name
292            dy = self.plots[self.graph.selected_plottable].dy
293            self.err_dy[name]= dy
294            ## Create a new dy for a new plottable
295            import numpy
296            dy= numpy.zeros(len(self.plots[self.graph.selected_plottable].y))
297            selected_plot= self.plots[self.graph.selected_plottable]
298           
299            if selected_plot.__class__.__name__=="Data1D":
300                # Make sure that we can pass a basic Data1D
301                dxl = None
302                dxw = None
303                if hasattr(selected_plot, "dxl"):
304                    dxl = selected_plot.dxl
305                if hasattr(selected_plot, "dxw"):
306                    dxw = selected_plot.dxw
307                new_plot = Data1D( x=selected_plot.x,
308                              y= selected_plot.y,
309                               dx=selected_plot.dx,
310                              dy=dy)
311                new_plot.dxl  = dxl
312                new_plot.dxw = dxw
313                             
314            else:
315                 new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
316            new_plot.interactive = True
317            self.errors_hide = True
318            new_plot.name = self.plots[self.graph.selected_plottable].name
319            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
320                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
321                new_plot.id = self.plots[self.graph.selected_plottable].id
322            else:
323                new_plot.group_id = str(time.time())
324                new_plot.id = str(time.time())
325            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
326            new_plot.xaxis(label, unit)
327            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
328            new_plot.yaxis(label, unit)
329            ## save the color of the selected plottable before it is deleted
330            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
331            self.graph.delete(self.plots[self.graph.selected_plottable])
332            ## add newly created plottable to the graph with the save color
333            self.graph.color += color
334            self.graph.add(new_plot,color)
335            ## transforming the view of the new data into the same of the previous data
336            self._onEVT_FUNC_PROPERTY()
337            ## save the plot
338            self.plots[self.graph.selected_plottable]=new_plot
339            ## Render the graph
340            self.graph.render(self)
341            self.subplot.figure.canvas.draw_idle() 
342           
343            event = ErrorDataEvent(err_dy=self.err_dy)
344            wx.PostEvent(self.parent, event)
345   
346   
347    def _on_add_errors(self, evt):
348        """
349            create a new data1D witht the errors saved in self.err_dy
350            to show errors of the plot.
351            Compute reasonable errors for a data set without
352            errors and transorm the plottable to a Data1D
353            @param evt: Menu event
354        """
355       
356       
357        if not self.graph.selected_plottable == None \
358            and self.graph.selected_plottable in self.plots.keys():
359            ##Reset the flag to display the hide option on the context menu
360            self.errors_hide = False
361            ## restore dy
362            length = len(self.plots[self.graph.selected_plottable].x)
363            dy = numpy.zeros(length)
364           
365            selected_plot= self.plots[self.graph.selected_plottable]
366           
367            try:
368                dy = self.err_dy[selected_plot.name]
369               
370            except:
371                #for i in range(length):
372                #dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])     
373                if hasattr(selected_plot,"dy"):
374                    dy= selected_plot.dy
375                else:
376                    dy = numpy.zeros(selected_plot.dy)
377                   
378            ## Create a new plottable data1D
379            if selected_plot.__class__.__name__=="Data1D":
380                # Make sure that we can pass a basic Data1D
381                dxl = None
382                dxw = None
383                if hasattr(selected_plot, "dxl"):
384                    dxl = selected_plot.dxl
385                if hasattr(selected_plot, "dxw"):
386                    dxw = selected_plot.dxw
387                new_plot = Data1D( x=selected_plot.x,
388                                               y= selected_plot.y,
389                                               dx=selected_plot.dx,
390                                               dy=dy)
391                new_plot.dxl = dxl
392                new_plot.dxw = dxw
393                                     
394            else:
395                ## Create a new plottable Theory1D
396                new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
397           
398            new_plot.interactive = True
399            new_plot.name = self.plots[self.graph.selected_plottable].name
400            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
401                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
402                new_plot.id = self.plots[self.graph.selected_plottable].id
403            else:
404                new_plot.group_id = str(time.time())
405                new_plot.id = str(time.time())
406           
407            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
408            new_plot.xaxis(label, unit)
409            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
410            new_plot.yaxis(label, unit)
411            ## save the color of the selected plottable before it is deleted
412            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
413            self.graph.delete(self.plots[self.graph.selected_plottable])
414            self.graph.color += color
415            ## add newly created plottable to the graph with the save color
416            self.graph.add(new_plot, color)
417            ## transforming the view of the new data into the same of the previous data
418            self._onEVT_FUNC_PROPERTY()
419            ## save the plot
420            self.plots[self.graph.selected_plottable]=new_plot
421            ## render the graph with its new content
422            self.graph.render(self)
423            self.subplot.figure.canvas.draw_idle() 
424               
425               
426    def _onsaveTXT(self, path):
427        """
428            Save file as txt
429           
430            TODO: Refactor and remove this method. See TODO in _onSave.
431        """
432        data = self.plots[self.graph.selected_plottable]
433       
434        if not path == None:
435            out = open(path, 'w')
436            has_errors = True
437            if data.dy==None or data.dy==[]:
438                has_errors = False
439               
440            # Sanity check
441            if has_errors:
442                try:
443                    if len(data.y) != len(data.dy):
444
445                        has_errors = False
446                except:
447                    has_errors = False
448           
449            if has_errors:
450                out.write("<X>   <Y>   <dY>\n")
451            else:
452                out.write("<X>   <Y>\n")
453               
454            for i in range(len(data.x)):
455                if has_errors:
456                    out.write("%g  %g  %g\n" % (data.x[i], 
457                                                data.y[i],
458                                               data.dy[i]))
459                else:
460                    out.write("%g  %g\n" % (data.x[i], 
461                                            data.y[i]))
462                   
463            out.close()                 
464            try:
465                self._default_save_location = os.path.dirname(path)
466            except:
467                pass   
468               
469    def _onSave(self, evt):
470        """
471            Save a data set to a text file
472            @param evt: Menu event
473        """
474       
475        id = str(evt.GetId())
476        if id in self.action_ids:         
477           
478            path = None
479            wildcard = "Text files (*.txt)|*.txt|"\
480            "CanSAS 1D files(*.xml)|*.xml" 
481            dlg = wx.FileDialog(self, "Choose a file",
482                                self._default_save_location, "",wildcard , wx.SAVE)
483           
484            if dlg.ShowModal() == wx.ID_OK:
485                path = dlg.GetPath()
486                mypath = os.path.basename(path)
487               
488                #TODO: This is bad design. The DataLoader is designed to recognize extensions.
489                # It should be a simple matter of calling the .save(file, data, '.xml') method
490                # of the DataLoader.loader.Loader class.
491                from DataLoader.loader import  Loader
492                #Instantiate a loader
493                loader = Loader() 
494                data = self.plots[self.graph.selected_plottable]
495                format=".txt"
496                if os.path.splitext(mypath)[1].lower() == format:
497                     self._onsaveTXT( path)
498
499                format= ".xml"
500                if os.path.splitext(mypath)[1].lower() ==format:
501                    loader.save( path, data, format)
502                try:
503                    self._default_save_location = os.path.dirname(path)
504                except:
505                    pass   
506            dlg.Destroy()
507           
508           
509   
510           
511           
512   
513   
514   
515       
Note: See TracBrowser for help on using the repository browser.