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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 9f25bce was cad617b, checked in by Piotr Rozyczko <rozyczko@…>, 8 years ago

Code review issues, fixing matplotlib behaviour in unit testing, minor fixes.

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