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

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 a7bd562 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
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, os
14import pylab, time,numpy
15
16import danse.common.plottools
17from danse.common.plottools.PlotPanel import PlotPanel
18from danse.common.plottools.plottables import Graph,Theory1D
19from sans.guiframe import dataFitting
20from sans.guicomm.events import EVT_NEW_PLOT
21from sans.guicomm.events import StatusEvent ,NewPlotEvent,SlicerEvent,ErrorDataEvent
22from sans.guicomm.events import RemoveDataEvent
23from sans.guiframe.utils import PanelMenu
24
25from binder import BindArtist
26
27
28DEFAULT_QMAX = 0.05
29DEFAULT_QSTEP = 0.001
30DEFAULT_BEAM = 0.005
31BIN_WIDTH =1
32
33
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 = {}
60        ## save errors dy  for each data plotted
61        self.err_dy={}
62        ## flag to determine if the hide or show context menu item should
63        ## be displayed
64        self.errors_hide=False
65        ## Unique ID (from gui_manager)
66        self.uid = None
67        ## Action IDs for internal call-backs
68        self.action_ids = {}
69        ## Default locations
70        self._default_save_location = os.getcwd()       
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   
77   
78    def _reset(self):
79        """
80            Resets internal data and graph
81        """   
82        self.graph.reset()
83        self.plots      = {}
84        self.action_ids = {}
85   
86   
87    def _onEVT_1DREPLOT(self, event):
88        """
89            Data is ready to be displayed
90            @param event: data event
91        """
92       
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__:
107                #overwrite a plottable using the same name
108                self.graph.delete(self.plots[event.plot.name])
109            else:
110                # plottable is already draw on the panel
111                is_new = False
112       
113        if is_new:
114            # a new plottable overwrites a plotted one  using the same id
115            for plottable in self.plots.itervalues():
116                if hasattr(event.plot,"id") and hasattr(plottable, "id"):
117                    if event.plot.id==plottable.id :
118                        self.graph.delete(plottable)
119           
120            self.plots[event.plot.name] = event.plot
121            self.graph.add(self.plots[event.plot.name])
122        else:
123            #replot the graph
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   
129         
130        #TODO: Should re-factor this
131        ## for all added plot the option to hide error show be displayed first
132        #self.errors_hide = 0
133        ## Set axis labels
134        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
135        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
136        ## Set the view scale for all plots
137        self._onEVT_FUNC_PROPERTY()
138        ## render the graph
139        self.graph.render(self)
140        self.subplot.figure.canvas.draw_idle()
141        if self.errors_hide:
142            self._on_remove_errors(evt=None)
143        else:
144            self._on_add_errors( evt=None)
145        return
146   
147    def onLeftDown(self,event): 
148        """
149            left button down and ready to drag
150            Display the position of the mouse on the statusbar
151        """
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
158
159    def _onRemove(self, event):
160        """
161            Remove a plottable from the graph and render the graph
162            @param event: Menu event
163        """
164        ## Check if there is a selected graph to remove
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)
171            self.graph.delete(self.plots[self.graph.selected_plottable])
172            del self.plots[self.graph.selected_plottable]
173            ## increment graph color
174            self.graph.color += color
175            self.graph.render(self)
176            self.subplot.figure.canvas.draw_idle()   
177           
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               
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 ')
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)
201           
202        slicerpop.AppendSeparator()
203        item_list = self.parent.get_context_menu(self.graph)
204       
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:
212                    wx.PostEvent(self.parent, StatusEvent(status=\
213                        "ModelPanel1D.onContextMenu: bad menu item  %s"%sys.exc_value))
214                    pass
215            slicerpop.AppendSeparator()
216       
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
221           
222            slicerpop.Append(id, "&Save points" )
223            self.action_ids[str(id)] = plot
224            wx.EVT_MENU(self, id, self._onSave)
225         
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)
230            slicerpop.AppendSeparator()
231            # Option to hide
232            #TODO: implement functionality to hide a plottable (legend click)
233       
234        if self.graph.selected_plottable in self.plots:
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:
248                    id = wx.NewId()
249                    slicerpop.Append(id, '&Hide Error bars')
250                    wx.EVT_MENU(self, id, self._on_remove_errors)
251           
252            id = wx.NewId()
253            slicerpop.Append(id, '&Linear fit')
254            wx.EVT_MENU(self, id, self.onFitting)
255               
256            slicerpop.AppendSeparator()
257       
258        id = wx.NewId()
259        slicerpop.Append(id, '&Change scale')
260        wx.EVT_MENU(self, id, self._onProperties)
261       
262        id = wx.NewId()
263        slicerpop.Append(id, '&Reset Graph')
264        wx.EVT_MENU(self, id, self.onResetGraph) 
265       
266        pos = event.GetPosition()
267        pos = self.ScreenToClient(pos)
268        self.PopupMenu(slicerpop, pos)
269       
270       
271    def _on_remove_errors(self, evt):
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        """
280        if not self.graph.selected_plottable == None:
281            ## store existing dy
282            name =self.plots[self.graph.selected_plottable].name
283            dy = self.plots[self.graph.selected_plottable].dy
284            self.err_dy[name]= dy
285            ## Create a new dy for a new plottable
286            import numpy
287            dy= numpy.zeros(len(self.plots[self.graph.selected_plottable].y))
288            selected_plot= self.plots[self.graph.selected_plottable]
289           
290            if selected_plot.__class__.__name__=="Data1D":
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
298                new_plot = dataFitting.Data1D( x=selected_plot.x,
299                              y= selected_plot.y,
300                               dx=selected_plot.dx,
301                              dy=dy,
302                              dxl=dxl,
303                              dxw=dxw)
304                           
305            else:
306                 new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
307            new_plot.interactive = True
308            self.errors_hide = True
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)
320            ## save the color of the selected plottable before it is deleted
321            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
322            self.graph.delete(self.plots[self.graph.selected_plottable])
323            ## add newly created plottable to the graph with the save color
324            self.graph.color += color
325            self.graph.add(new_plot,color)
326            ## transforming the view of the new data into the same of the previous data
327            self._onEVT_FUNC_PROPERTY()
328            ## save the plot
329            self.plots[self.graph.selected_plottable]=new_plot
330            ## Render the graph
331            self.graph.render(self)
332            self.subplot.figure.canvas.draw_idle() 
333           
334            event = ErrorDataEvent(err_dy=self.err_dy)
335            wx.PostEvent(self.parent, event)
336   
337   
338    def _on_add_errors(self, evt):
339        """
340            create a new data1D witht the errors saved in self.err_dy
341            to show errors of the plot.
342            Compute reasonable errors for a data set without
343            errors and transorm the plottable to a Data1D
344            @param evt: Menu event
345        """
346        import math
347        import numpy
348        import time
349       
350        if not self.graph.selected_plottable == None \
351            and self.graph.selected_plottable in self.plots.keys():
352            ##Reset the flag to display the hide option on the context menu
353            self.errors_hide = False
354            ## restore dy
355            length = len(self.plots[self.graph.selected_plottable].x)
356            dy = numpy.zeros(length)
357           
358            selected_plot= self.plots[self.graph.selected_plottable]
359           
360            try:
361                dy = self.err_dy[selected_plot.name]
362               
363            except:
364                #for i in range(length):
365                #dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])     
366                if hasattr(selected_plot,"dy"):
367                    dy= selected_plot.dy
368                else:
369                    dy = numpy.zeros(selected_plot.dy)
370                   
371            ## Create a new plottable data1D
372            if selected_plot.__class__.__name__=="Data1D":
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
380                new_plot = dataFitting.Data1D( x=selected_plot.x,
381                                               y= selected_plot.y,
382                                               dx=selected_plot.dx,
383                                               dy=dy,
384                                               dxl=dxl,
385                                               dxw=dxw)
386            else:
387                ## Create a new plottable Theory1D
388                new_plot = Theory1D(x=selected_plot.x,y=selected_plot.y,dy=dy)
389           
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
394                new_plot.id = self.plots[self.graph.selected_plottable].id
395            else:
396                new_plot.group_id = str(time.time())
397                new_plot.id = str(time.time())
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)
403            ## save the color of the selected plottable before it is deleted
404            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
405            self.graph.delete(self.plots[self.graph.selected_plottable])
406            self.graph.color += color
407            ## add newly created plottable to the graph with the save color
408            self.graph.add(new_plot, color)
409            ## transforming the view of the new data into the same of the previous data
410            self._onEVT_FUNC_PROPERTY()
411            ## save the plot
412            self.plots[self.graph.selected_plottable]=new_plot
413            ## render the graph with its new content
414            self.graph.render(self)
415            self.subplot.figure.canvas.draw_idle() 
416               
417               
418    def _onSaveXML(self, path):
419        """
420            Save 1D  Data to  XML file
421           
422            TODO: Refactor and remove this method. See TODO in _onSave.
423           
424            @param evt: Menu event
425        """
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)
432           
433            try:
434                self._default_save_location = os.path.dirname(path)
435            except:
436                pass
437       
438        return 
439   
440   
441    def _onsaveTXT(self, path):
442        """
443            Save file as txt
444           
445            TODO: Refactor and remove this method. See TODO in _onSave.
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
463           
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()                 
479            try:
480                self._default_save_location = os.path.dirname(path)
481            except:
482                pass   
483               
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
494            wildcard = "Text files (*.txt)|*.txt|"\
495            "CanSAS 1D files(*.xml)|*.xml" 
496            dlg = wx.FileDialog(self, "Choose a file",
497                                self._default_save_location, "",wildcard , wx.SAVE)
498           
499            if dlg.ShowModal() == wx.ID_OK:
500                path = dlg.GetPath()
501                mypath = os.path.basename(path)
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               
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)
511           
512            dlg.Destroy()
513           
514           
515   
516   
517   
518       
Note: See TracBrowser for help on using the repository browser.