source: sasview/plottools/src/danse/common/plottools/PlotPanel.py @ 70ec588d

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 70ec588d was 70ec588d, checked in by Jae Cho <jhjcho@…>, 12 years ago

reorganized the list of context menu items

  • Property mode set to 100644
File size: 72.1 KB
Line 
1"""
2"""
3import logging
4import wx.lib.newevent
5
6# Try a normal import first
7# If it fails, try specifying a version
8import matplotlib
9matplotlib.interactive(False)
10#Use the WxAgg back end. The Wx one takes too long to render
11matplotlib.use('WXAgg')
12from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
13from matplotlib.figure import Figure
14import os
15import fittings
16import transform
17from matplotlib.widgets import RectangleSelector
18from pylab import  gca, gcf
19from plottables import Data1D
20#TODO: make the plottables interactive
21from binder import BindArtist
22from matplotlib.font_manager import FontProperties
23
24DEBUG = False
25
26from plottables import Graph
27from plottables import Text
28from TextDialog import TextDialog
29from LabelDialog import LabelDialog
30import operator
31
32import math, pylab, re
33DEFAULT_CMAP = pylab.cm.jet
34import copy
35import numpy
36
37def show_tree(obj,d=0):
38    """Handy function for displaying a tree of graph objects"""
39    print "%s%s" % ("-"*d, obj.__class__.__name__)
40    if 'get_children' in dir(obj):
41        for a in obj.get_children(): show_tree(a, d+1)
42     
43from unitConverter import UnitConvertion as convertUnit   
44
45def _rescale(lo, hi, step, pt=None, bal=None, scale='linear'):
46        """
47        Rescale (lo,hi) by step, returning the new (lo,hi)
48        The scaling is centered on pt, with positive values of step
49        driving lo/hi away from pt and negative values pulling them in.
50        If bal is given instead of point, it is already in [0,1] coordinates.
51   
52        This is a helper function for step-based zooming.
53       
54        """
55        # Convert values into the correct scale for a linear transformation
56        # TODO: use proper scale transformers
57        loprev = lo
58        hiprev = hi
59        ptprev = pt
60        if scale == 'log':
61            assert lo > 0
62            if lo > 0 :
63                lo = math.log10(lo)
64            if hi > 0 :
65                hi = math.log10(hi)
66            if pt is not None: pt = math.log10(pt)
67       
68        # Compute delta from axis range * %, or 1-% if persent is negative
69        if step > 0:
70            delta = float(hi - lo) * step / 100
71        else:
72            delta = float(hi - lo) * step / (100 - step)
73   
74        # Add scale factor proportionally to the lo and hi values,
75        # preserving the
76        # point under the mouse
77        if bal is None:
78            bal = float(pt - lo) / (hi - lo)
79        lo = lo - (bal * delta)
80        hi = hi + (1 - bal) * delta
81   
82        # Convert transformed values back to the original scale
83        if scale == 'log':
84            if (lo <= -250) or (hi >= 250):
85                lo = loprev
86                hi = hiprev
87            else:
88                lo, hi = math.pow(10., lo), math.pow(10., hi)
89        return (lo, hi)
90
91
92def CopyImage(canvas):
93     """
94     0: matplotlib plot
95     1: wx.lib.plot
96     2: other
97     
98     """
99     bmp = wx.BitmapDataObject()
100     bmp.SetBitmap(canvas.bitmap)
101
102     wx.TheClipboard.Open()
103     wx.TheClipboard.SetData(bmp)
104     wx.TheClipboard.Close()
105
106
107class PlotPanel(wx.Panel):
108    """
109    The PlotPanel has a Figure and a Canvas. OnSize events simply set a
110    flag, and the actually redrawing of the
111    figure is triggered by an Idle event.
112   
113    """
114    def __init__(self, parent, id=-1, xtransform=None,
115                  ytransform=None, scale='log', 
116                  color=None, dpi=None, **kwargs):
117        """
118        """
119        wx.Panel.__init__(self, parent, id=id, **kwargs)
120        self.parent = parent
121        self.gotLegend = 0  #to begin, legend is not picked.
122        self.legend_pos_loc = None
123        self.legend = None
124        self.line_collections_list = [] 
125        self.figure = Figure(None, dpi, linewidth=2.0)
126        self.color = '#b3b3b3' 
127        from canvas import FigureCanvas
128        self.canvas = FigureCanvas(self, -1, self.figure) 
129        self.SetColor(color)
130        #self.SetBackgroundColour(parent.GetBackgroundColour())
131        self._resizeflag = True
132        self._SetInitialSize() 
133        self.subplot = self.figure.add_subplot(111)
134        self.figure.subplots_adjust(left=0.2, bottom=.2)
135        self.yscale = 'linear'
136        self.xscale = 'linear'
137        self.sizer = wx.BoxSizer(wx.VERTICAL)
138        self.sizer.Add(self.canvas, 1, wx.EXPAND)
139        #add toolbar
140        self.enable_toolbar = True
141        self.toolbar = None
142        self.add_toolbar()
143        self.SetSizer(self.sizer)
144       
145        # Graph object to manage the plottables
146        self.graph = Graph()
147       
148        #Boolean value to keep track of whether current legend is
149        #visible or not
150        self.legend_on = True
151        self.grid_on = False
152        #Location of legend, default is 0 or 'best'
153        self.legendLoc = 0
154        self.position = None
155        self._loc_labels = self.get_loc_label()
156     
157        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
158       
159        # Define some constants
160        self.colorlist = ['b','g','r','c','m','y','k']
161        self.symbollist = ['o','x','^','v','<','>','+',
162                           's','d','D','h','H','p', '-']
163       
164        #List of texts currently on the plot
165        self.textList = []
166        #User scale
167        if xtransform != None:
168            self.xLabel = xtransform
169        else:   
170            self.xLabel = "log10(x)"
171        if ytransform != None:
172            self.yLabel = ytransform
173        else:
174            self.yLabel = "log10(y)"
175        self.viewModel = "--"
176        # keep track if the previous transformation of x
177        # and y in Property dialog
178        self.prevXtrans = "log10(x)"
179        self.prevYtrans = "log10(y)"
180        self.canvas.mpl_connect('scroll_event', self.onWheel)
181        #taking care of dragging
182        self.canvas.mpl_connect('motion_notify_event', self.onMouseMotion)
183        self.canvas.mpl_connect('button_press_event', self.onLeftDown)
184        self.canvas.mpl_connect('pick_event', self.onPick)
185        self.canvas.mpl_connect('button_release_event', self.onLeftUp)
186       
187        wx.EVT_RIGHT_DOWN(self, self.onLeftDown)
188        # to turn axis off whenn resizing the panel
189        self.resizing = False
190       
191        self.leftdown = False
192        self.leftup = False
193        self.mousemotion = False
194        self.axes = [self.subplot]
195        ## Fit dialog
196        self._fit_dialog = None
197        # Interactor
198        self.connect = BindArtist(self.subplot.figure)
199        #self.selected_plottable = None
200       
201        # new data for the fit
202        self.fit_result = Data1D(x=[], y=[], dy=None)
203        self.fit_result.symbol = 13
204        #self.fit_result = Data1D(x=[], y=[],dx=None, dy=None)
205        self.fit_result.name = "Fit"
206        # For fit Dialog initial display
207        self.xmin = 0.0
208        self.xmax = 0.0
209        self.xminView = 0.0
210        self.xmaxView = 0.0
211        self._scale_xlo = None
212        self._scale_xhi = None
213        self._scale_ylo = None
214        self._scale_yhi = None
215        self.Avalue = None
216        self.Bvalue = None
217        self.ErrAvalue = None
218        self.ErrBvalue = None
219        self.Chivalue = None
220       
221        # for 2D scale
222        if scale != 'linear':
223            scale = 'log'
224        self.scale = scale
225        self.data = None
226        self.qx_data = None
227        self.qy_data = None
228        self.xmin_2D = None
229        self.xmax_2D = None
230        self.ymin_2D = None
231        self.ymax_2D = None
232        ## store reference to the current plotted vmin and vmax of plotted image
233        ##z range in linear scale
234        self.zmin_2D = None
235        self.zmax_2D = None
236       
237        #index array
238        self.index_x = None
239        self.index_y = None
240       
241        #number of bins
242        self.x_bins = None
243        self.y_bins = None
244       
245        ## default color map
246        self.cmap = DEFAULT_CMAP
247       
248        # Dragging info
249        self.begDrag = False
250        self.xInit = None
251        self.yInit = None
252        self.xFinal = None
253        self.yFinal = None
254       
255        #axes properties
256        self.xaxis_font = None
257        self.xaxis_label = None
258        self.xaxis_unit = None
259        self.xaxis_color = 'black'
260        self.yaxis_font = None
261        self.yaxis_label = None
262        self.yaxis_unit = None
263        self.yaxis_color = 'black'
264       
265        # check if zoomed.
266        self.is_zoomed = False
267        # Plottables
268        self.plots = {}
269       
270        # Default locations
271        self._default_save_location = os.getcwd()   
272        # let canvas know about axes
273        self.canvas.set_panel(self)
274       
275        #Bind focus to change the border color     
276        self.canvas.Bind(wx.EVT_SET_FOCUS, self.on_set_focus)
277        self.canvas.Bind(wx.EVT_KILL_FOCUS, self.on_kill_focus)
278
279    def _SetInitialSize(self,):
280        """
281        """
282        pixels = self.parent.GetClientSize()
283        self.canvas.SetSize(pixels)
284        self.figure.set_size_inches( (pixels[0])/self.figure.get_dpi(),
285         (pixels[1])/self.figure.get_dpi(), forward=True )   
286         
287    def On_Paint(self, event):
288        """
289        """
290        self.canvas.SetBackgroundColour(self.color)
291        #dc = wx.PaintDC(self.canvas)
292        #dc.SetPen(wx.Pen(self.color))
293        #x, y = self.GetSize()
294        #dc.DrawRectangle(0, 0, x, y)
295        #dc.DrawRectangle(1, 1, x-1, y-1)
296        #self.draw()
297    def on_set_focus(self, event):
298        """
299        Send to the parenet the current panel on focus
300        """
301        # light blue
302        self.color = '#0099f7'
303        self.figure.set_edgecolor(self.color)
304        self.draw()
305       
306    def on_kill_focus(self, event):
307        """
308        Reset the panel color
309        """
310        # light grey
311        self.color = '#b3b3b3'
312        self.figure.set_edgecolor(self.color)
313        self.draw()
314           
315    def set_resizing(self, resizing=False):
316        """
317        Set the resizing (True/False)
318        """
319        pass # Not impleeted
320   
321    def schedule_full_draw(self, func='append'):   
322        """
323        Put self in schedule to full redraw list
324        """
325        pass # Not implemeted
326   
327    def add_toolbar(self):
328        """
329        add toolbar
330        """
331        self.enable_toolbar = True
332        from toolbar import NavigationToolBar
333        self.toolbar = NavigationToolBar(parent=self, canvas=self.canvas)
334        self.toolbar.Realize()
335        ## The 'SetToolBar()' is not working on MAC: JHC
336        #if IS_MAC:
337        # Mac platform (OSX 10.3, MacPython) does not seem to cope with
338        # having a toolbar in a sizer. This work-around gets the buttons
339        # back, but at the expense of having the toolbar at the top
340        #self.SetToolBar(self.toolbar)
341        #else:
342        # On Windows platform, default window size is incorrect, so set
343        # toolbar width to figure width.
344        tw, th = self.toolbar.GetSizeTuple()
345        fw, fh = self.canvas.GetSizeTuple()
346        # By adding toolbar in sizer, we are able to put it at the bottom
347        # of the frame - so appearance is closer to GTK version.
348        # As noted above, doesn't work for Mac.
349        self.toolbar.SetSize(wx.Size(fw, th))
350        self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
351       
352        # update the axes menu on the toolbar
353        self.toolbar.update()
354       
355    def onLeftDown(self, event): 
356        """
357        left button down and ready to drag
358       
359        """
360        # Check that the LEFT button was pressed
361        if event.button == 1:
362            self.leftdown = True
363            ax = event.inaxes
364            if ax != None:
365                self.xInit, self.yInit = event.xdata, event.ydata
366                try:
367                    pos_x = float(event.xdata)# / size_x
368                    pos_y = float(event.ydata)# / size_y
369                    pos_x = "%8.3g"% pos_x
370                    pos_y = "%8.3g"% pos_y
371                    self.position = str(pos_x), str(pos_y)
372                    wx.PostEvent(self.parent, StatusEvent(status=self.position))
373                except:
374                    self.position = None 
375        #size_x, size_y =  self.GetClientSizeTuple()
376
377     
378    def onLeftUp(self, event): 
379        """
380        Dragging is done
381       
382        """
383        # Check that the LEFT button was released
384        if event.button == 1:
385            self.leftdown = False
386            self.mousemotion = False 
387            self.leftup = True
388           
389        #release the legend   
390        if self.gotLegend == 1:
391            self.gotLegend = 0
392            self.set_legend_alpha(1)
393           
394    def set_legend_alpha(self, alpha=1):
395        """
396        Set legend alpha
397        """
398        if self.legend != None:
399            self.legend.legendPatch.set_alpha(alpha)
400       
401    def onPick(self, event):   
402        """
403        On pick legend
404        """
405        legend = self.legend
406        if event.artist == legend:
407            #gets the box of the legend.
408            bbox = self.legend.get_window_extent() 
409            #get mouse coordinates at time of pick.
410            self.mouse_x = event.mouseevent.
411            self.mouse_y = event.mouseevent.y
412            #get legend coordinates at time of pick.
413            self.legend_x = bbox.xmin         
414            self.legend_y = bbox.ymin
415            #indicates we picked up the legend.
416            self.gotLegend = 1   
417            self.set_legend_alpha(0.5)
418 
419           
420    def _on_legend_motion(self, event): 
421        """
422        On legend in motion
423        """
424        ax = event.inaxes
425        if ax == None:
426            return
427        # Event occurred inside a plotting area
428        lo_x, hi_x = ax.get_xlim()
429        lo_y, hi_y = ax.get_ylim()
430        # How much the mouse moved.
431        x = mouse_diff_x = self.mouse_x - event.
432        y = mouse_diff_y = self.mouse_y - event.y
433        # Put back inside
434        if x < lo_x:
435            x = lo_x
436        if x > hi_x:
437            x = hi_x
438        if y < lo_y:
439            y = lo_y
440        if y> hi_y:
441            y = hi_y
442        # Move the legend from its previous location by that same amount
443        loc_in_canvas = self.legend_x - mouse_diff_x, \
444                        self.legend_y - mouse_diff_y
445        # Transform into legend coordinate system
446        trans_axes = self.legend.parent.transAxes.inverted()
447        loc_in_norm_axes = trans_axes.transform_point(loc_in_canvas)
448        self.legend_pos_loc = tuple(loc_in_norm_axes)
449        self.legend._loc = self.legend_pos_loc
450        self.resizing = True
451        self.canvas.set_resizing(self.resizing)
452        self.canvas.draw() 
453           
454    def onMouseMotion(self, event): 
455        """
456        check if the left button is press and the mouse in moving.
457        computer delta for x and y coordinates and then calls draghelper
458        to perform the drag
459       
460        """
461        if self.gotLegend == 1:
462            self._on_legend_motion(event)
463            return
464        if self.enable_toolbar:
465            #Disable dragging without the toolbar to allow zooming with toolbar
466            return
467        self.mousemotion = True 
468        if self.leftdown == True and self.mousemotion == True:
469            ax = event.inaxes
470            if ax != None:#the dragging is perform inside the figure
471                self.xFinal, self.yFinal = event.xdata, event.ydata
472                # Check whether this is the first point
473                if self.xInit == None:
474                    self.xInit = self.xFinal
475                    self.yInit = self.yFinal
476                   
477                xdelta = self.xFinal - self.xInit
478                ydelta = self.yFinal - self.yInit
479               
480                if self.xscale == 'log':
481                    xdelta = math.log10(self.xFinal) - math.log10(self.xInit)
482                if self.yscale == 'log':
483                    ydelta = math.log10(self.yFinal) - math.log10(self.yInit)
484                self._dragHelper(xdelta, ydelta)
485            else:# no dragging is perform elsewhere
486                self._dragHelper(0,0)
487
488               
489    def _offset_graph(self):
490        """
491        Zoom and offset the graph to the last known settings
492       
493        """
494        for ax in self.axes:
495            if self._scale_xhi is not None and self._scale_xlo is not None:
496                ax.set_xlim(self._scale_xlo, self._scale_xhi)
497            if self._scale_yhi is not None and self._scale_ylo is not None:
498                ax.set_ylim(self._scale_ylo, self._scale_yhi)
499           
500    def _dragHelper(self, xdelta, ydelta):
501        """
502        dragging occurs here
503       
504        """
505        # Event occurred inside a plotting area
506        for ax in self.axes:
507            lo, hi = ax.get_xlim()
508            #print "x lo %f and x hi %f"%(lo,hi)
509            newlo, newhi = lo - xdelta, hi - xdelta
510            if self.xscale == 'log':
511                if lo > 0:
512                    newlo = math.log10(lo) - xdelta
513                if hi > 0:
514                    newhi = math.log10(hi) - xdelta
515            if self.xscale == 'log':
516                self._scale_xlo = math.pow(10, newlo)
517                self._scale_xhi = math.pow(10, newhi)
518                ax.set_xlim(math.pow(10, newlo), math.pow(10, newhi))
519            else:
520                self._scale_xlo = newlo
521                self._scale_xhi = newhi
522                ax.set_xlim(newlo, newhi)
523            #print "new lo %f and new hi %f"%(newlo,newhi)
524           
525            lo, hi = ax.get_ylim()
526            #print "y lo %f and y hi %f"%(lo,hi)
527            newlo, newhi = lo - ydelta, hi - ydelta
528            if self.yscale == 'log':
529                if lo > 0:
530                    newlo = math.log10(lo) - ydelta
531                if hi > 0:
532                    newhi = math.log10(hi) - ydelta
533                #print "new lo %f and new hi %f"%(newlo,newhi)
534            if  self.yscale == 'log':
535                self._scale_ylo = math.pow(10, newlo)
536                self._scale_yhi = math.pow(10, newhi)
537                ax.set_ylim(math.pow(10, newlo), math.pow(10, newhi))
538            else:
539                self._scale_ylo = newlo
540                self._scale_yhi = newhi
541                ax.set_ylim(newlo, newhi)
542        self.canvas.draw_idle()
543
544    def resetFitView(self):
545        """
546        For fit Dialog initial display
547       
548        """
549        self.xmin = 0.0
550        self.xmax = 0.0
551        self.xminView = 0.0
552        self.xmaxView = 0.0
553        self._scale_xlo = None
554        self._scale_xhi = None
555        self._scale_ylo = None
556        self._scale_yhi = None
557        self.Avalue = None
558        self.Bvalue = None
559        self.ErrAvalue = None
560        self.ErrBvalue = None
561        self.Chivalue = None
562   
563    def onWheel(self, event):
564        """
565        Process mouse wheel as zoom events
566       
567        :param event: Wheel event
568       
569        """
570        ax = event.inaxes
571        step = event.step
572
573        if ax != None:
574            # Event occurred inside a plotting area
575            lo, hi = ax.get_xlim()
576            lo, hi = _rescale(lo, hi, step, 
577                             pt=event.xdata, scale=ax.get_xscale())
578            if not self.xscale == 'log' or lo > 0:
579                self._scale_xlo = lo
580                self._scale_xhi = hi
581                ax.set_xlim((lo,hi))
582
583            lo, hi = ax.get_ylim()
584            lo, hi = _rescale(lo, hi, step, pt=event.ydata,
585                             scale=ax.get_yscale())
586            if not self.yscale == 'log' or lo > 0:
587                self._scale_ylo = lo
588                self._scale_yhi = hi
589                ax.set_ylim((lo,hi))
590        else:
591             # Check if zoom happens in the axes
592            xdata, ydata = None, None
593            x, y = event.x, event.y
594           
595            for ax in self.axes:
596                insidex, _ = ax.xaxis.contains(event)
597                if insidex:
598                    xdata, _ = ax.transAxes.inverted().transform_point((x, y)) 
599                    #xdata,_ = ax.transAxes.inverse_xy_tup((x,y))
600                insidey, _ = ax.yaxis.contains(event)
601                if insidey:
602                    _, ydata = ax.transAxes.inverted().transform_point((x, y)) 
603                    #_,ydata = ax.transAxes.inverse_xy_tup((x,y))
604            if xdata is not None:
605                lo, hi = ax.get_xlim()
606                lo, hi = _rescale(lo, hi, step, 
607                                  bal=xdata, scale=ax.get_xscale())
608                if not self.xscale == 'log' or lo > 0:
609                    self._scale_xlo = lo
610                    self._scale_xhi = hi
611                    ax.set_xlim((lo, hi))
612            if ydata is not None:
613                lo, hi = ax.get_ylim()
614                lo, hi = _rescale(lo, hi, step, bal=ydata, 
615                                  scale=ax.get_yscale())
616                if not self.yscale=='log' or lo>0:
617                    self._scale_ylo = lo
618                    self._scale_yhi = hi
619                    ax.set_ylim((lo, hi))
620        self.canvas.draw_idle()
621
622    def returnTrans(self):
623        """
624        Return values and labels used by Fit Dialog
625        """
626        return self.xLabel,self.yLabel, self.Avalue, self.Bvalue,\
627                self.ErrAvalue, self.ErrBvalue, self.Chivalue
628   
629    def setTrans(self, xtrans, ytrans): 
630        """
631       
632        :param xtrans: set x transformation on Property dialog
633        :param ytrans: set y transformation on Property dialog
634       
635        """
636        self.prevXtrans = xtrans
637        self.prevYtrans = ytrans
638   
639    def onFitting(self, event): 
640        """
641        when clicking on linear Fit on context menu , display Fitting Dialog
642        """
643        list = {}
644        menu = event.GetEventObject()
645        id = event.GetId()
646        self.set_selected_from_menu(menu, id)
647        plotlist = self.graph.returnPlottable()
648        if self.graph.selected_plottable is not None:
649            for item in plotlist:
650                if item.id == self.graph.selected_plottable:
651                    list[item] = plotlist[item]
652        else:
653            list = plotlist
654        from fitDialog import LinearFit
655       
656        if len(list.keys())>0:
657            first_item = list.keys()[0]
658            dlg = LinearFit(parent=None, plottable=first_item, 
659                            push_data=self.onFitDisplay,
660                            transform=self.returnTrans, 
661                            title='Linear Fit')
662           
663            if (self.xmin != 0.0)and (self.xmax != 0.0)\
664                and(self.xminView != 0.0)and (self.xmaxView != 0.0):
665                dlg.setFitRange(self.xminView, self.xmaxView, 
666                                self.xmin, self.xmax)
667            dlg.ShowModal() 
668           
669    def set_selected_from_menu(self, menu, id):
670        """
671        Set selected_plottable from context menu selection
672       
673        :param menu: context menu item
674        :param id: menu item id
675        """
676        if len(self.plots) < 1:
677            return
678        name = menu.GetHelpString(id)
679        for plot in self.plots.values():
680            if plot.name == name:
681                self.graph.selected_plottable = plot.id
682                break
683           
684    def linear_plottable_fit(self, plot): 
685        """
686            when clicking on linear Fit on context menu , display Fitting Dialog
687           
688            :param plot: PlotPanel owning the graph
689           
690        """
691        from fitDialog import LinearFit
692        if self._fit_dialog is not None:
693            return
694        self._fit_dialog = LinearFit(None, plot, self.onFitDisplay,
695                                      self.returnTrans, -1, 'Linear Fit')
696        # Set the zoom area
697        if self._scale_xhi is not None and self._scale_xlo is not None:
698            self._fit_dialog.set_fit_region(self._scale_xlo, self._scale_xhi)
699        # Register the close event
700        self._fit_dialog.register_close(self._linear_fit_close)
701        # Show a non-model dialog
702        self._fit_dialog.Show() 
703
704    def _linear_fit_close(self):
705        """
706        A fit dialog was closed
707        """
708        self._fit_dialog = None
709       
710
711    def _onProperties(self, event):
712        """
713        when clicking on Properties on context menu ,
714        The Property dialog is displayed
715        The user selects a transformation for x or y value and
716        a new plot is displayed
717        """
718        if self._fit_dialog is not None:
719            self._fit_dialog.Destroy()
720            self._fit_dialog = None
721        list = []
722        list = self.graph.returnPlottable()
723        if len(list.keys()) > 0:
724            first_item = list.keys()[0]
725            if first_item.x != []:
726                from PropertyDialog import Properties
727                dial = Properties(self, -1, 'Properties')
728                dial.setValues(self.prevXtrans, self.prevYtrans, self.viewModel)
729                if dial.ShowModal() == wx.ID_OK:
730                    self.xLabel, self.yLabel,self.viewModel = dial.getValues()
731                    if self.viewModel == "Guinier lny vs x^(2)":
732                        self.xLabel = "x^(2)"
733                        self.yLabel = "ln(y)"
734                        self.viewModel = "--"
735                        dial.setValues(self.xLabel, self.yLabel, self.viewModel)
736                    self._onEVT_FUNC_PROPERTY()
737                dial.Destroy()
738           
739    def set_yscale(self, scale='linear'):
740        """
741        Set the scale on Y-axis
742       
743        :param scale: the scale of y-axis
744       
745        """
746        self.subplot.set_yscale(scale, nonposy='clip')
747        self.yscale = scale
748       
749    def get_yscale(self):
750        """
751       
752        :return: Y-axis scale
753       
754        """
755        return self.yscale
756   
757    def set_xscale(self, scale='linear'):
758        """
759        Set the scale on x-axis
760       
761        :param scale: the scale of x-axis
762       
763        """
764        self.subplot.set_xscale(scale)
765        self.xscale = scale
766       
767    def get_xscale(self):
768        """
769       
770        :return: x-axis scale
771       
772        """
773        return self.xscale
774
775    def SetColor(self, rgbtuple):
776        """
777        Set figure and canvas colours to be the same
778       
779        """
780        if not rgbtuple:
781            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
782        col = [c/255.0 for c in rgbtuple]
783        self.figure.set_facecolor(col)
784        self.figure.set_edgecolor(self.color)
785        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))
786
787    def _onSize(self, event):
788        """
789        """
790        self._resizeflag = True
791
792    def _onIdle(self, evt):
793        """
794        """
795        if self._resizeflag:
796            self._resizeflag = False
797            self._SetSize()
798            self.draw()
799
800    def _SetSize(self, pixels = None):
801        """
802        This method can be called to force the Plot to be a desired size,
803         which defaults to the ClientSize of the panel
804         
805        """
806        if not pixels:
807            pixels = tuple(self.GetClientSize())
808        self.canvas.SetSize(pixels)
809        self.figure.set_size_inches( float( pixels[0] )/self.figure.get_dpi(),
810                                     float( pixels[1] )/self.figure.get_dpi() ) 
811
812    def draw(self):
813        """
814        Where the actual drawing happens
815       
816        """
817        self.figure.canvas.draw_idle()
818       
819       
820    #pick up the legend patch
821    def legend_picker(self, legend, event):
822        return self.legend.legendPatch.contains(event)     
823       
824    def get_loc_label(self):
825        """
826        Associates label to a specific legend location
827        """
828        _labels = {}
829        i = 0
830        _labels['best'] = i
831        i += 1
832        _labels['upper right'] = i
833        i += 1
834        _labels['upper left'] = i
835        i += 1
836        _labels['lower left'] = i
837        i += 1
838        _labels['lower right'] = i
839        i += 1
840        _labels['right'] = i
841        i += 1
842        _labels['center left'] = i
843        i += 1
844        _labels['center right'] = i
845        i += 1
846        _labels['lower center'] = i
847        i += 1
848        _labels['upper center'] = i
849        i += 1
850        _labels['center'] = i
851        return _labels
852       
853    def onSaveImage(self, evt):
854        """
855        Implement save image
856        """
857        self.toolbar.save(evt)
858       
859    def onContextMenu(self, event):
860        """
861        Default context menu for a plot panel
862       
863        """
864        # Slicer plot popup menu
865        id = wx.NewId()
866        slicerpop = wx.Menu()
867        slicerpop.Append(id, '&Save image', 'Save image as PNG')
868        wx.EVT_MENU(self, id, self.onSaveImage)
869       
870        id = wx.NewId()
871        slicerpop.Append(id,'&Printer setup', 'Set image size')
872        wx.EVT_MENU(self, id, self.onPrinterSetup)
873       
874        id = wx.NewId()
875        slicerpop.Append(id,'&Printer Preview', 'Set image size')
876        wx.EVT_MENU(self, id, self.onPrinterPreview)
877   
878        id = wx.NewId()
879        slicerpop.Append(id, '&Print image', 'Print image ')
880        wx.EVT_MENU(self, id, self.onPrint)
881       
882        id = wx.NewId()
883        slicerpop.Append(id, '&Copy', 'Copy to the clipboard')
884        wx.EVT_MENU(self, id, self.OnCopyFigureMenu)
885
886        #id = wx.NewId()
887        #slicerpop.Append(id, '&Load 1D data file')
888        #wx.EVT_MENU(self, id, self._onLoad1DData)
889       
890        id = wx.NewId()
891        slicerpop.AppendSeparator()
892        slicerpop.Append(id, '&Properties')
893        wx.EVT_MENU(self, id, self._onProperties)
894       
895        id = wx.NewId()
896        slicerpop.AppendSeparator()
897        slicerpop.Append(id, '&Linear Fit')
898        wx.EVT_MENU(self, id, self.onFitting)
899       
900        id = wx.NewId()
901        slicerpop.AppendSeparator()
902        slicerpop.Append(id, '&Toggle Legned On/Off', 'Toggle Legend On/Off')
903        wx.EVT_MENU(self, id, self.onLegend)
904       
905        loc_menu = wx.Menu()
906        for label in self._loc_labels:
907            id = wx.NewId()
908            loc_menu.Append(id, str(label), str(label))
909            wx.EVT_MENU(self, id, self.onChangeLegendLoc)
910        id = wx.NewId()
911        slicerpop.AppendMenu(id, '&Modify Legend Location', loc_menu)
912       
913        id = wx.NewId()
914        slicerpop.Append(id, '&Modify Y Axis Label')
915        wx.EVT_MENU(self, id, self._on_yaxis_label)
916        id = wx.NewId()
917        slicerpop.Append(id, '&Modify X Axis Label')
918        wx.EVT_MENU(self, id, self._on_xaxis_label)
919       
920        try:
921            # mouse event
922            pos_evt = event.GetPosition()
923            pos = self.ScreenToClient(pos_evt)
924        except:
925            # toolbar event
926            pos_x, pos_y = self.toolbar.GetPositionTuple()
927            pos = (pos_x, pos_y + 5)
928           
929        self.PopupMenu(slicerpop, pos)
930       
931    def onToolContextMenu(self, event):
932        """
933        ContextMenu from toolbar
934       
935        :param event: toolbar event
936        """
937        # reset postion
938        self.position = None
939        if self.graph.selected_plottable != None:
940            self.graph.selected_plottable = None
941       
942        self.onContextMenu(event)
943       
944    def onLegend(self, event):
945        """
946        Toggles whether legend is visible/not visible
947        """
948       
949        if self.legend_on:
950            for ax in self.axes:
951                self.remove_legend(ax)
952        else:
953            # sort them by labels
954            handles, labels = self.subplot.get_legend_handles_labels()
955            hl = sorted(zip(handles, labels),
956                        key=operator.itemgetter(1))
957            handles2, labels2 = zip(*hl)
958            self.line_collections_list = handles2
959            self.legend = self.subplot.legend(handles2, labels2, 
960                                prop=FontProperties(size=10), numpoints=1,
961                                handletextsep=.05, loc=self.legendLoc)
962            if self.legend != None:
963                self.legend.set_picker(self.legend_picker) 
964                self.legend.set_axes(self.subplot) 
965       
966        self.subplot.figure.canvas.draw_idle()       
967        self.legend_on = not self.legend_on
968   
969    def onChangeLegendLoc(self, event):
970        """
971        Changes legend loc based on user input
972        """
973        menu = event.GetEventObject()
974        id = event.GetId()
975        label =  menu.GetLabel(id)
976       
977        self.legendLoc = label
978        self.legend_pos_loc = None
979        # sort them by labels
980        handles, labels = self.subplot.get_legend_handles_labels()
981        hl = sorted(zip(handles, labels),
982                    key=operator.itemgetter(1))
983        handles2, labels2 = zip(*hl)
984        self.line_collections_list = handles2
985        self.legend = self.subplot.legend(handles2, labels2, 
986                            prop=FontProperties(size=10),  numpoints=1,
987                            handletextsep=.05, loc=self.legendLoc)
988        if self.legend != None:
989                self.legend.set_picker(self.legend_picker) 
990                self.legend.set_axes(self.subplot) 
991        self.subplot.figure.canvas.draw_idle() 
992        #self._onEVT_FUNC_PROPERTY()
993       
994    def remove_legend(self, ax=None):
995        """
996        Remove legend for ax or the current axes.
997        """
998        from pylab import gca, draw
999        if ax is None:
1000            ax = gca()
1001        ax.legend_ = None
1002        #draw()
1003       
1004    def _on_addtext(self, event): 
1005        """
1006        Allows you to add text to the plot
1007        """
1008        pos_x = 0
1009        pos_y = 0
1010        if self.position != None:
1011            pos_x, pos_y = self.position
1012        else:
1013            pos_x, pos_y = 0.01, 1.00
1014
1015        textdial = TextDialog(None, -1, 'Add Custom Text')
1016        if textdial.ShowModal() == wx.ID_OK:
1017            try:
1018                FONT = FontProperties()
1019                label = textdial.getText()
1020                xpos = pos_x
1021                ypos = pos_y
1022                font = FONT.copy()
1023                font.set_size(textdial.getSize())
1024                font.set_family(textdial.getFamily())
1025                font.set_style(textdial.getStyle())
1026                font.set_weight(textdial.getWeight())
1027                colour = textdial.getColor()
1028                if len(label) > 0 and xpos > 0 and ypos > 0:
1029                    new_text = self.subplot.text(str(xpos), str(ypos), label, 
1030                                                   fontproperties=font,
1031                                                   color=colour)
1032                    self.textList.append(new_text)
1033                    self.subplot.figure.canvas.draw_idle() 
1034            except:
1035                if self.parent != None:
1036                    from sans.guiframe.events import StatusEvent
1037                    msg= "Add Text: Error. Check your property values..."
1038                    wx.PostEvent(self.parent, StatusEvent(status = msg ))
1039                else:
1040                    raise
1041        textdial.Destroy()
1042        #Have a pop up box come up for user to type in the
1043        #text that they want to add...then create text Plottable
1044        #based on this and plot it at user designated coordinates
1045
1046    def onGridOnOff(self, event): 
1047        """
1048        Allows ON/OFF Grid
1049        """
1050        if self.grid_on:
1051            self.grid_on = False
1052        else:
1053            self.grid_on = True
1054        self.subplot.figure.canvas.draw_idle()
1055       
1056    def _on_xaxis_label(self, event): 
1057        """
1058        Allows you to add text to the plot
1059        """
1060        xaxis_label, xaxis_unit, xaxis_font, xaxis_color,\
1061                     is_ok = self._on_axis_label(axis='x')
1062        if not is_ok:
1063            return
1064       
1065        self.xaxis_label = xaxis_label
1066        self.xaxis_unit = xaxis_unit
1067        self.xaxis_font = xaxis_font
1068        self.xaxis_color = xaxis_color
1069       
1070        if self.data != None:
1071            # 2D
1072            self.xaxis(self.xaxis_label, self.xaxis_unit,\
1073                        self.xaxis_font, self.xaxis_color)
1074            self.subplot.figure.canvas.draw_idle()
1075        else:
1076            # 1D
1077            self._check_zoom_plot()
1078   
1079    def _check_zoom_plot(self): 
1080        """
1081        Check the zoom range and plot (1D only)
1082        """
1083        xlo, xhi = self.subplot.get_xlim()
1084        ylo, yhi = self.subplot.get_ylim()
1085        ## Set the view scale for all plots
1086        self._onEVT_FUNC_PROPERTY(False)
1087        # Check if zoomed
1088        toolbar_zoomed = self.toolbar.GetToolEnabled(self.toolbar._NTB2_BACK)
1089        if self.is_zoomed or toolbar_zoomed:
1090            # Recover the x,y limits
1091            self.subplot.set_xlim((xlo, xhi))     
1092            self.subplot.set_ylim((ylo, yhi)) 
1093                   
1094    def _on_yaxis_label(self, event): 
1095        """
1096        Allows you to add text to the plot
1097        """
1098        yaxis_label, yaxis_unit, yaxis_font, yaxis_color,\
1099                        is_ok = self._on_axis_label(axis='y')
1100        if not is_ok:
1101            return
1102       
1103        self.yaxis_label = yaxis_label
1104        self.yaxis_unit = yaxis_unit
1105        self.yaxis_font = yaxis_font
1106        self.yaxis_color = yaxis_color
1107
1108        if self.data != None:
1109            # 2D
1110            self.yaxis(self.yaxis_label, self.yaxis_unit,\
1111                        self.yaxis_font, self.yaxis_color)
1112            self.subplot.figure.canvas.draw_idle()
1113        else:
1114            # 1D
1115            self._check_zoom_plot()
1116           
1117       
1118    def _on_axis_label(self, axis='x'):
1119        """
1120        Modify axes labels
1121       
1122        :param axis: x or y axis [string]
1123        """
1124        is_ok = True
1125        title = 'Modify %s axis label'% axis
1126        font = 'serif'
1127        colour = 'black'
1128        if axis == 'x':
1129            label = self.xaxis_label
1130            unit = self.xaxis_unit
1131        else:
1132            label = self.yaxis_label
1133            unit = self.yaxis_unit
1134        textdial = TextDialog(None, -1, title, label, unit) 
1135        if textdial.ShowModal() == wx.ID_OK:
1136            try:
1137                FONT = FontProperties()
1138                font = FONT.copy()
1139                font.set_size(textdial.getSize())
1140                font.set_family(textdial.getFamily())
1141                font.set_style(textdial.getStyle())
1142                font.set_weight(textdial.getWeight())
1143                unit = textdial.getUnit()
1144                colour = textdial.getColor()
1145                label_temp = textdial.getText()
1146                if label_temp.count("\%s"% "\\") > 0:
1147                    if self.parent != None:
1148                        from sans.guiframe.events import StatusEvent
1149                        msg= "Add Label: Error. Can not use double '\\' "
1150                        msg += "characters..."
1151                        wx.PostEvent(self.parent, StatusEvent(status = msg ))
1152                else:
1153                    label = label_temp
1154            except:
1155                if self.parent != None:
1156                    from sans.guiframe.events import StatusEvent
1157                    msg= "Add Label: Error. Check your property values..."
1158                    wx.PostEvent(self.parent, StatusEvent(status = msg ))
1159                else:
1160                    pass
1161        else:
1162            is_ok = False
1163        textdial.Destroy()
1164        return label, unit, font, colour, is_ok
1165       
1166    def _on_removetext(self, event):
1167        """
1168        Removes all text from the plot.
1169        Eventually, add option to remove specific text boxes
1170        """
1171        num_text = len(self.textList)
1172        if num_text < 1:
1173            if self.parent != None:
1174                from sans.guiframe.events import StatusEvent
1175                msg= "Remove Text: Nothing to remove.  "
1176                wx.PostEvent(self.parent, StatusEvent(status = msg ))
1177            else:
1178                raise
1179            return
1180        txt = self.textList[num_text-1]
1181        try:
1182            text_remove = txt.get_text()
1183            txt.remove()
1184            if self.parent != None:
1185                from sans.guiframe.events import StatusEvent
1186                msg= "Removed Text: '%s'. " % text_remove
1187                wx.PostEvent(self.parent, StatusEvent(status = msg ))
1188        except:
1189            if self.parent != None:
1190                from sans.guiframe.events import StatusEvent
1191                msg= "Remove Text: Error occurred. "
1192                wx.PostEvent(self.parent, StatusEvent(status = msg ))
1193            else:
1194                raise
1195        self.textList.remove(txt)
1196           
1197        self.subplot.figure.canvas.draw_idle() 
1198
1199    def properties(self, prop):
1200        """
1201        Set some properties of the graph.
1202        The set of properties is not yet determined.
1203       
1204        """
1205        # The particulars of how they are stored and manipulated (e.g., do
1206        # we want an inventory internally) is not settled.  I've used a
1207        # property dictionary for now.
1208        #
1209        # How these properties interact with a user defined style file is
1210        # even less clear.
1211
1212        # Properties defined by plot
1213        self.subplot.set_xlabel(r"$%s$" % prop["xlabel"])
1214        self.subplot.set_ylabel(r"$%s$" % prop["ylabel"])
1215        self.subplot.set_title(prop["title"])
1216
1217        # Properties defined by user
1218        #self.axes.grid(True)
1219
1220    def clear(self):
1221        """Reset the plot"""
1222        # TODO: Redraw is brutal.  Render to a backing store and swap in
1223        # TODO: rather than redrawing on the fly.
1224        self.subplot.clear()
1225        self.subplot.hold(True)
1226   
1227    def render(self):
1228        """Commit the plot after all objects are drawn"""
1229        # TODO: this is when the backing store should be swapped in.
1230        if self.legend_on:         
1231            ax = self.subplot
1232            ax.texts = self.textList
1233            try:
1234                handles, labels = ax.get_legend_handles_labels()
1235                # sort them by labels
1236                hl = sorted(zip(handles, labels),
1237                            key=operator.itemgetter(1))
1238                handles2, labels2 = zip(*hl)
1239                self.line_collections_list = handles2
1240                self.legend = ax.legend(handles2, labels2,  numpoints=1,
1241                                prop=FontProperties(size=10), 
1242                                handletextsep=.05, loc=self.legendLoc)
1243                if self.legend != None:
1244                    self.legend.set_picker(self.legend_picker)
1245                    self.legend.set_axes(self.subplot) 
1246               
1247            except:
1248                self.legend = ax.legend(prop=FontProperties(size=10),  numpoints=1,
1249                                handletextsep=.05, loc=self.legendLoc)
1250                 
1251    def xaxis(self, label, units, font=None, color='black'):
1252        """xaxis label and units.
1253       
1254        Axis labels know about units.
1255       
1256        We need to do this so that we can detect when axes are not
1257        commesurate.  Currently this is ignored other than for formatting
1258        purposes.
1259       
1260        """
1261        if units != "": 
1262            label = label + " (" + units + ")"
1263        if label.count("{") > 0 and label.count("$") < 2:
1264            label = '$' + label + '$'
1265        if font:
1266            self.subplot.set_xlabel(label, fontproperties=font, color=color)
1267        else:
1268            self.subplot.set_xlabel(label, color=color)
1269        pass
1270   
1271    def yaxis(self, label, units, font=None, color='black'):
1272        """yaxis label and units."""
1273        if units != "": 
1274            label = label + " (" + units + ")"
1275        if label.count("{") > 0 and label.count("$") < 2:
1276            label = '$' + label + '$'
1277        if font:
1278            self.subplot.set_ylabel(label, fontproperties=font, color=color)
1279        else:
1280            self.subplot.set_ylabel(label, color=color)
1281        pass
1282
1283    def _connect_to_xlim(self,callback):
1284        """Bind the xlim change notification to the callback"""
1285        def process_xlim(axes):
1286            lo, hi = subplot.get_xlim()
1287            callback(lo, hi)
1288        self.subplot.callbacks.connect('xlim_changed', process_xlim)
1289   
1290    #def connect(self,trigger,callback):
1291    #    print "PlotPanel.connect???"
1292    #    if trigger == 'xlim': self._connect_to_xlim(callback)
1293
1294    def interactive_points(self, x, y, dx=None, dy=None, name='', color=0,
1295                           symbol=0, markersize=5, id=None, label=None, hide_error=False):
1296        """Draw markers with error bars"""
1297        self.subplot.set_yscale('linear')
1298        self.subplot.set_xscale('linear')
1299        if id is None:
1300            id = name
1301        from plottable_interactor import PointInteractor
1302        p = PointInteractor(self, self.subplot, zorder=3, id=id)
1303        if p.markersize != None:
1304            markersize = p.markersize
1305        p.points(x, y, dx=dx, dy=dy, color=color, symbol=symbol, markersize=markersize, label=label,
1306                 hide_error=hide_error)
1307       
1308        self.subplot.set_yscale(self.yscale,nonposy='clip')
1309        self.subplot.set_xscale(self.xscale)
1310       
1311   
1312    def interactive_curve(self,x,y,dy=None,name='',color=0,symbol=0,id=None,label=None):
1313        """Draw markers with error bars"""
1314        self.subplot.set_yscale('linear')
1315        self.subplot.set_xscale('linear')
1316        if id is None:
1317            id = name
1318        from plottable_interactor import PointInteractor
1319        p = PointInteractor(self, self.subplot, zorder=4, id=id)
1320        p.curve(x, y, dy=dy, color=color, symbol=symbol, label=label)
1321       
1322        self.subplot.set_yscale(self.yscale,nonposy='clip')
1323        self.subplot.set_xscale(self.xscale)
1324       
1325
1326    def plottable_selected(self, id):
1327        """
1328        Called to register a plottable as selected
1329        """
1330        #TODO: check that it really is in the list of plottables
1331        self.graph.selected_plottable = id
1332
1333    def points(self, x, y, dx=None, dy=None,
1334               color=0, symbol=0, marker_size=5, label=None,id=None,  hide_error=False):
1335        """Draw markers with error bars"""
1336        #self.subplot.set_yscale('linear')
1337        #self.subplot.set_xscale('linear')
1338       
1339        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
1340        if dx != None and type(dx) == type(()):
1341            dx = nx.vstack((x-dx[0], dx[1]-x)).transpose()
1342        if dy != None and type(dy) == type(()):
1343            dy = nx.vstack((y-dy[0], dy[1]-y)).transpose()
1344        if dx == None and dy == None:
1345            h = self.subplot.plot(x, y, color=self._color(color),
1346                                   marker=self._symbol(symbol), markersize=marker_size,
1347                                   linestyle='',
1348                                   label=label)
1349           
1350        else:
1351            col = self._color(color)
1352            if hide_error:
1353                h = self.subplot.plot(x, y, color=col,
1354                                   marker=self._symbol(symbol), markersize=marker_size,
1355                                   linestyle='',
1356                                   label=label)
1357               
1358            else:
1359                h= self.subplot.errorbar(x, y, yerr=dy, xerr=None,
1360                                  ecolor=col, capsize=2, linestyle='', 
1361                                  barsabove=False,
1362                                  mec=col, mfc=col,
1363                                  marker=self._symbol(symbol),
1364                                  markersize=marker_size,
1365                                  lolims=False, uplims=False,
1366                                  xlolims=False, xuplims=False, label=label)
1367       
1368        self.subplot.set_yscale(self.yscale, nonposy='clip')
1369        self.subplot.set_xscale(self.xscale)
1370       
1371    def _onToggleScale(self, event):
1372        """
1373        toggle axis and replot image
1374       
1375        """
1376        zmin_2D_temp = self.zmin_2D
1377        zmax_2D_temp = self.zmax_2D
1378        if self.scale == 'log':
1379            self.scale = 'linear'
1380            if not self.zmin_2D is None:
1381                zmin_2D_temp = math.exp(self.zmin_2D)
1382            if not self.zmax_2D is None:
1383                zmax_2D_temp = math.exp(self.zmax_2D)
1384        else:
1385            self.scale = 'log'
1386            if not self.zmin_2D is None:
1387                # min log value: no log(negative)
1388                if self.zmin_2D <= 0:
1389                    zmin_2D_temp = -32
1390                else:
1391                    zmin_2D_temp = math.log(self.zmin_2D)
1392            if not self.zmax_2D is None:
1393                zmax_2D_temp = math.log(self.zmax_2D)
1394                 
1395        self.image(data=self.data, qx_data=self.qx_data, 
1396                   qy_data=self.qy_data, xmin=self.xmin_2D, 
1397                   xmax=self.xmax_2D,
1398                   ymin=self.ymin_2D, ymax=self.ymax_2D, 
1399                   cmap=self.cmap, zmin=zmin_2D_temp,
1400                   zmax=zmax_2D_temp)
1401     
1402    def image(self, data, qx_data, qy_data, xmin, xmax, ymin, ymax, 
1403              zmin, zmax, color=0, symbol=0, markersize=0, label='data2D', cmap=DEFAULT_CMAP):
1404        """
1405        Render the current data
1406       
1407        """
1408        #Re-adjust colorbar
1409        self.figure.subplots_adjust(left=0.2, right=.8, bottom=.2)       
1410        self.data = data
1411        self.qx_data = qx_data
1412        self.qy_data = qy_data
1413        self.xmin_2D = xmin
1414        self.xmax_2D = xmax
1415        self.ymin_2D = ymin
1416        self.ymax_2D = ymax
1417        self.zmin_2D = zmin
1418        self.zmax_2D = zmax
1419        c = self._color(color)
1420        # If we don't have any data, skip.
1421        if self.data == None:
1422            return
1423        if self.data.ndim == 1:   
1424            output = self._build_matrix()
1425        else:
1426            output = copy.deepcopy(self.data)
1427        # check scale
1428        if self.scale == 'log':
1429            try:
1430                if  self.zmin_2D  <= 0  and  len(output[output > 0]) > 0:
1431                    zmin_temp = self.zmin_2D
1432                    output[output>0] = numpy.log(output[output>0])
1433                    #In log scale Negative values are not correct in general
1434                    #output[output<=0] = math.log(numpy.min(output[output>0]))
1435                elif self.zmin_2D  <= 0:
1436                    zmin_temp = self.zmin_2D
1437                    output[output>0] = numpy.zeros(len(output)) 
1438                    output[output<=0] = -32       
1439                else: 
1440                    zmin_temp = self.zmin_2D
1441                    output[output>0] = numpy.log(output[output>0])
1442                    #In log scale Negative values are not correct in general
1443                    #output[output<=0] = math.log(numpy.min(output[output>0]))
1444            except:
1445                #Too many problems in 2D plot with scale
1446                pass
1447                     
1448        else:
1449            zmin_temp = self.zmin_2D
1450        self.cmap = cmap
1451        im = self.subplot.imshow(output, interpolation='nearest', 
1452                                 origin='lower',
1453                                 vmin=zmin_temp, vmax=self.zmax_2D,
1454                                 cmap=self.cmap, 
1455                                 extent=(self.xmin_2D, self.xmax_2D,
1456                                            self.ymin_2D, self.ymax_2D))
1457       
1458        #Add color bar and its label
1459        #TODO: make the size and location interactive to the image plot.
1460        cbax = self.subplot.figure.add_axes([0.84,0.2,0.02,0.7])
1461        cb = self.subplot.figure.colorbar(im, cax=cbax)
1462        cb.update_bruteforce(im)
1463        cb.set_label(self.scale)
1464        self.subplot.figure.canvas.draw_idle()
1465   
1466    def _build_matrix(self):
1467        """
1468        Build a matrix for 2d plot from a vector
1469        Returns a matrix (image) with ~ square binning
1470        Requirement: need 1d array formats of
1471        self.data, self.qx_data, and self.qy_data
1472        where each one corresponds to z, x, or y axis values
1473       
1474        """
1475        # No qx or qy given in a vector format
1476        if self.qx_data == None or self.qy_data == None \
1477                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1:
1478            # do we need deepcopy here?
1479            return copy.deepcopy(self.data)
1480     
1481        # maximum # of loops to fillup_pixels
1482        # otherwise, loop could never stop depending on data
1483        max_loop = 1
1484        # get the x and y_bin arrays.
1485        self._get_bins()
1486        # set zero to None
1487       
1488        #Note: Can not use scipy.interpolate.Rbf:
1489        # 'cause too many data points (>10000)<=JHC.
1490        # 1d array to use for weighting the data point averaging
1491        #when they fall into a same bin.         
1492        weights_data  = numpy.ones([self.data.size])
1493        # get histogram of ones w/len(data); this will provide
1494        #the weights of data on each bins
1495        weights, xedges, yedges = numpy.histogram2d(x=self.qy_data, 
1496                                                    y=self.qx_data,
1497                                                bins=[self.y_bins, self.x_bins],
1498                                                    weights=weights_data)
1499        # get histogram of data, all points into a bin in a way of summing
1500        image, xedges, yedges = numpy.histogram2d(x=self.qy_data, 
1501                                                  y=self.qx_data,
1502                                                bins=[self.y_bins, self.x_bins],
1503                                                weights=self.data)
1504        # Now, normalize the image by weights only for weights>1:
1505        # If weight == 1, there is only one data point in the bin so
1506        # that no normalization is required.
1507        image[weights>1] = image[weights>1]/weights[weights>1]
1508        # Set image bins w/o a data point (weight==0) as None (was set to zero
1509        # by histogram2d.)
1510        image[weights==0] = None
1511
1512        # Fill empty bins with 8 nearest neighbors only when at least
1513        #one None point exists
1514        loop = 0
1515       
1516        # do while loop until all vacant bins are filled up up
1517        #to loop = max_loop
1518        while(not(numpy.isfinite(image[weights == 0])).all()):
1519            if loop >= max_loop: # this protects never-ending loop
1520                break
1521            image = self._fillup_pixels(image=image, weights=weights)
1522            loop += 1
1523               
1524        return image
1525   
1526    def _get_bins(self): 
1527        """
1528        get bins
1529        set x_bins and y_bins into self, 1d arrays of the index with
1530        ~ square binning
1531        Requirement: need 1d array formats of
1532        self.qx_data, and self.qy_data
1533        where each one corresponds to  x, or y axis values
1534       
1535        """
1536        # No qx or qy given in a vector format
1537        if self.qx_data == None or self.qy_data == None \
1538                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1:
1539            # do we need deepcopy here?
1540            return copy.deepcopy(self.data)
1541       
1542        # find max and min values of qx and qy
1543        xmax = numpy.max(self.qx_data)
1544        xmin = numpy.min(self.qx_data)
1545        ymax = numpy.max(self.qy_data)
1546        ymin = numpy.min(self.qy_data)
1547       
1548        # calculate the range of qx and qy: this way, it is a little
1549        # more independent
1550        x_size = xmax - xmin
1551        y_size = ymax - ymin
1552       
1553        # estimate the # of pixels on each axes 
1554        npix_y = int(math.floor(math.sqrt(len(self.qy_data))))
1555        npix_x = int(math.floor(len(self.qy_data) / npix_y))
1556
1557        # bin size: x- & y-directions     
1558        xstep = x_size / (npix_x - 1)
1559        ystep = y_size / (npix_y - 1)
1560
1561        # max and min taking account of the bin sizes
1562        xmax = xmax + xstep / 2
1563        xmin = xmin - xstep / 2
1564        ymax = ymax + ystep / 2
1565        ymin = ymin - ystep / 2
1566       
1567        # store x and y bin centers in q space
1568        x_bins = numpy.arange(xmin, xmax + xstep / 10, xstep)
1569        y_bins = numpy.arange(ymin, ymax + ystep / 10, ystep) 
1570     
1571        #set x_bins and y_bins
1572        self.x_bins = x_bins
1573        self.y_bins = y_bins
1574
1575    def _fillup_pixels(self, image=None, weights=None): 
1576        """
1577        Fill z values of the empty cells of 2d image matrix
1578        with the average over up-to next nearest neighbor points
1579       
1580        :param image: (2d matrix with some zi = None)
1581       
1582        :return: image (2d array )
1583       
1584        :TODO: Find better way to do for-loop below
1585       
1586        """
1587        # No image matrix given
1588        if image == None or numpy.ndim(image) != 2 \
1589                or numpy.isfinite(image).all() \
1590                or weights == None:
1591            return image
1592        # Get bin size in y and x directions
1593        len_y = len(image)
1594        len_x = len(image[1])
1595        temp_image = numpy.zeros([len_y, len_x])
1596        weit = numpy.zeros([len_y, len_x])
1597        # do for-loop for all pixels
1598        for n_y in range(len(image)):
1599            for n_x in range(len(image[1])):
1600                # find only null pixels
1601                if weights[n_y][n_x] > 0 or numpy.isfinite(image[n_y][n_x]):
1602                    continue
1603                else:
1604                    # find 4 nearest neighbors
1605                    # check where or not it is at the corner
1606                    if n_y != 0 and numpy.isfinite(image[n_y-1][n_x]):
1607                        temp_image[n_y][n_x] += image[n_y-1][n_x]
1608                        weit[n_y][n_x] += 1
1609                    if n_x != 0 and numpy.isfinite(image[n_y][n_x-1]):
1610                        temp_image[n_y][n_x] += image[n_y][n_x-1]
1611                        weit[n_y][n_x] += 1
1612                    if n_y != len_y -1 and numpy.isfinite(image[n_y+1][n_x]):
1613                        temp_image[n_y][n_x] += image[n_y+1][n_x] 
1614                        weit[n_y][n_x] += 1
1615                    if n_x != len_x -1 and numpy.isfinite(image[n_y][n_x+1]):
1616                        temp_image[n_y][n_x] += image[n_y][n_x+1]
1617                        weit[n_y][n_x] += 1
1618                    # go 4 next nearest neighbors when no non-zero
1619                    # neighbor exists
1620                    #if weight[n_y][n_x] == 0:
1621                    if n_y != 0 and n_x != 0 and\
1622                         numpy.isfinite(image[n_y-1][n_x-1]):
1623                        temp_image[n_y][n_x] += image[n_y-1][n_x-1]
1624                        weit[n_y][n_x] += 1
1625                    if n_y != len_y -1 and n_x != 0 and \
1626                        numpy.isfinite(image[n_y+1][n_x-1]):
1627                        temp_image[n_y][n_x] += image[n_y+1][n_x-1]
1628                        weit[n_y][n_x] += 1
1629                    if n_y != len_y and n_x != len_x -1 and \
1630                        numpy.isfinite(image[n_y-1][n_x+1]):
1631                        temp_image[n_y][n_x] += image[n_y-1][n_x+1] 
1632                        weit[n_y][n_x] += 1
1633                    if n_y != len_y -1 and n_x != len_x -1 and \
1634                        numpy.isfinite(image[n_y+1][n_x+1]):
1635                        temp_image[n_y][n_x] += image[n_y+1][n_x+1]
1636                        weit[n_y][n_x] += 1
1637
1638        # get it normalized
1639        ind = (weit > 0)
1640        image[ind] = temp_image[ind] / weit[ind]
1641       
1642        return image
1643         
1644    def curve(self, x, y, dy=None, color=0, symbol=0, label=None):
1645        """Draw a line on a graph, possibly with confidence intervals."""
1646        c = self._color(color)
1647        self.subplot.set_yscale('linear')
1648        self.subplot.set_xscale('linear')
1649       
1650        hlist = self.subplot.plot(x, y, color=c, marker='', 
1651                                  linestyle='-', label=label)
1652        self.subplot.set_yscale(self.yscale)
1653        self.subplot.set_xscale(self.xscale)
1654
1655    def _color(self, c):
1656        """Return a particular colour"""
1657        return self.colorlist[c%len(self.colorlist)]
1658
1659    def _symbol(self, s):
1660        """Return a particular symbol"""
1661        return self.symbollist[s%len(self.symbollist)]
1662   
1663    def _replot(self, remove_fit=False):
1664        """
1665        Rescale the plottables according to the latest
1666        user selection and update the plot
1667       
1668        :param remove_fit: Fit line will be removed if True
1669       
1670        """
1671        self.graph.reset_scale()
1672        self._onEVT_FUNC_PROPERTY(remove_fit=remove_fit)
1673        #TODO: Why do we have to have the following line?
1674        self.fit_result.reset_view()
1675        self.graph.render(self)
1676        self.subplot.figure.canvas.draw_idle()
1677   
1678    def _onEVT_FUNC_PROPERTY(self, remove_fit=True):
1679        """
1680        Receive the x and y transformation from myDialog,
1681        Transforms x and y in View
1682        and set the scale   
1683        """ 
1684        # The logic should be in the right order
1685        # Delete first, and then get the whole list...
1686        if remove_fit:
1687            self.graph.delete(self.fit_result)
1688           
1689        list = []
1690        list = self.graph.returnPlottable()
1691        # Changing the scale might be incompatible with
1692        # currently displayed data (for instance, going
1693        # from ln to log when all plotted values have
1694        # negative natural logs).
1695        # Go linear and only change the scale at the end.
1696        self.set_xscale("linear")
1697        self.set_yscale("linear")
1698        _xscale = 'linear'
1699        _yscale = 'linear'
1700        for item in list:
1701            item.setLabel(self.xLabel, self.yLabel)
1702           
1703            # control axis labels from the panel itself
1704            yname, yunits = item.get_yaxis()
1705            if self.yaxis_label != None:
1706                yname = self.yaxis_label
1707                yunits = self.yaxis_unit
1708            else:
1709                self.yaxis_label = yname
1710                self.yaxis_unit = yunits
1711            xname, xunits = item.get_xaxis()
1712            if self.xaxis_label != None:
1713                xname = self.xaxis_label
1714                xunits = self.xaxis_unit
1715            else:
1716                self.xaxis_label = xname
1717                self.xaxis_unit = xunits
1718            # Goes through all possible scales   
1719            if(self.xLabel == "x"):
1720                item.transformX(transform.toX, transform.errToX)
1721                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
1722            if(self.xLabel == "x^(2)"):
1723                item.transformX(transform.toX2, transform.errToX2)
1724                xunits = convertUnit(2,xunits) 
1725                self.graph._xaxis_transformed("%s^{2}" % xname, "%s" % xunits)
1726            if(self.xLabel == "x^(4)"):
1727                item.transformX(transform.toX4, transform.errToX4)
1728                xunits = convertUnit(4,xunits) 
1729                self.graph._xaxis_transformed("%s^{4}" % xname, "%s" % xunits)
1730            if(self.xLabel == "ln(x)"):
1731                item.transformX(transform.toLogX,transform.errToLogX)
1732                self.graph._xaxis_transformed("\ln\\ %s" % xname, "%s" % xunits) 
1733            if(self.xLabel == "log10(x)"):
1734                item.transformX(transform.toX_pos, transform.errToX_pos)
1735                _xscale = 'log'
1736                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
1737            if(self.xLabel == "log10(x^(4))"):
1738                item.transformX(transform.toX4, transform.errToX4)
1739                xunits = convertUnit(4,xunits) 
1740                self.graph._xaxis_transformed("%s^{4}" % xname, "%s" % xunits)
1741                _xscale = 'log'
1742            if(self.yLabel == "ln(y)"):
1743                item.transformY(transform.toLogX, transform.errToLogX)
1744                self.graph._yaxis_transformed("\ln\\ %s" % yname, "%s" % yunits)
1745            if(self.yLabel == "y"):
1746                item.transformY(transform.toX, transform.errToX)
1747                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
1748            if(self.yLabel == "log10(y)"): 
1749                item.transformY(transform.toX_pos, transform.errToX_pos)
1750                _yscale = 'log' 
1751                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
1752            if(self.yLabel == "y^(2)"):
1753                item.transformY(transform.toX2, transform.errToX2)   
1754                yunits = convertUnit(2, yunits) 
1755                self.graph._yaxis_transformed("%s^{2}" % yname, "%s" % yunits)
1756            if(self.yLabel == "1/y"):
1757                item.transformY(transform.toOneOverX, transform.errOneOverX)
1758                yunits = convertUnit(-1, yunits)
1759                self.graph._yaxis_transformed("1/%s" % yname, "%s" % yunits)
1760            if(self.yLabel == "y*x^(4)"):
1761                item.transformY(transform.toYX4, transform.errToYX4)
1762                xunits = convertUnit(4, self.xaxis_unit) 
1763                self.graph._yaxis_transformed("%s \ \ %s^{4}" % (yname,xname), 
1764                                               "%s%s" % (yunits,xunits))
1765            if(self.yLabel == "1/sqrt(y)"):
1766                item.transformY(transform.toOneOverSqrtX,
1767                                transform.errOneOverSqrtX )
1768                yunits = convertUnit(-0.5, yunits)
1769                self.graph._yaxis_transformed("1/\sqrt{%s}" %yname, "%s" % yunits)
1770            if(self.yLabel == "ln(y*x)"):
1771                item.transformY(transform.toLogXY, transform.errToLogXY)
1772                self.graph._yaxis_transformed("\ln (%s \ \ %s)" % (yname,xname),
1773                                                "%s%s" % (yunits, self.xaxis_unit))
1774            if(self.yLabel == "ln(y*x^(2))"):
1775                item.transformY( transform.toLogYX2, transform.errToLogYX2) 
1776                xunits = convertUnit(2, self.xaxis_unit) 
1777                self.graph._yaxis_transformed("\ln (%s \ \ %s^{2})" % (yname,xname), 
1778                                               "%s%s" % (yunits,xunits))
1779            if(self.yLabel == "ln(y*x^(4))"):
1780                item.transformY(transform.toLogYX4, transform.errToLogYX4)
1781                xunits = convertUnit(4, self.xaxis_unit) 
1782                self.graph._yaxis_transformed("\ln (%s \ \ %s^{4})" % (yname,xname), 
1783                                               "%s%s" % (yunits,xunits))
1784            if(self.yLabel == "log10(y*x^(4))"):
1785                item.transformY(transform.toYX4, transform.errToYX4)
1786                xunits = convertUnit(4, self.xaxis_unit) 
1787                _yscale = 'log' 
1788                self.graph._yaxis_transformed("%s \ \ %s^{4}" % (yname,xname), 
1789                                               "%s%s" % (yunits,xunits))
1790            if(self.viewModel == "Guinier lny vs x^(2)"):
1791                item.transformX(transform.toX2, transform.errToX2)
1792                xunits = convertUnit(2, xunits) 
1793                self.graph._xaxis_transformed("%s^{2}" % xname,  "%s" % xunits)
1794                item.transformY(transform.toLogX,transform.errToLogX )
1795                self.graph._yaxis_transformed("\ln\ \ %s" % yname,  "%s" % yunits)
1796            item.transformView()
1797 
1798        # set new label and units
1799        yname = self.graph.prop["ylabel"]
1800        yunits = ''
1801        xname = self.graph.prop["xlabel"]
1802        xunits = ''
1803        #self.yaxis_label = yname
1804        #self.yaxis_unit = yunits
1805        #self.xaxis_label = xname
1806        #self.xaxis_unit = xunits
1807               
1808        self.resetFitView()   
1809        self.prevXtrans = self.xLabel
1810        self.prevYtrans = self.yLabel 
1811        self.graph.render(self)
1812        self.set_xscale(_xscale)
1813        self.set_yscale(_yscale)
1814       
1815        self.xaxis(xname, xunits, self.xaxis_font, self.xaxis_color)
1816        self.yaxis(yname, yunits, self.yaxis_font, self.yaxis_color)
1817        self.subplot.texts = self.textList
1818        self.subplot.figure.canvas.draw_idle()
1819       
1820    def onFitDisplay(self, tempx, tempy, xminView, 
1821                     xmaxView, xmin, xmax, func):
1822        """
1823        Add a new plottable into the graph .In this case this plottable
1824        will be used to fit some data
1825       
1826        :param tempx: The x data of fit line
1827        :param tempy: The y data of fit line
1828        :param xminView: the lower bound of fitting range
1829        :param xminView: the upper bound of  fitting range
1830        :param xmin: the lowest value of data to fit to the line
1831        :param xmax: the highest value of data to fit to the line
1832       
1833        """
1834        # Saving value to redisplay in Fit Dialog when it is opened again
1835        self.Avalue, self.Bvalue, self.ErrAvalue, \
1836                      self.ErrBvalue, self.Chivalue = func
1837        self.xminView = xminView
1838        self.xmaxView = xmaxView
1839        self.xmin = xmin
1840        self.xmax = xmax
1841        #In case need to change the range of data plotted
1842        list = []
1843        list = self.graph.returnPlottable()
1844        for item in list:
1845            #item.onFitRange(xminView,xmaxView)
1846            item.onFitRange(None, None)
1847        # Create new data plottable with result
1848        self.fit_result.x = [] 
1849        self.fit_result.y = []
1850        self.fit_result.x = tempx 
1851        self.fit_result.y = tempy     
1852        self.fit_result.dx = None
1853        self.fit_result.dy = None
1854        #Load the view with the new values
1855        self.fit_result.reset_view()
1856        # Add the new plottable to the graph
1857        self.graph.add(self.fit_result) 
1858        self.graph.render(self)
1859        self._offset_graph()
1860        self.subplot.figure.canvas.draw_idle()
1861
1862    def onChangeCaption(self, event):
1863        """
1864        """
1865        if self.parent == None:
1866            return
1867        # get current caption
1868        old_caption = self.window_caption
1869        # Get new caption dialog
1870        dial = LabelDialog(None, -1, 'Modify Window Title', old_caption)
1871        if dial.ShowModal() == wx.ID_OK:
1872            new_caption = dial.getText() 
1873         
1874            # send to guiframe to change the panel caption
1875            caption = self.parent.on_change_caption(self.window_name, 
1876                                                    old_caption, new_caption) 
1877           
1878            # also set new caption in plot_panels list
1879            self.parent.plot_panels[self.uid].window_caption = caption
1880            # set new caption
1881            self.window_caption = caption
1882           
1883        dial.Destroy()
1884         
1885    def onResetGraph(self, event):
1886        """
1887        Reset the graph by plotting the full range of data
1888        """
1889        list = []
1890        list = self.graph.returnPlottable()
1891        for item in list:
1892            item.onReset()
1893        self.graph.render(self)
1894        self._onEVT_FUNC_PROPERTY(False)
1895        if self.is_zoomed:
1896                self.is_zoomed = False
1897        self.toolbar.update()
1898       
1899    def onPrinterSetup(self, event=None):
1900        """
1901        """
1902        self.canvas.Printer_Setup(event=event)
1903        self.Update()
1904
1905    def onPrinterPreview(self, event=None):
1906        """
1907        """
1908        try:
1909            self.canvas.Printer_Preview(event=event)
1910            self.Update()
1911        except:
1912            pass
1913       
1914    def onPrint(self, event=None):
1915        """
1916        """
1917        try:
1918            self.canvas.Printer_Print(event=event)
1919            self.Update()
1920        except:
1921            pass
1922     
1923    def OnCopyFigureMenu(self, evt):
1924        """
1925        Copy the current figure to clipboard
1926        """
1927        try:
1928            CopyImage(self.canvas)
1929        except:
1930            print "Error in copy Image"
1931
1932
1933           
1934#---------------------------------------------------------------       
1935class NoRepaintCanvas(FigureCanvasWxAgg):
1936    """
1937    We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that
1938    the draw method is only called for the first two paint events. After that,
1939    the canvas will only be redrawn when it is resized.
1940   
1941    """
1942    def __init__(self, *args, **kwargs):
1943        """
1944        """
1945        FigureCanvasWxAgg.__init__(self, *args, **kwargs)
1946        self._drawn = 0
1947
1948    def _onPaint(self, evt):
1949        """
1950        Called when wxPaintEvt is generated
1951       
1952        """
1953        if not self._isRealized:
1954            self.realize()
1955        if self._drawn < 2:
1956            self.draw(repaint = False)
1957            self._drawn += 1
1958        self.gui_repaint(drawDC=wx.PaintDC(self))
1959           
1960
Note: See TracBrowser for help on using the repository browser.