source: sasview/guiframe/local_perspectives/plotting/Plotter1D.py @ 69b4027

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 69b4027 was a45037aa, checked in by Gervaise Alina <gervyh@…>, 14 years ago

working on toolbar

  • Property mode set to 100644
File size: 22.1 KB
Line 
1
2################################################################################
3#This software was developed by the University of Tennessee as part of the
4#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
5#project funded by the US National Science Foundation.
6#
7#See the license text in license.txt
8#
9#copyright 2008, University of Tennessee
10################################################################################
11
12
13import wx
14import sys
15import os
16import pylab
17import math
18import numpy
19import time
20import danse.common.plottools
21from danse.common.plottools.PlotPanel import PlotPanel
22from danse.common.plottools.plottables import Graph
23from sans.guiframe import dataFitting
24from sans.guiframe.events import EVT_NEW_PLOT
25from sans.guiframe.events import StatusEvent
26from sans.guiframe.events import NewPlotEvent
27from sans.guiframe.events import SlicerEvent
28from sans.guiframe.events import ErrorDataEvent
29from sans.guiframe.events import RemoveDataEvent
30from sans.guiframe.events import PanelOnFocusEvent
31from sans.guiframe.events import EVT_NEW_LOADED_DATA
32from sans.guiframe.utils import PanelMenu
33from sans.guiframe.dataFitting import Data1D
34from sans.guiframe.dataFitting import Theory1D
35from sans.guiframe.panel_base import PanelBase
36from binder import BindArtist
37
38DEFAULT_QMAX = 0.05
39DEFAULT_QSTEP = 0.001
40DEFAULT_BEAM = 0.005
41BIN_WIDTH = 1
42
43
44class ModelPanel1D(PlotPanel, PanelBase):
45    """
46    Plot panel for use with the GUI manager
47    """
48   
49    ## Internal name for the AUI manager
50    window_name = "plotpanel"
51    ## Title to appear on top of the window
52    window_caption = "Plot Panel"
53    ## Flag to tell the GUI manager that this panel is not
54    #  tied to any perspective
55    ALWAYS_ON = True
56    ## Group ID
57    group_id = None
58   
59    def __init__(self, parent, id=-1, color = None,
60                 dpi=None, style=wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
61        PlotPanel.__init__(self, parent, id=id, style=style, **kwargs)
62        PanelBase.__init__(self, parent)
63        ## Reference to the parent window
64        self.parent = parent
65        ## Plottables
66        self.plots = {}
67        #context menu
68        self._slicerpop = None
69        self._menu_add_plot = None
70        self._available_data = []
71        self._menu_add_ids = []
72        ## save errors dy  for each data plotted
73        self.err_dy = {}
74        ## flag to determine if the hide or show context menu item should
75        ## be displayed
76        self.errors_hide = False
77        ## Unique ID (from gui_manager)
78        self.uid = None
79        ## Action IDs for internal call-backs
80        self.action_ids = {}
81        ## Default locations
82        self._default_save_location = os.getcwd()       
83        ## Graph       
84        self.graph = Graph()
85        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
86        self.graph.yaxis("\\rm{Intensity} ", "cm^{-1}")
87        self.graph.render(self)
88        #bind with event to contrst context menu
89        #self.parent.Bind(EVT_NEW_LOADED_DATA, self._add_new_plot)
90       
91    def on_add_new_plot(self, event):
92        """
93        """
94    def _add_new_plot(self, event):
95        """
96        Construct the context menu given available data
97        """
98        self._available_data = event.data_to_add
99   
100    def _fill_menu_add_plot(self):
101        """
102        """
103        if self._menu_add_plot is not None:
104            if self._available_data:
105                for id in self._menu_add_ids:
106                    item = self._menu_add_plot.FindItemById(id)
107                    if item is None:
108                        for data in self._available_data:
109                            new_id = wx.NewId()
110                            hint = 'Add %s to this panel' % str(data.name)
111                            self._menu_add_plot.Append(new_id, str(data.name), hint)
112                            wx.EVT_MENU(self, id, self.on_add_new_plot)
113                            self._menu_add_ids.append(new_id)
114                    else:
115                        if item.GetLabel() == 'No Loaded Data':
116                            self._menu_add_plot.RemoveItem(item)
117                       
118    def set_data(self, list=None):
119        """
120        """
121        pass
122   
123    def _reset(self):
124        """
125        Resets internal data and graph
126        """   
127        self.graph.reset()
128        self.plots      = {}
129        self.action_ids = {}
130   
131    def _onEVT_1DREPLOT(self, event):
132        """
133        Data is ready to be displayed
134       
135        :param event: data event
136        """
137        #TODO: Check for existence of plot attribute
138        # Check whether this is a replot. If we ask for a replot
139        # and the plottable no longer exists, ignore the event.
140        if hasattr(event, "update") and event.update == True \
141            and event.plot.name not in self.plots.keys():
142            return
143        if hasattr(event, "reset"):
144            self._reset()
145        # Check whether the plottable is empty
146        is_empty = len(event.plot.x) == 0
147               
148        is_new = True
149        if event.plot.name in self.plots.keys():
150            # If the plottable is empty, just remove the plottable from the graph
151            if is_empty:
152                self.graph.delete(self.plots[event.plot.name])
153                del self.plots[event.plot.name]
154            else: 
155                # Check whether the class of plottable changed
156                if not event.plot.__class__ == self.plots[event.plot.name].__class__:
157                    #overwrite a plottable using the same name
158                    self.graph.delete(self.plots[event.plot.name])             
159                else:
160                    # plottable is already draw on the panel
161                    is_new = False
162       
163        if not is_empty:
164            if is_new:
165                # a new plottable overwrites a plotted one  using the same id
166                for plottable in self.plots.itervalues():
167                    if hasattr(event.plot, "id") and hasattr(plottable, "id"):
168                        #remove the existing plot and same id and same name
169                        if event.plot.id == plottable.id and \
170                            event.plot.name == plottable.name:
171                            self.graph.delete(plottable)
172               
173                self.plots[event.plot.name] = event.plot
174                self.graph.add(self.plots[event.plot.name])
175            else:
176                #replot the graph
177                self.plots[event.plot.name].x = event.plot.x   
178                self.plots[event.plot.name].y = event.plot.y   
179                self.plots[event.plot.name].dy = event.plot.dy 
180                if hasattr(event.plot, 'dx') and \
181                    hasattr(self.plots[event.plot.name], 'dx'):
182                    self.plots[event.plot.name].dx = event.plot.dx   
183         
184        #TODO: Should re-factor this
185        ## for all added plot the option to hide error show be displayed first
186        #self.errors_hide = 0
187        ## Set axis labels
188        self.graph.xaxis(event.plot._xaxis, event.plot._xunit)
189        self.graph.yaxis(event.plot._yaxis, event.plot._yunit)
190        ## Set the view scale for all plots
191        self._onEVT_FUNC_PROPERTY()
192        ## render the graph
193        self.graph.render(self)
194        self.subplot.figure.canvas.draw_idle()
195        #if self.errors_hide:
196        #    self._on_remove_errors(evt=None)
197        #else:
198        #    self._on_add_errors( evt=None)
199   
200    def onLeftDown(self,event): 
201        """
202        left button down and ready to drag
203        Display the position of the mouse on the statusbar
204        """
205        PlotPanel.onLeftDown(self, event)
206        ax = event.inaxes
207        if ax != None:
208            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
209            wx.PostEvent(self.parent, StatusEvent(status=position))
210           
211        #post nd event to notify guiframe that this panel is on focus
212        wx.PostEvent(self.parent, PanelOnFocusEvent(panel=self))
213       
214    def _onRemove(self, event):
215        """
216        Remove a plottable from the graph and render the graph
217       
218        :param event: Menu event
219       
220        """
221        selected_plot = self.plots[self.graph.selected_plottable]
222        ## Check if there is a selected graph to remove
223        if not self.graph.selected_plottable == None and\
224            self.graph.selected_plottable in self.plots.keys():
225            color = self.graph.plottables[selected_plot]
226            event = RemoveDataEvent(data=selected_plot)
227            wx.PostEvent(self.parent, event)
228            self.graph.delete(selected_plot)
229            del selected_plot
230            ## increment graph color
231            self.graph.color += color
232            self.graph.render(self)
233            self.subplot.figure.canvas.draw_idle()   
234           
235    def onContextMenu(self, event):
236        """
237        1D plot context menu
238       
239        :param event: wx context event
240       
241        """
242        self._slicerpop = PanelMenu()
243        self._slicerpop.set_plots(self.plots)
244        self._slicerpop.set_graph(self.graph)     
245        # Various plot options
246        id = wx.NewId()
247        self._slicerpop.Append(id, '&Save image', 'Save image as PNG')
248        wx.EVT_MENU(self, id, self.onSaveImage)
249        id = wx.NewId()
250        self._slicerpop.Append(id, '&Print image', 'Print image ')
251        wx.EVT_MENU(self, id, self.onPrint)
252        id = wx.NewId()
253        self._slicerpop.Append(id, '&Print Preview', 'image preview for print')
254        wx.EVT_MENU(self, id, self.onPrinterPreview)
255        """
256        #add plot
257        self._menu_add_plot = wx.Menu()
258        id = wx.NewId()
259        self._menu_add_plot.Append(id, '&No Loaded Data', 'Add new plot')
260        self._menu_add_plot.FindItemByPosition(0).Enable(False)
261        self._menu_add_ids.append(id)
262       
263        self._slicerpop.AppendSubMenu(self._menu_add_plot, '&Add New Plot')
264        self._fill_menu_add_plot()
265        """
266        self._slicerpop.AppendSeparator()
267        #add menu of other plugins
268        item_list = self.parent.get_context_menu(self.graph)
269        if (not item_list == None) and (not len(item_list) == 0):
270            for item in item_list:
271                try:
272                    id = wx.NewId()
273                    self._slicerpop.Append(id, item[0], item[1])
274                    wx.EVT_MENU(self, id, item[2])
275                except:
276                    msg = "ModelPanel1D.onContextMenu: "
277                    msg += "bad menu item  %s"%sys.exc_value
278                    wx.PostEvent(self.parent, StatusEvent(status=msg))
279                    pass
280            self._slicerpop.AppendSeparator()
281        id = wx.NewId()
282        self._menu_add_plot = wx.Menu()
283        self._slicerpop.Append(id, '&Print image', 'Print image')
284        if self.graph.selected_plottable in self.plots:
285            plot = self.plots[self.graph.selected_plottable]
286            id = wx.NewId()
287            name = plot.name
288            self._slicerpop.Append(id, "&Save points")
289            self.action_ids[str(id)] = plot
290            wx.EVT_MENU(self, id, self._onSave)
291            id = wx.NewId()
292            self._slicerpop.Append(id, "Remove %s curve" % name)
293            self.action_ids[str(id)] = plot
294            wx.EVT_MENU(self, id, self._onRemove)
295            self._slicerpop.AppendSeparator()
296            # Option to hide
297            #TODO: implement functionality to hide a plottable (legend click)
298        if self.graph.selected_plottable in self.plots:
299            selected_plot = self.plots[self.graph.selected_plottable]
300            id = wx.NewId()
301            self._slicerpop.Append(id, '&Linear fit')
302            wx.EVT_MENU(self, id, self.onFitting)
303            self._slicerpop.AppendSeparator()
304        id = wx.NewId()
305        self._slicerpop.Append(id, '&Change scale')
306        wx.EVT_MENU(self, id, self._onProperties)
307        id = wx.NewId()
308        self._slicerpop.Append(id, '&Reset Graph')
309        wx.EVT_MENU(self, id, self.onResetGraph) 
310        pos = event.GetPosition()
311        pos = self.ScreenToClient(pos)
312        self.PopupMenu(self._slicerpop, pos)
313       
314    def _on_remove_errors(self, evt):
315        """
316        Save name and dy of data in dictionary self.err_dy
317        Create a new data1D with the same x, y
318        vector and dy with zeros.
319        post self.err_dy as event (ErrorDataEvent) for any object
320        which wants to reconstruct the initial data.
321       
322        :param evt: Menu event
323       
324        """
325        if not self.graph.selected_plottable == None:
326            ## store existing dy
327            name =self.plots[self.graph.selected_plottable].name
328            dy = self.plots[self.graph.selected_plottable].dy
329            self.err_dy[name] = dy
330            ## Create a new dy for a new plottable
331            #import numpy
332            dy = numpy.zeros(len(self.plots[self.graph.selected_plottable].y))
333            selected_plot = self.plots[self.graph.selected_plottable]
334           
335            if selected_plot.__class__.__name__ == "Data1D":
336                # Make sure that we can pass a basic Data1D
337                dxl = None
338                dxw = None
339                if hasattr(selected_plot, "dxl"):
340                    dxl = selected_plot.dxl
341                if hasattr(selected_plot, "dxw"):
342                    dxw = selected_plot.dxw
343                new_plot = Data1D(x=selected_plot.x, y=selected_plot.y,
344                               dx=selected_plot.dx, dy=dy)
345                new_plot.dxl = dxl
346                new_plot.dxw = dxw           
347            else:
348                 new_plot = Theory1D(x=selected_plot.x, 
349                                     y=selected_plot.y, dy=dy)
350            new_plot.interactive = True
351            self.errors_hide = True
352            new_plot.name = self.plots[self.graph.selected_plottable].name
353            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
354                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
355                if hasattr(self.plots[self.graph.selected_plottable], "id"):
356                    new_plot.id = self.plots[self.graph.selected_plottable].id
357                else:
358                    new_plot.id = str(time.time())
359            else:
360                new_plot.group_id = str(time.time())
361                new_plot.id = str(time.time())
362            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
363            new_plot.xaxis(label, unit)
364            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
365            new_plot.yaxis(label, unit)
366            ## save the color of the selected plottable before it is deleted
367            color = self.graph.plottables[self.plots[self.graph.selected_plottable]]
368            self.graph.delete(self.plots[self.graph.selected_plottable])
369            ## add newly created plottable to the graph with the save color
370            self.graph.color += color
371            self.graph.add(new_plot, color)
372            ## transforming the view of the new data into the same of
373            #the previous data
374            self._onEVT_FUNC_PROPERTY()
375            ## save the plot
376            self.plots[self.graph.selected_plottable] = new_plot
377            ## Render the graph
378            self.graph.render(self)
379            self.subplot.figure.canvas.draw_idle() 
380            event = ErrorDataEvent(err_dy=self.err_dy)
381            wx.PostEvent(self.parent, event)
382
383    def _on_add_errors(self, evt):
384        """
385        create a new data1D witht the errors saved in self.err_dy
386        to show errors of the plot.
387        Compute reasonable errors for a data set without
388        errors and transorm the plottable to a Data1D
389       
390        :param evt: Menu event
391       
392        """
393        if not self.graph.selected_plottable == None \
394            and self.graph.selected_plottable in self.plots.keys():
395            ##Reset the flag to display the hide option on the context menu
396            self.errors_hide = False
397            ## restore dy
398            length = len(self.plots[self.graph.selected_plottable].x)
399            dy = numpy.zeros(length)
400            selected_plot = self.plots[self.graph.selected_plottable]
401            try:
402                dy = self.err_dy[selected_plot.name]
403            except:
404                if hasattr(selected_plot, "dy"):
405                    dy = selected_plot.dy
406                else:
407                    dy = numpy.zeros(selected_plot.dy)
408            ## Create a new plottable data1D
409            if selected_plot.__class__.__name__ == "Data1D":
410                # Make sure that we can pass a basic Data1D
411                dxl = None
412                dxw = None
413                if hasattr(selected_plot, "dxl"):
414                    dxl = selected_plot.dxl
415                if hasattr(selected_plot, "dxw"):
416                    dxw = selected_plot.dxw
417                new_plot = Data1D(x=selected_plot.x,
418                                               y=selected_plot.y,
419                                               dx=selected_plot.dx,
420                                               dy=dy)
421                new_plot.dxl = dxl
422                new_plot.dxw = dxw                       
423            else:
424                ## Create a new plottable Theory1D
425                new_plot = Theory1D(x=selected_plot.x, y=selected_plot.y, dy=dy)
426            new_plot.interactive = True
427            new_plot.name = self.plots[self.graph.selected_plottable].name
428            if hasattr(self.plots[self.graph.selected_plottable], "group_id"):
429                new_plot.group_id = self.plots[self.graph.selected_plottable].group_id
430                if hasattr(self.plots[self.graph.selected_plottable],"id"):
431                    new_plot.id = self.plots[self.graph.selected_plottable].id
432                else:
433                    new_plot.id = str(time.time())
434            else:
435                new_plot.group_id = str(time.time())
436                new_plot.id = str(time.time())
437           
438            label, unit = self.plots[self.graph.selected_plottable].get_xaxis()
439            new_plot.xaxis(label, unit)
440            label, unit = self.plots[self.graph.selected_plottable].get_yaxis()
441            new_plot.yaxis(label, unit)
442            ## save the color of the selected plottable before it is deleted
443            color=self.graph.plottables[self.plots[self.graph.selected_plottable]]
444            self.graph.delete(self.plots[self.graph.selected_plottable])
445            self.graph.color += color
446            ## add newly created plottable to the graph with the save color
447            self.graph.add(new_plot, color)
448            ## transforming the view of the new data into the same of
449            # the previous data
450            self._onEVT_FUNC_PROPERTY()
451            ## save the plot
452            self.plots[self.graph.selected_plottable]=new_plot
453            ## render the graph with its new content
454            self.graph.render(self)
455            self.subplot.figure.canvas.draw_idle() 
456               
457               
458    def _onsaveTXT(self, path):
459        """
460        Save file as txt
461           
462        :TODO: Refactor and remove this method. See TODO in _onSave.
463       
464        """
465        data = self.plots[self.graph.selected_plottable]
466       
467        if not path == None:
468            out = open(path, 'w')
469            has_errors = True
470            if data.dy == None or data.dy == []:
471                has_errors = False
472            # Sanity check
473            if has_errors:
474                try:
475                    if len(data.y) != len(data.dy):
476                        has_errors = False
477                except:
478                    has_errors = False
479            if has_errors:
480                out.write("<X>   <Y>   <dY>\n")
481            else:
482                out.write("<X>   <Y>\n")
483               
484            for i in range(len(data.x)):
485                if has_errors:
486                    out.write("%g  %g  %g\n" % (data.x[i], 
487                                                data.y[i],
488                                               data.dy[i]))
489                else:
490                    out.write("%g  %g\n" % (data.x[i], 
491                                            data.y[i]))
492            out.close()                 
493            try:
494                self._default_save_location = os.path.dirname(path)
495            except:
496                pass   
497               
498    def _onSave(self, evt):
499        """
500        Save a data set to a text file
501       
502        :param evt: Menu event
503       
504        """
505        id = str(evt.GetId())
506        if id in self.action_ids:         
507           
508            path = None
509            wildcard = "Text files (*.txt)|*.txt|"\
510            "CanSAS 1D files(*.xml)|*.xml" 
511            dlg = wx.FileDialog(self, "Choose a file",
512                                self._default_save_location,
513                                 "", wildcard , wx.SAVE)
514           
515            if dlg.ShowModal() == wx.ID_OK:
516                path = dlg.GetPath()
517                mypath = os.path.basename(path)
518               
519                #TODO: This is bad design. The DataLoader is designed
520                #to recognize extensions.
521                # It should be a simple matter of calling the .
522                #save(file, data, '.xml') method
523                # of the DataLoader.loader.Loader class.
524                from DataLoader.loader import  Loader
525                #Instantiate a loader
526                loader = Loader() 
527                data = self.plots[self.graph.selected_plottable]
528                format = ".txt"
529                if os.path.splitext(mypath)[1].lower() == format:
530                     self._onsaveTXT( path)
531                format = ".xml"
532                if os.path.splitext(mypath)[1].lower() == format:
533                    loader.save(path, data, format)
534                try:
535                    self._default_save_location = os.path.dirname(path)
536                except:
537                    pass   
538            dlg.Destroy()
Note: See TracBrowser for help on using the repository browser.