source: sasview/sansguiframe/src/sans/guiframe/local_perspectives/plotting/Plotter1D.py @ db7a82e

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 db7a82e was 2636188, checked in by Jessica Tumarkin <jtumarki@…>, 13 years ago

Committing menu changes that were not committed earlier

  • Property mode set to 100644
File size: 22.3 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
20
21from danse.common.plottools.PlotPanel import PlotPanel
22#from 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 NewColorEvent
28from sans.guiframe.events import SlicerEvent
29from sans.guiframe.events import PanelOnFocusEvent
30from sans.guiframe.events import EVT_NEW_LOADED_DATA
31from sans.guiframe.utils import PanelMenu
32from sans.guiframe.dataFitting import Data1D
33from sans.guiframe.panel_base import PanelBase
34from binder import BindArtist
35
36DEFAULT_QMAX = 0.05
37DEFAULT_QSTEP = 0.001
38DEFAULT_BEAM = 0.005
39BIN_WIDTH = 1
40
41class SizeDialog(wx.Dialog):
42    def __init__(self, parent, id, title):
43        wx.Dialog.__init__(self, parent, id, title, size=(300, 175))
44
45        #panel = wx.Panel(self, -1)
46       
47        mainbox = wx.BoxSizer(wx.VERTICAL)
48        vbox = wx.BoxSizer(wx.VERTICAL)
49        textbox = wx.BoxSizer(wx.HORIZONTAL)
50       
51        text1 = "Enter in a custom size (float values > 0 accepted)"
52        msg = wx.StaticText(self, -1, text1,(30,15), style=wx.ALIGN_CENTRE)
53        msg.SetLabel(text1)
54        self.myTxtCtrl = wx.TextCtrl(self, -1, '', (100, 50))
55       
56        textbox.Add(self.myTxtCtrl, flag=wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 
57                 border=10, proportion=2)
58        vbox.Add(msg, flag=wx.ALL, border=10, proportion=1)
59        vbox.Add(textbox, flag=wx.EXPAND|wx.TOP|wx.BOTTOM|wx.ADJUST_MINSIZE,
60                 border=10)
61   
62        hbox = wx.BoxSizer(wx.HORIZONTAL)
63        okButton = wx.Button(self,wx.ID_OK, 'OK', size=(70, 30))
64        closeButton = wx.Button(self,wx.ID_CANCEL, 'Close', size=(70, 30))
65        hbox.Add(okButton, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 
66                 border=10)
67        hbox.Add(closeButton, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 
68                 border=10)
69       
70        mainbox.Add(vbox, flag=wx.ALL, border=10)
71        mainbox.Add(hbox, flag=wx.EXPAND|wx.TOP|wx.BOTTOM|wx.ADJUST_MINSIZE, 
72                    border=10)
73        self.SetSizer(mainbox)
74   
75    def getText(self):
76        return self.myTxtCtrl.GetValue()
77
78class ModelPanel1D(PlotPanel, PanelBase):
79    """
80    Plot panel for use with the GUI manager
81    """
82   
83    ## Internal name for the AUI manager
84    window_name = "plotpanel"
85    ## Title to appear on top of the window
86    window_caption = "Plot Panel"
87    ## Flag to tell the GUI manager that this panel is not
88    #  tied to any perspective
89    ALWAYS_ON = True
90    ## Group ID
91    group_id = None
92   
93    def __init__(self, parent, id=-1, color = None,
94                 dpi=None, style=wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
95        PlotPanel.__init__(self, parent, id=id, style=style, **kwargs)
96        PanelBase.__init__(self, parent)
97        ## Reference to the parent window
98        self.parent = parent
99        ## Plottables
100        self.plots = {}
101        #context menu
102        self._slicerpop = None
103       
104        self._available_data = []
105        self._menu_add_ids = []
106        self._symbol_labels = self.get_symbol_label()
107        self._color_labels = self.get_color_label()
108        self.currColorIndex = ""
109     
110        self.hide_menu = None
111        ## Unique ID (from gui_manager)
112        self.uid = None
113        self.x_size = None
114        ## Default locations
115        self._default_save_location = os.getcwd() 
116        self.size = None       
117        ## Graph       
118        #self.graph = Graph()
119        self.graph.xaxis("\\rm{Q}", 'A^{-1}')
120        self.graph.yaxis("\\rm{Intensity} ", "cm^{-1}")
121        self.graph.render(self)
122       
123        # In resizing event
124        self.resizing = False
125        self.canvas.set_resizing(self.resizing)
126        self.Bind(wx.EVT_SIZE, self._OnReSize)
127        self._add_more_tool()
128       
129    def get_symbol_label(self):
130        """
131        Associates label to symbol
132        """
133        _labels = {}
134        i = 0
135        _labels['Circle'] = i
136        i += 1
137        _labels['Cross X '] = i
138        i += 1
139        _labels['Triangle Down'] = i
140        i += 1
141        _labels['Triangle Up'] = i
142        i += 1
143        _labels['Triangle Left'] = i
144        i += 1
145        _labels['Triangle Right'] = i
146        i += 1
147        _labels['Cross +'] = i
148        i += 1
149        _labels['Square'] = i
150        i += 1
151        _labels['Diamond'] = i
152        i += 1
153        _labels['Diamond'] = i
154        i += 1
155        _labels['Hexagon1'] = i
156        i += 1
157        _labels['Hexagon2'] = i
158        i += 1
159        _labels['Pentagon'] = i
160        i += 1
161        _labels['Line'] = i
162        return _labels
163   
164    def get_color_label(self):
165        """
166        Associates label to a specific color
167        """
168        _labels = {}
169        i = 0
170        _labels['Blue'] = i
171        i += 1
172        _labels['Green'] = i
173        i += 1
174        _labels['Red'] = i
175        i += 1
176        _labels['Cyan'] = i
177        i += 1
178        _labels['Magenta'] = i
179        i += 1
180        _labels['Yellow'] = i
181        return _labels
182
183   
184    def set_data(self, list=None):
185        """
186        """
187        pass
188   
189    def _reset(self):
190        """
191        Resets internal data and graph
192        """   
193        self.graph.reset()
194        self.plots      = {}
195       
196    def _OnReSize(self, event):   
197        """
198        On response of the resize of a panel, set axes_visiable False
199        """
200        # It was found that wx >= 2.9.3 sends an event even if no size changed.
201        # So manually recode the size (=x_size) and compare here.
202        if self.x_size != None:
203            if self.x_size == self.GetSize():
204                self.resizing = False
205                self.canvas.set_resizing(self.resizing)
206                return
207        self.x_size = self.GetSize()
208
209        # Ready for another event
210        # Do not remove this Skip. Otherwise it will get runtime error on wx>=2.9.
211        event.Skip() 
212        # set the resizing flag
213        self.resizing = True
214        self.canvas.set_resizing(self.resizing)
215        self.parent.set_schedule(True)
216        pos_x, pos_y = self.GetPositionTuple()
217        if pos_x != 0 and pos_y != 0:
218            self.size, _ = self.GetClientSizeTuple()
219       
220    def set_resizing(self, resizing=False):
221        """
222        Set the resizing (True/False)
223        """
224        self.resizing = resizing
225        #self.canvas.set_resizing(resizing)
226   
227    def schedule_full_draw(self, func='append'):   
228        """
229        Put self in schedule to full redraw list
230        """
231        # append/del this panel in the schedule list
232        self.parent.set_schedule_full_draw(self, func)
233       
234
235    def remove_data_by_id(self, id):
236        """'
237        remove data from plot
238        """
239        if id in self.plots.keys():
240            data =  self.plots[id]
241            self.graph.delete(data)
242            data_manager = self._manager.parent.get_data_manager()
243            data_list, theory_list = data_manager.get_by_id(id_list=[id])
244           
245            if id in data_list.keys():
246                data = data_list[id]
247            if id in theory_list.keys():
248                data = theory_list[id]
249           
250            del self.plots[id]
251            self.graph.render(self)
252            self.subplot.figure.canvas.draw_idle()   
253            if len(self.graph.plottables) == 0:
254                #onRemove: graph is empty must be the panel must be destroyed
255                self.parent.delete_panel(self.uid)
256       
257    def plot_data(self, data):
258        """
259        Data is ready to be displayed
260       
261        :param event: data event
262        """
263        if data.id in self.plots.keys():
264            #replace
265            self.graph.replace(data)
266            self.plots[data.id] = data
267        else:
268            self.plots[data.id] = data
269            self.graph.add(self.plots[data.id]) 
270
271        ## Set the view scale for all plots
272        self._onEVT_FUNC_PROPERTY()
273        ## render the graph<=No need this done in canvas
274        #self.graph.render(self)
275        #self.subplot.figure.canvas.draw_idle()
276   
277    def draw_plot(self):
278        """
279        Draw plot
280        """
281        self.draw() 
282
283
284       
285    def onLeftDown(self,event): 
286        """
287        left button down and ready to drag
288        Display the position of the mouse on the statusbar
289        """
290        PlotPanel.onLeftDown(self, event)
291        ax = event.inaxes
292        if ax != None:
293            position = "x: %8.3g    y: %8.3g" % (event.xdata, event.ydata)
294            wx.PostEvent(self.parent, StatusEvent(status=position))
295        # unfocus all
296        self.parent.set_plot_unfocus() 
297        #post nd event to notify guiframe that this panel is on focus
298        wx.PostEvent(self.parent, PanelOnFocusEvent(panel=self))
299
300       
301    def _ontoggle_hide_error(self, event):
302        """
303        Toggle error display to hide or show
304        """
305       
306        selected_plot = self.plots[self.graph.selected_plottable]
307        if self.hide_menu.GetText() == "Hide Error":
308            selected_plot.hide_error = True
309        else:
310            selected_plot.hide_error = False
311        ## increment graph color
312        self.graph.render(self)
313        self.subplot.figure.canvas.draw_idle() 
314         
315    def _onRemove(self, event):
316        """
317        Remove a plottable from the graph and render the graph
318       
319        :param event: Menu event
320       
321        """
322        ## Check if there is a selected graph to remove
323        if self.graph.selected_plottable in self.plots.keys():
324            selected_plot = self.plots[self.graph.selected_plottable]
325            id = self.graph.selected_plottable
326            self.remove_data_by_id(id)
327           
328    def onContextMenu(self, event):
329        """
330        1D plot context menu
331       
332        :param event: wx context event
333       
334        """
335        self._slicerpop = PanelMenu()
336        self._slicerpop.set_plots(self.plots)
337        self._slicerpop.set_graph(self.graph)     
338        # Various plot options
339        id = wx.NewId()
340        self._slicerpop.Append(id, '&Save Image', 'Save image as PNG')
341        wx.EVT_MENU(self, id, self.onSaveImage)
342        id = wx.NewId()
343        self._slicerpop.Append(id, '&Print Image', 'Print image ')
344        wx.EVT_MENU(self, id, self.onPrint)
345        id = wx.NewId()
346        self._slicerpop.Append(id, '&Print Preview', 'Print preview')
347        wx.EVT_MENU(self, id, self.onPrinterPreview)
348       
349        id = wx.NewId()
350        self._slicerpop.Append(id, '&Copy to Clipboard', 'Copy to the clipboard')
351        wx.EVT_MENU(self, id, self.OnCopyFigureMenu)
352       
353        self._slicerpop.AppendSeparator()
354
355        #add menu of other plugins
356        item_list = self.parent.get_context_menu(self)
357
358        if (not item_list == None) and (not len(item_list) == 0):
359            for item in item_list:
360                try:
361                    id = wx.NewId()
362                    self._slicerpop.Append(id, item[0], item[1])
363                    wx.EVT_MENU(self, id, item[2])
364                except:
365                    msg = "ModelPanel1D.onContextMenu: "
366                    msg += "bad menu item  %s" % sys.exc_value
367                    wx.PostEvent(self.parent, StatusEvent(status=msg))
368                    pass
369            self._slicerpop.AppendSeparator()
370        #id = wx.NewId()
371        #self._slicerpop.Append(id, '&Print image', 'Print image')
372        if self.graph.selected_plottable in self.plots:
373            plot = self.plots[self.graph.selected_plottable]
374           
375            id = wx.NewId()
376            name = plot.name
377            self._slicerpop.Append(id, "&Save Points as a File")
378            self._slicerpop.AppendSeparator()
379            if plot.name != 'SLD':
380                wx.EVT_MENU(self, id, self._onSave)
381                id = wx.NewId()
382                self._slicerpop.Append(id, '&Linear Fit')
383                wx.EVT_MENU(self, id, self.onFitting)
384                self._slicerpop.AppendSeparator()
385   
386                id = wx.NewId()
387                self._slicerpop.Append(id, "Remove %s Curve" % name)
388                wx.EVT_MENU(self, id, self._onRemove)
389                if not plot.is_data:
390                    id = wx.NewId()
391                    self._slicerpop.Append(id, '&Freeze', 'Freeze')
392                    wx.EVT_MENU(self, id, self.onFreeze)
393               
394                symbol_menu = wx.Menu()
395                for label in self._symbol_labels:
396                    id = wx.NewId()
397                    symbol_menu.Append(id, str(label), str(label))
398                    wx.EVT_MENU(self, id, self.onChangeSymbol)
399                id = wx.NewId()
400                self._slicerpop.AppendMenu(id,'&Modify Symbol',  symbol_menu)
401               
402                color_menu = wx.Menu()
403                for label in self._color_labels:
404                    id = wx.NewId()
405                    color_menu.Append(id, str(label), str(label))
406                    wx.EVT_MENU(self, id, self.onChangeColor)
407                id = wx.NewId()
408                self._slicerpop.AppendMenu(id, '&Modify Symbol Color', color_menu)
409               
410               
411                size_menu = wx.Menu()
412                for i in range(10):
413                    id = wx.NewId()
414                    size_menu.Append(id, str(i), str(i))
415                    wx.EVT_MENU(self, id, self.onChangeSize)
416                id = wx.NewId()
417                size_menu.Append(id, '&Custom', 'Custom')
418                wx.EVT_MENU(self, id, self.onChangeSize)
419                id = wx.NewId()
420                self._slicerpop.AppendMenu(id, '&Modify Symbol Size', size_menu)
421               
422                self._slicerpop.AppendSeparator()
423   
424                id = wx.NewId()
425                self.hide_menu = self._slicerpop.Append(id, "Hide Error")
426   
427                if plot.dy is not None and plot.dy != []:
428                    if plot.hide_error :
429                        self.hide_menu.SetText('Show Error')
430                    else:
431                        self.hide_menu.SetText('Hide Error')
432                else:
433                    self.hide_menu.Enable(False)
434                wx.EVT_MENU(self, id, self._ontoggle_hide_error)
435               
436                self._slicerpop.AppendSeparator()
437                # Option to hide
438                #TODO: implement functionality to hide a plottable (legend click)
439       
440       
441        id = wx.NewId()
442        self._slicerpop.Append(id, '&Change scale')
443        wx.EVT_MENU(self, id, self._onProperties)
444        id = wx.NewId()
445        self._slicerpop.Append(id, '&Reset Graph')
446        wx.EVT_MENU(self, id, self.onResetGraph) 
447        pos = event.GetPosition()
448        pos = self.ScreenToClient(pos)
449        self.PopupMenu(self._slicerpop, pos)
450     
451    def onFreeze(self, event):
452        """
453        """
454        plot = self.plots[self.graph.selected_plottable]
455        self.parent.onfreeze([plot.id])
456   
457    def onChangeColor(self, event):
458        """
459        Changes the color of the graph when selected
460        """
461        menu = event.GetEventObject()
462        id = event.GetId()
463        label =  menu.GetLabel(id)
464        selected_plot = self.plots[self.graph.selected_plottable]
465        selected_plot.custom_color = self._color_labels[label]
466        ## Set the view scale for all plots
467        self._onEVT_FUNC_PROPERTY()
468        ## render the graph
469        #self.graph.render(self)
470        #self.subplot.figure.canvas.draw_idle()
471        print "PARENT: ", self.parent
472        wx.PostEvent(self.parent,
473                      NewColorEvent(color=selected_plot.custom_color,
474                                             id=selected_plot.id))
475   
476    def onChangeSize(self, event):
477       
478        menu = event.GetEventObject()
479        id = event.GetId()
480        label =  menu.GetLabel(id)
481        selected_plot = self.plots[self.graph.selected_plottable]
482       
483        if label == "&Custom":
484            sizedial = SizeDialog(None, -1, 'Change Marker Size')
485            if sizedial.ShowModal() == wx.ID_OK:
486                label = sizedial.getText()
487            sizedial.Destroy()
488
489        selected_plot.marker_size = int(label)
490        self._onEVT_FUNC_PROPERTY()
491        ## Set the view scale for all plots
492       
493        ## render the graph
494        #self.graph.render(self)
495        #self.subplot.figure.canvas.draw_idle()
496
497   
498    def onChangeSymbol(self, event):
499        """
500        """
501        menu = event.GetEventObject()
502        id = event.GetId()
503        label =  menu.GetLabel(id)
504        selected_plot = self.plots[self.graph.selected_plottable]
505        selected_plot.symbol = self._symbol_labels[label]
506        ## Set the view scale for all plots
507        self._onEVT_FUNC_PROPERTY()
508        ## render the graph
509        #self.graph.render(self)
510        #self.subplot.figure.canvas.draw_idle()
511       
512       
513       
514    def _onsaveTXT(self, path):
515        """
516        Save file as txt
517           
518        :TODO: Refactor and remove this method. See TODO in _onSave.
519       
520        """
521        data = self.plots[self.graph.selected_plottable]
522       
523        if not path == None:
524            out = open(path, 'w')
525            has_errors = True
526            if data.dy == None or data.dy == []:
527                has_errors = False
528            # Sanity check
529            if has_errors:
530                try:
531                    if len(data.y) != len(data.dy):
532                        has_errors = False
533                except:
534                    has_errors = False
535            if has_errors:
536                if data.dx != None:
537                    out.write("<X>   <Y>   <dY>   <dX>\n")
538                else:
539                    out.write("<X>   <Y>   <dY>\n")
540            else:
541                out.write("<X>   <Y>\n")
542               
543            for i in range(len(data.x)):
544                if has_errors:
545                    if data.dx != None:
546                        out.write("%g  %g  %g  %g\n" % (data.x[i], 
547                                                    data.y[i],
548                                                    data.dy[i],
549                                                    data.dx[i]))
550                    else:
551                        out.write("%g  %g  %g\n" % (data.x[i], 
552                                                    data.y[i],
553                                                    data.dy[i]))
554                else:
555                    out.write("%g  %g\n" % (data.x[i], 
556                                            data.y[i]))
557            out.close()                 
558            try:
559                self._default_save_location = os.path.dirname(path)
560            except:
561                pass   
562               
563    def _onSave(self, evt):
564        """
565        Save a data set to a text file
566       
567        :param evt: Menu event
568       
569        """
570       
571        path = None
572        wildcard = "Text files (*.txt)|*.txt|"\
573        "CanSAS 1D files(*.xml)|*.xml" 
574        dlg = wx.FileDialog(self, "Choose a file",
575                            self._default_save_location,
576                             "", wildcard , wx.SAVE)
577       
578        if dlg.ShowModal() == wx.ID_OK:
579            path = dlg.GetPath()
580            # ext_num = 0 for .txt, ext_num = 1 for .xml
581            # This is MAC Fix
582            ext_num = dlg.GetFilterIndex()
583            if ext_num == 0:
584                format = '.txt'
585            else:
586                format = '.xml'
587            path = os.path.splitext(path)[0] + format
588            mypath = os.path.basename(path)
589           
590            #TODO: This is bad design. The DataLoader is designed
591            #to recognize extensions.
592            # It should be a simple matter of calling the .
593            #save(file, data, '.xml') method
594            # of the DataLoader.loader.Loader class.
595            from DataLoader.loader import  Loader
596            #Instantiate a loader
597            loader = Loader() 
598            data = self.plots[self.graph.selected_plottable]
599            format = ".txt"
600            if os.path.splitext(mypath)[1].lower() == format:
601                # Make sure the ext included in the file name
602                # especially on MAC
603                fName = os.path.splitext(path)[0] + format
604                self._onsaveTXT(fName)
605            format = ".xml"
606            if os.path.splitext(mypath)[1].lower() == format:
607                # Make sure the ext included in the file name
608                # especially on MAC
609                fName = os.path.splitext(path)[0] + format
610                loader.save(fName, data, format)
611            try:
612                self._default_save_location = os.path.dirname(path)
613            except:
614                pass   
615        dlg.Destroy()
616
617    def _add_more_tool(self):
618        """
619        Add refresh button in the tool bar
620        """
621        if self.parent.__class__.__name__ != 'ViewerFrame':
622            return
623        self.toolbar.AddSeparator()
624        id_delete = wx.NewId()
625        delete =  wx.ArtProvider.GetBitmap(wx.ART_DELETE, wx.ART_TOOLBAR)
626        self.toolbar.AddSimpleTool(id_delete, delete,
627                           'Delete', 'permanently Delete')
628
629        self.toolbar.Realize()
630        wx.EVT_TOOL(self, id_delete,  self._on_delete)
631
632    def _on_delete(self, event): 
633        """
634        Refreshes the plotpanel on refresh tollbar button
635        """
636       
637        if self.parent is not None:
638            wx.PostEvent(self.parent, 
639                         NewPlotEvent(group_id=self.group_id,
640                                      action="delete"))
641           
Note: See TracBrowser for help on using the repository browser.