source: sasview/guiframe/local_perspectives/plotting/plotting.py @ 31a5842

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

new panel added for model 2D

  • Property mode set to 100644
File size: 21.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
18from sans.guicomm.events import StatusEvent
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           
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        wx.PostEvent(self.parent, StatusEvent(status="Image is in %s scale"%self.scale))
445       
446class View1DModelPanel2D( View1DPanel2D):
447    """
448        Plot panel for use with the GUI manager
449    """
450   
451    ## Internal name for the AUI manager
452    window_name = "plotpanel"
453    ## Title to appear on top of the window
454    window_caption = "Plot Panel"
455    ## Flag to tell the GUI manager that this panel is not
456    #  tied to any perspective
457    ALWAYS_ON = True
458    ## Group ID
459    group_id = None
460   
461    def __init__(self, parent, id = -1, color = None,\
462        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
463        """
464            Initialize the panel
465        """
466        View1DPanel2D.__init__(self, parent, id = id, style = style, **kwargs)
467       
468       
469class Plugin:
470    """
471        Plug-in class to be instantiated by the GUI manager
472    """
473   
474    def __init__(self):
475        """
476            Initialize the plug-in
477        """
478        ## Plug-in name
479        self.sub_menu = "Plotting"
480       
481        ## Reference to the parent window
482        self.parent = None
483       
484        ## List of panels for the simulation perspective (names)
485        self.perspective = []
486       
487        ## Plot panels
488        self.plot_panels = []
489       
490
491    def populate_menu(self, id, parent):
492        """
493            Create a 'Plot' menu to list the panels
494            available for displaying
495            @param id: next available unique ID for wx events
496            @param parent: parent window
497        """
498        self.menu = wx.Menu()
499        return [(id, self.menu, "Plot")]
500   
501       
502    def get_panels(self, parent):
503        """
504            Create and return a list of panel objects
505        """
506        ## Save a reference to the parent
507        self.parent = parent
508        # Connect to plotting events
509        self.parent.Bind(EVT_NEW_PLOT, self._on_plot_event)
510       
511        # We have no initial panels for this plug-in
512        return []
513   
514    def get_perspective(self):
515        """
516            Get the list of panel names for this perspective
517        """
518        return self.perspective
519   
520    def on_perspective(self, event):
521        """
522            Call back function for the perspective menu item.
523            We notify the parent window that the perspective
524            has changed.
525            @param event: menu event
526        """
527        self.parent.set_perspective(self.perspective)
528   
529    def post_init(self):
530        """
531            Post initialization call back to close the loose ends
532            [Somehow openGL needs this call]
533        """
534        pass
535   
536    def _on_show_panel(self, event):
537        print "_on_show_panel"
538   
539    def _on_plot_event(self, event):
540        """
541            A new plottable is being shipped to the plotting plug-in.
542            Check whether we have a panel to put in on, or create
543            a new one
544            @param event: EVT_NEW_PLOT event
545        """
546        # Check whether we already have a graph with the same units
547        # as the plottable we just received.
548        is_available = False
549        for panel in self.plot_panels:
550            if event.plot._xunit == panel.graph.prop["xunit_base"] \
551            and event.plot._yunit == panel.graph.prop["yunit_base"]:
552                if hasattr(event.plot, "group_id"):
553                    if not event.plot.group_id==None \
554                        and event.plot.group_id==panel.group_id:
555                        is_available = True
556                        panel._onEVT_1DREPLOT(event)
557                        self.parent.show_panel(panel.uid)
558                else:
559                    # Check that the plot panel has no group ID
560                    if panel.group_id==None:
561                        is_available = True
562                        panel._onEVT_1DREPLOT(event)
563                        self.parent.show_panel(panel.uid)
564       
565        # Create a new plot panel if none was available       
566        if not is_available:
567            if event.plot.__class__.__name__=='Data1D':
568                new_panel = View1DPanel1D(self.parent, -1, style=wx.RAISED_BORDER)
569            if event.plot.__class__.__name__=='Data2D':
570                new_panel = View1DPanel2D(self.parent, -1, style=wx.RAISED_BORDER)
571            if event.plot.__class__.__name__=='Theory2D':
572                new_panel = View1DModelPanel2D(self.parent, -1, style=wx.RAISED_BORDER)
573            # Set group ID if available
574            group_id_str = ''
575            if hasattr(event.plot, "group_id"):
576                if not event.plot.group_id==None:
577                    new_panel.group_id = event.plot.group_id
578                    group_id_str = ' [%s]' % event.plot.group_id
579           
580            if hasattr(event, "title"):
581                new_panel.window_caption = event.title
582                new_panel.window_name = event.title
583                #new_panel.window_caption = event.title+group_id_str
584                #new_panel.window_name = event.title+group_id_str
585           
586            event_id = self.parent.popup_panel(new_panel)
587            self.menu.Append(event_id, new_panel.window_caption, 
588                             "Show %s plot panel" % new_panel.window_caption)
589            # Set UID to allow us to reference the panel later
590            new_panel.uid = event_id
591            # Ship the plottable to its panel
592            new_panel._onEVT_1DREPLOT(event)
593            self.plot_panels.append(new_panel)       
594           
595        return
596       
Note: See TracBrowser for help on using the repository browser.