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

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 f29a433 was 4ed210f4, checked in by Gervaise Alina <gervyh@…>, 15 years ago

set focus onthe panel by left mouse clicking

  • Property mode set to 100644
File size: 20.6 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, AddManyDataEvent
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    def _reset(self):
83        """
84            Resets internal data and graph
85        """   
86        self.graph.reset()
87        self.plots      = {}
88        self.action_ids = {}
89   
90    def _onEVT_1DREPLOT(self, event):
91        """
92            Data is ready to be displayed
93            @param event: data event
94        """
95       
96        #TODO: Check for existence of plot attribute
97        # Check whether this is a replot. If we ask for a replot
98        # and the plottable no longer exists, ignore the event.
99        if hasattr(event, "update") and event.update==True \
100            and event.plot.name not in self.plots.keys():
101            return
102       
103        if hasattr(event, "reset"):
104            self._reset()
105       
106        # Check whether the plottable is empty
107        is_empty = len(event.plot.x)==0
108               
109        is_new = True
110        if event.plot.name in self.plots.keys():
111            # If the plottable is empty, just remove the plottable from the graph
112            if is_empty:
113                self.graph.delete(self.plots[event.plot.name])
114                del self.plots[event.plot.name]
115            else: 
116                # Check whether the class of plottable changed
117                if not event.plot.__class__==self.plots[event.plot.name].__class__:
118                    #overwrite a plottable using the same name
119                    self.graph.delete(self.plots[event.plot.name])             
120                else:
121                    # plottable is already draw on the panel
122                    is_new = False
123       
124        if not is_empty:
125            if is_new:
126                # a new plottable overwrites a plotted one  using the same id
127                for plottable in self.plots.itervalues():
128                    if hasattr(event.plot,"id") and hasattr(plottable, "id"):
129                        if event.plot.id==plottable.id :
130                            self.graph.delete(plottable)
131               
132                self.plots[event.plot.name] = event.plot
133                self.graph.add(self.plots[event.plot.name])
134            else:
135                #replot the graph
136                self.plots[event.plot.name].x = event.plot.x   
137                self.plots[event.plot.name].y = event.plot.y   
138                self.plots[event.plot.name].dy = event.plot.dy 
139                if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
140                    self.plots[event.plot.name].dx = event.plot.dx   
141         
142        #TODO: Should re-factor this
143        ## for all added plot the option to hide error show be displayed first
144        #self.errors_hide = 0
145        ## Set axis labels
146        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
147        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
148        ## Set the view scale for all plots
149        self._onEVT_FUNC_PROPERTY()
150        ## render the graph
151        self.graph.render(self)
152        self.subplot.figure.canvas.draw_idle()
153        if self.errors_hide:
154            self._on_remove_errors(evt=None)
155        else:
156            self._on_add_errors( evt=None)
157   
158    def onLeftDown(self,event): 
159        """
160            left button down and ready to drag
161            Display the position of the mouse on the statusbar
162        """
163        PlotPanel.onLeftDown(self, event)
164        ax = event.inaxes
165        if ax != None:
166            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
167            wx.PostEvent(self.parent, StatusEvent(status=position))
168           
169        #post nd event to notify guiframe that this panel is on focus
170        wx.PostEvent(self.parent, AddManyDataEvent(panel=self))
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                if hasattr(self.plots[self.graph.selected_plottable],"id"):
322                    new_plot.id = self.plots[self.graph.selected_plottable].id
323                else:
324                    new_plot.id = str(time.time())
325            else:
326                new_plot.group_id = str(time.time())
327                new_plot.id = str(time.time())
328            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
329            new_plot.xaxis(label, unit)
330            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
331            new_plot.yaxis(label, unit)
332            ## save the color of the selected plottable before it is deleted
333            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
334            self.graph.delete(self.plots[self.graph.selected_plottable])
335            ## add newly created plottable to the graph with the save color
336            self.graph.color += color
337            self.graph.add(new_plot,color)
338            ## transforming the view of the new data into the same of the previous data
339            self._onEVT_FUNC_PROPERTY()
340            ## save the plot
341            self.plots[self.graph.selected_plottable]=new_plot
342            ## Render the graph
343            self.graph.render(self)
344            self.subplot.figure.canvas.draw_idle() 
345           
346            event = ErrorDataEvent(err_dy=self.err_dy)
347            wx.PostEvent(self.parent, event)
348   
349   
350    def _on_add_errors(self, evt):
351        """
352            create a new data1D witht the errors saved in self.err_dy
353            to show errors of the plot.
354            Compute reasonable errors for a data set without
355            errors and transorm the plottable to a Data1D
356            @param evt: Menu event
357        """
358       
359       
360        if not self.graph.selected_plottable == None \
361            and self.graph.selected_plottable in self.plots.keys():
362            ##Reset the flag to display the hide option on the context menu
363            self.errors_hide = False
364            ## restore dy
365            length = len(self.plots[self.graph.selected_plottable].x)
366            dy = numpy.zeros(length)
367           
368            selected_plot= self.plots[self.graph.selected_plottable]
369           
370            try:
371                dy = self.err_dy[selected_plot.name]
372               
373            except:
374                #for i in range(length):
375                #dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])     
376                if hasattr(selected_plot,"dy"):
377                    dy= selected_plot.dy
378                else:
379                    dy = numpy.zeros(selected_plot.dy)
380                   
381            ## Create a new plottable data1D
382            if selected_plot.__class__.__name__=="Data1D":
383                # Make sure that we can pass a basic Data1D
384                dxl = None
385                dxw = None
386                if hasattr(selected_plot, "dxl"):
387                    dxl = selected_plot.dxl
388                if hasattr(selected_plot, "dxw"):
389                    dxw = selected_plot.dxw
390                new_plot = Data1D( x=selected_plot.x,
391                                               y= selected_plot.y,
392                                               dx=selected_plot.dx,
393                                               dy=dy)
394                new_plot.dxl = dxl
395                new_plot.dxw = dxw
396                                     
397            else:
398                ## Create a new plottable Theory1D
399                new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
400           
401            new_plot.interactive = True
402            new_plot.name = self.plots[self.graph.selected_plottable].name
403            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
404                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
405                if hasattr(self.plots[self.graph.selected_plottable],"id"):
406                    new_plot.id = self.plots[self.graph.selected_plottable].id
407                else:
408                    new_plot.id = str(time.time())
409            else:
410                new_plot.group_id = str(time.time())
411                new_plot.id = str(time.time())
412           
413            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
414            new_plot.xaxis(label, unit)
415            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
416            new_plot.yaxis(label, unit)
417            ## save the color of the selected plottable before it is deleted
418            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
419            self.graph.delete(self.plots[self.graph.selected_plottable])
420            self.graph.color += color
421            ## add newly created plottable to the graph with the save color
422            self.graph.add(new_plot, color)
423            ## transforming the view of the new data into the same of the previous data
424            self._onEVT_FUNC_PROPERTY()
425            ## save the plot
426            self.plots[self.graph.selected_plottable]=new_plot
427            ## render the graph with its new content
428            self.graph.render(self)
429            self.subplot.figure.canvas.draw_idle() 
430               
431               
432    def _onsaveTXT(self, path):
433        """
434            Save file as txt
435           
436            TODO: Refactor and remove this method. See TODO in _onSave.
437        """
438        data = self.plots[self.graph.selected_plottable]
439       
440        if not path == None:
441            out = open(path, 'w')
442            has_errors = True
443            if data.dy==None or data.dy==[]:
444                has_errors = False
445               
446            # Sanity check
447            if has_errors:
448                try:
449                    if len(data.y) != len(data.dy):
450
451                        has_errors = False
452                except:
453                    has_errors = False
454           
455            if has_errors:
456                out.write("<X>   <Y>   <dY>\n")
457            else:
458                out.write("<X>   <Y>\n")
459               
460            for i in range(len(data.x)):
461                if has_errors:
462                    out.write("%g  %g  %g\n" % (data.x[i], 
463                                                data.y[i],
464                                               data.dy[i]))
465                else:
466                    out.write("%g  %g\n" % (data.x[i], 
467                                            data.y[i]))
468                   
469            out.close()                 
470            try:
471                self._default_save_location = os.path.dirname(path)
472            except:
473                pass   
474               
475    def _onSave(self, evt):
476        """
477            Save a data set to a text file
478            @param evt: Menu event
479        """
480       
481        id = str(evt.GetId())
482        if id in self.action_ids:         
483           
484            path = None
485            wildcard = "Text files (*.txt)|*.txt|"\
486            "CanSAS 1D files(*.xml)|*.xml" 
487            dlg = wx.FileDialog(self, "Choose a file",
488                                self._default_save_location, "",wildcard , wx.SAVE)
489           
490            if dlg.ShowModal() == wx.ID_OK:
491                path = dlg.GetPath()
492                mypath = os.path.basename(path)
493               
494                #TODO: This is bad design. The DataLoader is designed to recognize extensions.
495                # It should be a simple matter of calling the .save(file, data, '.xml') method
496                # of the DataLoader.loader.Loader class.
497                from DataLoader.loader import  Loader
498                #Instantiate a loader
499                loader = Loader() 
500                data = self.plots[self.graph.selected_plottable]
501                format=".txt"
502                if os.path.splitext(mypath)[1].lower() == format:
503                     self._onsaveTXT( path)
504
505                format= ".xml"
506                if os.path.splitext(mypath)[1].lower() ==format:
507                    loader.save( path, data, format)
508                try:
509                    self._default_save_location = os.path.dirname(path)
510                except:
511                    pass   
512            dlg.Destroy()
513           
514           
515   
516           
517           
518   
519   
520   
521       
Note: See TracBrowser for help on using the repository browser.