source: sasview/src/sas/sasgui/plottools/PlotPanel.py @ 9cc1f49

ticket-1094-headless
Last change on this file since 9cc1f49 was 75313af, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

fix 2D log-scale plotting; maybe broke vmin/vmax limits

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