source: sasview/src/sas/plottools/PlotPanel.py @ be34c71

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 be34c71 was 1170492, checked in by butler, 9 years ago

2 fixes:
1)Fix the save feature from plot panels — replace canvas.save with new
canvas.save_figure.

2)Fix printing problem .. kindof. Works but a bit ugly.

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