source: sasview/guiframe/local_perspectives/plotting/plotting.py @ 92320e5

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 92320e5 was a8b073d, checked in by Gervaise Alina <gervyh@…>, 16 years ago

fix add error

  • 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
14import danse.common.plottools
15from danse.common.plottools.PlotPanel import PlotPanel
16from danse.common.plottools.plottables import Graph,Data1D
17from sans.guicomm.events import EVT_NEW_PLOT
18
19class PanelMenu(wx.Menu):
20    plots = None
21    graph = None
22   
23    def set_plots(self, plots):
24        self.plots = plots
25   
26    def set_graph(self, graph):
27        self.graph = graph
28       
29class View1DPanel1D(PlotPanel):
30    """
31        Plot panel for use with the GUI manager
32    """
33   
34    ## Internal name for the AUI manager
35    window_name = "plotpanel"
36    ## Title to appear on top of the window
37    window_caption = "Plot Panel"
38    ## Flag to tell the GUI manager that this panel is not
39    #  tied to any perspective
40    ALWAYS_ON = True
41    ## Group ID
42    group_id = None
43   
44    def __init__(self, parent, id = -1, color = None,\
45        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
46        """
47            Initialize the panel
48        """
49        PlotPanel.__init__(self, parent, id = id, style = style, **kwargs)
50       
51        ## Reference to the parent window
52        self.parent = parent
53        ## Plottables
54        self.plots = {}
55       
56        ## Unique ID (from gui_manager)
57        self.uid = None
58       
59        ## Action IDs for internal call-backs
60        self.action_ids = {}
61       
62        ## Graph       
63        self.graph = Graph()
64        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
65        self.graph.yaxis("\\rm{Intensity} ","cm^{-1}")
66        self.graph.render(self)
67   
68    def _reset(self):
69        """
70            Resets internal data and graph
71        """   
72        self.graph.reset()
73        self.plots      = {}
74        self.action_ids = {}
75   
76    def _onEVT_1DREPLOT(self, event):
77        """
78            Data is ready to be displayed
79            @param event: data event
80        """
81        #TODO: Check for existence of plot attribute
82
83        # Check whether this is a replot. If we ask for a replot
84        # and the plottable no longer exists, ignore the event.
85        if hasattr(event, "update") and event.update==True \
86            and event.plot.name not in self.plots.keys():
87            return
88       
89        if hasattr(event, "reset"):
90            self._reset()
91       
92        is_new = True
93        if event.plot.name in self.plots.keys():
94            # Check whether the class of plottable changed
95            if not event.plot.__class__==self.plots[event.plot.name].__class__:
96                self.graph.delete(self.plots[event.plot.name])
97            else:
98                is_new = False
99       
100        if is_new:
101            self.plots[event.plot.name] = event.plot
102            self.graph.add(self.plots[event.plot.name])
103        else:
104            self.plots[event.plot.name].x = event.plot.x   
105            self.plots[event.plot.name].y = event.plot.y   
106            self.plots[event.plot.name].dy = event.plot.dy 
107            if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
108                self.plots[event.plot.name].dx = event.plot.dx   
109 
110       
111        # Check axis labels
112        #TODO: Should re-factor this
113        #if event.plot._xunit != self.graph.prop["xunit"]:
114        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
115           
116        #if event.plot._yunit != self.graph.prop["yunit"]:
117        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
118     
119        # Set the view scale for all plots
120        self._onEVT_FUNC_PROPERTY()
121     
122        self.graph.render(self)
123        self.subplot.figure.canvas.draw_idle()
124
125    def onLeftDown(self,event): 
126        """ left button down and ready to drag"""
127        from sans.guicomm.events import StatusEvent   
128        PlotPanel.onLeftDown(self, event)
129        ax = event.inaxes
130        if ax != None:
131            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
132            wx.PostEvent(self.parent, StatusEvent(status=position))
133
134    def _onRemove(self, event):
135        """
136        """
137        if not self.graph.selected_plottable == None:
138            print self.graph.selected_plottable
139           
140           
141            self.graph.delete(self.plots[self.graph.selected_plottable])
142            del self.plots[self.graph.selected_plottable]
143            self.graph.render(self)
144            self.subplot.figure.canvas.draw_idle()   
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        if self.graph.selected_plottable in self.plots:
199            if self.plots[self.graph.selected_plottable].__class__.__name__=="Theory1D":
200                id = wx.NewId()
201                slicerpop.Append(id, '&Add errors to data')
202                wx.EVT_MENU(self, id, self._on_add_errors)
203            else:
204                id = wx.NewId()
205                slicerpop.Append(id, '&Linear fit')
206                wx.EVT_MENU(self, id, self.onFitting)
207               
208       
209
210        id = wx.NewId()
211        slicerpop.Append(id, '&Change scale')
212        wx.EVT_MENU(self, id, self._onProperties)
213       
214        id = wx.NewId()
215        #slicerpop.AppendSeparator()
216        slicerpop.Append(id, '&Reset Graph')
217        wx.EVT_MENU(self, id, self.onResetGraph)       
218
219        pos = event.GetPosition()
220        pos = self.ScreenToClient(pos)
221        self.PopupMenu(slicerpop, pos)
222   
223   
224    def _on_add_errors(self, evt):
225        """
226            Compute reasonable errors for a data set without
227            errors and transorm the plottable to a Data1D
228        """
229        import math
230        import numpy
231        import time
232       
233        if not self.graph.selected_plottable == None:
234            length = len(self.plots[self.graph.selected_plottable].x)
235            dy = numpy.zeros(length)
236            for i in range(length):
237                dy[i] = math.sqrt(self.plots[self.graph.selected_plottable].y[i])
238               
239            new_plot = Data1D(self.plots[self.graph.selected_plottable].x,
240                              self.plots[self.graph.selected_plottable].y,
241                              dy=dy)
242            new_plot.interactive = True
243            new_plot.name = self.plots[self.graph.selected_plottable].name
244            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
245                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
246            else:
247                new_plot.group_id = str(time.time())
248           
249            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
250            new_plot.xaxis(label, unit)
251            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
252            new_plot.yaxis(label, unit)
253           
254            self.graph.delete(self.plots[self.graph.selected_plottable])
255           
256            self.graph.add(new_plot)
257            self.plots[self.graph.selected_plottable]=new_plot
258           
259            self.graph.render(self)
260            self.subplot.figure.canvas.draw_idle()   
261   
262    def _onSave(self, evt):
263        """
264            Save a data set to a text file
265            @param evt: Menu event
266        """
267        import os
268        id = str(evt.GetId())
269        if id in self.action_ids:         
270           
271            path = None
272            dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.txt", wx.SAVE)
273            if dlg.ShowModal() == wx.ID_OK:
274                path = dlg.GetPath()
275                mypath = os.path.basename(path)
276                print path
277            dlg.Destroy()
278           
279            if not path == None:
280                out = open(path, 'w')
281                has_errors = True
282                if self.action_ids[id].dy==None or self.action_ids[id].dy==[]:
283                    has_errors = False
284                   
285                # Sanity check
286                if has_errors:
287                    try:
288                        if len(self.action_ids[id].y) != len(self.action_ids[id].dy):
289                            print "Y and dY have different lengths"
290                            has_errors = False
291                    except:
292                        has_errors = False
293               
294                if has_errors:
295                    out.write("<X>   <Y>   <dY>\n")
296                else:
297                    out.write("<X>   <Y>\n")
298                   
299                for i in range(len(self.action_ids[id].x)):
300                    if has_errors:
301                        out.write("%g  %g  %g\n" % (self.action_ids[id].x[i], 
302                                                    self.action_ids[id].y[i],
303                                                    self.action_ids[id].dy[i]))
304                    else:
305                        out.write("%g  %g\n" % (self.action_ids[id].x[i], 
306                                                self.action_ids[id].y[i]))
307                       
308                out.close()
309   
310   
311    def _onToggleScale(self, event):
312        if self.get_yscale() == 'log':
313            self.set_yscale('linear')
314        else:
315            self.set_yscale('log')
316        self.subplot.figure.canvas.draw_idle()   
317       
318class View1DPanel2D( View1DPanel1D):
319    """
320        Plot panel for use with the GUI manager
321    """
322   
323    ## Internal name for the AUI manager
324    window_name = "plotpanel"
325    ## Title to appear on top of the window
326    window_caption = "Plot Panel"
327    ## Flag to tell the GUI manager that this panel is not
328    #  tied to any perspective
329    ALWAYS_ON = True
330    ## Group ID
331    group_id = None
332   
333    def __init__(self, parent, id = -1, color = None,\
334        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
335        """
336            Initialize the panel
337        """
338        View1DPanel1D.__init__(self, parent, id = id, style = style, **kwargs)
339       
340        ## Reference to the parent window
341        self.parent = parent
342        ## Plottables
343        self.plots = {}
344       
345        ## Unique ID (from gui_manager)
346        self.uid = None
347       
348        ## Action IDs for internal call-backs
349        self.action_ids = {}
350       
351        ## Graph       
352        self.graph = Graph()
353        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
354        self.graph.yaxis("\\rm{Intensity} ","cm^{-1}")
355        self.graph.render(self)
356 
357    def _onEVT_1DREPLOT(self, event):
358        """
359            Data is ready to be displayed
360            @param event: data event
361        """
362        #TODO: Check for existence of plot attribute
363        # Check whether this is a replot. If we ask for a replot
364        # and the plottable no longer exists, ignore the event.
365        if hasattr(event, "update") and event.update==True \
366            and event.plot.name not in self.plots.keys():
367            return
368        if hasattr(event, "reset"):
369            self._reset()
370        is_new = True
371        if event.plot.name in self.plots.keys():
372            # Check whether the class of plottable changed
373            if not event.plot.__class__==self.plots[event.plot.name].__class__:
374                self.graph.delete(self.plots[event.plot.name])
375            else:
376                is_new = False
377        self.plots[event.plot.name] = event.plot
378        #if is_new:
379        self.graph.add(self.plots[event.plot.name])
380       
381
382        # Check axis labels
383        #TODO: Should re-factor this
384        #if event.plot._xunit != self.graph.prop["xunit"]:
385       
386        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
387        #if event.plot._yunit != self.graph.prop["yunit"]:
388        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
389        self.graph.render(self)
390        self.subplot.figure.canvas.draw_idle()
391
392
393    def onContextMenu(self, event):
394        """
395            2D plot context menu
396            @param event: wx context event
397        """
398       
399        #slicerpop = wx.Menu()
400        slicerpop = PanelMenu()
401        slicerpop.set_plots(self.plots)
402        slicerpop.set_graph(self.graph)
403               
404        # Option to save the data displayed
405   
406        # Various plot options
407        id = wx.NewId()
408        slicerpop.Append(id,'&Save image', 'Save image as PNG')
409        wx.EVT_MENU(self, id, self.onSaveImage)
410       
411       
412        item_list = self.parent.get_context_menu(self.graph)
413        if (not item_list==None) and (not len(item_list)==0):
414                slicerpop.AppendSeparator()
415                for item in item_list:
416                    try:
417                        id = wx.NewId()
418                        slicerpop.Append(id, item[0], item[1])
419                        wx.EVT_MENU(self, id, item[2])
420                    except:
421                        print sys.exc_value
422                        print RuntimeError, "View1DPanel2D.onContextMenu: bad menu item"
423       
424        slicerpop.AppendSeparator()
425     
426        id = wx.NewId()
427        slicerpop.Append(id, '&Toggle Linear/Log scale')
428        wx.EVT_MENU(self, id, self._onToggleScale) 
429
430        pos = event.GetPosition()
431        pos = self.ScreenToClient(pos)
432        self.PopupMenu(slicerpop, pos)
433   
434    def _onToggleScale(self, event):
435        """
436            toggle axis and replot image
437        """
438        if self.scale == 'log':
439            self.scale = 'linear'
440        else:
441            self.scale = 'log'
442        self.image(self.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
443                   self.ymax_2D,self.zmin_2D ,self.zmax_2D )
444 
445     
446class Plugin:
447    """
448        Plug-in class to be instantiated by the GUI manager
449    """
450   
451    def __init__(self):
452        """
453            Initialize the plug-in
454        """
455        ## Plug-in name
456        self.sub_menu = "Plotting"
457       
458        ## Reference to the parent window
459        self.parent = None
460       
461        ## List of panels for the simulation perspective (names)
462        self.perspective = []
463       
464        ## Plot panels
465        self.plot_panels = []
466       
467
468    def populate_menu(self, id, parent):
469        """
470            Create a 'Plot' menu to list the panels
471            available for displaying
472            @param id: next available unique ID for wx events
473            @param parent: parent window
474        """
475        self.menu = wx.Menu()
476        return [(id, self.menu, "Plot")]
477   
478       
479    def get_panels(self, parent):
480        """
481            Create and return a list of panel objects
482        """
483        ## Save a reference to the parent
484        self.parent = parent
485        # Connect to plotting events
486        self.parent.Bind(EVT_NEW_PLOT, self._on_plot_event)
487       
488        # We have no initial panels for this plug-in
489        return []
490   
491    def get_perspective(self):
492        """
493            Get the list of panel names for this perspective
494        """
495        return self.perspective
496   
497    def on_perspective(self, event):
498        """
499            Call back function for the perspective menu item.
500            We notify the parent window that the perspective
501            has changed.
502            @param event: menu event
503        """
504        self.parent.set_perspective(self.perspective)
505   
506    def post_init(self):
507        """
508            Post initialization call back to close the loose ends
509            [Somehow openGL needs this call]
510        """
511        pass
512   
513    def _on_show_panel(self, event):
514        print "_on_show_panel"
515   
516    def _on_plot_event(self, event):
517        """
518            A new plottable is being shipped to the plotting plug-in.
519            Check whether we have a panel to put in on, or create
520            a new one
521            @param event: EVT_NEW_PLOT event
522        """
523        # Check whether we already have a graph with the same units
524        # as the plottable we just received.
525        is_available = False
526        for panel in self.plot_panels:
527            if event.plot._xunit == panel.graph.prop["xunit_base"] \
528            and event.plot._yunit == panel.graph.prop["yunit_base"]:
529                if hasattr(event.plot, "group_id"):
530                    if not event.plot.group_id==None \
531                        and event.plot.group_id==panel.group_id:
532                        is_available = True
533                        panel._onEVT_1DREPLOT(event)
534                        self.parent.show_panel(panel.uid)
535                else:
536                    # Check that the plot panel has no group ID
537                    if panel.group_id==None:
538                        is_available = True
539                        panel._onEVT_1DREPLOT(event)
540                        self.parent.show_panel(panel.uid)
541       
542        # Create a new plot panel if none was available       
543        if not is_available:
544            if not hasattr(event.plot,'image'):
545                new_panel = View1DPanel1D(self.parent, -1, style=wx.RAISED_BORDER)
546            else:
547                new_panel = View1DPanel2D(self.parent, -1, style=wx.RAISED_BORDER)
548            # Set group ID if available
549            group_id_str = ''
550            if hasattr(event.plot, "group_id"):
551                if not event.plot.group_id==None:
552                    new_panel.group_id = event.plot.group_id
553                    group_id_str = ' [%s]' % event.plot.group_id
554           
555            if hasattr(event, "title"):
556                new_panel.window_caption = event.title
557                new_panel.window_name = event.title
558                #new_panel.window_caption = event.title+group_id_str
559                #new_panel.window_name = event.title+group_id_str
560           
561            event_id = self.parent.popup_panel(new_panel)
562            self.menu.Append(event_id, new_panel.window_caption, 
563                             "Show %s plot panel" % new_panel.window_caption)
564            # Set UID to allow us to reference the panel later
565            new_panel.uid = event_id
566            # Ship the plottable to its panel
567            new_panel._onEVT_1DREPLOT(event)
568            self.plot_panels.append(new_panel)       
569           
570        return
571       
Note: See TracBrowser for help on using the repository browser.