source: sasview/guiframe/local_perspectives/plotting/Plotter1D.py @ 1b69256

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 1b69256 was 8068b52, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

guiframe: repair a breaking change with bad assumptions about Data1D. Allow to specify a file when using the choose_file method in order to re-use the code elsewhere.

  • Property mode set to 100644
File size: 19.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, 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"):
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            @param evt: Menu event
422        """
423        if not path == None:
424            out = open(path, 'w')
425            from DataLoader.readers import cansas_reader
426            reader = cansas_reader.Reader()
427            datainfo= self.plots[self.graph.selected_plottable].info
428            reader.write( path, datainfo)
429           
430            try:
431                self._default_save_location = os.path.dirname(path)
432            except:
433                pass
434       
435        return 
436   
437   
438    def _onsaveTXT(self, path):
439        """
440            Save file as txt
441        """
442        data = self.plots[self.graph.selected_plottable]
443       
444        if not path == None:
445            out = open(path, 'w')
446            has_errors = True
447            if data.dy==None or data.dy==[]:
448                has_errors = False
449               
450            # Sanity check
451            if has_errors:
452                try:
453                    if len(data.y) != len(data.dy):
454
455                        has_errors = False
456                except:
457                    has_errors = False
458           
459            if has_errors:
460                out.write("<X>   <Y>   <dY>\n")
461            else:
462                out.write("<X>   <Y>\n")
463               
464            for i in range(len(data.x)):
465                if has_errors:
466                    out.write("%g  %g  %g\n" % (data.x[i], 
467                                                data.y[i],
468                                               data.dy[i]))
469                else:
470                    out.write("%g  %g\n" % (data.x[i], 
471                                            data.y[i]))
472                   
473            out.close()                 
474            try:
475                self._default_save_location = os.path.dirname(path)
476            except:
477                pass   
478               
479    def _onSave(self, evt):
480        """
481            Save a data set to a text file
482            @param evt: Menu event
483        """
484        import os
485        id = str(evt.GetId())
486        if id in self.action_ids:         
487           
488            path = None
489            wildcard = "Text files (*.txt)|*.txt|"\
490            "CanSAS 1D files(*.xml)|*.xml" 
491            dlg = wx.FileDialog(self, "Choose a file",
492                                self._default_save_location, "",wildcard , wx.SAVE)
493           
494            if dlg.ShowModal() == wx.ID_OK:
495                path = dlg.GetPath()
496                mypath = os.path.basename(path)
497                if os.path.splitext(mypath)[1].lower() ==".txt":
498                    self._onsaveTXT(path)
499                if os.path.splitext(mypath)[1].lower() ==".xml":
500                    self._onSaveXML(path)
501           
502            dlg.Destroy()
503           
504           
505   
506   
507   
508       
Note: See TracBrowser for help on using the repository browser.