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

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 dd5bf63 was dd5bf63, checked in by butler, 4 years ago

close #590. A lot of problems were caused by LineModel? and LinerFit?
having different equations (ax+b vs a+bx). Further errors in
calculations, particularly of uncertainties were fixed. The fact that
the fits to not account for smearing was verified and a warning added.
Code was also modified to update the qmin and qmax to match changes in
the transformed xmin xmax. Lots of documentation was added and the
fitdialog layout was cleaned up considerably. This is now usable though
the design of the user interface (and the whole design of linear fits)
could use a rethink.

  • Property mode set to 100644
File size: 75.0 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       
1217        if prop["xlabel"]:
1218            self.subplot.set_xlabel(r"$%s$"%prop["xlabel"])
1219        if prop["ylabel"]:
1220            self.subplot.set_ylabel(r"$%s$"%prop["ylabel"])
1221        self.subplot.set_title(prop["title"])
1222       
1223
1224    def clear(self):
1225        """Reset the plot"""
1226        # TODO: Redraw is brutal.  Render to a backing store and swap in
1227        # TODO: rather than redrawing on the fly.
1228        self.subplot.clear()
1229        self.subplot.hold(True)
1230
1231    def render(self):
1232        """Commit the plot after all objects are drawn"""
1233        # TODO: this is when the backing store should be swapped in.
1234        if self.legend_on:
1235            ax = self.subplot
1236            ax.texts = self.textList
1237            try:
1238                handles, labels = ax.get_legend_handles_labels()
1239                # sort them by labels
1240                hl = sorted(zip(handles, labels),
1241                            key=operator.itemgetter(1))
1242                handles2, labels2 = zip(*hl)
1243                self.line_collections_list = handles2
1244                self.legend = ax.legend(handles2, labels2,
1245                                        prop=FontProperties(size=10),
1246                                        loc=self.legendLoc)
1247                if self.legend != None:
1248                    self.legend.set_picker(self.legend_picker)
1249                    self.legend.set_axes(self.subplot)
1250                    self.legend.set_zorder(20)
1251
1252            except:
1253                self.legend = ax.legend(prop=FontProperties(size=10),
1254                                        loc=self.legendLoc)
1255
1256    def xaxis(self, label, units, font=None, color='black', t_font=None):
1257        """xaxis label and units.
1258
1259        Axis labels know about units.
1260
1261        We need to do this so that we can detect when axes are not
1262        commesurate.  Currently this is ignored other than for formatting
1263        purposes.
1264
1265        """
1266
1267        self.xcolor = color
1268        if units.count("{") > 0 and units.count("$") < 2:
1269            units = '$' + units + '$'
1270        if label.count("{") > 0 and label.count("$") < 2:
1271            label = '$' + label + '$'
1272        if units != "":
1273            label = label + " (" + units + ")"
1274        if font:
1275            self.subplot.set_xlabel(label, fontproperties=font, color=color)
1276            if t_font != None:
1277                for tick in self.subplot.xaxis.get_major_ticks():
1278                    tick.label.set_fontproperties(t_font)
1279                for line in self.subplot.xaxis.get_ticklines():
1280                    size = t_font.get_size()
1281                    line.set_markersize(size / 3)
1282        else:
1283            self.subplot.set_xlabel(label, color=color)
1284        pass
1285
1286    def yaxis(self, label, units, font=None, color='black', t_font=None):
1287        """yaxis label and units."""
1288        self.ycolor = color
1289        if units.count("{") > 0 and units.count("$") < 2:
1290            units = '$' + units + '$'
1291        if label.count("{") > 0 and label.count("$") < 2:
1292            label = '$' + label + '$'
1293        if units != "":
1294            label = label + " (" + units + ")"
1295        if font:
1296            self.subplot.set_ylabel(label, fontproperties=font, color=color)
1297            if t_font != None:
1298                for tick_label in self.subplot.get_yticklabels():
1299                    tick_label.set_fontproperties(t_font)
1300                for line in self.subplot.yaxis.get_ticklines():
1301                    size = t_font.get_size()
1302                    line.set_markersize(size / 3)
1303        else:
1304            self.subplot.set_ylabel(label, color=color)
1305        pass
1306
1307    def _connect_to_xlim(self, callback):
1308        """Bind the xlim change notification to the callback"""
1309        def process_xlim(axes):
1310            lo, hi = subplot.get_xlim()
1311            callback(lo, hi)
1312        self.subplot.callbacks.connect('xlim_changed', process_xlim)
1313
1314    def interactive_points(self, x, y, dx=None, dy=None, name='', color=0,
1315                           symbol=0, markersize=5, zorder=1, id=None,
1316                           label=None, hide_error=False):
1317        """Draw markers with error bars"""
1318        self.subplot.set_yscale('linear')
1319        self.subplot.set_xscale('linear')
1320        if id is None:
1321            id = name
1322        from plottable_interactor import PointInteractor
1323        p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
1324        if p.markersize != None:
1325            markersize = p.markersize
1326        p.points(x, y, dx=dx, dy=dy, color=color, symbol=symbol, zorder=zorder,
1327                 markersize=markersize, label=label, hide_error=hide_error)
1328
1329        self.subplot.set_yscale(self.yscale, nonposy='clip')
1330        self.subplot.set_xscale(self.xscale)
1331
1332    def interactive_curve(self, x, y, dy=None, name='', color=0,
1333                          symbol=0, zorder=1, id=None, label=None):
1334        """Draw markers with error bars"""
1335        self.subplot.set_yscale('linear')
1336        self.subplot.set_xscale('linear')
1337        if id is None:
1338            id = name
1339        from plottable_interactor import PointInteractor
1340        p = PointInteractor(self, self.subplot, zorder=zorder, id=id)
1341        p.curve(x, y, dy=dy, color=color, symbol=symbol, zorder=zorder,
1342                label=label)
1343
1344        self.subplot.set_yscale(self.yscale, nonposy='clip')
1345        self.subplot.set_xscale(self.xscale)
1346
1347    def plottable_selected(self, id):
1348        """
1349        Called to register a plottable as selected
1350        """
1351        #TODO: check that it really is in the list of plottables
1352        self.graph.selected_plottable = id
1353
1354    def points(self, x, y, dx=None, dy=None,
1355               color=0, symbol=0, marker_size=5, label=None,
1356               id=None, hide_error=False):
1357        """Draw markers with error bars"""
1358
1359        # Convert tuple (lo,hi) to array [(x-lo),(hi-x)]
1360        if dx != None and type(dx) == type(()):
1361            dx = nx.vstack((x - dx[0], dx[1] - x)).transpose()
1362        if dy != None and type(dy) == type(()):
1363            dy = nx.vstack((y - dy[0], dy[1] - y)).transpose()
1364        if dx == None and dy == None:
1365            self.subplot.plot(x, y, color=self._color(color),
1366                              marker=self._symbol(symbol),
1367                              markersize=marker_size,
1368                              linestyle='',
1369                              label=label)
1370        else:
1371            col = self._color(color)
1372            if hide_error:
1373                self.subplot.plot(x, y, color=col,
1374                                  marker=self._symbol(symbol),
1375                                  markersize=marker_size,
1376                                  linestyle='',
1377                                  label=label)
1378            else:
1379                self.subplot.errorbar(x, y, yerr=dy, xerr=None,
1380                                      ecolor=col, capsize=2, linestyle='',
1381                                      barsabove=False,
1382                                      mec=col, mfc=col,
1383                                      marker=self._symbol(symbol),
1384                                      markersize=marker_size,
1385                                      lolims=False, uplims=False,
1386                                      xlolims=False, xuplims=False, label=label)
1387
1388        self.subplot.set_yscale(self.yscale, nonposy='clip')
1389        self.subplot.set_xscale(self.xscale)
1390
1391    def _onToggleScale(self, event):
1392        """
1393        toggle axis and replot image
1394
1395        """
1396        zmin_2D_temp = self.zmin_2D
1397        zmax_2D_temp = self.zmax_2D
1398        if self.scale == 'log_{10}':
1399            self.scale = 'linear'
1400            if not self.zmin_2D is None:
1401                zmin_2D_temp = math.pow(10, self.zmin_2D)
1402            if not self.zmax_2D is None:
1403                zmax_2D_temp = math.pow(10, self.zmax_2D)
1404        else:
1405            self.scale = 'log_{10}'
1406            if not self.zmin_2D is None:
1407                # min log value: no log(negative)
1408                if self.zmin_2D <= 0:
1409                    zmin_2D_temp = -32
1410                else:
1411                    zmin_2D_temp = math.log10(self.zmin_2D)
1412            if not self.zmax_2D is None:
1413                zmax_2D_temp = math.log10(self.zmax_2D)
1414
1415        self.image(data=self.data, qx_data=self.qx_data,
1416                   qy_data=self.qy_data, xmin=self.xmin_2D,
1417                   xmax=self.xmax_2D,
1418                   ymin=self.ymin_2D, ymax=self.ymax_2D,
1419                   cmap=self.cmap, zmin=zmin_2D_temp,
1420                   zmax=zmax_2D_temp)
1421
1422    def image(self, data, qx_data, qy_data, xmin, xmax, ymin, ymax,
1423              zmin, zmax, color=0, symbol=0, markersize=0,
1424              label='data2D', cmap=DEFAULT_CMAP):
1425        """
1426        Render the current data
1427
1428        """
1429        self.data = data
1430        self.qx_data = qx_data
1431        self.qy_data = qy_data
1432        self.xmin_2D = xmin
1433        self.xmax_2D = xmax
1434        self.ymin_2D = ymin
1435        self.ymax_2D = ymax
1436        self.zmin_2D = zmin
1437        self.zmax_2D = zmax
1438        c = self._color(color)
1439        # If we don't have any data, skip.
1440        if self.data == None:
1441            return
1442        if self.data.ndim == 1:
1443            output = self._build_matrix()
1444        else:
1445            output = copy.deepcopy(self.data)
1446        # check scale
1447        if self.scale == 'log_{10}':
1448            try:
1449                if  self.zmin_2D <= 0  and len(output[output > 0]) > 0:
1450                    zmin_temp = self.zmin_2D
1451                    output[output > 0] = numpy.log10(output[output > 0])
1452                    #In log scale Negative values are not correct in general
1453                    #output[output<=0] = math.log(numpy.min(output[output>0]))
1454                elif self.zmin_2D <= 0:
1455                    zmin_temp = self.zmin_2D
1456                    output[output > 0] = numpy.zeros(len(output))
1457                    output[output <= 0] = -32
1458                else:
1459                    zmin_temp = self.zmin_2D
1460                    output[output > 0] = numpy.log10(output[output > 0])
1461                    #In log scale Negative values are not correct in general
1462                    #output[output<=0] = math.log(numpy.min(output[output>0]))
1463            except:
1464                #Too many problems in 2D plot with scale
1465                pass
1466
1467        else:
1468            zmin_temp = self.zmin_2D
1469        self.cmap = cmap
1470        if self.dimension != 3:
1471            #Re-adjust colorbar
1472            self.subplot.figure.subplots_adjust(left=0.2, right=.8, bottom=.2)
1473
1474            im = self.subplot.imshow(output, interpolation='nearest',
1475                                     origin='lower',
1476                                     vmin=zmin_temp, vmax=self.zmax_2D,
1477                                     cmap=self.cmap,
1478                                     extent=(self.xmin_2D, self.xmax_2D,
1479                                             self.ymin_2D, self.ymax_2D))
1480
1481            cbax = self.subplot.figure.add_axes([0.84, 0.2, 0.02, 0.7])
1482        else:
1483            # clear the previous 2D from memory
1484            # mpl is not clf, so we do
1485            self.subplot.figure.clear()
1486
1487            self.subplot.figure.subplots_adjust(left=0.1, right=.8, bottom=.1)
1488
1489            X = self.x_bins[0:-1]
1490            Y = self.y_bins[0:-1]
1491            X, Y = numpy.meshgrid(X, Y)
1492
1493            try:
1494                # mpl >= 1.0.0
1495                ax = self.subplot.figure.gca(projection='3d')
1496                #ax.disable_mouse_rotation()
1497                cbax = self.subplot.figure.add_axes([0.84, 0.1, 0.02, 0.8])
1498                if len(X) > 60:
1499                    ax.disable_mouse_rotation()
1500            except:
1501                # mpl < 1.0.0
1502                try:
1503                    from mpl_toolkits.mplot3d import Axes3D
1504                except:
1505                    logging.error("PlotPanel could not import Axes3D")
1506                self.subplot.figure.clear()
1507                ax = Axes3D(self.subplot.figure)
1508                if len(X) > 60:
1509                    ax.cla()
1510                cbax = None
1511            self.subplot.figure.canvas.resizing = False
1512            im = ax.plot_surface(X, Y, output, rstride=1, cstride=1, cmap=cmap,
1513                                 linewidth=0, antialiased=False)
1514            self.subplot.set_axis_off()
1515
1516        if cbax == None:
1517            ax.set_frame_on(False)
1518            cb = self.subplot.figure.colorbar(im, shrink=0.8, aspect=20)
1519        else:
1520            cb = self.subplot.figure.colorbar(im, cax=cbax)
1521        cb.update_bruteforce(im)
1522        cb.set_label('$' + self.scale + '$')
1523        if self.dimension != 3:
1524            self.figure.canvas.draw_idle()
1525        else:
1526            self.figure.canvas.draw()
1527
1528    def _build_matrix(self):
1529        """
1530        Build a matrix for 2d plot from a vector
1531        Returns a matrix (image) with ~ square binning
1532        Requirement: need 1d array formats of
1533        self.data, self.qx_data, and self.qy_data
1534        where each one corresponds to z, x, or y axis values
1535
1536        """
1537        # No qx or qy given in a vector format
1538        if self.qx_data == None or self.qy_data == None \
1539                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1:
1540            # do we need deepcopy here?
1541            return copy.deepcopy(self.data)
1542
1543        # maximum # of loops to fillup_pixels
1544        # otherwise, loop could never stop depending on data
1545        max_loop = 1
1546        # get the x and y_bin arrays.
1547        self._get_bins()
1548        # set zero to None
1549
1550        #Note: Can not use scipy.interpolate.Rbf:
1551        # 'cause too many data points (>10000)<=JHC.
1552        # 1d array to use for weighting the data point averaging
1553        #when they fall into a same bin.
1554        weights_data = numpy.ones([self.data.size])
1555        # get histogram of ones w/len(data); this will provide
1556        #the weights of data on each bins
1557        weights, xedges, yedges = numpy.histogram2d(x=self.qy_data,
1558                                                    y=self.qx_data,
1559                                                    bins=[self.y_bins, self.x_bins],
1560                                                    weights=weights_data)
1561        # get histogram of data, all points into a bin in a way of summing
1562        image, xedges, yedges = numpy.histogram2d(x=self.qy_data,
1563                                                  y=self.qx_data,
1564                                                  bins=[self.y_bins, self.x_bins],
1565                                                  weights=self.data)
1566        # Now, normalize the image by weights only for weights>1:
1567        # If weight == 1, there is only one data point in the bin so
1568        # that no normalization is required.
1569        image[weights > 1] = image[weights > 1] / weights[weights > 1]
1570        # Set image bins w/o a data point (weight==0) as None (was set to zero
1571        # by histogram2d.)
1572        image[weights == 0] = None
1573
1574        # Fill empty bins with 8 nearest neighbors only when at least
1575        #one None point exists
1576        loop = 0
1577
1578        # do while loop until all vacant bins are filled up up
1579        #to loop = max_loop
1580        while not(numpy.isfinite(image[weights == 0])).all():
1581            if loop >= max_loop:  # this protects never-ending loop
1582                break
1583            image = self._fillup_pixels(image=image, weights=weights)
1584            loop += 1
1585
1586        return image
1587
1588    def _get_bins(self):
1589        """
1590        get bins
1591        set x_bins and y_bins into self, 1d arrays of the index with
1592        ~ square binning
1593        Requirement: need 1d array formats of
1594        self.qx_data, and self.qy_data
1595        where each one corresponds to  x, or y axis values
1596        """
1597        # No qx or qy given in a vector format
1598        if self.qx_data == None or self.qy_data == None \
1599                or self.qx_data.ndim != 1 or self.qy_data.ndim != 1:
1600            # do we need deepcopy here?
1601            return copy.deepcopy(self.data)
1602
1603        # find max and min values of qx and qy
1604        xmax = self.qx_data.max()
1605        xmin = self.qx_data.min()
1606        ymax = self.qy_data.max()
1607        ymin = self.qy_data.min()
1608
1609        # calculate the range of qx and qy: this way, it is a little
1610        # more independent
1611        x_size = xmax - xmin
1612        y_size = ymax - ymin
1613
1614        # estimate the # of pixels on each axes
1615        npix_y = int(math.floor(math.sqrt(len(self.qy_data))))
1616        npix_x = int(math.floor(len(self.qy_data) / npix_y))
1617
1618        # bin size: x- & y-directions
1619        xstep = x_size / (npix_x - 1)
1620        ystep = y_size / (npix_y - 1)
1621
1622        # max and min taking account of the bin sizes
1623        xmax = xmax + xstep / 2.0
1624        xmin = xmin - xstep / 2.0
1625        ymax = ymax + ystep / 2.0
1626        ymin = ymin - ystep / 2.0
1627
1628        # store x and y bin centers in q space
1629        x_bins = numpy.linspace(xmin, xmax, npix_x)
1630        y_bins = numpy.linspace(ymin, ymax, npix_y)
1631
1632        #set x_bins and y_bins
1633        self.x_bins = x_bins
1634        self.y_bins = y_bins
1635
1636    def _fillup_pixels(self, image=None, weights=None):
1637        """
1638        Fill z values of the empty cells of 2d image matrix
1639        with the average over up-to next nearest neighbor points
1640
1641        :param image: (2d matrix with some zi = None)
1642
1643        :return: image (2d array )
1644
1645        :TODO: Find better way to do for-loop below
1646
1647        """
1648        # No image matrix given
1649        if image == None or numpy.ndim(image) != 2 \
1650                or numpy.isfinite(image).all() \
1651                or weights == None:
1652            return image
1653        # Get bin size in y and x directions
1654        len_y = len(image)
1655        len_x = len(image[1])
1656        temp_image = numpy.zeros([len_y, len_x])
1657        weit = numpy.zeros([len_y, len_x])
1658        # do for-loop for all pixels
1659        for n_y in range(len(image)):
1660            for n_x in range(len(image[1])):
1661                # find only null pixels
1662                if weights[n_y][n_x] > 0 or numpy.isfinite(image[n_y][n_x]):
1663                    continue
1664                else:
1665                    # find 4 nearest neighbors
1666                    # check where or not it is at the corner
1667                    if n_y != 0 and numpy.isfinite(image[n_y - 1][n_x]):
1668                        temp_image[n_y][n_x] += image[n_y - 1][n_x]
1669                        weit[n_y][n_x] += 1
1670                    if n_x != 0 and numpy.isfinite(image[n_y][n_x - 1]):
1671                        temp_image[n_y][n_x] += image[n_y][n_x - 1]
1672                        weit[n_y][n_x] += 1
1673                    if n_y != len_y - 1 and numpy.isfinite(image[n_y + 1][n_x]):
1674                        temp_image[n_y][n_x] += image[n_y + 1][n_x]
1675                        weit[n_y][n_x] += 1
1676                    if n_x != len_x - 1 and numpy.isfinite(image[n_y][n_x + 1]):
1677                        temp_image[n_y][n_x] += image[n_y][n_x + 1]
1678                        weit[n_y][n_x] += 1
1679                    # go 4 next nearest neighbors when no non-zero
1680                    # neighbor exists
1681                    if n_y != 0 and n_x != 0 and\
1682                         numpy.isfinite(image[n_y - 1][n_x - 1]):
1683                        temp_image[n_y][n_x] += image[n_y - 1][n_x - 1]
1684                        weit[n_y][n_x] += 1
1685                    if n_y != len_y - 1 and n_x != 0 and \
1686                        numpy.isfinite(image[n_y + 1][n_x - 1]):
1687                        temp_image[n_y][n_x] += image[n_y + 1][n_x - 1]
1688                        weit[n_y][n_x] += 1
1689                    if n_y != len_y and n_x != len_x - 1 and \
1690                        numpy.isfinite(image[n_y - 1][n_x + 1]):
1691                        temp_image[n_y][n_x] += image[n_y - 1][n_x + 1]
1692                        weit[n_y][n_x] += 1
1693                    if n_y != len_y - 1 and n_x != len_x - 1 and \
1694                        numpy.isfinite(image[n_y + 1][n_x + 1]):
1695                        temp_image[n_y][n_x] += image[n_y + 1][n_x + 1]
1696                        weit[n_y][n_x] += 1
1697
1698        # get it normalized
1699        ind = (weit > 0)
1700        image[ind] = temp_image[ind] / weit[ind]
1701
1702        return image
1703
1704    def curve(self, x, y, dy=None, color=0, symbol=0, label=None):
1705        """Draw a line on a graph, possibly with confidence intervals."""
1706        c = self._color(color)
1707        self.subplot.set_yscale('linear')
1708        self.subplot.set_xscale('linear')
1709
1710        self.subplot.plot(x, y, color=c, marker='',
1711                          linestyle='-', label=label)
1712        self.subplot.set_yscale(self.yscale)
1713        self.subplot.set_xscale(self.xscale)
1714
1715    def _color(self, c):
1716        """Return a particular colour"""
1717        return self.colorlist[c % len(self.colorlist)]
1718
1719    def _symbol(self, s):
1720        """Return a particular symbol"""
1721        return self.symbollist[s % len(self.symbollist)]
1722
1723    def _replot(self, remove_fit=False):
1724        """
1725        Rescale the plottables according to the latest
1726        user selection and update the plot
1727
1728        :param remove_fit: Fit line will be removed if True
1729
1730        """
1731        self.graph.reset_scale()
1732        self._onEVT_FUNC_PROPERTY(remove_fit=remove_fit)
1733        #TODO: Why do we have to have the following line?
1734        self.fit_result.reset_view()
1735        self.graph.render(self)
1736        self.subplot.figure.canvas.draw_idle()
1737
1738    def _onEVT_FUNC_PROPERTY(self, remove_fit=True, show=True):
1739        """
1740        Receive the x and y transformation from myDialog,
1741        Transforms x and y in View
1742        and set the scale
1743        """
1744        # The logic should be in the right order
1745        # Delete first, and then get the whole list...
1746        if remove_fit:
1747            self.graph.delete(self.fit_result)
1748        self.ly = None
1749        self.q_ctrl = None
1750        list = self.graph.returnPlottable()
1751        # Changing the scale might be incompatible with
1752        # currently displayed data (for instance, going
1753        # from ln to log when all plotted values have
1754        # negative natural logs).
1755        # Go linear and only change the scale at the end.
1756        self.set_xscale("linear")
1757        self.set_yscale("linear")
1758        _xscale = 'linear'
1759        _yscale = 'linear'
1760        for item in list:
1761            item.setLabel(self.xLabel, self.yLabel)
1762
1763            # control axis labels from the panel itself
1764            yname, yunits = item.get_yaxis()
1765            if self.yaxis_label != None:
1766                yname = self.yaxis_label
1767                yunits = self.yaxis_unit
1768            else:
1769                self.yaxis_label = yname
1770                self.yaxis_unit = yunits
1771            xname, xunits = item.get_xaxis()
1772            if self.xaxis_label != None:
1773                xname = self.xaxis_label
1774                xunits = self.xaxis_unit
1775            else:
1776                self.xaxis_label = xname
1777                self.xaxis_unit = xunits
1778            # Goes through all possible scales
1779            if self.xLabel == "x":
1780                item.transformX(transform.toX, transform.errToX)
1781                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
1782            if self.xLabel == "x^(2)":
1783                item.transformX(transform.toX2, transform.errToX2)
1784                xunits = convert_unit(2, xunits)
1785                self.graph._xaxis_transformed("%s^{2}" % xname, "%s" % xunits)
1786            if self.xLabel == "x^(4)":
1787                item.transformX(transform.toX4, transform.errToX4)
1788                xunits = convert_unit(4, xunits)
1789                self.graph._xaxis_transformed("%s^{4}" % xname, "%s" % xunits)
1790            if self.xLabel == "ln(x)":
1791                item.transformX(transform.toLogX, transform.errToLogX)
1792                self.graph._xaxis_transformed("\ln\\ %s" % xname, "%s" % xunits)
1793            if self.xLabel == "log10(x)":
1794                item.transformX(transform.toX_pos, transform.errToX_pos)
1795                _xscale = 'log'
1796                self.graph._xaxis_transformed("%s" % xname, "%s" % xunits)
1797            if self.xLabel == "log10(x^(4))":
1798                item.transformX(transform.toX4, transform.errToX4)
1799                xunits = convert_unit(4, xunits)
1800                self.graph._xaxis_transformed("%s^{4}" % xname, "%s" % xunits)
1801                _xscale = 'log'
1802            if self.yLabel == "ln(y)":
1803                item.transformY(transform.toLogX, transform.errToLogX)
1804                self.graph._yaxis_transformed("\ln\\ %s" % yname, "%s" % yunits)
1805            if self.yLabel == "y":
1806                item.transformY(transform.toX, transform.errToX)
1807                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
1808            if self.yLabel == "log10(y)":
1809                item.transformY(transform.toX_pos, transform.errToX_pos)
1810                _yscale = 'log'
1811                self.graph._yaxis_transformed("%s" % yname, "%s" % yunits)
1812            if self.yLabel == "y^(2)":
1813                item.transformY(transform.toX2, transform.errToX2)
1814                yunits = convert_unit(2, yunits)
1815                self.graph._yaxis_transformed("%s^{2}" % yname, "%s" % yunits)
1816            if self.yLabel == "1/y":
1817                item.transformY(transform.toOneOverX, transform.errOneOverX)
1818                yunits = convert_unit(-1, yunits)
1819                self.graph._yaxis_transformed("1/%s" % yname, "%s" % yunits)
1820            if self.yLabel == "y*x^(4)":
1821                item.transformY(transform.toYX4, transform.errToYX4)
1822                xunits = convert_unit(4, self.xaxis_unit)
1823                self.graph._yaxis_transformed("%s \ \ %s^{4}" % (yname, xname),
1824                                              "%s%s" % (yunits, xunits))
1825            if self.yLabel == "1/sqrt(y)":
1826                item.transformY(transform.toOneOverSqrtX,
1827                                transform.errOneOverSqrtX)
1828                yunits = convert_unit(-0.5, yunits)
1829                self.graph._yaxis_transformed("1/\sqrt{%s}" % yname,
1830                                              "%s" % yunits)
1831            if self.yLabel == "ln(y*x)":
1832                item.transformY(transform.toLogXY, transform.errToLogXY)
1833                self.graph._yaxis_transformed("\ln (%s \ \ %s)" % (yname, xname),
1834                                              "%s%s" % (yunits, self.xaxis_unit))
1835            if self.yLabel == "ln(y*x^(2))":
1836                item.transformY(transform.toLogYX2, transform.errToLogYX2)
1837                xunits = convert_unit(2, self.xaxis_unit)
1838                self.graph._yaxis_transformed("\ln (%s \ \ %s^{2})" % (yname, xname),
1839                                              "%s%s" % (yunits, xunits))
1840            if self.yLabel == "ln(y*x^(4))":
1841                item.transformY(transform.toLogYX4, transform.errToLogYX4)
1842                xunits = convert_unit(4, self.xaxis_unit)
1843                self.graph._yaxis_transformed("\ln (%s \ \ %s^{4})" % (yname, xname),
1844                                              "%s%s" % (yunits, xunits))
1845            if self.yLabel == "log10(y*x^(4))":
1846                item.transformY(transform.toYX4, transform.errToYX4)
1847                xunits = convert_unit(4, self.xaxis_unit)
1848                _yscale = 'log'
1849                self.graph._yaxis_transformed("%s \ \ %s^{4}" % (yname, xname),
1850                                              "%s%s" % (yunits, xunits))
1851            if self.viewModel == "Guinier lny vs x^(2)":
1852                item.transformX(transform.toX2, transform.errToX2)
1853                xunits = convert_unit(2, xunits)
1854                self.graph._xaxis_transformed("%s^{2}" % xname, "%s" % xunits)
1855                item.transformY(transform.toLogX, transform.errToLogX)
1856                self.graph._yaxis_transformed("\ln\ \ %s" % yname, "%s" % yunits)
1857            if self.viewModel == "Porod y*x^(4) vs x^(4)":
1858                item.transformX(transform.toX4, transform.errToX4)
1859                xunits = convert_unit(4, self.xaxis_unit)
1860                self.graph._xaxis_transformed("%s^{4}" % xname, "%s" % xunits)
1861                item.transformY(transform.toYX4, transform.errToYX4)
1862                self.graph._yaxis_transformed("%s \ \ %s^{4}" % (yname, xname),
1863                                              "%s%s" % (yunits, xunits))
1864            item.transformView()
1865
1866        # set new label and units
1867        yname = self.graph.prop["ylabel"]
1868        yunits = ''
1869        xname = self.graph.prop["xlabel"]
1870        xunits = ''
1871
1872        self.resetFitView()
1873        self.prevXtrans = self.xLabel
1874        self.prevYtrans = self.yLabel
1875        self.graph.render(self)
1876        self.set_xscale(_xscale)
1877        self.set_yscale(_yscale)
1878
1879        self.xaxis(xname, xunits, self.xaxis_font,
1880                   self.xaxis_color, self.xaxis_tick)
1881        self.yaxis(yname, yunits, self.yaxis_font,
1882                   self.yaxis_color, self.yaxis_tick)
1883        self.subplot.texts = self.textList
1884        if show:
1885            self.subplot.figure.canvas.draw_idle()
1886
1887    def onFitDisplay(self, tempx, tempy, xminView,
1888                     xmaxView, xmin, xmax, func):
1889        """
1890        Add a new plottable into the graph .In this case this plottable
1891        will be used to fit some data
1892
1893        :param tempx: The x data of fit line
1894        :param tempy: The y data of fit line
1895        :param xminView: the lower bound of fitting range
1896        :param xminView: the upper bound of  fitting range
1897        :param xmin: the lowest value of data to fit to the line
1898        :param xmax: the highest value of data to fit to the line
1899
1900        """
1901        # Saving value to redisplay in Fit Dialog when it is opened again
1902        self.Avalue, self.Bvalue, self.ErrAvalue, \
1903                      self.ErrBvalue, self.Chivalue = func
1904        self.xminView = xminView
1905        self.xmaxView = xmaxView
1906        self.xmin = xmin
1907        self.xmax = xmax
1908        #In case need to change the range of data plotted
1909        for item in self.graph.returnPlottable():
1910            item.onFitRange(None, None)
1911        # Create new data plottable with result
1912        self.fit_result.x = []
1913        self.fit_result.y = []
1914        self.fit_result.x = tempx
1915        self.fit_result.y = tempy
1916        self.fit_result.dx = None
1917        self.fit_result.dy = None
1918        #Load the view with the new values
1919        self.fit_result.reset_view()
1920        # Add the new plottable to the graph
1921        self.graph.add(self.fit_result)
1922        self.graph.render(self)
1923        self._offset_graph()
1924        self.subplot.figure.canvas.draw_idle()
1925
1926    def onChangeCaption(self, event):
1927        """
1928        """
1929        if self.parent == None:
1930            return
1931        # get current caption
1932        old_caption = self.window_caption
1933        # Get new caption dialog
1934        dial = LabelDialog(None, -1, 'Modify Window Title', old_caption)
1935        if dial.ShowModal() == wx.ID_OK:
1936            new_caption = dial.getText()
1937
1938            # send to guiframe to change the panel caption
1939            caption = self.parent.on_change_caption(self.window_name,
1940                                                    old_caption, new_caption)
1941
1942            # also set new caption in plot_panels list
1943            self.parent.plot_panels[self.uid].window_caption = caption
1944            # set new caption
1945            self.window_caption = caption
1946
1947        dial.Destroy()
1948
1949    def onResetGraph(self, event):
1950        """
1951        Reset the graph by plotting the full range of data
1952        """
1953        for item in self.graph.returnPlottable():
1954            item.onReset()
1955        self.graph.render(self)
1956        self._onEVT_FUNC_PROPERTY(False)
1957        self.is_zoomed = False
1958        self.toolbar.update()
1959
1960    def onPrint(self, event=None):
1961        self.toolbar.print_figure(event)
1962
1963    def onPrinterSetup(self, event=None):
1964        """
1965        """
1966        self.canvas.Printer_Setup(event=event)
1967        self.Update()
1968
1969    def onPrinterPreview(self, event=None):
1970        """
1971        Matplotlib camvas can no longer print itself.  Thus need to do
1972        everything ourselves: need to create a printpreview frame to to
1973        see the preview but needs a parent frame object.  Also needs a
1974        printout object (just as any printing task).
1975        """
1976        try:
1977            #check if parent is a frame.  If not keep getting the higher
1978            #parent till we find a frame
1979            _plot = self
1980            while not isinstance(_plot, wx.Frame):
1981                _plot = _plot.GetParent()
1982                assert _plot is not None
1983
1984            #now create the printpeview object
1985            _preview = wx.PrintPreview(PlotPrintout(self.canvas),
1986                                       PlotPrintout(self.canvas))
1987            #and tie it to a printpreview frame then show it
1988            _frame = wx.PreviewFrame(_preview, _plot, "Print Preview", wx.Point(100, 100), wx.Size(600, 650))
1989            _frame.Centre(wx.BOTH)
1990            _frame.Initialize()
1991            _frame.Show(True)
1992        except:
1993            traceback.print_exc()
1994            pass
1995
1996    def OnCopyFigureMenu(self, evt):
1997        """
1998        Copy the current figure to clipboard
1999        """
2000        try:
2001            self.toolbar.copy_figure(self.canvas)
2002        except:
2003            print "Error in copy Image"
2004
2005
2006#---------------------------------------------------------------
2007class NoRepaintCanvas(FigureCanvasWxAgg):
2008    """
2009    We subclass FigureCanvasWxAgg, overriding the _onPaint method, so that
2010    the draw method is only called for the first two paint events. After that,
2011    the canvas will only be redrawn when it is resized.
2012
2013    """
2014    def __init__(self, *args, **kwargs):
2015        """
2016        """
2017        FigureCanvasWxAgg.__init__(self, *args, **kwargs)
2018        self._drawn = 0
2019
2020    def _onPaint(self, evt):
2021        """
2022        Called when wxPaintEvt is generated
2023
2024        """
2025        if not self._isRealized:
2026            self.realize()
2027        if self._drawn < 2:
2028            self.draw(repaint=False)
2029            self._drawn += 1
2030        self.gui_repaint(drawDC=wx.PaintDC(self))
Note: See TracBrowser for help on using the repository browser.