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

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

data instead of image

  • Property mode set to 100644
File size: 20.3 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            #print "panel1D ",event.plot.__class_
96            #print "event panel 1d",self.plots[event.plot.name].__class__
97            if not event.plot.__class__==self.plots[event.plot.name].__class__:
98                self.graph.delete(self.plots[event.plot.name])
99            else:
100                is_new = False
101       
102        if is_new:
103            self.plots[event.plot.name] = event.plot
104            self.graph.add(self.plots[event.plot.name])
105        else:
106            self.plots[event.plot.name].x = event.plot.x   
107            self.plots[event.plot.name].y = event.plot.y   
108            self.plots[event.plot.name].dy = event.plot.dy 
109            if hasattr(event.plot, 'dx') and hasattr(self.plots[event.plot.name], 'dx'):
110                self.plots[event.plot.name].dx = event.plot.dx   
111 
112       
113        # Check axis labels
114        #TODO: Should re-factor this
115        #if event.plot._xunit != self.graph.prop["xunit"]:
116        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
117           
118        #if event.plot._yunit != self.graph.prop["yunit"]:
119        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
120     
121        # Set the view scale for all plots
122        self._onEVT_FUNC_PROPERTY()
123     
124        self.graph.render(self)
125        self.subplot.figure.canvas.draw_idle()
126
127    def onLeftDown(self,event): 
128        """ left button down and ready to drag"""
129           
130        PlotPanel.onLeftDown(self, event)
131        ax = event.inaxes
132        if ax != None:
133            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
134            wx.PostEvent(self.parent, StatusEvent(status=position))
135
136    def _onRemove(self, event):
137        """
138        """
139        if not self.graph.selected_plottable == None:
140            print self.graph.selected_plottable
141           
142           
143            self.graph.delete(self.plots[self.graph.selected_plottable])
144            del self.plots[self.graph.selected_plottable]
145            self.graph.render(self)
146            self.subplot.figure.canvas.draw_idle()   
147           
148
149    def onContextMenu(self, event):
150        """
151            1D plot context menu
152            @param event: wx context event
153        """
154        #slicerpop = wx.Menu()
155        slicerpop = PanelMenu()
156        slicerpop.set_plots(self.plots)
157        slicerpop.set_graph(self.graph)
158               
159        # Option to save the data displayed
160       
161        #for plot in self.graph.plottables:
162        if self.graph.selected_plottable in self.plots:
163            plot = self.plots[self.graph.selected_plottable]
164            id = wx.NewId()
165            name = plot.name
166            slicerpop.Append(id, "&Save %s points" % name)
167            self.action_ids[str(id)] = plot
168            wx.EVT_MENU(self, id, self._onSave)
169               
170            # Option to delete plottable
171            id = wx.NewId()
172            slicerpop.Append(id, "Remove %s curve" % name)
173            self.action_ids[str(id)] = plot
174            wx.EVT_MENU(self, id, self._onRemove)
175           
176            # Option to hide
177            #TODO: implement functionality to hide a plottable (legend click)
178            slicerpop.AppendSeparator()
179               
180        # Various plot options
181        id = wx.NewId()
182        slicerpop.Append(id,'&Save image', 'Save image as PNG')
183        wx.EVT_MENU(self, id, self.onSaveImage)
184       
185       
186        item_list = self.parent.get_context_menu(self.graph)
187        if (not item_list==None) and (not len(item_list)==0):
188                slicerpop.AppendSeparator()
189                for item in item_list:
190                    try:
191                        id = wx.NewId()
192                        slicerpop.Append(id, item[0], item[1])
193                        wx.EVT_MENU(self, id, item[2])
194                    except:
195                        print sys.exc_value
196                        print RuntimeError, "View1DPanel.onContextMenu: bad menu item"
197       
198        slicerpop.AppendSeparator()
199       
200        if self.graph.selected_plottable in self.plots:
201            if self.plots[self.graph.selected_plottable].__class__.__name__=="Theory1D":
202                id = wx.NewId()
203                slicerpop.Append(id, '&Add errors to data')
204                wx.EVT_MENU(self, id, self._on_add_errors)
205            else:
206                id = wx.NewId()
207                slicerpop.Append(id, '&Linear fit')
208                wx.EVT_MENU(self, id, self.onFitting)
209               
210       
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   
226    def _on_add_errors(self, evt):
227        """
228            Compute reasonable errors for a data set without
229            errors and transorm the plottable to a Data1D
230        """
231        import math
232        import numpy
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
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 View1DPanel2D( View1DPanel1D):
321    """
322        Plot panel for use with the GUI manager
323    """
324   
325    ## Internal name for the AUI manager
326    window_name = "plotpanel"
327    ## Title to appear on top of the window
328    window_caption = "Plot Panel"
329    ## Flag to tell the GUI manager that this panel is not
330    #  tied to any perspective
331    ALWAYS_ON = True
332    ## Group ID
333    group_id = None
334   
335    def __init__(self, parent, id = -1, color = None,\
336        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
337        """
338            Initialize the panel
339        """
340        View1DPanel1D.__init__(self, parent, id = id, style = style, **kwargs)
341       
342        ## Reference to the parent window
343        self.parent = parent
344        ## Plottables
345        self.plots = {}
346       
347        ## Unique ID (from gui_manager)
348        self.uid = None
349       
350        ## Action IDs for internal call-backs
351        self.action_ids = {}
352       
353        ## Graph       
354        self.graph = Graph()
355        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
356        self.graph.yaxis("\\rm{Intensity} ","cm^{-1}")
357        self.graph.render(self)
358 
359    def _onEVT_1DREPLOT(self, event):
360        """
361            Data is ready to be displayed
362            @param event: data event
363        """
364        #TODO: Check for existence of plot attribute
365        # Check whether this is a replot. If we ask for a replot
366        # and the plottable no longer exists, ignore the event.
367        if hasattr(event, "update") and event.update==True \
368            and event.plot.name not in self.plots.keys():
369            return
370        if hasattr(event, "reset"):
371            self._reset()
372        is_new = True
373        if event.plot.name in self.plots.keys():
374            # Check whether the class of plottable changed
375            if not event.plot.__class__==self.plots[event.plot.name].__class__:
376                self.graph.delete(self.plots[event.plot.name])
377            else:
378                is_new = False
379        self.plots[event.plot.name] = event.plot
380        #if is_new:
381        self.graph.add(self.plots[event.plot.name])
382       
383
384        # Check axis labels
385        #TODO: Should re-factor this
386        #if event.plot._xunit != self.graph.prop["xunit"]:
387       
388        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
389        #if event.plot._yunit != self.graph.prop["yunit"]:
390        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
391        self.graph.render(self)
392        self.subplot.figure.canvas.draw_idle()
393
394
395    def onContextMenu(self, event):
396        """
397            2D plot context menu
398            @param event: wx context event
399        """
400       
401        #slicerpop = wx.Menu()
402        slicerpop = PanelMenu()
403        slicerpop.set_plots(self.plots)
404        slicerpop.set_graph(self.graph)
405               
406        # Option to save the data displayed
407   
408        # Various plot options
409        id = wx.NewId()
410        slicerpop.Append(id,'&Save image', 'Save image as PNG')
411        wx.EVT_MENU(self, id, self.onSaveImage)
412       
413       
414        item_list = self.parent.get_context_menu(self.graph)
415        if (not item_list==None) and (not len(item_list)==0):
416                slicerpop.AppendSeparator()
417                for item in item_list:
418                    try:
419                        id = wx.NewId()
420                        slicerpop.Append(id, item[0], item[1])
421                        wx.EVT_MENU(self, id, item[2])
422                    except:
423                        print sys.exc_value
424                        print RuntimeError, "View1DPanel2D.onContextMenu: bad menu item"
425       
426        slicerpop.AppendSeparator()
427     
428        id = wx.NewId()
429        slicerpop.Append(id, '&Toggle Linear/Log scale')
430        wx.EVT_MENU(self, id, self._onToggleScale) 
431
432        pos = event.GetPosition()
433        pos = self.ScreenToClient(pos)
434        self.PopupMenu(slicerpop, pos)
435   
436    def _onToggleScale(self, event):
437        """
438            toggle axis and replot image
439        """
440        if self.scale == 'log':
441            self.scale = 'linear'
442        else:
443            self.scale = 'log'
444        self.image(self.data,self.xmin_2D,self.xmax_2D,self.ymin_2D,
445                   self.ymax_2D,self.zmin_2D ,self.zmax_2D )
446        wx.PostEvent(self.parent, StatusEvent(status="Image is in %s scale"%self.scale))
447     
448class Plugin:
449    """
450        Plug-in class to be instantiated by the GUI manager
451    """
452   
453    def __init__(self):
454        """
455            Initialize the plug-in
456        """
457        ## Plug-in name
458        self.sub_menu = "Plotting"
459       
460        ## Reference to the parent window
461        self.parent = None
462       
463        ## List of panels for the simulation perspective (names)
464        self.perspective = []
465       
466        ## Plot panels
467        self.plot_panels = []
468       
469
470    def populate_menu(self, id, parent):
471        """
472            Create a 'Plot' menu to list the panels
473            available for displaying
474            @param id: next available unique ID for wx events
475            @param parent: parent window
476        """
477        self.menu = wx.Menu()
478        return [(id, self.menu, "Plot")]
479   
480       
481    def get_panels(self, parent):
482        """
483            Create and return a list of panel objects
484        """
485        ## Save a reference to the parent
486        self.parent = parent
487        # Connect to plotting events
488        self.parent.Bind(EVT_NEW_PLOT, self._on_plot_event)
489       
490        # We have no initial panels for this plug-in
491        return []
492   
493    def get_perspective(self):
494        """
495            Get the list of panel names for this perspective
496        """
497        return self.perspective
498   
499    def on_perspective(self, event):
500        """
501            Call back function for the perspective menu item.
502            We notify the parent window that the perspective
503            has changed.
504            @param event: menu event
505        """
506        self.parent.set_perspective(self.perspective)
507   
508    def post_init(self):
509        """
510            Post initialization call back to close the loose ends
511            [Somehow openGL needs this call]
512        """
513        pass
514   
515    def _on_show_panel(self, event):
516        print "_on_show_panel"
517   
518    def _on_plot_event(self, event):
519        """
520            A new plottable is being shipped to the plotting plug-in.
521            Check whether we have a panel to put in on, or create
522            a new one
523            @param event: EVT_NEW_PLOT event
524        """
525        # Check whether we already have a graph with the same units
526        # as the plottable we just received.
527        is_available = False
528        for panel in self.plot_panels:
529            if event.plot._xunit == panel.graph.prop["xunit_base"] \
530            and event.plot._yunit == panel.graph.prop["yunit_base"]:
531                if hasattr(event.plot, "group_id"):
532                    if not event.plot.group_id==None \
533                        and event.plot.group_id==panel.group_id:
534                        is_available = True
535                        panel._onEVT_1DREPLOT(event)
536                        self.parent.show_panel(panel.uid)
537                else:
538                    # Check that the plot panel has no group ID
539                    if panel.group_id==None:
540                        is_available = True
541                        panel._onEVT_1DREPLOT(event)
542                        self.parent.show_panel(panel.uid)
543       
544        # Create a new plot panel if none was available       
545        if not is_available:
546            if not hasattr(event.plot,'data'):
547                new_panel = View1DPanel1D(self.parent, -1, style=wx.RAISED_BORDER)
548            else:
549                new_panel = View1DPanel2D(self.parent, -1, style=wx.RAISED_BORDER)
550            # Set group ID if available
551            group_id_str = ''
552            if hasattr(event.plot, "group_id"):
553                if not event.plot.group_id==None:
554                    new_panel.group_id = event.plot.group_id
555                    group_id_str = ' [%s]' % event.plot.group_id
556           
557            if hasattr(event, "title"):
558                new_panel.window_caption = event.title
559                new_panel.window_name = event.title
560                #new_panel.window_caption = event.title+group_id_str
561                #new_panel.window_name = event.title+group_id_str
562           
563            event_id = self.parent.popup_panel(new_panel)
564            self.menu.Append(event_id, new_panel.window_caption, 
565                             "Show %s plot panel" % new_panel.window_caption)
566            # Set UID to allow us to reference the panel later
567            new_panel.uid = event_id
568            # Ship the plottable to its panel
569            new_panel._onEVT_1DREPLOT(event)
570            self.plot_panels.append(new_panel)       
571           
572        return
573       
Note: See TracBrowser for help on using the repository browser.