source: sasview/src/sas/sasgui/plottools/PlotPanel.py @ 34f23c8

ticket-1249
Last change on this file since 34f23c8 was 34f23c8, checked in by Paul Kienzle <pkienzle@…>, 6 months ago

py3/wx4 compatibility changes for gui. Refs #1249

  • 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 math
9import os
10import operator
11import copy
12
13import wx
14import numpy as np
15
16# Try a normal import first
17# If it fails, try specifying a version
18import matplotlib
19matplotlib.interactive(False)
20#Use the WxAgg back end. The Wx one takes too long to render
21matplotlib.use('WXAgg')
22from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
23from matplotlib.figure import Figure
24from matplotlib.font_manager import FontProperties
25import pylab
26DEFAULT_CMAP = pylab.cm.jet
27
28from sas.sasgui.guiframe.events import StatusEvent
29
30#TODO: make the plottables interactive
31from . import transform
32from .TextDialog import TextDialog
33from .LabelDialog import LabelDialog
34from .binder import BindArtist
35from .convert_units import convert_unit
36from .plottables import Graph
37from .toolbar import NavigationToolBar, PlotPrintout, bind
38
39DEBUG = False
40logger = logging.getLogger(__name__)
41
42def show_tree(obj, d=0):
43    """Handy function for displaying a tree of graph objects"""
44    print("%s%s" % ("-"*d, obj.__class__.__name__))
45    if 'get_children' in dir(obj):
46        for a in obj.get_children(): show_tree(a, d + 1)
47
48
49
50def _rescale(lo, hi, step, pt=None, bal=None, scale='linear'):
51    """
52        Rescale (lo,hi) by step, returning the new (lo,hi)
53        The scaling is centered on pt, with positive values of step
54        driving lo/hi away from pt and negative values pulling them in.
55        If bal is given instead of point, it is already in [0,1] coordinates.
56
57        This is a helper function for step-based zooming.
58
59    """
60    # Convert values into the correct scale for a linear transformation
61    # TODO: use proper scale transformers
62    loprev = lo
63    hiprev = hi
64    if scale == 'log':
65        assert lo > 0
66        if lo > 0:
67            lo = math.log10(lo)
68        if hi > 0:
69            hi = math.log10(hi)
70        if pt is not None:
71            pt = math.log10(pt)
72
73    # Compute delta from axis range * %, or 1-% if persent is negative
74    if step > 0:
75        delta = float(hi - lo) * step / 100
76    else:
77        delta = float(hi - lo) * step / (100 - step)
78
79    # Add scale factor proportionally to the lo and hi values,
80    # preserving the
81    # point under the mouse
82    if bal is None:
83        bal = float(pt - lo) / (hi - lo)
84    lo = lo - (bal * delta)
85    hi = hi + (1 - bal) * delta
86
87    # Convert transformed values back to the original scale
88    if scale == 'log':
89        if (lo <= -250) or (hi >= 250):
90            lo = loprev
91            hi = hiprev
92        else:
93            lo, hi = math.pow(10., lo), math.pow(10., hi)
94    return (lo, hi)
95
96
97class PlotPanel(wx.Panel):
98    """
99    The PlotPanel has a Figure and a Canvas. OnSize events simply set a
100    flag, and the actually redrawing of the
101    figure is triggered by an Idle event.
102    """
103    def __init__(self, parent, id=-1, xtransform=None,
104                 ytransform=None, scale='log_{10}',
105                 color=None, dpi=None, **kwargs):
106        """
107        """
108        wx.Panel.__init__(self, parent, id=id, **kwargs)
109        self.parent = parent
110        if hasattr(parent, "parent"):
111            self.parent = self.parent.parent
112        self.dimension = 1
113        self.gotLegend = 0  # to begin, legend is not picked.
114        self.legend_pos_loc = None
115        self.legend = None
116        self.line_collections_list = []
117        self.figure = Figure(None, dpi, linewidth=2.0)
118        self.color = '#b3b3b3'
119        from .canvas import FigureCanvas
120        self.canvas = FigureCanvas(self, -1, self.figure)
121        self.SetColor(color)
122        self._resizeflag = True
123        self._SetSize()
124        self.subplot = self.figure.add_subplot(111)
125        self.figure.subplots_adjust(left=0.2, bottom=.2)
126        self.yscale = 'linear'
127        self.xscale = 'linear'
128        self.sizer = wx.BoxSizer(wx.VERTICAL)
129        self.sizer.Add(self.canvas, 1, wx.EXPAND)
130        #add toolbar
131        self.enable_toolbar = True
132        self.toolbar = None
133        self.add_toolbar()
134        self.SetSizer(self.sizer)
135
136        # Graph object to manage the plottables
137        self.graph = Graph()
138
139        #Boolean value to keep track of whether current legend is
140        #visible or not
141        self.legend_on = True
142        self.grid_on = False
143        #Location of legend, default is 0 or 'best'
144        self.legendLoc = 0
145        self.position = None
146        self._loc_labels = self.get_loc_label()
147
148        self.Bind(wx.EVT_CONTEXT_MENU, self.onContextMenu)
149
150        # Define some constants
151        self.colorlist = ['b', 'g', 'r', 'c', 'm', 'y', 'k']
152        self.symbollist = ['o', 'x', '^', 'v', '<', '>', '+',
153                           's', 'd', 'D', 'h', 'H', 'p', '-']
154
155        #List of texts currently on the plot
156        self.textList = []
157        self.selectedText = None
158        #User scale
159        if xtransform is not None:
160            self.xLabel = xtransform
161        else:
162            self.xLabel = "log10(x)"
163        if ytransform is not None:
164            self.yLabel = ytransform
165        else:
166            self.yLabel = "log10(y)"
167        self.viewModel = "--"
168        # keep track if the previous transformation of x
169        # and y in Property dialog
170        self.prevXtrans = "log10(x)"
171        self.prevYtrans = "log10(y)"
172        self.scroll_id = self.canvas.mpl_connect('scroll_event', self.onWheel)
173        #taking care of dragging
174        self.motion_id = self.canvas.mpl_connect('motion_notify_event',
175                                                 self.onMouseMotion)
176        self.press_id = self.canvas.mpl_connect('button_press_event',
177                                                self.onLeftDown)
178        self.pick_id = self.canvas.mpl_connect('pick_event', self.onPick)
179        self.release_id = self.canvas.mpl_connect('button_release_event',
180                                                  self.onLeftUp)
181
182        wx.EVT_RIGHT_DOWN(self, self.onLeftDown)
183        # to turn axis off whenn resizing the panel
184        self.resizing = False
185
186        self.leftdown = False
187        self.leftup = False
188        self.mousemotion = False
189        self.axes = [self.subplot]
190        ## Fit dialog
191        self._fit_dialog = None
192        # Interactor
193        self.connect = BindArtist(self.subplot.figure)
194        #self.selected_plottable = None
195
196        # new data for the fit
197        from sas.sasgui.guiframe.dataFitting import Data1D
198        self.fit_result = Data1D(x=[], y=[], dy=None)
199        self.fit_result.symbol = 13
200        #self.fit_result = Data1D(x=[], y=[],dx=None, dy=None)
201        self.fit_result.name = "Fit"
202        # For fit Dialog initial display
203        self.xmin = 0.0
204        self.xmax = 0.0
205        self.xminView = 0.0
206        self.xmaxView = 0.0
207        self._scale_xlo = None
208        self._scale_xhi = None
209        self._scale_ylo = None
210        self._scale_yhi = None
211        self.Avalue = None
212        self.Bvalue = None
213        self.ErrAvalue = None
214        self.ErrBvalue = None
215        self.Chivalue = None
216
217        # for 2D scale
218        if scale != 'linear':
219            scale = 'log_{10}'
220        self.scale = scale
221        self.data = None
222        self.qx_data = None
223        self.qy_data = None
224        self.xmin_2D = None
225        self.xmax_2D = None
226        self.ymin_2D = None
227        self.ymax_2D = None
228        ## store reference to the current plotted vmin and vmax of plotted image
229        ##z range in linear scale
230        self.zmin_2D = None
231        self.zmax_2D = None
232
233        #index array
234        self.index_x = None
235        self.index_y = None
236
237        #number of bins
238        self.x_bins = None
239        self.y_bins = None
240
241        ## default color map
242        self.cmap = DEFAULT_CMAP
243
244        # Dragging info
245        self.begDrag = False
246        self.xInit = None
247        self.yInit = None
248        self.xFinal = None
249        self.yFinal = None
250
251        #axes properties
252        self.xaxis_font = None
253        self.xaxis_label = None
254        self.xaxis_unit = None
255        self.xaxis_color = 'black'
256        self.xaxis_tick = None
257        self.yaxis_font = None
258        self.yaxis_label = None
259        self.yaxis_unit = None
260        self.yaxis_color = 'black'
261        self.yaxis_tick = None
262
263        # check if zoomed.
264        self.is_zoomed = False
265        # Plottables
266        self.plots = {}
267
268        # Default locations
269        self._default_save_location = os.getcwd()
270        # let canvas know about axes
271        self.canvas.set_panel(self)
272        self.ly = None
273        self.q_ctrl = None
274        #Bind focus to change the border color
275        self.canvas.Bind(wx.EVT_SET_FOCUS, self.on_set_focus)
276        self.canvas.Bind(wx.EVT_KILL_FOCUS, self.on_kill_focus)
277
278    def _SetInitialSize(self,):
279        """
280        """
281        pixels = self.parent.GetClientSize()
282        self.canvas.SetSize(pixels)
283        self.figure.set_size_inches(pixels[0] / self.figure.get_dpi(),
284                                    pixels[1] / self.figure.get_dpi(), forward=True)
285
286    def On_Paint(self, event):
287        """
288        """
289        self.canvas.SetBackgroundColour(self.color)
290
291    def on_set_focus(self, event):
292        """
293        Send to the parenet the current panel on focus
294        """
295        # light blue
296        self.color = '#0099f7'
297        self.figure.set_edgecolor(self.color)
298        if self.parent and self.window_caption:
299            self.parent.send_focus_to_datapanel(self.window_caption)
300        self.draw()
301
302    def on_kill_focus(self, event):
303        """
304        Reset the panel color
305        """
306        # light grey
307        self.color = '#b3b3b3'
308        self.figure.set_edgecolor(self.color)
309        self.draw()
310
311    def set_resizing(self, resizing=False):
312        """
313        Set the resizing (True/False)
314        """
315        pass  # Not implemented
316
317    def schedule_full_draw(self, func='append'):
318        """
319        Put self in schedule to full redraw list
320        """
321        pass  # Not implemented
322
323    def add_toolbar(self):
324        """
325        add toolbar
326        """
327        self.enable_toolbar = True
328        self.toolbar = NavigationToolBar(parent=self, canvas=self.canvas)
329        bind(self.toolbar, wx.EVT_TOOL, self.onResetGraph, id=self.toolbar._NTB2_RESET)
330        bind(self.toolbar, wx.EVT_TOOL, self.onContextMenu, id=self.toolbar._NTB2_HOME)
331        self.toolbar.Realize()
332        ## The 'SetToolBar()' is not working on MAC: JHC
333        #if IS_MAC:
334        # Mac platform (OSX 10.3, MacPython) does not seem to cope with
335        # having a toolbar in a sizer. This work-around gets the buttons
336        # back, but at the expense of having the toolbar at the top
337        #self.SetToolBar(self.toolbar)
338        #else:
339        # On Windows platform, default window size is incorrect, so set
340        # toolbar width to figure width.
341        tw, th = self.toolbar.GetSize()
342        fw, fh = self.canvas.GetSize()
343        # By adding toolbar in sizer, we are able to put it at the bottom
344        # of the frame - so appearance is closer to GTK version.
345        # As noted above, doesn't work for Mac.
346        self.toolbar.SetSize(wx.Size(fw, th))
347        self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
348
349        # update the axes menu on the toolbar
350        self.toolbar.update()
351
352    def onLeftDown(self, event):
353        """
354        left button down and ready to drag
355        """
356        # Check that the LEFT button was pressed
357        if event.button == 1:
358            self.leftdown = True
359            ax = event.inaxes
360            for text in self.textList:
361                if text.contains(event)[0]: # If user has clicked on text
362                    self.selectedText = text
363                    return
364
365            if ax is not None:
366                self.xInit, self.yInit = event.xdata, event.ydata
367                try:
368                    pos_x = float(event.xdata)  # / size_x
369                    pos_y = float(event.ydata)  # / size_y
370                    pos_x = "%8.3g" % pos_x
371                    pos_y = "%8.3g" % pos_y
372                    self.position = str(pos_x), str(pos_y)
373                    wx.PostEvent(self.parent, StatusEvent(status=self.position))
374                except:
375                    self.position = None
376
377    def onLeftUp(self, event):
378        """
379        Dragging is done
380        """
381        # Check that the LEFT button was released
382        if event.button == 1:
383            self.leftdown = False
384            self.mousemotion = False
385            self.leftup = True
386            self.selectedText = None
387
388        #release the legend
389        if self.gotLegend == 1:
390            self.gotLegend = 0
391            self.set_legend_alpha(1)
392
393    def set_legend_alpha(self, alpha=1):
394        """
395        Set legend alpha
396        """
397        if self.legend is not None:
398            self.legend.legendPatch.set_alpha(alpha)
399
400    def onPick(self, event):
401        """
402        On pick legend
403        """
404        legend = self.legend
405        if event.artist == legend:
406            #gets the box of the legend.
407            bbox = self.legend.get_window_extent()
408            #get mouse coordinates at time of pick.
409            self.mouse_x = event.mouseevent.x
410            self.mouse_y = event.mouseevent.y
411            #get legend coordinates at time of pick.
412            self.legend_x = bbox.xmin
413            self.legend_y = bbox.ymin
414            #indicates we picked up the legend.
415            self.gotLegend = 1
416            self.set_legend_alpha(0.5)
417
418    def _on_legend_motion(self, event):
419        """
420        On legend in motion
421        """
422        ax = event.inaxes
423        if ax is None:
424            return
425        # Event occurred inside a plotting area
426        lo_x, hi_x = ax.get_xlim()
427        lo_y, hi_y = ax.get_ylim()
428        # How much the mouse moved.
429        x = mouse_diff_x = self.mouse_x - event.x
430        y = mouse_diff_y = self.mouse_y - event.y
431        # Put back inside
432        if x < lo_x:
433            x = lo_x
434        if x > hi_x:
435            x = hi_x
436        if y < lo_y:
437            y = lo_y
438        if y > hi_y:
439            y = hi_y
440        # Move the legend from its previous location by that same amount
441        loc_in_canvas = (self.legend_x - mouse_diff_x,
442                         self.legend_y - mouse_diff_y)
443        # Transform into legend coordinate system
444        trans_axes = self.legend.parent.transAxes.inverted()
445        loc_in_norm_axes = trans_axes.transform_point(loc_in_canvas)
446        self.legend_pos_loc = tuple(loc_in_norm_axes)
447        self.legend._loc = self.legend_pos_loc
448        self.resizing = True
449        self.canvas.set_resizing(self.resizing)
450        self.canvas.draw()
451
452    def onMouseMotion(self, event):
453        """
454        check if the left button is press and the mouse in moving.
455        computer delta for x and y coordinates and then calls draghelper
456        to perform the drag
457        """
458        self.cusor_line(event)
459        if self.gotLegend == 1 and self.leftdown:
460            self._on_legend_motion(event)
461            return
462
463        if self.leftdown and self.selectedText is not None:
464            # User has clicked on text and is dragging
465            ax = event.inaxes
466            if ax is not None:
467                # Only move text if mouse is within axes
468                self.selectedText.set_position((event.xdata, event.ydata))
469                self._dragHelper(0, 0)
470            else:
471                # User has dragged outside of axes
472                self.selectedText = None
473            return
474
475        if self.enable_toolbar:
476            #Disable dragging without the toolbar to allow zooming with toolbar
477            return
478        self.mousemotion = True
479        if self.leftdown and self.mousemotion:
480            ax = event.inaxes
481            if ax is not None:  # the dragging is perform inside the figure
482                self.xFinal, self.yFinal = event.xdata, event.ydata
483                # Check whether this is the first point
484                if self.xInit is None:
485                    self.xInit = self.xFinal
486                    self.yInit = self.yFinal
487
488                xdelta = self.xFinal - self.xInit
489                ydelta = self.yFinal - self.yInit
490
491                if self.xscale == 'log':
492                    xdelta = math.log10(self.xFinal) - math.log10(self.xInit)
493                if self.yscale == 'log':
494                    ydelta = math.log10(self.yFinal) - math.log10(self.yInit)
495                self._dragHelper(xdelta, ydelta)
496            else:  # no dragging is perform elsewhere
497                self._dragHelper(0, 0)
498
499    def cusor_line(self, event):
500        """
501        """
502        pass
503
504    def _offset_graph(self):
505        """
506        Zoom and offset the graph to the last known settings
507        """
508        for ax in self.axes:
509            if self._scale_xhi is not None and self._scale_xlo is not None:
510                ax.set_xlim(self._scale_xlo, self._scale_xhi)
511            if self._scale_yhi is not None and self._scale_ylo is not None:
512                ax.set_ylim(self._scale_ylo, self._scale_yhi)
513
514    def _dragHelper(self, xdelta, ydelta):
515        """
516        dragging occurs here
517        """
518        # Event occurred inside a plotting area
519        for ax in self.axes:
520            lo, hi = ax.get_xlim()
521            newlo, newhi = lo - xdelta, hi - xdelta
522            if self.xscale == 'log':
523                if lo > 0:
524                    newlo = math.log10(lo) - xdelta
525                if hi > 0:
526                    newhi = math.log10(hi) - xdelta
527            if self.xscale == 'log':
528                self._scale_xlo = math.pow(10, newlo)
529                self._scale_xhi = math.pow(10, newhi)
530                ax.set_xlim(math.pow(10, newlo), math.pow(10, newhi))
531            else:
532                self._scale_xlo = newlo
533                self._scale_xhi = newhi
534                ax.set_xlim(newlo, newhi)
535
536            lo, hi = ax.get_ylim()
537            newlo, newhi = lo - ydelta, hi - ydelta
538            if self.yscale == 'log':
539                if lo > 0:
540                    newlo = math.log10(lo) - ydelta
541                if hi > 0:
542                    newhi = math.log10(hi) - ydelta
543            if  self.yscale == 'log':
544                self._scale_ylo = math.pow(10, newlo)
545                self._scale_yhi = math.pow(10, newhi)
546                ax.set_ylim(math.pow(10, newlo), math.pow(10, newhi))
547            else:
548                self._scale_ylo = newlo
549                self._scale_yhi = newhi
550                ax.set_ylim(newlo, newhi)
551        self.canvas.draw_idle()
552
553    def resetFitView(self):
554        """
555        For fit Dialog initial display
556        """
557        self.xmin = 0.0
558        self.xmax = 0.0
559        self.xminView = 0.0
560        self.xmaxView = 0.0
561        self._scale_xlo = None
562        self._scale_xhi = None
563        self._scale_ylo = None
564        self._scale_yhi = None
565        self.Avalue = None
566        self.Bvalue = None
567        self.ErrAvalue = None
568        self.ErrBvalue = None
569        self.Chivalue = None
570
571    def onWheel(self, event):
572        """
573        Process mouse wheel as zoom events
574
575        :param event: Wheel event
576
577        """
578        ax = event.inaxes
579        step = event.step
580
581        if ax is not None:
582            # Event occurred inside a plotting area
583            lo, hi = ax.get_xlim()
584            lo, hi = _rescale(lo, hi, step,
585                              pt=event.xdata, scale=ax.get_xscale())
586            if not self.xscale == 'log' or lo > 0:
587                self._scale_xlo = lo
588                self._scale_xhi = hi
589                ax.set_xlim((lo, hi))
590
591            lo, hi = ax.get_ylim()
592            lo, hi = _rescale(lo, hi, step, pt=event.ydata,
593                              scale=ax.get_yscale())
594            if not self.yscale == 'log' or lo > 0:
595                self._scale_ylo = lo
596                self._scale_yhi = hi
597                ax.set_ylim((lo, hi))
598        else:
599            # Check if zoom happens in the axes
600            xdata, ydata = None, None
601            x, y = event.x, event.y
602
603            for ax in self.axes:
604                insidex, _ = ax.xaxis.contains(event)
605                if insidex:
606                    xdata, _ = ax.transAxes.inverted().transform_point((x, y))
607                insidey, _ = ax.yaxis.contains(event)
608                if insidey:
609                    _, ydata = ax.transAxes.inverted().transform_point((x, y))
610            if xdata is not None:
611                lo, hi = ax.get_xlim()
612                lo, hi = _rescale(lo, hi, step,
613                                  bal=xdata, scale=ax.get_xscale())
614                if not self.xscale == 'log' or lo > 0:
615                    self._scale_xlo = lo
616                    self._scale_xhi = hi
617                    ax.set_xlim((lo, hi))
618            if ydata is not None:
619                lo, hi = ax.get_ylim()
620                lo, hi = _rescale(lo, hi, step, bal=ydata,
621                                  scale=ax.get_yscale())
622                if not self.yscale == 'log' or lo > 0:
623                    self._scale_ylo = lo
624                    self._scale_yhi = hi
625                    ax.set_ylim((lo, hi))
626        self.canvas.draw_idle()
627
628    def returnTrans(self):
629        """
630        Return values and labels used by Fit Dialog
631        """
632        return (self.xLabel, self.yLabel, self.Avalue, self.Bvalue,
633                self.ErrAvalue, self.ErrBvalue, self.Chivalue)
634
635    def setTrans(self, xtrans, ytrans):
636        """
637
638        :param xtrans: set x transformation on Property dialog
639        :param ytrans: set y transformation on Property dialog
640
641        """
642        self.prevXtrans = xtrans
643        self.prevYtrans = ytrans
644
645    def onFitting(self, event):
646        """
647        when clicking on linear Fit on context menu , display Fitting Dialog
648        """
649        plot_dict = {}
650        menu = event.GetEventObject()
651        event_id = event.GetId()
652        self.set_selected_from_menu(menu, event_id)
653        plotlist = self.graph.returnPlottable()
654        if self.graph.selected_plottable is not None:
655            for item in plotlist:
656                if item.id == self.graph.selected_plottable:
657                    plot_dict[item] = plotlist[item]
658        else:
659            plot_dict = plotlist
660        from .fitDialog import LinearFit
661
662        if len(plot_dict.keys()) > 0:
663            first_item = list(plot_dict.keys())[0]
664            dlg = LinearFit(parent=None, plottable=first_item,
665                            push_data=self.onFitDisplay,
666                            transform=self.returnTrans,
667                            title='Linear Fit')
668
669            if ((self.xmin != 0.0) and (self.xmax != 0.0)
670                    and (self.xminView != 0.0) and (self.xmaxView != 0.0)):
671                dlg.setFitRange(self.xminView, self.xmaxView,
672                                self.xmin, self.xmax)
673            else:
674                xlim = self.subplot.get_xlim()
675                ylim = self.subplot.get_ylim()
676                dlg.setFitRange(xlim[0], xlim[1], ylim[0], ylim[1])
677            # It would be nice for this to NOT be modal (i.e. Show).
678            # Not sure about other ramifications - for example
679            # if a second linear fit is started before the first is closed.
680            # consider for future - being able to work on the plot while
681            # seing the fit values would be very nice  -- PDB 7/10/16
682            dlg.ShowModal()
683
684    def set_selected_from_menu(self, menu, id):
685        """
686        Set selected_plottable from context menu selection
687
688        :param menu: context menu item
689        :param id: menu item id
690        """
691        if len(self.plots) < 1:
692            return
693        name = menu.GetHelpString(id)
694        for plot in self.plots.values():
695            if plot.name == name:
696                self.graph.selected_plottable = plot.id
697                break
698
699    def linear_plottable_fit(self, plot):
700        """
701            when clicking on linear Fit on context menu, display Fitting Dialog
702
703            :param plot: PlotPanel owning the graph
704
705        """
706        from .fitDialog import LinearFit
707        if self._fit_dialog is not None:
708            return
709        self._fit_dialog = LinearFit(None, plot, self.onFitDisplay,
710                                     self.returnTrans, -1, 'Linear Fit')
711        # Set the zoom area
712        if self._scale_xhi is not None and self._scale_xlo is not None:
713            self._fit_dialog.set_fit_region(self._scale_xlo, self._scale_xhi)
714        # Register the close event
715        self._fit_dialog.register_close(self._linear_fit_close)
716        # Show a non-model dialog
717        self._fit_dialog.Show()
718
719    def _linear_fit_close(self):
720        """
721        A fit dialog was closed
722        """
723        self._fit_dialog = None
724
725    def _onProperties(self, event):
726        """
727        when clicking on Properties on context menu ,
728        The Property dialog is displayed
729        The user selects a transformation for x or y value and
730        a new plot is displayed
731        """
732        if self._fit_dialog is not None:
733            self._fit_dialog.Destroy()
734            self._fit_dialog = None
735        plot_list = self.graph.returnPlottable()
736        if len(plot_list.keys()) > 0:
737            first_item = plot_list.keys()[0]
738            if first_item.x != []:
739                from .PropertyDialog import Properties
740                dial = Properties(self, -1, 'Properties')
741                dial.setValues(self.prevXtrans, self.prevYtrans, self.viewModel)
742                if dial.ShowModal() == wx.ID_OK:
743                    self.xLabel, self.yLabel, self.viewModel = dial.getValues()
744                    self._onEVT_FUNC_PROPERTY()
745                dial.Destroy()
746
747    def set_yscale(self, scale='linear'):
748        """
749        Set the scale on Y-axis
750
751        :param scale: the scale of y-axis
752
753        """
754        self.subplot.set_yscale(scale, nonposy='clip')
755        self.yscale = scale
756
757    def get_yscale(self):
758        """
759
760        :return: Y-axis scale
761
762        """
763        return self.yscale
764
765    def set_xscale(self, scale='linear'):
766        """
767        Set the scale on x-axis
768
769        :param scale: the scale of x-axis
770
771        """
772        self.subplot.set_xscale(scale)
773        self.xscale = scale
774
775    def get_xscale(self):
776        """
777
778        :return: x-axis scale
779
780        """
781        return self.xscale
782
783    def SetColor(self, rgbtuple):
784        """
785        Set figure and canvas colours to be the same
786
787        """
788        if not rgbtuple:
789            rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
790        col = [c / 255.0 for c in rgbtuple]
791        self.figure.set_facecolor(col)
792        self.figure.set_edgecolor(self.color)
793        self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))
794
795    def _onSize(self, event):
796        """
797        """
798        self._resizeflag = True
799
800    def _onIdle(self, evt):
801        """
802        """
803        if self._resizeflag:
804            self._resizeflag = False
805            self._SetSize()
806            self.draw()
807
808    def _SetSize(self, pixels=None):
809        """
810        This method can be called to force the Plot to be a desired size,
811         which defaults to the ClientSize of the panel
812
813        """
814        if not pixels:
815            pixels = tuple(self.GetClientSize())
816        self.canvas.SetSize(pixels)
817        self.figure.set_size_inches(float(pixels[0]) / self.figure.get_dpi(),
818                                    float(pixels[1]) / self.figure.get_dpi())
819
820    def draw(self):
821        """
822        Where the actual drawing happens
823
824        """
825        self.figure.canvas.draw_idle()
826
827    def legend_picker(self, legend, event):
828        """
829            Pick up the legend patch
830        """
831        return self.legend.legendPatch.contains(event)
832
833    def get_loc_label(self):
834        """
835        Associates label to a specific legend location
836        """
837        _labels = {}
838        i = 0
839        _labels['best'] = i
840        i += 1
841        _labels['upper right'] = i
842        i += 1
843        _labels['upper left'] = i
844        i += 1
845        _labels['lower left'] = i
846        i += 1
847        _labels['lower right'] = i
848        i += 1
849        _labels['right'] = i
850        i += 1
851        _labels['center left'] = i
852        i += 1
853        _labels['center right'] = i
854        i += 1
855        _labels['lower center'] = i
856        i += 1
857        _labels['upper center'] = i
858        i += 1
859        _labels['center'] = i
860        return _labels
861
862    def onSaveImage(self, evt):
863        """
864        Implement save image
865        """
866        self.toolbar.save_figure(evt)
867
868    def onContextMenu(self, event):
869        """
870        Default context menu for a plot panel
871
872        """
873        # Slicer plot popup menu
874        wx_id = wx.NewId()
875        slicerpop = wx.Menu()
876        slicerpop.Append(wx_id, '&Save image', 'Save image as PNG')
877        wx.EVT_MENU(self, wx_id, self.onSaveImage)
878
879        wx_id = wx.NewId()
880        slicerpop.Append(wx_id, '&Printer setup', 'Set image size')
881        wx.EVT_MENU(self, wx_id, self.onPrinterSetup)
882
883        wx_id = wx.NewId()
884        slicerpop.Append(wx_id, '&Print image', 'Print image ')
885        wx.EVT_MENU(self, wx_id, self.onPrint)
886
887        wx_id = wx.NewId()
888        slicerpop.Append(wx_id, '&Copy', 'Copy to the clipboard')
889        wx.EVT_MENU(self, wx_id, self.OnCopyFigureMenu)
890
891        wx_id = wx.NewId()
892        slicerpop.AppendSeparator()
893        slicerpop.Append(wx_id, '&Properties')
894        wx.EVT_MENU(self, wx_id, self._onProperties)
895
896        wx_id = wx.NewId()
897        slicerpop.AppendSeparator()
898        slicerpop.Append(wx_id, '&Linear Fit')
899        wx.EVT_MENU(self, wx_id, self.onFitting)
900
901        wx_id = wx.NewId()
902        slicerpop.AppendSeparator()
903        slicerpop.Append(wx_id, '&Toggle Legend On/Off', 'Toggle Legend On/Off')
904        wx.EVT_MENU(self, wx_id, self.onLegend)
905
906        loc_menu = wx.Menu()
907        for label in self._loc_labels:
908            wx_id = wx.NewId()
909            loc_menu.Append(wx_id, str(label), str(label))
910            wx.EVT_MENU(self, wx_id, self.onChangeLegendLoc)
911        wx_id = wx.NewId()
912        slicerpop.AppendMenu(wx_id, '&Modify Legend Location', loc_menu)
913
914        wx_id = wx.NewId()
915        slicerpop.Append(wx_id, '&Modify Y Axis Label')
916        wx.EVT_MENU(self, wx_id, self._on_yaxis_label)
917        wx_id = wx.NewId()
918        slicerpop.Append(wx_id, '&Modify X Axis Label')
919        wx.EVT_MENU(self, wx_id, self._on_xaxis_label)
920
921        try:
922            # mouse event
923            pos_evt = event.GetPosition()
924            pos = self.ScreenToClient(pos_evt)
925        except:
926            # toolbar event
927            pos_x, pos_y = self.toolbar.GetPosition()
928            pos = (pos_x, pos_y + 5)
929
930        self.PopupMenu(slicerpop, pos)
931
932    def onToolContextMenu(self, event):
933        """
934        ContextMenu from toolbar
935
936        :param event: toolbar event
937        """
938        # reset postion
939        self.position = None
940        if self.graph.selected_plottable is not None:
941            self.graph.selected_plottable = None
942
943        self.onContextMenu(event)
944
945# modified kieranrcampbell ILL june2012
946    def onLegend(self, legOnOff):
947        """
948        Toggles whether legend is visible/not visible
949        """
950        self.legend_on = legOnOff
951        if not self.legend_on:
952            for ax in self.axes:
953                self.remove_legend(ax)
954        else:
955            # sort them by labels
956            handles, labels = self.subplot.get_legend_handles_labels()
957            hl = sorted(zip(handles, labels),
958                        key=operator.itemgetter(1))
959            handles2, labels2 = zip(*hl)
960            self.line_collections_list = handles2
961            self.legend = self.subplot.legend(handles2, labels2,
962                                              prop=FontProperties(size=10),
963                                              loc=self.legendLoc)
964            if self.legend is not None:
965                self.legend.set_picker(self.legend_picker)
966                self.legend.set_axes(self.subplot)
967                self.legend.set_zorder(20)
968
969        self.subplot.figure.canvas.draw_idle()
970
971    def onChangeLegendLoc(self, event):
972        """
973        Changes legend loc based on user input
974        """
975        menu = event.GetEventObject()
976        label = menu.GetLabel(event.GetId())
977        self.ChangeLegendLoc(label)
978
979    def ChangeLegendLoc(self, label):
980        """
981        Changes legend loc based on user input
982        """
983        self.legendLoc = label
984        self.legend_pos_loc = None
985        # sort them by labels
986        handles, labels = self.subplot.get_legend_handles_labels()
987        hl = sorted(zip(handles, labels),
988                    key=operator.itemgetter(1))
989        handles2, labels2 = zip(*hl)
990        self.line_collections_list = handles2
991        self.legend = self.subplot.legend(handles2, labels2,
992                                          prop=FontProperties(size=10),
993                                          loc=self.legendLoc)
994        if self.legend is not None:
995            self.legend.set_picker(self.legend_picker)
996            self.legend.set_axes(self.subplot)
997            self.legend.set_zorder(20)
998        self.subplot.figure.canvas.draw_idle()
999
1000    def remove_legend(self, ax=None):
1001        """
1002        Remove legend for ax or the current axes.
1003        """
1004        if ax is None:
1005            ax = pylab.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, is_ok, is_tick \
1062            = 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, is_ok, is_tick \
1109            = 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 r"\\" in label_temp:
1159                    label = label_temp.replace(r"\\", r"??")
1160                    if self.parent is not None:
1161                        msg = r"Add Label error: Can not use double '\\' "
1162                        msg += "characters..."
1163                        wx.PostEvent(self.parent, StatusEvent(status=msg))
1164                else:
1165                    label = label_temp
1166            except:
1167                if self.parent is not None:
1168                    msg = "Add Label: Error. Check your property values..."
1169                    wx.PostEvent(self.parent, StatusEvent(status=msg))
1170                else:
1171                    pass
1172        else:
1173            is_ok = False
1174            is_tick = True
1175        textdial.Destroy()
1176        return label, unit, font, colour, is_ok, is_tick
1177
1178    def _on_removetext(self, event):
1179        """
1180        Removes all text from the plot.
1181        Eventually, add option to remove specific text boxes
1182        """
1183        num_text = len(self.textList)
1184        if num_text < 1:
1185            if self.parent is not None:
1186                msg = "Remove Text: Nothing to remove.  "
1187                wx.PostEvent(self.parent, StatusEvent(status=msg))
1188            else:
1189                raise
1190            return
1191        txt = self.textList[num_text - 1]
1192        try:
1193            text_remove = txt.get_text()
1194            txt.remove()
1195            if self.parent is not None:
1196                msg = "Removed Text: '%s'. " % text_remove
1197                wx.PostEvent(self.parent, StatusEvent(status=msg))
1198        except:
1199            if self.parent is not None:
1200                msg = "Remove Text: Error occurred. "
1201                wx.PostEvent(self.parent, StatusEvent(status=msg))
1202            else:
1203                raise
1204        self.textList.remove(txt)
1205
1206        self.subplot.figure.canvas.draw_idle()
1207
1208    def properties(self, prop):
1209        """
1210        Set some properties of the graph.
1211        The set of properties is not yet determined.
1212
1213        """
1214        # The particulars of how they are stored and manipulated (e.g., do
1215        # we want an inventory internally) is not settled.  I've used a
1216        # property dictionary for now.
1217        #
1218        # How these properties interact with a user defined style file is
1219        # even less clear.
1220
1221        # Properties defined by plot
1222
1223        # Ricardo:
1224        # A empty label "$$" will prevent the panel from displaying!
1225        if prop["xlabel"]:
1226            self.subplot.set_xlabel(r"$%s$"%prop["xlabel"])
1227        if prop["ylabel"]:
1228            self.subplot.set_ylabel(r"$%s$"%prop["ylabel"])
1229        self.subplot.set_title(prop["title"])
1230
1231
1232    def clear(self):
1233        """Reset the plot"""
1234        # TODO: Redraw is brutal.  Render to a backing store and swap in
1235        # TODO: rather than redrawing on the fly.
1236        self.subplot.clear()
1237        self.subplot.hold(True)
1238
1239    def render(self):
1240        """Commit the plot after all objects are drawn"""
1241        # TODO: this is when the backing store should be swapped in.
1242        if self.legend_on:
1243            ax = self.subplot
1244            ax.texts = self.textList
1245            try:
1246                handles, labels = ax.get_legend_handles_labels()
1247                # sort them by labels
1248                hl = sorted(zip(handles, labels),
1249                            key=operator.itemgetter(1))
1250                handles2, labels2 = zip(*hl)
1251                self.line_collections_list = handles2
1252                self.legend = ax.legend(handles2, labels2,
1253                                        prop=FontProperties(size=10),
1254                                        loc=self.legendLoc)
1255                if self.legend is not None:
1256                    self.legend.set_picker(self.legend_picker)
1257                    self.legend.set_axes(self.subplot)
1258                    self.legend.set_zorder(20)
1259
1260            except:
1261                self.legend = ax.legend(prop=FontProperties(size=10),
1262                                        loc=self.legendLoc)
1263
1264    def xaxis(self, label, units, font=None, color='black', t_font=None):
1265        """xaxis label and units.
1266
1267        Axis labels know about units.
1268
1269        We need to do this so that we can detect when axes are not
1270        commesurate.  Currently this is ignored other than for formatting
1271        purposes.
1272
1273        """
1274
1275        self.xcolor = color
1276        if units.count("{") > 0 and units.count("$") < 2:
1277            units = '$' + units + '$'
1278        if label.count("{") > 0 and label.count("$") < 2:
1279            label = '$' + label + '$'
1280        if units != "":
1281            label = label + " (" + units + ")"
1282        if font:
1283            self.subplot.set_xlabel(label, fontproperties=font, color=color)
1284            if t_font is not None:
1285                for tick in self.subplot.xaxis.get_major_ticks():
1286                    tick.label.set_fontproperties(t_font)
1287                for line in self.subplot.xaxis.get_ticklines():
1288                    size = t_font.get_size()
1289                    line.set_markersize(size / 3)
1290        else:
1291            self.subplot.set_xlabel(label, color=color)
1292        pass
1293
1294    def yaxis(self, label, units, font=None, color='black', t_font=None):
1295        """yaxis label and units."""
1296        self.ycolor = color
1297        if units.count("{") > 0 and units.count("$") < 2:
1298            units = '$' + units + '$'
1299        if label.count("{") > 0 and label.count("$") < 2:
1300            label = '$' + label + '$'
1301        if units != "":
1302            label = label + " (" + units + ")"
1303        if font:
1304            self.subplot.set_ylabel(label, fontproperties=font, color=color)
1305            if t_font is not None:
1306                for tick_label in self.subplot.get_yticklabels():
1307                    tick_label.set_fontproperties(t_font)
1308                for line in self.subplot.yaxis.get_ticklines():
1309                    size = t_font.get_size()
1310                    line.set_markersize(size / 3)
1311        else:
1312            self.subplot.set_ylabel(label, color=color)
1313        pass
1314
1315    def _connect_to_xlim(self, callback):
1316        """Bind the xlim change notification to the callback"""
1317        def process_xlim(axes):
1318            lo, hi = subplot.get_xlim()
1319            callback(lo, hi)
1320        self.subplot.callbacks.connect('xlim_changed', process_xlim)
1321
1322    def interactive_points(self, x, y, dx=None, dy=None, name='', color=0,
1323                           symbol=0, markersize=5, zorder=1, id=None,
1324                           label=None, hide_error=False):
1325        """Draw markers with error bars"""
1326        self.subplot.set_yscale('linear')
1327        self.subplot.set_xscale('linear')
1328        if id is None:
1329            id = name
1330        from .plottable_interactor import PointInteractor
1331        p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
1332        if p.markersize is not None:
1333            markersize = p.markersize
1334        p.points(x, y, dx=dx, dy=dy, color=color, symbol=symbol, zorder=zorder,
1335                 markersize=markersize, label=label, hide_error=hide_error)
1336
1337        self.subplot.set_yscale(self.yscale, nonposy='clip')
1338        self.subplot.set_xscale(self.xscale)
1339
1340    def interactive_curve(self, x, y, dy=None, name='', color=0,
1341                          symbol=0, zorder=1, id=None, label=None):
1342        """Draw markers with error bars"""
1343        self.subplot.set_yscale('linear')
1344        self.subplot.set_xscale('linear')
1345        if id is None:
1346            id = name
1347        from .plottable_interactor import PointInteractor
1348        p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
1349        p.curve(x, y, dy=dy, color=color, symbol=symbol, zorder=zorder,
1350                label=label)
1351
1352        self.subplot.set_yscale(self.yscale, nonposy='clip')
1353        self.subplot.set_xscale(self.xscale)
1354
1355    def plottable_selected(self, id):
1356        """
1357        Called to register a plottable as selected
1358        """
1359        #TODO: check that it really is in the list of plottables
1360        self.graph.selected_plottable = id
1361
1362    def points(self, x, y, dx=None, dy=None,
1363               color=0, symbol=0, marker_size=5, label=None,
1364               id=None, hide_error=False):
1365        """Draw markers with error bars"""
1366
1367        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
1368        if dx is not None and type(dx) == type(()):
1369            dx = nx.vstack((x - dx[0], dx[1] - x)).transpose()
1370        if dy is not None and type(dy) == type(()):
1371            dy = nx.vstack((y - dy[0], dy[1] - y)).transpose()
1372        if dx is None and dy is None:
1373            self.subplot.plot(x, y, color=self._color(color),
1374                              marker=self._symbol(symbol),
1375                              markersize=marker_size,
1376                              linestyle='',
1377                              label=label)
1378        else:
1379            col = self._color(color)
1380            if hide_error:
1381                self.subplot.plot(x, y, color=col,
1382                                  marker=self._symbol(symbol),
1383                                  markersize=marker_size,
1384                                  linestyle='',
1385                                  label=label)
1386            else:
1387                self.subplot.errorbar(x, y, yerr=dy, xerr=None,
1388                                      ecolor=col, capsize=2, linestyle='',
1389                                      barsabove=False,
1390                                      mec=col, mfc=col,
1391                                      marker=self._symbol(symbol),
1392                                      markersize=marker_size,
1393                                      lolims=False, uplims=False,
1394                                      xlolims=False, xuplims=False, label=label)
1395
1396        self.subplot.set_yscale(self.yscale, nonposy='clip')
1397        self.subplot.set_xscale(self.xscale)
1398
1399    def _onToggleScale(self, event):
1400        """
1401        toggle axis and replot image
1402
1403        """
1404        zmin_2D_temp = self.zmin_2D
1405        zmax_2D_temp = self.zmax_2D
1406        if self.scale == 'log_{10}':
1407            self.scale = 'linear'
1408            if self.zmin_2D is not None:
1409                zmin_2D_temp = math.pow(10, self.zmin_2D)
1410            if self.zmax_2D is not None:
1411                zmax_2D_temp = math.pow(10, self.zmax_2D)
1412        else:
1413            self.scale = 'log_{10}'
1414            if self.zmin_2D is not None:
1415                # min log value: no log(negative)
1416                if self.zmin_2D <= 0:
1417                    zmin_2D_temp = -32
1418                else:
1419                    zmin_2D_temp = math.log10(self.zmin_2D)
1420            if self.zmax_2D is not None:
1421                zmax_2D_temp = math.log10(self.zmax_2D)
1422
1423        self.image(data=self.data, qx_data=self.qx_data,
1424                   qy_data=self.qy_data, xmin=self.xmin_2D,
1425                   xmax=self.xmax_2D,
1426                   ymin=self.ymin_2D, ymax=self.ymax_2D,
1427                   cmap=self.cmap, zmin=zmin_2D_temp,
1428                   zmax=zmax_2D_temp)
1429
1430    def image(self, data, qx_data, qy_data, xmin, xmax, ymin, ymax,
1431              zmin, zmax, color=0, symbol=0, markersize=0,
1432              label='data2D', cmap=DEFAULT_CMAP):
1433        """
1434        Render the current data
1435
1436        """
1437        # TODO: include mask info in plotter
1438        self.data = data
1439        self.qx_data = qx_data
1440        self.qy_data = qy_data
1441        self.xmin_2D = xmin
1442        self.xmax_2D = xmax
1443        self.ymin_2D = ymin
1444        self.ymax_2D = ymax
1445        self.zmin_2D = zmin
1446        self.zmax_2D = zmax
1447        c = self._color(color)
1448        # If we don't have any data, skip.
1449        if self.data is None:
1450            return
1451        if self.data.ndim == 1:
1452            output = self._build_matrix()
1453        else:
1454            output = copy.deepcopy(self.data)
1455        # rescale data if necessary
1456        if self.scale == 'log_{10}':
1457            with np.errstate(all='ignore'):
1458                output = np.log10(output)
1459            index = np.isfinite(output)
1460            if not index.all():
1461                cutoff = (np.min(output[index]) - np.log10(2)
1462                          if index.any() else 0.)
1463                output[~index] = cutoff
1464        # TODO: fix handling of zmin_2D/zmax_2D in _onToggleScale
1465        # For now, use default vmin/vmax from data
1466        #vmin, vmax = self.zmin_2D, self.zmax_2D
1467        vmin, vmax = None, None
1468        self.cmap = cmap
1469        if self.dimension != 3:
1470            #Re-adjust colorbar
1471            self.subplot.figure.subplots_adjust(left=0.2, right=.8, bottom=.2)
1472            im = self.subplot.imshow(output, interpolation='nearest',
1473                                     origin='lower',
1474                                     vmin=vmin, vmax=vmax,
1475                                     cmap=self.cmap,
1476                                     extent=(self.xmin_2D, self.xmax_2D,
1477                                             self.ymin_2D, self.ymax_2D))
1478
1479            cbax = self.subplot.figure.add_axes([0.84, 0.2, 0.02, 0.7])
1480        else:
1481            # clear the previous 2D from memory
1482            # mpl is not clf, so we do
1483            self.subplot.figure.clear()
1484
1485            self.subplot.figure.subplots_adjust(left=0.1, right=.8, bottom=.1)
1486
1487            X = self.x_bins[0:-1]
1488            Y = self.y_bins[0:-1]
1489            X, Y = np.meshgrid(X, Y)
1490
1491            try:
1492                # mpl >= 1.0.0
1493                ax = self.subplot.figure.gca(projection='3d')
1494                #ax.disable_mouse_rotation()
1495                cbax = self.subplot.figure.add_axes([0.84, 0.1, 0.02, 0.8])
1496                if len(X) > 60:
1497                    ax.disable_mouse_rotation()
1498            except:
1499                # mpl < 1.0.0
1500                try:
1501                    from mpl_toolkits.mplot3d import Axes3D
1502                except:
1503                    logger.error("PlotPanel could not import Axes3D")
1504                self.subplot.figure.clear()
1505                ax = Axes3D(self.subplot.figure)
1506                if len(X) > 60:
1507                    ax.cla()
1508                cbax = None
1509            self.subplot.figure.canvas.resizing = False
1510            im = ax.plot_surface(X, Y, output, rstride=1, cstride=1, cmap=cmap,
1511                                 linewidth=0, antialiased=False)
1512            self.subplot.set_axis_off()
1513
1514        if cbax is None:
1515            ax.set_frame_on(False)
1516            cb = self.subplot.figure.colorbar(im, shrink=0.8, aspect=20)
1517        else:
1518            cb = self.subplot.figure.colorbar(im, cax=cbax)
1519        cb.update_bruteforce(im)
1520        cb.set_label('$' + self.scale + '$')
1521        if self.dimension != 3:
1522            self.figure.canvas.draw_idle()
1523        else:
1524            self.figure.canvas.draw()
1525
1526    def _build_matrix(self):
1527        """
1528        Build a matrix for 2d plot from a vector
1529        Returns a matrix (image) with ~ square binning
1530        Requirement: need 1d array formats of
1531        self.data, self.qx_data, and self.qy_data
1532        where each one corresponds to z, x, or y axis values
1533
1534        """
1535        # No qx or qy given in a vector format
1536        if (self.qx_data is None or self.qy_data is None
1537                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1):
1538            # do we need deepcopy here?
1539            return copy.deepcopy(self.data)
1540
1541        # maximum # of loops to fillup_pixels
1542        # otherwise, loop could never stop depending on data
1543        max_loop = 1
1544        # get the x and y_bin arrays.
1545        self._get_bins()
1546        # set zero to None
1547
1548        #Note: Can not use scipy.interpolate.Rbf:
1549        # 'cause too many data points (>10000)<=JHC.
1550        # 1d array to use for weighting the data point averaging
1551        #when they fall into a same bin.
1552        weights_data = np.ones([self.data.size])
1553        # get histogram of ones w/len(data); this will provide
1554        #the weights of data on each bins
1555        weights, xedges, yedges = np.histogram2d(x=self.qy_data,
1556                                                    y=self.qx_data,
1557                                                    bins=[self.y_bins, self.x_bins],
1558                                                    weights=weights_data)
1559        # get histogram of data, all points into a bin in a way of summing
1560        image, xedges, yedges = np.histogram2d(x=self.qy_data,
1561                                                  y=self.qx_data,
1562                                                  bins=[self.y_bins, self.x_bins],
1563                                                  weights=self.data)
1564        # Now, normalize the image by weights only for weights>1:
1565        # If weight == 1, there is only one data point in the bin so
1566        # that no normalization is required.
1567        image[weights > 1] = image[weights > 1] / weights[weights > 1]
1568        # Set image bins w/o a data point (weight==0) as None (was set to zero
1569        # by histogram2d.)
1570        image[weights == 0] = None
1571
1572        # Fill empty bins with 8 nearest neighbors only when at least
1573        #one None point exists
1574        loop = 0
1575
1576        # do while loop until all vacant bins are filled up up
1577        #to loop = max_loop
1578        while not(np.isfinite(image[weights == 0])).all():
1579            if loop >= max_loop:  # this protects never-ending loop
1580                break
1581            image = self._fillup_pixels(image=image, weights=weights)
1582            loop += 1
1583
1584        return image
1585
1586    def _get_bins(self):
1587        """
1588        get bins
1589        set x_bins and y_bins into self, 1d arrays of the index with
1590        ~ square binning
1591        Requirement: need 1d array formats of
1592        self.qx_data, and self.qy_data
1593        where each one corresponds to  x, or y axis values
1594        """
1595        # No qx or qy given in a vector format
1596        if (self.qx_data is None or self.qy_data is None
1597                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1):
1598            # do we need deepcopy here?
1599            return copy.deepcopy(self.data)
1600
1601        # find max and min values of qx and qy
1602        xmax = self.qx_data.max()
1603        xmin = self.qx_data.min()
1604        ymax = self.qy_data.max()
1605        ymin = self.qy_data.min()
1606
1607        # calculate the range of qx and qy: this way, it is a little
1608        # more independent
1609        x_size = xmax - xmin
1610        y_size = ymax - ymin
1611
1612        # estimate the # of pixels on each axes
1613        npix_y = int(math.floor(math.sqrt(len(self.qy_data))))
1614        npix_x = int(math.floor(len(self.qy_data) / npix_y))
1615
1616        # bin size: x- & y-directions
1617        xstep = x_size / (npix_x - 1)
1618        ystep = y_size / (npix_y - 1)
1619
1620        # max and min taking account of the bin sizes
1621        xmax = xmax + xstep / 2.0
1622        xmin = xmin - xstep / 2.0
1623        ymax = ymax + ystep / 2.0
1624        ymin = ymin - ystep / 2.0
1625
1626        # store x and y bin centers in q space
1627        x_bins = np.linspace(xmin, xmax, npix_x)
1628        y_bins = np.linspace(ymin, ymax, npix_y)
1629
1630        #set x_bins and y_bins
1631        self.x_bins = x_bins
1632        self.y_bins = y_bins
1633
1634    def _fillup_pixels(self, image=None, weights=None):
1635        """
1636        Fill z values of the empty cells of 2d image matrix
1637        with the average over up-to next nearest neighbor points
1638
1639        :param image: (2d matrix with some zi = None)
1640
1641        :return: image (2d array )
1642
1643        :TODO: Find better way to do for-loop below
1644
1645        """
1646        # No image matrix given
1647        if (image is None or np.ndim(image) != 2
1648                or np.isfinite(image).all() 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
1679                            and 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
1683                            and 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
1687                            and 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
1691                            and 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:
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(r"\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(r"\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(r"%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(r"%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(r"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(r"\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(r"\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(r"\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(r"%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.