source: sasview/src/sas/sasgui/plottools/PlotPanel.py @ 6d4cf68

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

Allow repositioning of PlotPanel? text (fixes #445)

Text can be moved by clicking on it and dragging

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