source: sasview/guiframe/local_perspectives/plotting/plotting.py @ 6f73a08

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 6f73a08 was 925155f, checked in by Gervaise Alina <gervyh@…>, 16 years ago

name of data loaded from theory 1d has changed.
linear fit add in context menu

  • Property mode set to 100644
File size: 15.8 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
14from sans.guitools.PlotPanel import PlotPanel
15from sans.guitools.plottables import Graph
16from sans.guicomm.events import EVT_NEW_PLOT
17
18class PanelMenu(wx.Menu):
19    plots = None
20    graph = None
21   
22    def set_plots(self, plots):
23        self.plots = plots
24   
25    def set_graph(self, graph):
26        self.graph = graph
27
28class View1DPanel(PlotPanel):
29    """
30        Plot panel for use with the GUI manager
31    """
32   
33    ## Internal name for the AUI manager
34    window_name = "plotpanel"
35    ## Title to appear on top of the window
36    window_caption = "Plot Panel"
37    ## Flag to tell the GUI manager that this panel is not
38    #  tied to any perspective
39    ALWAYS_ON = True
40    ## Group ID
41    group_id = None
42   
43    def __init__(self, parent, id = -1, color = None,\
44        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
45        """
46            Initialize the panel
47        """
48        PlotPanel.__init__(self, parent, id = id, style = style, **kwargs)
49       
50        ## Reference to the parent window
51        self.parent = parent
52        ## Plottables
53        self.plots = {}
54       
55        ## Unique ID (from gui_manager)
56        self.uid = None
57       
58        ## Action IDs for internal call-backs
59        self.action_ids = {}
60       
61        ## Graph       
62        self.graph = Graph()
63        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
64        self.graph.yaxis("\\rm{Intensity} ","cm^{-1}")
65        self.graph.render(self)
66   
67    def _reset(self):
68        """
69            Resets internal data and graph
70        """   
71        self.graph.reset()
72        self.plots      = {}
73        self.action_ids = {}
74   
75    def _onEVT_1DREPLOT(self, event):
76        """
77            Data is ready to be displayed
78            @param event: data event
79        """
80        #TODO: Check for existence of plot attribute
81
82        # Check whether this is a replot. If we ask for a replot
83        # and the plottable no longer exists, ignore the event.
84        if hasattr(event, "update") and event.update==True \
85            and event.plot.name not in self.plots.keys():
86            return
87       
88        if hasattr(event, "reset"):
89            self._reset()
90       
91        is_new = True
92        if event.plot.name in self.plots.keys():
93            # Check whether the class of plottable changed
94            if not event.plot.__class__==self.plots[event.plot.name].__class__:
95                self.graph.delete(self.plots[event.plot.name])
96            else:
97                is_new = False
98       
99        if is_new:
100            self.plots[event.plot.name] = event.plot
101            self.graph.add(self.plots[event.plot.name])
102        else:
103            self.plots[event.plot.name].x = event.plot.x   
104            self.plots[event.plot.name].y = event.plot.y   
105            self.plots[event.plot.name].dy = event.plot.dy 
106            if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
107                self.plots[event.plot.name].dx = event.plot.dx   
108 
109       
110        # Check axis labels
111        #TODO: Should re-factor this
112        #if event.plot._xunit != self.graph.prop["xunit"]:
113        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
114           
115        #if event.plot._yunit != self.graph.prop["yunit"]:
116        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
117     
118        # Set the view scale for all plots
119        self._onEVT_FUNC_PROPERTY()
120     
121        self.graph.render(self)
122        self.subplot.figure.canvas.draw_idle()
123
124    def onLeftDown(self,event): 
125        """ left button down and ready to drag"""
126        from sans.guicomm.events import StatusEvent   
127        PlotPanel.onLeftDown(self, event)
128        ax = event.inaxes
129        if ax != None:
130            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
131            wx.PostEvent(self.parent, StatusEvent(status=position))
132
133    def _onRemove(self, event):
134        """
135        """
136        if not self.graph.selected_plottable == None:
137            print self.graph.selected_plottable
138           
139           
140            self.graph.delete(self.plots[self.graph.selected_plottable])
141            del self.plots[self.graph.selected_plottable]
142            self.graph.render(self)
143            self.subplot.figure.canvas.draw_idle()   
144           
145           
146
147    def onContextMenu(self, event):
148        """
149            1D plot context menu
150            @param event: wx context event
151        """
152        #slicerpop = wx.Menu()
153        slicerpop = PanelMenu()
154        slicerpop.set_plots(self.plots)
155        slicerpop.set_graph(self.graph)
156               
157        # Option to save the data displayed
158       
159        #for plot in self.graph.plottables:
160        if self.graph.selected_plottable in self.plots:
161            plot = self.plots[self.graph.selected_plottable]
162            id = wx.NewId()
163            name = plot.name
164            slicerpop.Append(id, "&Save %s points" % name)
165            self.action_ids[str(id)] = plot
166            wx.EVT_MENU(self, id, self._onSave)
167               
168            # Option to delete plottable
169            id = wx.NewId()
170            slicerpop.Append(id, "Remove %s curve" % name)
171            self.action_ids[str(id)] = plot
172            wx.EVT_MENU(self, id, self._onRemove)
173           
174            # Option to hide
175            #TODO: implement functionality to hide a plottable (legend click)
176            slicerpop.AppendSeparator()
177               
178        # Various plot options
179        id = wx.NewId()
180        slicerpop.Append(id,'&Save image', 'Save image as PNG')
181        wx.EVT_MENU(self, id, self.onSaveImage)
182       
183       
184        item_list = self.parent.get_context_menu(self.graph)
185        if (not item_list==None) and (not len(item_list)==0):
186                slicerpop.AppendSeparator()
187                for item in item_list:
188                    try:
189                        id = wx.NewId()
190                        slicerpop.Append(id, item[0], item[1])
191                        wx.EVT_MENU(self, id, item[2])
192                    except:
193                        print sys.exc_value
194                        print RuntimeError, "View1DPanel.onContextMenu: bad menu item"
195       
196        slicerpop.AppendSeparator()
197       
198        #id = wx.NewId()
199        #slicerpop.Append(id, '&Toggle Linear/Log scale')
200        #wx.EVT_MENU(self, id, self._onToggleScale)
201
202        if self.graph.selected_plottable in self.plots:
203            if self.plots[self.graph.selected_plottable].__class__.__name__=="Theory1D":
204                id = wx.NewId()
205                slicerpop.Append(id, '&Add errors to data')
206                wx.EVT_MENU(self, id, self._on_add_errors)
207            else:
208                id = wx.NewId()
209                slicerpop.Append(id, '&Linear fit')
210                wx.EVT_MENU(self, id, self.onFitting)
211
212        id = wx.NewId()
213        slicerpop.Append(id, '&Change scale')
214        wx.EVT_MENU(self, id, self._onProperties)
215       
216        id = wx.NewId()
217        #slicerpop.AppendSeparator()
218        slicerpop.Append(id, '&Reset Graph')
219        wx.EVT_MENU(self, id, self.onResetGraph)       
220
221        pos = event.GetPosition()
222        pos = self.ScreenToClient(pos)
223        self.PopupMenu(slicerpop, pos)
224   
225    def _on_add_errors(self, evt):
226        """
227            Compute reasonable errors for a data set without
228            errors and transorm the plottable to a Data1D
229        """
230        import math
231        import numpy
232        from sans.guitools.plottables import Data1D
233        import time
234       
235        if not self.graph.selected_plottable == None:
236            length = len(self.plots[self.graph.selected_plottable].x)
237            dy = numpy.zeros(length)
238            for i in range(length):
239                dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])
240               
241            new_plot = Data1D(self.plots[self.graph.selected_plottable].x,
242                              self.plots[self.graph.selected_plottable].y,
243                              dy=dy)
244            new_plot.interactive = True
245            new_plot.name = self.plots[self.graph.selected_plottable].name #+" data"
246            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
247                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
248            else:
249                new_plot.group_id = str(time.time())
250           
251            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
252            new_plot.xaxis(label, unit)
253            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
254            new_plot.yaxis(label, unit)
255           
256            self.graph.delete(self.plots[self.graph.selected_plottable])
257           
258            self.graph.add(new_plot)
259            self.plots[self.graph.selected_plottable]=new_plot
260           
261            self.graph.render(self)
262            self.subplot.figure.canvas.draw_idle()   
263   
264    def _onSave(self, evt):
265        """
266            Save a data set to a text file
267            @param evt: Menu event
268        """
269        import os
270        id = str(evt.GetId())
271        if id in self.action_ids:         
272           
273            path = None
274            dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.txt", wx.SAVE)
275            if dlg.ShowModal() == wx.ID_OK:
276                path = dlg.GetPath()
277                mypath = os.path.basename(path)
278                print path
279            dlg.Destroy()
280           
281            if not path == None:
282                out = open(path, 'w')
283                has_errors = True
284                if self.action_ids[id].dy==None or self.action_ids[id].dy==[]:
285                    has_errors = False
286                   
287                # Sanity check
288                if has_errors:
289                    try:
290                        if len(self.action_ids[id].y) != len(self.action_ids[id].dy):
291                            print "Y and dY have different lengths"
292                            has_errors = False
293                    except:
294                        has_errors = False
295               
296                if has_errors:
297                    out.write("<X>   <Y>   <dY>\n")
298                else:
299                    out.write("<X>   <Y>\n")
300                   
301                for i in range(len(self.action_ids[id].x)):
302                    if has_errors:
303                        out.write("%g  %g  %g\n" % (self.action_ids[id].x[i], 
304                                                    self.action_ids[id].y[i],
305                                                    self.action_ids[id].dy[i]))
306                    else:
307                        out.write("%g  %g\n" % (self.action_ids[id].x[i], 
308                                                self.action_ids[id].y[i]))
309                       
310                out.close()
311   
312   
313    def _onToggleScale(self, event):
314        if self.get_yscale() == 'log':
315            self.set_yscale('linear')
316        else:
317            self.set_yscale('log')
318        self.subplot.figure.canvas.draw_idle()   
319   
320class Plugin:
321    """
322        Plug-in class to be instantiated by the GUI manager
323    """
324   
325    def __init__(self):
326        """
327            Initialize the plug-in
328        """
329        ## Plug-in name
330        self.sub_menu = "Plotting"
331       
332        ## Reference to the parent window
333        self.parent = None
334       
335        ## List of panels for the simulation perspective (names)
336        self.perspective = []
337       
338        ## Plot panels
339        self.plot_panels = []
340       
341
342    def populate_menu(self, id, parent):
343        """
344            Create a 'Plot' menu to list the panels
345            available for displaying
346            @param id: next available unique ID for wx events
347            @param parent: parent window
348        """
349        self.menu = wx.Menu()
350        return [(id, self.menu, "Plot")]
351   
352       
353    def get_panels(self, parent):
354        """
355            Create and return a list of panel objects
356        """
357        ## Save a reference to the parent
358        self.parent = parent
359        # Connect to plotting events
360        self.parent.Bind(EVT_NEW_PLOT, self._on_plot_event)
361       
362        # We have no initial panels for this plug-in
363        return []
364   
365    def get_perspective(self):
366        """
367            Get the list of panel names for this perspective
368        """
369        return self.perspective
370   
371    def on_perspective(self, event):
372        """
373            Call back function for the perspective menu item.
374            We notify the parent window that the perspective
375            has changed.
376            @param event: menu event
377        """
378        self.parent.set_perspective(self.perspective)
379   
380    def post_init(self):
381        """
382            Post initialization call back to close the loose ends
383            [Somehow openGL needs this call]
384        """
385        pass
386   
387    def _on_show_panel(self, event):
388        print "_on_show_panel"
389   
390    def _on_plot_event(self, event):
391        """
392            A new plottable is being shipped to the plotting plug-in.
393            Check whether we have a panel to put in on, or create
394            a new one
395            @param event: EVT_NEW_PLOT event
396        """
397        # Check whether we already have a graph with the same units
398        # as the plottable we just received.
399        is_available = False
400        for panel in self.plot_panels:
401            if event.plot._xunit == panel.graph.prop["xunit_base"] \
402            and event.plot._yunit == panel.graph.prop["yunit_base"]:
403                if hasattr(event.plot, "group_id"):
404                    if not event.plot.group_id==None \
405                        and event.plot.group_id==panel.group_id:
406                        is_available = True
407                        panel._onEVT_1DREPLOT(event)
408                        self.parent.show_panel(panel.uid)
409                else:
410                    # Check that the plot panel has no group ID
411                    if panel.group_id==None:
412                        is_available = True
413                        panel._onEVT_1DREPLOT(event)
414                        self.parent.show_panel(panel.uid)
415       
416        # Create a new plot panel if none was available       
417        if not is_available:
418            new_panel = View1DPanel(self.parent, -1, style=wx.RAISED_BORDER)
419            # Set group ID if available
420            group_id_str = ''
421            if hasattr(event.plot, "group_id"):
422                if not event.plot.group_id==None:
423                    new_panel.group_id = event.plot.group_id
424                    group_id_str = ' [%s]' % event.plot.group_id
425           
426            if hasattr(event, "title"):
427                new_panel.window_caption = event.title
428                new_panel.window_name = event.title
429                #new_panel.window_caption = event.title+group_id_str
430                #new_panel.window_name = event.title+group_id_str
431           
432            event_id = self.parent.popup_panel(new_panel)
433            self.menu.Append(event_id, new_panel.window_caption, 
434                             "Show %s plot panel" % new_panel.window_caption)
435           
436            # Set UID to allow us to reference the panel later
437            new_panel.uid = event_id
438           
439            # Ship the plottable to its panel
440            new_panel._onEVT_1DREPLOT(event)
441            self.plot_panels.append(new_panel)       
442           
443        return
444       
Note: See TracBrowser for help on using the repository browser.