source: sasview/guiframe/local_perspectives/plotting/plotting.py @ 4ddf1067

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 4ddf1067 was 7764b41, checked in by Gervaise Alina <gervyh@…>, 16 years ago

modify panel for data2d purposed

  • Property mode set to 100644
File size: 20.2 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
364        # Check whether this is a replot. If we ask for a replot
365        # and the plottable no longer exists, ignore the event.
366        if hasattr(event, "update") and event.update==True \
367            and event.plot.name not in self.plots.keys():
368            return
369       
370        if hasattr(event, "reset"):
371            self._reset()
372       
373        is_new = True
374        if event.plot.name in self.plots.keys():
375            # Check whether the class of plottable changed
376            if not event.plot.__class__==self.plots[event.plot.name].__class__:
377                self.graph.delete(self.plots[event.plot.name])
378            else:
379                is_new = False
380       
381        if is_new:
382            self.plots[event.plot.name] = event.plot
383            self.graph.add(self.plots[event.plot.name])
384        else:
385            self.plots[event.plot.name].x = event.plot.x   
386            self.plots[event.plot.name].y = event.plot.y   
387            self.plots[event.plot.name].dy = event.plot.dy 
388            if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
389                self.plots[event.plot.name].dx = event.plot.dx   
390 
391       
392        # Check axis labels
393        #TODO: Should re-factor this
394        #if event.plot._xunit != self.graph.prop["xunit"]:
395        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
396           
397        #if event.plot._yunit != self.graph.prop["yunit"]:
398        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
399     
400       
401        self.graph.render(self)
402        self.subplot.figure.canvas.draw_idle()
403
404
405    def onContextMenu(self, event):
406        """
407            1D plot context menu
408            @param event: wx context event
409        """
410        #slicerpop = wx.Menu()
411        slicerpop = PanelMenu()
412        slicerpop.set_plots(self.plots)
413        slicerpop.set_graph(self.graph)
414               
415        # Option to save the data displayed
416       
417       
418        # Various plot options
419        id = wx.NewId()
420        slicerpop.Append(id,'&Save image', 'Save image as PNG')
421        wx.EVT_MENU(self, id, self.onSaveImage)
422       
423       
424        item_list = self.parent.get_context_menu(self.graph)
425        if (not item_list==None) and (not len(item_list)==0):
426                slicerpop.AppendSeparator()
427                for item in item_list:
428                    try:
429                        id = wx.NewId()
430                        slicerpop.Append(id, item[0], item[1])
431                        wx.EVT_MENU(self, id, item[2])
432                    except:
433                        print sys.exc_value
434                        print RuntimeError, "View1DPanel2D.onContextMenu: bad menu item"
435       
436        slicerpop.AppendSeparator()
437       
438               
439        id = wx.NewId()
440        slicerpop.Append(id, '&Toggle Linear/Log scale')
441        wx.EVT_MENU(self, id,  self._onToggleScale)   
442
443        pos = event.GetPosition()
444        pos = self.ScreenToClient(pos)
445        self.PopupMenu(slicerpop, pos)
446   
447   
448 
449     
450class Plugin:
451    """
452        Plug-in class to be instantiated by the GUI manager
453    """
454   
455    def __init__(self):
456        """
457            Initialize the plug-in
458        """
459        ## Plug-in name
460        self.sub_menu = "Plotting"
461       
462        ## Reference to the parent window
463        self.parent = None
464       
465        ## List of panels for the simulation perspective (names)
466        self.perspective = []
467       
468        ## Plot panels
469        self.plot_panels = []
470       
471
472    def populate_menu(self, id, parent):
473        """
474            Create a 'Plot' menu to list the panels
475            available for displaying
476            @param id: next available unique ID for wx events
477            @param parent: parent window
478        """
479        self.menu = wx.Menu()
480        return [(id, self.menu, "Plot")]
481   
482       
483    def get_panels(self, parent):
484        """
485            Create and return a list of panel objects
486        """
487        ## Save a reference to the parent
488        self.parent = parent
489        # Connect to plotting events
490        self.parent.Bind(EVT_NEW_PLOT, self._on_plot_event)
491       
492        # We have no initial panels for this plug-in
493        return []
494   
495    def get_perspective(self):
496        """
497            Get the list of panel names for this perspective
498        """
499        return self.perspective
500   
501    def on_perspective(self, event):
502        """
503            Call back function for the perspective menu item.
504            We notify the parent window that the perspective
505            has changed.
506            @param event: menu event
507        """
508        self.parent.set_perspective(self.perspective)
509   
510    def post_init(self):
511        """
512            Post initialization call back to close the loose ends
513            [Somehow openGL needs this call]
514        """
515        pass
516   
517    def _on_show_panel(self, event):
518        print "_on_show_panel"
519   
520    def _on_plot_event(self, event):
521        """
522            A new plottable is being shipped to the plotting plug-in.
523            Check whether we have a panel to put in on, or create
524            a new one
525            @param event: EVT_NEW_PLOT event
526        """
527        # Check whether we already have a graph with the same units
528        # as the plottable we just received.
529        is_available = False
530        for panel in self.plot_panels:
531            if event.plot._xunit == panel.graph.prop["xunit_base"] \
532            and event.plot._yunit == panel.graph.prop["yunit_base"]:
533                if hasattr(event.plot, "group_id"):
534                    if not event.plot.group_id==None \
535                        and event.plot.group_id==panel.group_id:
536                        is_available = True
537                        panel._onEVT_1DREPLOT(event)
538                        self.parent.show_panel(panel.uid)
539                else:
540                    # Check that the plot panel has no group ID
541                    if panel.group_id==None:
542                        is_available = True
543                        panel._onEVT_1DREPLOT(event)
544                        self.parent.show_panel(panel.uid)
545       
546        # Create a new plot panel if none was available       
547        if not is_available:
548            if not hasattr(event.plot,'image'):
549                new_panel = View1DPanel1D(self.parent, -1, style=wx.RAISED_BORDER)
550            else:
551                new_panel = View1DPanel2D(self.parent, -1, style=wx.RAISED_BORDER)
552            # Set group ID if available
553            group_id_str = ''
554            if hasattr(event.plot, "group_id"):
555                if not event.plot.group_id==None:
556                    new_panel.group_id = event.plot.group_id
557                    group_id_str = ' [%s]' % event.plot.group_id
558           
559            if hasattr(event, "title"):
560                new_panel.window_caption = event.title
561                new_panel.window_name = event.title
562                #new_panel.window_caption = event.title+group_id_str
563                #new_panel.window_name = event.title+group_id_str
564           
565            event_id = self.parent.popup_panel(new_panel)
566            self.menu.Append(event_id, new_panel.window_caption, 
567                             "Show %s plot panel" % new_panel.window_caption)
568            # Set UID to allow us to reference the panel later
569            new_panel.uid = event_id
570            # Ship the plottable to its panel
571            new_panel._onEVT_1DREPLOT(event)
572            self.plot_panels.append(new_panel)       
573           
574        return
575       
Note: See TracBrowser for help on using the repository browser.