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

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 87a8a78 was 9c0f3c17, checked in by Ricardo Ferraz Leal <ricleal@…>, 8 years ago

After merge conflict

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