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

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 1c153e9 was c966e1c, checked in by Jessica Tumarkin <jtumarki@…>, 14 years ago

Added menus for toggling legend, changing legend location

  • Property mode set to 100644
File size: 22.8 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        id = wx.NewId()
354        self._slicerpop.AppendSeparator()
355        self._slicerpop.Append(id, '&Toggle Legend On/Off', 'Toggle Legend On/Off')
356        wx.EVT_MENU(self, id, self.onLegend)
357       
358        loc_menu = wx.Menu()
359        for label in self._loc_labels:
360            id = wx.NewId()
361            loc_menu.Append(id, str(label), str(label))
362            wx.EVT_MENU(self, id, self.onChangeLegendLoc)
363        id = wx.NewId()
364        self._slicerpop.AppendMenu(id, '&Modify Legend Location', loc_menu)
365       
366        self._slicerpop.AppendSeparator()
367
368        #add menu of other plugins
369        item_list = self.parent.get_context_menu(self)
370
371        if (not item_list == None) and (not len(item_list) == 0):
372            for item in item_list:
373                try:
374                    id = wx.NewId()
375                    self._slicerpop.Append(id, item[0], item[1])
376                    wx.EVT_MENU(self, id, item[2])
377                except:
378                    msg = "ModelPanel1D.onContextMenu: "
379                    msg += "bad menu item  %s" % sys.exc_value
380                    wx.PostEvent(self.parent, StatusEvent(status=msg))
381                    pass
382            self._slicerpop.AppendSeparator()
383        #id = wx.NewId()
384        #self._slicerpop.Append(id, '&Print image', 'Print image')
385        if self.graph.selected_plottable in self.plots:
386            plot = self.plots[self.graph.selected_plottable]
387           
388            id = wx.NewId()
389            name = plot.name
390            self._slicerpop.Append(id, "&Save Points as a File")
391            self._slicerpop.AppendSeparator()
392            if plot.name != 'SLD':
393                wx.EVT_MENU(self, id, self._onSave)
394                id = wx.NewId()
395                self._slicerpop.Append(id, '&Linear Fit')
396                wx.EVT_MENU(self, id, self.onFitting)
397                self._slicerpop.AppendSeparator()
398   
399                id = wx.NewId()
400                self._slicerpop.Append(id, "Remove %s Curve" % name)
401                wx.EVT_MENU(self, id, self._onRemove)
402                if not plot.is_data:
403                    id = wx.NewId()
404                    self._slicerpop.Append(id, '&Freeze', 'Freeze')
405                    wx.EVT_MENU(self, id, self.onFreeze)
406               
407                symbol_menu = wx.Menu()
408                for label in self._symbol_labels:
409                    id = wx.NewId()
410                    symbol_menu.Append(id, str(label), str(label))
411                    wx.EVT_MENU(self, id, self.onChangeSymbol)
412                id = wx.NewId()
413                self._slicerpop.AppendMenu(id,'&Modify Symbol',  symbol_menu)
414               
415                color_menu = wx.Menu()
416                for label in self._color_labels:
417                    id = wx.NewId()
418                    color_menu.Append(id, str(label), str(label))
419                    wx.EVT_MENU(self, id, self.onChangeColor)
420                id = wx.NewId()
421                self._slicerpop.AppendMenu(id, '&Modify Symbol Color', color_menu)
422               
423               
424                size_menu = wx.Menu()
425                for i in range(10):
426                    id = wx.NewId()
427                    size_menu.Append(id, str(i), str(i))
428                    wx.EVT_MENU(self, id, self.onChangeSize)
429                id = wx.NewId()
430                size_menu.Append(id, '&Custom', 'Custom')
431                wx.EVT_MENU(self, id, self.onChangeSize)
432                id = wx.NewId()
433                self._slicerpop.AppendMenu(id, '&Modify Symbol Size', size_menu)
434               
435                self._slicerpop.AppendSeparator()
436   
437                id = wx.NewId()
438                self.hide_menu = self._slicerpop.Append(id, "Hide Error")
439   
440                if plot.dy is not None and plot.dy != []:
441                    if plot.hide_error :
442                        self.hide_menu.SetText('Show Error')
443                    else:
444                        self.hide_menu.SetText('Hide Error')
445                else:
446                    self.hide_menu.Enable(False)
447                wx.EVT_MENU(self, id, self._ontoggle_hide_error)
448               
449                self._slicerpop.AppendSeparator()
450                # Option to hide
451                #TODO: implement functionality to hide a plottable (legend click)
452       
453       
454        id = wx.NewId()
455        self._slicerpop.Append(id, '&Change scale')
456        wx.EVT_MENU(self, id, self._onProperties)
457        id = wx.NewId()
458        self._slicerpop.Append(id, '&Reset Graph')
459        wx.EVT_MENU(self, id, self.onResetGraph) 
460        pos = event.GetPosition()
461        pos = self.ScreenToClient(pos)
462        self.PopupMenu(self._slicerpop, pos)
463     
464    def onFreeze(self, event):
465        """
466        """
467        plot = self.plots[self.graph.selected_plottable]
468        self.parent.onfreeze([plot.id])
469   
470    def onChangeColor(self, event):
471        """
472        Changes the color of the graph when selected
473        """
474        menu = event.GetEventObject()
475        id = event.GetId()
476        label =  menu.GetLabel(id)
477        selected_plot = self.plots[self.graph.selected_plottable]
478        selected_plot.custom_color = self._color_labels[label]
479        ## Set the view scale for all plots
480        self._onEVT_FUNC_PROPERTY()
481        ## render the graph
482        #self.graph.render(self)
483        #self.subplot.figure.canvas.draw_idle()
484        print "PARENT: ", self.parent
485        wx.PostEvent(self.parent,
486                      NewColorEvent(color=selected_plot.custom_color,
487                                             id=selected_plot.id))
488   
489    def onChangeSize(self, event):
490       
491        menu = event.GetEventObject()
492        id = event.GetId()
493        label =  menu.GetLabel(id)
494        selected_plot = self.plots[self.graph.selected_plottable]
495       
496        if label == "&Custom":
497            sizedial = SizeDialog(None, -1, 'Change Marker Size')
498            if sizedial.ShowModal() == wx.ID_OK:
499                label = sizedial.getText()
500            sizedial.Destroy()
501
502        selected_plot.marker_size = int(label)
503        self._onEVT_FUNC_PROPERTY()
504        ## Set the view scale for all plots
505       
506        ## render the graph
507        #self.graph.render(self)
508        #self.subplot.figure.canvas.draw_idle()
509
510   
511    def onChangeSymbol(self, event):
512        """
513        """
514        menu = event.GetEventObject()
515        id = event.GetId()
516        label =  menu.GetLabel(id)
517        selected_plot = self.plots[self.graph.selected_plottable]
518        selected_plot.symbol = self._symbol_labels[label]
519        ## Set the view scale for all plots
520        self._onEVT_FUNC_PROPERTY()
521        ## render the graph
522        #self.graph.render(self)
523        #self.subplot.figure.canvas.draw_idle()
524       
525       
526       
527    def _onsaveTXT(self, path):
528        """
529        Save file as txt
530           
531        :TODO: Refactor and remove this method. See TODO in _onSave.
532       
533        """
534        data = self.plots[self.graph.selected_plottable]
535       
536        if not path == None:
537            out = open(path, 'w')
538            has_errors = True
539            if data.dy == None or data.dy == []:
540                has_errors = False
541            # Sanity check
542            if has_errors:
543                try:
544                    if len(data.y) != len(data.dy):
545                        has_errors = False
546                except:
547                    has_errors = False
548            if has_errors:
549                if data.dx != None:
550                    out.write("<X>   <Y>   <dY>   <dX>\n")
551                else:
552                    out.write("<X>   <Y>   <dY>\n")
553            else:
554                out.write("<X>   <Y>\n")
555               
556            for i in range(len(data.x)):
557                if has_errors:
558                    if data.dx != None:
559                        out.write("%g  %g  %g  %g\n" % (data.x[i], 
560                                                    data.y[i],
561                                                    data.dy[i],
562                                                    data.dx[i]))
563                    else:
564                        out.write("%g  %g  %g\n" % (data.x[i], 
565                                                    data.y[i],
566                                                    data.dy[i]))
567                else:
568                    out.write("%g  %g\n" % (data.x[i], 
569                                            data.y[i]))
570            out.close()                 
571            try:
572                self._default_save_location = os.path.dirname(path)
573            except:
574                pass   
575               
576    def _onSave(self, evt):
577        """
578        Save a data set to a text file
579       
580        :param evt: Menu event
581       
582        """
583       
584        path = None
585        wildcard = "Text files (*.txt)|*.txt|"\
586        "CanSAS 1D files(*.xml)|*.xml" 
587        dlg = wx.FileDialog(self, "Choose a file",
588                            self._default_save_location,
589                             "", wildcard , wx.SAVE)
590       
591        if dlg.ShowModal() == wx.ID_OK:
592            path = dlg.GetPath()
593            # ext_num = 0 for .txt, ext_num = 1 for .xml
594            # This is MAC Fix
595            ext_num = dlg.GetFilterIndex()
596            if ext_num == 0:
597                format = '.txt'
598            else:
599                format = '.xml'
600            path = os.path.splitext(path)[0] + format
601            mypath = os.path.basename(path)
602           
603            #TODO: This is bad design. The DataLoader is designed
604            #to recognize extensions.
605            # It should be a simple matter of calling the .
606            #save(file, data, '.xml') method
607            # of the sans.dataloader.loader.Loader class.
608            from sans.dataloader.loader import  Loader
609            #Instantiate a loader
610            loader = Loader() 
611            data = self.plots[self.graph.selected_plottable]
612            format = ".txt"
613            if os.path.splitext(mypath)[1].lower() == format:
614                # Make sure the ext included in the file name
615                # especially on MAC
616                fName = os.path.splitext(path)[0] + format
617                self._onsaveTXT(fName)
618            format = ".xml"
619            if os.path.splitext(mypath)[1].lower() == format:
620                # Make sure the ext included in the file name
621                # especially on MAC
622                fName = os.path.splitext(path)[0] + format
623                loader.save(fName, data, format)
624            try:
625                self._default_save_location = os.path.dirname(path)
626            except:
627                pass   
628        dlg.Destroy()
629
630    def _add_more_tool(self):
631        """
632        Add refresh button in the tool bar
633        """
634        if self.parent.__class__.__name__ != 'ViewerFrame':
635            return
636        self.toolbar.AddSeparator()
637        id_delete = wx.NewId()
638        delete =  wx.ArtProvider.GetBitmap(wx.ART_DELETE, wx.ART_TOOLBAR)
639        self.toolbar.AddSimpleTool(id_delete, delete,
640                           'Delete', 'permanently Delete')
641
642        self.toolbar.Realize()
643        wx.EVT_TOOL(self, id_delete,  self._on_delete)
644
645    def _on_delete(self, event): 
646        """
647        Refreshes the plotpanel on refresh tollbar button
648        """
649       
650        if self.parent is not None:
651            wx.PostEvent(self.parent, 
652                         NewPlotEvent(group_id=self.group_id,
653                                      action="delete"))
654           
Note: See TracBrowser for help on using the repository browser.