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

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 b8ff610 was ef4a4ea, checked in by Jae Cho <jhjcho@…>, 13 years ago

fixed add/remove text in plot: now it stays even when plot updates

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