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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 20fa5fe was a1b8fee, checked in by andyfaff, 8 years ago

MAINT: from future import print_function

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