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

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

fixing toggle lin\log

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