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

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 1a8e2e8e was 1a8e2e8e, checked in by lewis, 8 years ago

Don't reset graph axis on linear fit (fixes #603)

Also sets the default fit limits of the fit dialog to the zoomed in region
if values haven't previously been entered

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