source: sasview/src/sas/sasgui/plottools/PlotPanel.py @ 82d88d5

magnetic_scattrelease-4.2.2ticket-1009ticket-1249
Last change on this file since 82d88d5 was 82d88d5, checked in by Paul Kienzle <pkienzle@…>, 6 months ago

Merge branch 'master' into py37-sasgui

  • 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.GetSizeTuple()
342        fw, fh = self.canvas.GetSizeTuple()
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.GetPositionTuple()
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, \
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:
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.