source: sasview/src/sas/sasgui/plottools/PlotPanel.py @ 5251ec6

magnetic_scattrelease-4.2.2ticket-1009ticket-1249
Last change on this file since 5251ec6 was 5251ec6, checked in by Paul Kienzle <pkienzle@…>, 5 years ago

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 72.9 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        self.data = data
1437        self.qx_data = qx_data
1438        self.qy_data = qy_data
1439        self.xmin_2D = xmin
1440        self.xmax_2D = xmax
1441        self.ymin_2D = ymin
1442        self.ymax_2D = ymax
1443        self.zmin_2D = zmin
1444        self.zmax_2D = zmax
1445        c = self._color(color)
1446        # If we don't have any data, skip.
1447        if self.data is None:
1448            return
1449        if self.data.ndim == 1:
1450            output = self._build_matrix()
1451        else:
1452            output = copy.deepcopy(self.data)
1453        # check scale
1454        if self.scale == 'log_{10}':
1455            try:
1456                if  self.zmin_2D <= 0  and len(output[output > 0]) > 0:
1457                    zmin_temp = self.zmin_2D
1458                    output[output > 0] = np.log10(output[output > 0])
1459                    #In log scale Negative values are not correct in general
1460                    #output[output<=0] = math.log(np.min(output[output>0]))
1461                elif self.zmin_2D <= 0:
1462                    zmin_temp = self.zmin_2D
1463                    output[output > 0] = np.zeros(len(output))
1464                    output[output <= 0] = -32
1465                else:
1466                    zmin_temp = self.zmin_2D
1467                    output[output > 0] = np.log10(output[output > 0])
1468                    #In log scale Negative values are not correct in general
1469                    #output[output<=0] = math.log(np.min(output[output>0]))
1470            except:
1471                #Too many problems in 2D plot with scale
1472                pass
1473
1474        else:
1475            zmin_temp = self.zmin_2D
1476        self.cmap = cmap
1477        if self.dimension != 3:
1478            #Re-adjust colorbar
1479            self.subplot.figure.subplots_adjust(left=0.2, right=.8, bottom=.2)
1480
1481            im = self.subplot.imshow(output, interpolation='nearest',
1482                                     origin='lower',
1483                                     vmin=zmin_temp, vmax=self.zmax_2D,
1484                                     cmap=self.cmap,
1485                                     extent=(self.xmin_2D, self.xmax_2D,
1486                                             self.ymin_2D, self.ymax_2D))
1487
1488            cbax = self.subplot.figure.add_axes([0.84, 0.2, 0.02, 0.7])
1489        else:
1490            # clear the previous 2D from memory
1491            # mpl is not clf, so we do
1492            self.subplot.figure.clear()
1493
1494            self.subplot.figure.subplots_adjust(left=0.1, right=.8, bottom=.1)
1495
1496            X = self.x_bins[0:-1]
1497            Y = self.y_bins[0:-1]
1498            X, Y = np.meshgrid(X, Y)
1499
1500            try:
1501                # mpl >= 1.0.0
1502                ax = self.subplot.figure.gca(projection='3d')
1503                #ax.disable_mouse_rotation()
1504                cbax = self.subplot.figure.add_axes([0.84, 0.1, 0.02, 0.8])
1505                if len(X) > 60:
1506                    ax.disable_mouse_rotation()
1507            except:
1508                # mpl < 1.0.0
1509                try:
1510                    from mpl_toolkits.mplot3d import Axes3D
1511                except:
1512                    logger.error("PlotPanel could not import Axes3D")
1513                self.subplot.figure.clear()
1514                ax = Axes3D(self.subplot.figure)
1515                if len(X) > 60:
1516                    ax.cla()
1517                cbax = None
1518            self.subplot.figure.canvas.resizing = False
1519            im = ax.plot_surface(X, Y, output, rstride=1, cstride=1, cmap=cmap,
1520                                 linewidth=0, antialiased=False)
1521            self.subplot.set_axis_off()
1522
1523        if cbax is None:
1524            ax.set_frame_on(False)
1525            cb = self.subplot.figure.colorbar(im, shrink=0.8, aspect=20)
1526        else:
1527            cb = self.subplot.figure.colorbar(im, cax=cbax)
1528        cb.update_bruteforce(im)
1529        cb.set_label('$' + self.scale + '$')
1530        if self.dimension != 3:
1531            self.figure.canvas.draw_idle()
1532        else:
1533            self.figure.canvas.draw()
1534
1535    def _build_matrix(self):
1536        """
1537        Build a matrix for 2d plot from a vector
1538        Returns a matrix (image) with ~ square binning
1539        Requirement: need 1d array formats of
1540        self.data, self.qx_data, and self.qy_data
1541        where each one corresponds to z, x, or y axis values
1542
1543        """
1544        # No qx or qy given in a vector format
1545        if self.qx_data is None or self.qy_data is None \
1546                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1:
1547            # do we need deepcopy here?
1548            return copy.deepcopy(self.data)
1549
1550        # maximum # of loops to fillup_pixels
1551        # otherwise, loop could never stop depending on data
1552        max_loop = 1
1553        # get the x and y_bin arrays.
1554        self._get_bins()
1555        # set zero to None
1556
1557        #Note: Can not use scipy.interpolate.Rbf:
1558        # 'cause too many data points (>10000)<=JHC.
1559        # 1d array to use for weighting the data point averaging
1560        #when they fall into a same bin.
1561        weights_data = np.ones([self.data.size])
1562        # get histogram of ones w/len(data); this will provide
1563        #the weights of data on each bins
1564        weights, xedges, yedges = np.histogram2d(x=self.qy_data,
1565                                                    y=self.qx_data,
1566                                                    bins=[self.y_bins, self.x_bins],
1567                                                    weights=weights_data)
1568        # get histogram of data, all points into a bin in a way of summing
1569        image, xedges, yedges = np.histogram2d(x=self.qy_data,
1570                                                  y=self.qx_data,
1571                                                  bins=[self.y_bins, self.x_bins],
1572                                                  weights=self.data)
1573        # Now, normalize the image by weights only for weights>1:
1574        # If weight == 1, there is only one data point in the bin so
1575        # that no normalization is required.
1576        image[weights > 1] = image[weights > 1] / weights[weights > 1]
1577        # Set image bins w/o a data point (weight==0) as None (was set to zero
1578        # by histogram2d.)
1579        image[weights == 0] = None
1580
1581        # Fill empty bins with 8 nearest neighbors only when at least
1582        #one None point exists
1583        loop = 0
1584
1585        # do while loop until all vacant bins are filled up up
1586        #to loop = max_loop
1587        while not(np.isfinite(image[weights == 0])).all():
1588            if loop >= max_loop:  # this protects never-ending loop
1589                break
1590            image = self._fillup_pixels(image=image, weights=weights)
1591            loop += 1
1592
1593        return image
1594
1595    def _get_bins(self):
1596        """
1597        get bins
1598        set x_bins and y_bins into self, 1d arrays of the index with
1599        ~ square binning
1600        Requirement: need 1d array formats of
1601        self.qx_data, and self.qy_data
1602        where each one corresponds to  x, or y axis values
1603        """
1604        # No qx or qy given in a vector format
1605        if self.qx_data is None or self.qy_data is None \
1606                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1:
1607            # do we need deepcopy here?
1608            return copy.deepcopy(self.data)
1609
1610        # find max and min values of qx and qy
1611        xmax = self.qx_data.max()
1612        xmin = self.qx_data.min()
1613        ymax = self.qy_data.max()
1614        ymin = self.qy_data.min()
1615
1616        # calculate the range of qx and qy: this way, it is a little
1617        # more independent
1618        x_size = xmax - xmin
1619        y_size = ymax - ymin
1620
1621        # estimate the # of pixels on each axes
1622        npix_y = int(math.floor(math.sqrt(len(self.qy_data))))
1623        npix_x = int(math.floor(len(self.qy_data) / npix_y))
1624
1625        # bin size: x- & y-directions
1626        xstep = x_size / (npix_x - 1)
1627        ystep = y_size / (npix_y - 1)
1628
1629        # max and min taking account of the bin sizes
1630        xmax = xmax + xstep / 2.0
1631        xmin = xmin - xstep / 2.0
1632        ymax = ymax + ystep / 2.0
1633        ymin = ymin - ystep / 2.0
1634
1635        # store x and y bin centers in q space
1636        x_bins = np.linspace(xmin, xmax, npix_x)
1637        y_bins = np.linspace(ymin, ymax, npix_y)
1638
1639        #set x_bins and y_bins
1640        self.x_bins = x_bins
1641        self.y_bins = y_bins
1642
1643    def _fillup_pixels(self, image=None, weights=None):
1644        """
1645        Fill z values of the empty cells of 2d image matrix
1646        with the average over up-to next nearest neighbor points
1647
1648        :param image: (2d matrix with some zi = None)
1649
1650        :return: image (2d array )
1651
1652        :TODO: Find better way to do for-loop below
1653
1654        """
1655        # No image matrix given
1656        if image is None or np.ndim(image) != 2 \
1657                or np.isfinite(image).all() \
1658                or weights is None:
1659            return image
1660        # Get bin size in y and x directions
1661        len_y = len(image)
1662        len_x = len(image[1])
1663        temp_image = np.zeros([len_y, len_x])
1664        weit = np.zeros([len_y, len_x])
1665        # do for-loop for all pixels
1666        for n_y in range(len(image)):
1667            for n_x in range(len(image[1])):
1668                # find only null pixels
1669                if weights[n_y][n_x] > 0 or np.isfinite(image[n_y][n_x]):
1670                    continue
1671                else:
1672                    # find 4 nearest neighbors
1673                    # check where or not it is at the corner
1674                    if n_y != 0 and np.isfinite(image[n_y - 1][n_x]):
1675                        temp_image[n_y][n_x] += image[n_y - 1][n_x]
1676                        weit[n_y][n_x] += 1
1677                    if n_x != 0 and np.isfinite(image[n_y][n_x - 1]):
1678                        temp_image[n_y][n_x] += image[n_y][n_x - 1]
1679                        weit[n_y][n_x] += 1
1680                    if n_y != len_y - 1 and np.isfinite(image[n_y + 1][n_x]):
1681                        temp_image[n_y][n_x] += image[n_y + 1][n_x]
1682                        weit[n_y][n_x] += 1
1683                    if n_x != len_x - 1 and np.isfinite(image[n_y][n_x + 1]):
1684                        temp_image[n_y][n_x] += image[n_y][n_x + 1]
1685                        weit[n_y][n_x] += 1
1686                    # go 4 next nearest neighbors when no non-zero
1687                    # neighbor exists
1688                    if n_y != 0 and n_x != 0 and \
1689                            np.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 != 0 and \
1693                            np.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                    if n_y != len_y and n_x != len_x - 1 and \
1697                            np.isfinite(image[n_y - 1][n_x + 1]):
1698                        temp_image[n_y][n_x] += image[n_y - 1][n_x + 1]
1699                        weit[n_y][n_x] += 1
1700                    if n_y != len_y - 1 and n_x != len_x - 1 and \
1701                            np.isfinite(image[n_y + 1][n_x + 1]):
1702                        temp_image[n_y][n_x] += image[n_y + 1][n_x + 1]
1703                        weit[n_y][n_x] += 1
1704
1705        # get it normalized
1706        ind = (weit > 0)
1707        image[ind] = temp_image[ind] / weit[ind]
1708
1709        return image
1710
1711    def curve(self, x, y, dy=None, color=0, symbol=0, label=None):
1712        """Draw a line on a graph, possibly with confidence intervals."""
1713        c = self._color(color)
1714        self.subplot.set_yscale('linear')
1715        self.subplot.set_xscale('linear')
1716
1717        self.subplot.plot(x, y, color=c, marker='',
1718                          linestyle='-', label=label)
1719        self.subplot.set_yscale(self.yscale)
1720        self.subplot.set_xscale(self.xscale)
1721
1722    def _color(self, c):
1723        """Return a particular colour"""
1724        return self.colorlist[c % len(self.colorlist)]
1725
1726    def _symbol(self, s):
1727        """Return a particular symbol"""
1728        return self.symbollist[s % len(self.symbollist)]
1729
1730    def _replot(self, remove_fit=False):
1731        """
1732        Rescale the plottables according to the latest
1733        user selection and update the plot
1734
1735        :param remove_fit: Fit line will be removed if True
1736
1737        """
1738        self.graph.reset_scale()
1739        self._onEVT_FUNC_PROPERTY(remove_fit=remove_fit)
1740        #TODO: Why do we have to have the following line?
1741        self.fit_result.reset_view()
1742        self.graph.render(self)
1743        self.subplot.figure.canvas.draw_idle()
1744
1745    def _onEVT_FUNC_PROPERTY(self, remove_fit=True, show=True):
1746        """
1747        Receive the x and y transformation from myDialog,
1748        Transforms x and y in View
1749        and set the scale
1750        """
1751        # The logic should be in the right order
1752        # Delete first, and then get the whole list...
1753        if remove_fit:
1754            self.graph.delete(self.fit_result)
1755            if hasattr(self, 'plots'):
1756                if 'fit' in self.plots:
1757                    del self.plots['fit']
1758        self.ly = None
1759        self.q_ctrl = None
1760        list = self.graph.returnPlottable()
1761        # Changing the scale might be incompatible with
1762        # currently displayed data (for instance, going
1763        # from ln to log when all plotted values have
1764        # negative natural logs).
1765        # Go linear and only change the scale at the end.
1766        self.set_xscale("linear")
1767        self.set_yscale("linear")
1768        _xscale = 'linear'
1769        _yscale = 'linear'
1770        for item in list:
1771            if item.id == 'fit':
1772                continue
1773            item.setLabel(self.xLabel, self.yLabel)
1774            # control axis labels from the panel itself
1775            yname, yunits = item.get_yaxis()
1776            if self.yaxis_label is not None:
1777                yname = self.yaxis_label
1778                yunits = self.yaxis_unit
1779            else:
1780                self.yaxis_label = yname
1781                self.yaxis_unit = yunits
1782            xname, xunits = item.get_xaxis()
1783            if self.xaxis_label is not None:
1784                xname = self.xaxis_label
1785                xunits = self.xaxis_unit
1786            else:
1787                self.xaxis_label = xname
1788                self.xaxis_unit = xunits
1789            # Goes through all possible scales
1790            if self.xLabel == "x":
1791                item.transformX(transform.toX, transform.errToX)
1792                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
1793            if self.xLabel == "x^(2)":
1794                item.transformX(transform.toX2, transform.errToX2)
1795                xunits = convert_unit(2, xunits)
1796                self.graph._xaxis_transformed("%s^{2}" % xname, "%s" % xunits)
1797            if self.xLabel == "x^(4)":
1798                item.transformX(transform.toX4, transform.errToX4)
1799                xunits = convert_unit(4, xunits)
1800                self.graph._xaxis_transformed("%s^{4}" % xname, "%s" % xunits)
1801            if self.xLabel == "ln(x)":
1802                item.transformX(transform.toLogX, transform.errToLogX)
1803                self.graph._xaxis_transformed("\ln{(%s)}" % xname, "%s" % xunits)
1804            if self.xLabel == "log10(x)":
1805                item.transformX(transform.toX_pos, transform.errToX_pos)
1806                _xscale = 'log'
1807                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
1808            if self.xLabel == "log10(x^(4))":
1809                item.transformX(transform.toX4, transform.errToX4)
1810                xunits = convert_unit(4, xunits)
1811                self.graph._xaxis_transformed("%s^{4}" % xname, "%s" % xunits)
1812                _xscale = 'log'
1813            if self.yLabel == "ln(y)":
1814                item.transformY(transform.toLogX, transform.errToLogX)
1815                self.graph._yaxis_transformed("\ln{(%s)}" % yname, "%s" % yunits)
1816            if self.yLabel == "y":
1817                item.transformY(transform.toX, transform.errToX)
1818                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
1819            if self.yLabel == "log10(y)":
1820                item.transformY(transform.toX_pos, transform.errToX_pos)
1821                _yscale = 'log'
1822                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
1823            if self.yLabel == "y^(2)":
1824                item.transformY(transform.toX2, transform.errToX2)
1825                yunits = convert_unit(2, yunits)
1826                self.graph._yaxis_transformed("%s^{2}" % yname, "%s" % yunits)
1827            if self.yLabel == "1/y":
1828                item.transformY(transform.toOneOverX, transform.errOneOverX)
1829                yunits = convert_unit(-1, yunits)
1830                self.graph._yaxis_transformed("1/%s" % yname, "%s" % yunits)
1831            if self.yLabel == "y*x^(2)":
1832                item.transformY(transform.toYX2, transform.errToYX2)
1833                xunits = convert_unit(2, self.xaxis_unit)
1834                self.graph._yaxis_transformed("%s \ \ %s^{2}" % (yname, xname),
1835                                              "%s%s" % (yunits, xunits))
1836            if self.yLabel == "y*x^(4)":
1837                item.transformY(transform.toYX4, transform.errToYX4)
1838                xunits = convert_unit(4, self.xaxis_unit)
1839                self.graph._yaxis_transformed("%s \ \ %s^{4}" % (yname, xname),
1840                                              "%s%s" % (yunits, xunits))
1841            if self.yLabel == "1/sqrt(y)":
1842                item.transformY(transform.toOneOverSqrtX,
1843                                transform.errOneOverSqrtX)
1844                yunits = convert_unit(-0.5, yunits)
1845                self.graph._yaxis_transformed("1/\sqrt{%s}" % yname,
1846                                              "%s" % yunits)
1847            if self.yLabel == "ln(y*x)":
1848                item.transformY(transform.toLogXY, transform.errToLogXY)
1849                self.graph._yaxis_transformed("\ln{(%s \ \ %s)}" % (yname, xname),
1850                                              "%s%s" % (yunits, self.xaxis_unit))
1851            if self.yLabel == "ln(y*x^(2))":
1852                item.transformY(transform.toLogYX2, transform.errToLogYX2)
1853                xunits = convert_unit(2, self.xaxis_unit)
1854                self.graph._yaxis_transformed("\ln (%s \ \ %s^{2})" % (yname, xname),
1855                                              "%s%s" % (yunits, xunits))
1856            if self.yLabel == "ln(y*x^(4))":
1857                item.transformY(transform.toLogYX4, transform.errToLogYX4)
1858                xunits = convert_unit(4, self.xaxis_unit)
1859                self.graph._yaxis_transformed("\ln (%s \ \ %s^{4})" % (yname, xname),
1860                                              "%s%s" % (yunits, xunits))
1861            if self.yLabel == "log10(y*x^(4))":
1862                item.transformY(transform.toYX4, transform.errToYX4)
1863                xunits = convert_unit(4, self.xaxis_unit)
1864                _yscale = 'log'
1865                self.graph._yaxis_transformed("%s \ \ %s^{4}" % (yname, xname),
1866                                              "%s%s" % (yunits, xunits))
1867            item.transformView()
1868
1869        # set new label and units
1870        yname = self.graph.prop["ylabel"]
1871        yunits = ''
1872        xname = self.graph.prop["xlabel"]
1873        xunits = ''
1874
1875        self.resetFitView()
1876        self.prevXtrans = self.xLabel
1877        self.prevYtrans = self.yLabel
1878        self.graph.render(self)
1879        self.set_xscale(_xscale)
1880        self.set_yscale(_yscale)
1881
1882        self.xaxis(xname, xunits, self.xaxis_font,
1883                   self.xaxis_color, self.xaxis_tick)
1884        self.yaxis(yname, yunits, self.yaxis_font,
1885                   self.yaxis_color, self.yaxis_tick)
1886        self.subplot.texts = self.textList
1887        if show:
1888            self.subplot.figure.canvas.draw_idle()
1889
1890    def onFitDisplay(self, tempx, tempy, xminView,
1891                     xmaxView, xmin, xmax, func):
1892        """
1893        Add a new plottable into the graph .In this case this plottable
1894        will be used to fit some data
1895
1896        :param tempx: The x data of fit line
1897        :param tempy: The y data of fit line
1898        :param xminView: the lower bound of fitting range
1899        :param xminView: the upper bound of  fitting range
1900        :param xmin: the lowest value of data to fit to the line
1901        :param xmax: the highest value of data to fit to the line
1902
1903        """
1904        xlim = self.subplot.get_xlim()
1905        ylim = self.subplot.get_ylim()
1906
1907        # Saving value to redisplay in Fit Dialog when it is opened again
1908        self.Avalue, self.Bvalue, self.ErrAvalue, \
1909                      self.ErrBvalue, self.Chivalue = func
1910        self.xminView = xminView
1911        self.xmaxView = xmaxView
1912        self.xmin = xmin
1913        self.xmax = xmax
1914        #In case need to change the range of data plotted
1915        for item in self.graph.returnPlottable():
1916            item.onFitRange(None, None)
1917        # Create new data plottable with result
1918        self.fit_result.x = []
1919        self.fit_result.y = []
1920        self.fit_result.x = tempx
1921        self.fit_result.y = tempy
1922        self.fit_result.dx = None
1923        self.fit_result.dy = None
1924        #Load the view with the new values
1925        self.fit_result.reset_view()
1926        # Add the new plottable to the graph
1927        self.graph.add(self.fit_result)
1928        self.graph.render(self)
1929        self._offset_graph()
1930        if hasattr(self, 'plots'):
1931            # Used by Plotter1D
1932            fit_id = 'fit'
1933            self.fit_result.id = fit_id
1934            self.fit_result.title = 'Fit'
1935            self.fit_result.name = 'Fit'
1936            self.plots[fit_id] = self.fit_result
1937        self.subplot.set_xlim(xlim)
1938        self.subplot.set_ylim(ylim)
1939        self.subplot.figure.canvas.draw_idle()
1940
1941    def onChangeCaption(self, event):
1942        """
1943        """
1944        if self.parent is None:
1945            return
1946        # get current caption
1947        old_caption = self.window_caption
1948        # Get new caption dialog
1949        dial = LabelDialog(None, -1, 'Modify Window Title', old_caption)
1950        if dial.ShowModal() == wx.ID_OK:
1951            new_caption = dial.getText()
1952
1953            # send to guiframe to change the panel caption
1954            caption = self.parent.on_change_caption(self.window_name,
1955                                                    old_caption, new_caption)
1956
1957            # also set new caption in plot_panels list
1958            self.parent.plot_panels[self.uid].window_caption = caption
1959            # set new caption
1960            self.window_caption = caption
1961
1962        dial.Destroy()
1963
1964    def onResetGraph(self, event):
1965        """
1966        Reset the graph by plotting the full range of data
1967        """
1968        for item in self.graph.returnPlottable():
1969            item.onReset()
1970        self.graph.render(self)
1971        self._onEVT_FUNC_PROPERTY(False)
1972        self.is_zoomed = False
1973        self.toolbar.update()
1974
1975    def onPrint(self, event=None):
1976        self.toolbar.print_figure(event)
1977
1978    def onPrinterSetup(self, event=None):
1979        """
1980        """
1981        self.canvas.Printer_Setup(event=event)
1982        self.Update()
1983
1984    def onPrinterPreview(self, event=None):
1985        """
1986        Matplotlib camvas can no longer print itself.  Thus need to do
1987        everything ourselves: need to create a printpreview frame to to
1988        see the preview but needs a parent frame object.  Also needs a
1989        printout object (just as any printing task).
1990        """
1991        try:
1992            #check if parent is a frame.  If not keep getting the higher
1993            #parent till we find a frame
1994            _plot = self
1995            while not isinstance(_plot, wx.Frame):
1996                _plot = _plot.GetParent()
1997                assert _plot is not None
1998
1999            #now create the printpeview object
2000            _preview = wx.PrintPreview(PlotPrintout(self.canvas),
2001                                       PlotPrintout(self.canvas))
2002            #and tie it to a printpreview frame then show it
2003            _frame = wx.PreviewFrame(_preview, _plot, "Print Preview", wx.Point(100, 100), wx.Size(600, 650))
2004            _frame.Centre(wx.BOTH)
2005            _frame.Initialize()
2006            _frame.Show(True)
2007        except:
2008            traceback.print_exc()
2009            pass
2010
2011    def OnCopyFigureMenu(self, evt):
2012        """
2013        Copy the current figure to clipboard
2014        """
2015        try:
2016            self.toolbar.copy_figure(self.canvas)
2017        except:
2018            print("Error in copy Image")
2019
2020
2021#---------------------------------------------------------------
2022class NoRepaintCanvas(FigureCanvasWxAgg):
2023    """
2024    We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that
2025    the draw method is only called for the first two paint events. After that,
2026    the canvas will only be redrawn when it is resized.
2027
2028    """
2029    def __init__(self, *args, **kwargs):
2030        """
2031        """
2032        FigureCanvasWxAgg.__init__(self, *args, **kwargs)
2033        self._drawn = 0
2034
2035    def _onPaint(self, evt):
2036        """
2037        Called when wxPaintEvt is generated
2038
2039        """
2040        if not self._isRealized:
2041            self.realize()
2042        if self._drawn < 2:
2043            self.draw(repaint=False)
2044            self._drawn += 1
2045        self.gui_repaint(drawDC=wx.PaintDC(self))
Note: See TracBrowser for help on using the repository browser.