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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 0d0aa40 was 0d0aa40, checked in by Piotr Rozyczko <rozyczko@…>, 5 years ago

Allow deletion of linear fits (closes #466)

Also fixes bug where the linear fit would disappear if the home button
was clicked whilst using a non-linear scale

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