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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since a51fed16 was 16b769b, checked in by butler, 8 years ago

Fixes - copy plot to clipboard was silently failing since July 2015
due to failure to pass canvas argument inside a try except.

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