source: sasview/src/sas/sasgui/plottools/PlotPanel.py @ 2f85af7

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 2f85af7 was 7432acb, checked in by andyfaff, 8 years ago

MAINT: search+replace '!= None' by 'is not None'

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