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

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.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since f2f6af9 was f2f6af9, checked in by lewis, 8 years ago

Set ylim when scale changed to Porod

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