source: sasview/guiframe/local_perspectives/plotting/plotting.py @ fc2b91a

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

Updated for interactive graphs.

  • Property mode set to 100644
File size: 13.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
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 _onEVT_1DREPLOT(self, event):
68        """
69            Data is ready to be displayed
70            @param event: data event
71        """
72        #TODO: Check for existence of plot attribute
73       
74        is_new = True
75        if event.plot.name in self.plots.keys():
76            # Check whether the class of plottable changed
77            if not event.plot.__class__==self.plots[event.plot.name].__class__:
78                self.graph.delete(self.plots[event.plot.name])
79            else:
80                is_new = False
81       
82        if is_new:
83            self.plots[event.plot.name] = event.plot
84            self.graph.add(self.plots[event.plot.name])
85        else:
86            self.plots[event.plot.name].x = event.plot.x   
87            self.plots[event.plot.name].y = event.plot.y   
88            self.plots[event.plot.name].dy = event.plot.dy 
89            if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
90                self.plots[event.plot.name].dx = event.plot.dx   
91 
92       
93        # Check axis labels
94        #TODO: Should re-factor this
95        #if event.plot._xunit != self.graph.prop["xunit"]:
96        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
97           
98        #if event.plot._yunit != self.graph.prop["yunit"]:
99        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
100     
101        self.graph.render(self)
102        self.subplot.figure.canvas.draw_idle()
103
104    def onContextMenu(self, event):
105        """
106            1D plot context menu
107            @param event: wx context event
108        """
109        #slicerpop = wx.Menu()
110        slicerpop = PanelMenu()
111        slicerpop.set_plots(self.plots)
112        slicerpop.set_graph(self.graph)
113               
114        # Option to save the data displayed
115       
116        #for plot in self.graph.plottables:
117        if self.graph.selected_plottable in self.plots:
118            plot = self.plots[self.graph.selected_plottable]
119            id = wx.NewId()
120            name = plot.name
121            slicerpop.Append(id, "&Save %s points" % name)
122            self.action_ids[str(id)] = plot
123            wx.EVT_MENU(self, id, self._onSave)
124               
125        # Various plot options
126        id = wx.NewId()
127        slicerpop.Append(id,'&Save image', 'Save image as PNG')
128        wx.EVT_MENU(self, id, self.onSaveImage)
129       
130       
131        item_list = self.parent.get_context_menu(self.graph)
132        if (not item_list==None) and (not len(item_list)==0):
133                slicerpop.AppendSeparator()
134                for item in item_list:
135                    try:
136                        id = wx.NewId()
137                        slicerpop.Append(id, item[0], item[1])
138                        wx.EVT_MENU(self, id, item[2])
139                    except:
140                        print sys.exc_value
141                        print RuntimeError, "View1DPanel.onContextMenu: bad menu item"
142       
143        slicerpop.AppendSeparator()
144       
145        #id = wx.NewId()
146        #slicerpop.Append(id, '&Toggle Linear/Log scale')
147        #wx.EVT_MENU(self, id, self._onToggleScale)
148
149        if self.graph.selected_plottable in self.plots:
150            if self.plots[self.graph.selected_plottable].__class__.__name__=="Theory1D":
151                id = wx.NewId()
152                slicerpop.Append(id, '&Add errors to data')
153                wx.EVT_MENU(self, id, self._on_add_errors)
154
155        id = wx.NewId()
156        slicerpop.Append(id, '&Change scale')
157        wx.EVT_MENU(self, id, self._onProperties)
158       
159        id = wx.NewId()
160        #slicerpop.AppendSeparator()
161        slicerpop.Append(id, '&Reset Graph')
162        wx.EVT_MENU(self, id, self.onResetGraph)       
163
164        pos = event.GetPosition()
165        pos = self.ScreenToClient(pos)
166        self.PopupMenu(slicerpop, pos)
167   
168    def _on_add_errors(self, evt):
169        """
170            Compute reasonable errors for a data set without
171            errors and transorm the plottable to a Data1D
172        """
173        import math
174        import numpy
175        from sans.guitools.plottables import Data1D
176       
177        if not self.graph.selected_plottable == None:
178            length = len(self.plots[self.graph.selected_plottable].x)
179            dy = numpy.zeros(length)
180            for i in range(length):
181                dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])
182               
183            new_plot = Data1D(self.plots[self.graph.selected_plottable].x,
184                              self.plots[self.graph.selected_plottable].y,
185                              dy=dy)
186            new_plot.interactive = True
187            new_plot.name = self.plots[self.graph.selected_plottable].name
188            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
189            new_plot.xaxis(label, unit)
190            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
191            new_plot.yaxis(label, unit)
192           
193            self.graph.delete(self.plots[self.graph.selected_plottable])
194           
195            self.graph.add(new_plot)
196            self.plots[self.graph.selected_plottable]=new_plot
197           
198            self.graph.render(self)
199            self.subplot.figure.canvas.draw_idle()   
200   
201    def _onSave(self, evt):
202        """
203            Save a data set to a text file
204            @param evt: Menu event
205        """
206        import os
207        id = str(evt.GetId())
208        if id in self.action_ids:         
209           
210            path = None
211            dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.txt", wx.SAVE)
212            if dlg.ShowModal() == wx.ID_OK:
213                path = dlg.GetPath()
214                mypath = os.path.basename(path)
215                print path
216            dlg.Destroy()
217           
218            if not path == None:
219                out = open(path, 'w')
220                has_errors = True
221                if self.action_ids[id].dy==None or self.action_ids[id].dy==[]:
222                    has_errors = False
223                   
224                # Sanity check
225                if has_errors:
226                    try:
227                        if len(self.action_ids[id].y) != len(self.action_ids[id].dy):
228                            print "Y and dY have different lengths"
229                            has_errors = False
230                    except:
231                        has_errors = False
232               
233                if has_errors:
234                    out.write("<X>   <Y>   <dY>\n")
235                else:
236                    out.write("<X>   <Y>\n")
237                   
238                for i in range(len(self.action_ids[id].x)):
239                    if has_errors:
240                        out.write("%g  %g  %g\n" % (self.action_ids[id].x[i], 
241                                                    self.action_ids[id].y[i],
242                                                    self.action_ids[id].dy[i]))
243                    else:
244                        out.write("%g  %g\n" % (self.action_ids[id].x[i], 
245                                                self.action_ids[id].y[i]))
246                       
247                out.close()
248   
249   
250    def _onToggleScale(self, event):
251        if self.get_yscale() == 'log':
252            self.set_yscale('linear')
253        else:
254            self.set_yscale('log')
255        self.subplot.figure.canvas.draw_idle()   
256   
257class Plugin:
258    """
259        Plug-in class to be instantiated by the GUI manager
260    """
261   
262    def __init__(self):
263        """
264            Initialize the plug-in
265        """
266        ## Plug-in name
267        self.sub_menu = "Plotting"
268       
269        ## Reference to the parent window
270        self.parent = None
271       
272        ## List of panels for the simulation perspective (names)
273        self.perspective = []
274       
275        ## Plot panels
276        self.plot_panels = []
277       
278
279    def populate_menu(self, id, parent):
280        """
281            Create a 'Plot' menu to list the panels
282            available for displaying
283            @param id: next available unique ID for wx events
284            @param parent: parent window
285        """
286        self.menu = wx.Menu()
287        return [(id, self.menu, "Plot")]
288   
289       
290    def get_panels(self, parent):
291        """
292            Create and return a list of panel objects
293        """
294        ## Save a reference to the parent
295        self.parent = parent
296        # Connect to plotting events
297        self.parent.Bind(EVT_NEW_PLOT, self._on_plot_event)
298       
299        # We have no initial panels for this plug-in
300        return []
301   
302    def get_perspective(self):
303        """
304            Get the list of panel names for this perspective
305        """
306        return self.perspective
307   
308    def on_perspective(self, event):
309        """
310            Call back function for the perspective menu item.
311            We notify the parent window that the perspective
312            has changed.
313            @param event: menu event
314        """
315        self.parent.set_perspective(self.perspective)
316   
317    def post_init(self):
318        """
319            Post initialization call back to close the loose ends
320            [Somehow openGL needs this call]
321        """
322        pass
323   
324    def _on_show_panel(self, event):
325        print "_on_show_panel"
326   
327    def _on_plot_event(self, event):
328        """
329            A new plottable is being shipped to the plotting plug-in.
330            Check whether we have a panel to put in on, or create
331            a new one
332            @param event: EVT_NEW_PLOT event
333        """
334        # Check whether we already have a graph with the same units
335        # as the plottable we just received.
336        is_available = False
337        for panel in self.plot_panels:
338            if event.plot._xunit == panel.graph.prop["xunit"] \
339            and event.plot._yunit == panel.graph.prop["yunit"]:
340                if hasattr(event.plot, "group_id"):
341                    if not event.plot.group_id==None \
342                        and event.plot.group_id==panel.group_id:
343                        is_available = True
344                        panel._onEVT_1DREPLOT(event)
345                        self.parent.show_panel(panel.uid)
346                else:
347                    # Check that the plot panel has no group ID
348                    if panel.group_id==None:
349                        is_available = True
350                        panel._onEVT_1DREPLOT(event)
351                        self.parent.show_panel(panel.uid)
352       
353        # Create a new plot panel if none was available       
354        if not is_available:
355            new_panel = View1DPanel(self.parent, -1, style=wx.RAISED_BORDER)
356            # Set group ID if available
357            group_id_str = ''
358            if hasattr(event.plot, "group_id"):
359                if not event.plot.group_id==None:
360                    new_panel.group_id = event.plot.group_id
361                    group_id_str = ' [%s]' % event.plot.group_id
362           
363            if hasattr(event, "title"):
364                new_panel.window_caption = event.title
365                new_panel.window_name = event.title
366                #new_panel.window_caption = event.title+group_id_str
367                #new_panel.window_name = event.title+group_id_str
368           
369            event_id = self.parent.popup_panel(new_panel)
370            self.menu.Append(event_id, new_panel.window_caption, 
371                             "Show %s plot panel" % new_panel.window_caption)
372           
373            # Set UID to allow us to reference the panel later
374            new_panel.uid = event_id
375           
376            # Ship the plottable to its panel
377            new_panel._onEVT_1DREPLOT(event)
378            self.plot_panels.append(new_panel)       
379           
380        return
381       
Note: See TracBrowser for help on using the repository browser.