source: sasview/src/sas/plottools/PlotPanel.py @ 0e33a8d

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 0e33a8d was 7e0f9b5, checked in by Ricardo M. Ferraz Leal <ricleal@…>, 9 years ago

String containing only "$$" was crashing the panel display.

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