]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/plot.py
frame should call skip on child focus event to give owner a chance
[wxWidgets.git] / wxPython / wx / lib / plot.py
index cf5df17ca106d82c44a0489e0515e4fc03b3ad4e..59f7f37c615a64feb7fc0fcb93b8207143310d62 100644 (file)
 #   - Modified line end shape from round to square.
 #   - Removed FloatDCWrapper for conversion to ints and ints in arguments
 #
-
+# Oct 15, 2004  Gordon Williams (g_will@cyberus.ca)
+#   - Imported modules given leading underscore to name.
+#   - Added Cursor Line Tracking and User Point Labels. 
+#   - Demo for Cursor Line Tracking and Point Labels.
+#   - Size of plot preview frame adjusted to show page better.
+#   - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas.
+#   - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve)
+#       can be in either user coords or screen coords.
+#   
+#
 
 """
 This is a simple light weight plotting module that can be used with
@@ -79,24 +88,27 @@ Zooming controls with mouse (when enabled):
     Right mouse click - zoom out centred on click location.
 """
 
-import  string
-import  time
+import  string as _string
+import  time as _time
 import  wx
 
-# Needs Numeric or numarray
+# Needs Numeric or numarray or NumPy
 try:
-    import Numeric
+    import numpy as _Numeric
 except:
     try:
-        import numarray as Numeric  #if numarray is used it is renamed Numeric
+        import numarray as _Numeric  #if numarray is used it is renamed Numeric
     except:
-        msg= """
-        This module requires the Numeric or numarray module,
-        which could not be imported.  It probably is not installed
-        (it's not part of the standard Python distribution). See the
-        Python site (http://www.python.org) for information on
-        downloading source or binaries."""
-        raise ImportError, "Numeric or numarray not found. \n" + msg
+        try:
+            import Numeric as _Numeric
+        except:
+            msg= """
+            This module requires the Numeric/numarray or NumPy module,
+            which could not be imported.  It probably is not installed
+            (it's not part of the standard Python distribution). See the
+            Numeric Python site (http://numpy.scipy.org) for information on
+            downloading source or binaries."""
+            raise ImportError, "Numeric,numarray or NumPy not found. \n" + msg
 
 
 
@@ -109,7 +121,8 @@ class PolyPoints:
     """
 
     def __init__(self, points, attr):
-        self.points = Numeric.array(points)
+        self._points = _Numeric.array(points).astype(_Numeric.Float64)
+        self._logscale = (False, False)
         self.currentScale= (1,1)
         self.currentShift= (0,0)
         self.scaled = self.points
@@ -120,15 +133,37 @@ class PolyPoints:
                 raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys()
             self.attributes[name] = value
         
+    def setLogScale(self, logscale):
+        self._logscale = logscale
+    
+    def __getattr__(self, name):
+        if name == 'points':
+            if len(self._points)>0:
+                data = _Numeric.array(self._points,copy=True)
+                if self._logscale[0]:
+                    data = self.log10(data, 0)
+                if self._logscale[1]:
+                    data = self.log10(data, 1)
+                return data
+            else:
+                return self._points
+        else:
+            raise AttributeError, name
+            
+    def log10(self, data, ind):
+        data = _Numeric.compress(data[:,ind]>0,data,0)
+        data[:,ind] = _Numeric.log10(data[:,ind])
+        return data
+
     def boundingBox(self):
         if len(self.points) == 0:
             # no curves to draw
             # defaults to (-1,-1) and (1,1) but axis can be set in Draw
-            minXY= Numeric.array([-1,-1])
-            maxXY= Numeric.array([ 1, 1])
+            minXY= _Numeric.array([-1.0,-1.0])
+            maxXY= _Numeric.array([ 1.0, 1.0])
         else:
-            minXY= Numeric.minimum.reduce(self.points)
-            maxXY= Numeric.maximum.reduce(self.points)
+            minXY= _Numeric.minimum.reduce(self.points)
+            maxXY= _Numeric.maximum.reduce(self.points)
         return minXY, maxXY
 
     def scaleAndShift(self, scale=(1,1), shift=(0,0)):
@@ -145,7 +180,27 @@ class PolyPoints:
     def getLegend(self):
         return self.attributes['legend']
 
-
+    def getClosestPoint(self, pntXY, pointScaled= True):
+        """Returns the index of closest point on the curve, pointXY, scaledXY, distance
+            x, y in user coords
+            if pointScaled == True based on screen coords
+            if pointScaled == False based on user coords
+        """
+        if pointScaled == True:
+            #Using screen coords
+            p = self.scaled
+            pxy = self.currentScale * _Numeric.array(pntXY)+ self.currentShift
+        else:
+            #Using user coords
+            p = self.points
+            pxy = _Numeric.array(pntXY)
+        #determine distance for each point
+        d= _Numeric.sqrt(_Numeric.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2)
+        pntIndex = _Numeric.argmin(d)
+        dist = d[pntIndex]
+        return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist]
+        
+        
 class PolyLine(PolyPoints):
     """Class to define line type and style
         - All methods except __init__ are private.
@@ -172,7 +227,9 @@ class PolyLine(PolyPoints):
         colour = self.attributes['colour']
         width = self.attributes['width'] * printerScale
         style= self.attributes['style']
-        pen = wx.Pen(wx.NamedColour(colour), width, style)
+        if not isinstance(colour, wx.Colour):
+            colour = wx.NamedColour(colour)
+        pen = wx.Pen(colour, width, style)
         pen.SetCap(wx.CAP_BUTT)
         dc.SetPen(pen)
         if coord == None:
@@ -233,11 +290,16 @@ class PolyMarker(PolyPoints):
         fillstyle = self.attributes['fillstyle']
         marker = self.attributes['marker']
 
-        dc.SetPen(wx.Pen(wx.NamedColour(colour), width))
+        if colour and not isinstance(colour, wx.Colour):
+            colour = wx.NamedColour(colour)
+        if fillcolour and not isinstance(fillcolour, wx.Colour):
+            fillcolour = wx.NamedColour(fillcolour)
+            
+        dc.SetPen(wx.Pen(colour, width))
         if fillcolour:
-            dc.SetBrush(wx.Brush(wx.NamedColour(fillcolour),fillstyle))
+            dc.SetBrush(wx.Brush(fillcolour,fillstyle))
         else:
-            dc.SetBrush(wx.Brush(wx.NamedColour(colour), fillstyle))
+            dc.SetBrush(wx.Brush(colour, fillstyle))
         if coord == None:
             self._drawmarkers(dc, self.scaled, marker, size)
         else:
@@ -255,9 +317,9 @@ class PolyMarker(PolyPoints):
     def _circle(self, dc, coords, size=1):
         fact= 2.5*size
         wh= 5.0*size
-        rect= Numeric.zeros((len(coords),4),Numeric.Float)+[0.0,0.0,wh,wh]
+        rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
         rect[:,0:2]= coords-[fact,fact]
-        dc.DrawEllipseList(rect.astype(Numeric.Int32))
+        dc.DrawEllipseList(rect.astype(_Numeric.Int32))
 
     def _dot(self, dc, coords, size=1):
         dc.DrawPointList(coords)
@@ -265,35 +327,35 @@ class PolyMarker(PolyPoints):
     def _square(self, dc, coords, size=1):
         fact= 2.5*size
         wh= 5.0*size
-        rect= Numeric.zeros((len(coords),4),Numeric.Float)+[0.0,0.0,wh,wh]
+        rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
         rect[:,0:2]= coords-[fact,fact]
-        dc.DrawRectangleList(rect.astype(Numeric.Int32))
+        dc.DrawRectangleList(rect.astype(_Numeric.Int32))
 
     def _triangle(self, dc, coords, size=1):
         shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)]
-        poly= Numeric.repeat(coords,3)
+        poly= _Numeric.repeat(coords,3)
         poly.shape= (len(coords),3,2)
         poly += shape
-        dc.DrawPolygonList(poly.astype(Numeric.Int32))
+        dc.DrawPolygonList(poly.astype(_Numeric.Int32))
 
     def _triangle_down(self, dc, coords, size=1):
         shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)]
-        poly= Numeric.repeat(coords,3)
+        poly= _Numeric.repeat(coords,3)
         poly.shape= (len(coords),3,2)
         poly += shape
-        dc.DrawPolygonList(poly.astype(Numeric.Int32))
+        dc.DrawPolygonList(poly.astype(_Numeric.Int32))
       
     def _cross(self, dc, coords, size=1):
         fact= 2.5*size
         for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]:
-            lines= Numeric.concatenate((coords,coords),axis=1)+f
-            dc.DrawLineList(lines.astype(Numeric.Int32))
+            lines= _Numeric.concatenate((coords,coords),axis=1)+f
+            dc.DrawLineList(lines.astype(_Numeric.Int32))
 
     def _plus(self, dc, coords, size=1):
         fact= 2.5*size
         for f in [[-fact,0,fact,0],[0,-fact,0,fact]]:
-            lines= Numeric.concatenate((coords,coords),axis=1)+f
-            dc.DrawLineList(lines.astype(Numeric.Int32))
+            lines= _Numeric.concatenate((coords,coords),axis=1)+f
+            dc.DrawLineList(lines.astype(_Numeric.Int32))
 
 class PlotGraphics:
     """Container to hold PolyXXX objects and graph labels
@@ -314,12 +376,20 @@ class PlotGraphics:
         self.xLabel= xLabel
         self.yLabel= yLabel
 
+    def setLogScale(self, logscale):
+        if type(logscale) != tuple:
+            raise TypeError, 'logscale must be a tuple of bools, e.g. (False, False)'
+        if len(self.objects) == 0:
+            return
+        for o in self.objects:
+            o.setLogScale(logscale)
+        
     def boundingBox(self):
         p1, p2 = self.objects[0].boundingBox()
         for o in self.objects[1:]:
             p1o, p2o = o.boundingBox()
-            p1 = Numeric.minimum(p1, p1o)
-            p2 = Numeric.maximum(p2, p2o)
+            p1 = _Numeric.minimum(p1, p1o)
+            p2 = _Numeric.maximum(p2, p2o)
         return p1, p2
 
     def scaleAndShift(self, scale=(1,1), shift=(0,0)):
@@ -356,9 +426,9 @@ class PlotGraphics:
 
     def draw(self, dc):
         for o in self.objects:
-            #t=time.clock()          # profile info
+            #t=_time.clock()          # profile info
             o.draw(dc, self.printerScale)
-            #dt= time.clock()-t
+            #dt= _time.clock()-t
             #print o, "time=", dt
 
     def getSymExtent(self, printerScale):
@@ -366,7 +436,7 @@ class PlotGraphics:
         symExt = self.objects[0].getSymExtent(printerScale)
         for o in self.objects[1:]:
             oSymExt = o.getSymExtent(printerScale)
-            symExt = Numeric.maximum(symExt, oSymExt)
+            symExt = _Numeric.maximum(symExt, oSymExt)
         return symExt
     
     def getLegendNames(self):
@@ -386,30 +456,63 @@ class PlotGraphics:
 #-------------------------------------------------------------------------------
 # Main window that you will want to import into your application.
 
-class PlotCanvas(wx.Window):
-    """Subclass of a wx.Window to allow simple general plotting
+class PlotCanvas(wx.Panel):
+    """
+    Subclass of a wx.Panel which holds two scrollbars and the actual
+    plotting canvas (self.canvas). It allows for simple general plotting
     of data with zoom, labels, and automatic axis scaling."""
 
-    def __init__(self, parent, id = -1, pos=wx.DefaultPosition,
-            size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""):
-        """Constucts a window, which can be a child of a frame, dialog or
+    def __init__(self, parent):
+        """Constructs a panel, which can be a child of a frame or
         any other non-control window"""
     
-        wx.Window.__init__(self, parent, id, pos, size, style, name)
+        wx.Panel.__init__(self, parent)
+
+        sizer = wx.FlexGridSizer(2,2,0,0)
+        self.canvas = wx.Window(self, -1)
+        self.sb_vert = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL)
+        self.sb_vert.SetScrollbar(0,1000,1000,1000)
+        self.sb_hor = wx.ScrollBar(self, -1, style=wx.SB_HORIZONTAL)
+        self.sb_hor.SetScrollbar(0,1000,1000,1000)
+
+        sizer.Add(self.canvas, 1, wx.EXPAND)
+        sizer.Add(self.sb_vert, 0, wx.EXPAND)
+        sizer.Add(self.sb_hor, 0, wx.EXPAND)
+        sizer.Add((0,0))
+        
+        sizer.AddGrowableRow(0, 1)
+        sizer.AddGrowableCol(0, 1)
+
+        self.sb_vert.Show(False)
+        self.sb_hor.Show(False)
+        
+        self.SetSizer(sizer)
+        self.Fit()
+
         self.border = (1,1)
 
         self.SetBackgroundColour("white")
         
         # Create some mouse events for zooming
-        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
-        self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
-        self.Bind(wx.EVT_MOTION, self.OnMotion)
-        self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick)
-        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
+        self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
+        self.canvas.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
+        self.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
+        self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick)
+        self.canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
+
+        # scrollbar events
+        self.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScroll)
+        self.Bind(wx.EVT_SCROLL_PAGEUP, self.OnScroll)
+        self.Bind(wx.EVT_SCROLL_PAGEDOWN, self.OnScroll)
+        self.Bind(wx.EVT_SCROLL_LINEUP, self.OnScroll)
+        self.Bind(wx.EVT_SCROLL_LINEDOWN, self.OnScroll)
 
         # set curser as cross-hairs
-        self.SetCursor(wx.CROSS_CURSOR)
-
+        self.canvas.SetCursor(wx.CROSS_CURSOR)
+        self.HandCursor = wx.CursorFromImage(getHandImage())
+        self.GrabHandCursor = wx.CursorFromImage(getGrabHandImage())
+        self.MagCursor = wx.CursorFromImage(getMagPlusImage())
+            
         # Things for printing
         self.print_data = wx.PrintData()
         self.print_data.SetPaperId(wx.PAPER_LETTER)
@@ -421,11 +524,24 @@ class PlotCanvas(wx.Window):
         self.printerScale = 1
         self.parent= parent
 
+        # scrollbar variables
+        self._sb_ignore = False
+        self._adjustingSB = False
+        self._sb_xfullrange = 0
+        self._sb_yfullrange = 0
+        self._sb_xunit = 0
+        self._sb_yunit = 0
+        
+        self._dragEnabled = False
+        self._screenCoordinates = _Numeric.array([0.0, 0.0])
+        
+        self._logscale = (False, False)
+
         # Zooming variables
         self._zoomInFactor =  0.5
         self._zoomOutFactor = 2
-        self._zoomCorner1= Numeric.array([0.0, 0.0]) # left mouse down corner
-        self._zoomCorner2= Numeric.array([0.0, 0.0])   # left mouse up corner
+        self._zoomCorner1= _Numeric.array([0.0, 0.0]) # left mouse down corner
+        self._zoomCorner2= _Numeric.array([0.0, 0.0])   # left mouse up corner
         self._zoomEnabled= False
         self._hasDragged= False
         
@@ -444,13 +560,32 @@ class PlotCanvas(wx.Window):
         self._fontSizeTitle= 15
         self._fontSizeLegend= 7
 
-        self.Bind(wx.EVT_PAINT, self.OnPaint)
-        self.Bind(wx.EVT_SIZE, self.OnSize)
+        # pointLabels
+        self._pointLabelEnabled= False
+        self.last_PointLabel= None
+        self._pointLabelFunc= None
+        self.canvas.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
+
+        self.canvas.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.canvas.Bind(wx.EVT_SIZE, self.OnSize)
         # OnSize called to make sure the buffer is initialized.
         # This might result in OnSize getting called twice on some
         # platforms at initialization, but little harm done.
-               if wx.Platform != "__WXMAC__":
-                   self.OnSize(None) # sets the initial size based on client size
+        self.OnSize(None) # sets the initial size based on client size
+
+        self._gridColour = wx.NamedColour('black')
+
+    def SetCursor(self, cursor):
+        self.canvas.SetCursor(cursor)
+        
+    def GetGridColour(self):
+        return self._gridColour
+
+    def SetGridColour(self, colour):
+        if isinstance(colour, wx.Colour):
+            self._gridColour = colour
+        else:
+            self._gridColour = wx.NamedColour(colour)
 
         
     # SaveFile
@@ -465,7 +600,7 @@ class PlotCanvas(wx.Window):
         .png  Save a Portable Network Graphics file.
         .jpg  Save a Joint Photographic Experts Group file.
         """
-        if string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
+        if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
             dlg1 = wx.FileDialog(
                     self, 
                     "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
@@ -477,7 +612,7 @@ class PlotCanvas(wx.Window):
                     if dlg1.ShowModal() == wx.ID_OK:
                         fileName = dlg1.GetPath()
                         # Check for proper exension
-                        if string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
+                        if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
                             dlg2 = wx.MessageDialog(self, 'File name extension\n'
                             'must be one of\n'
                             'bmp, xbm, xpm, png, or jpg',
@@ -494,7 +629,7 @@ class PlotCanvas(wx.Window):
                 dlg1.Destroy()
 
         # File name has required extension
-        fType = string.lower(fileName[-3:])
+        fType = _string.lower(fileName[-3:])
         if fType == "bmp":
             tp= wx.BITMAP_TYPE_BMP       # Save a Windows bitmap file.
         elif fType == "xbm":
@@ -521,7 +656,7 @@ class PlotCanvas(wx.Window):
                 self.pageSetupData.SetMarginBottomRight(data.GetMarginBottomRight())
                 self.pageSetupData.SetMarginTopLeft(data.GetMarginTopLeft())
                 self.pageSetupData.SetPrintData(data.GetPrintData())
-                self.print_data=data.GetPrintData() # updates print_data
+                self.print_data=wx.PrintData(data.GetPrintData()) # updates print_data
         finally:
             dlg.Destroy()
                 
@@ -529,13 +664,12 @@ class PlotCanvas(wx.Window):
         """Print current plot."""
         if paper != None:
             self.print_data.SetPaperId(paper)
-        pdd = wx.PrintDialogData()
-        pdd.SetPrintData(self.print_data)
+        pdd = wx.PrintDialogData(self.print_data)
         printer = wx.Printer(pdd)
         out = PlotPrintout(self)
         print_ok = printer.Print(self.parent, out)
         if print_ok:
-            self.print_data = printer.GetPrintDialogData().GetPrintData()
+            self.print_data = wx.PrintData(printer.GetPrintDialogData().GetPrintData())
         out.Destroy()
 
     def PrintPreview(self):
@@ -547,7 +681,7 @@ class PlotCanvas(wx.Window):
             wx.MessageDialog(self, "Print Preview failed.\n" \
                                "Check that default printer is configured\n", \
                                "Print error", wx.OK|wx.CENTRE).ShowModal()
-        self.preview.SetZoom(30)
+        self.preview.SetZoom(40)
         # search up tree to find frame instance
         frameInst= self
         while not isinstance(frameInst, wx.Frame):
@@ -555,10 +689,24 @@ class PlotCanvas(wx.Window):
         frame = wx.PreviewFrame(self.preview, frameInst, "Preview")
         frame.Initialize()
         frame.SetPosition(self.GetPosition())
-        frame.SetSize((500,400))
+        frame.SetSize((600,550))
         frame.Centre(wx.BOTH)
         frame.Show(True)
 
+    def setLogScale(self, logscale):
+        if type(logscale) != tuple:
+            raise TypeError, 'logscale must be a tuple of bools, e.g. (False, False)'
+        if self.last_draw is not None:
+            graphics, xAxis, yAxis= self.last_draw
+            graphics.setLogScale(logscale)
+            self.last_draw = (graphics, None, None)
+        self.SetXSpec('min')
+        self.SetYSpec('min')
+        self._logscale = logscale
+
+    def getLogScale(self):
+        return self._logscale
+    
     def SetFontSizeAxis(self, point= 10):
         """Set the tick and axis label font size (default is 10 point)"""
         self._fontSizeAxis= point
@@ -583,10 +731,45 @@ class PlotCanvas(wx.Window):
         """Get current Legend font size in points"""
         return self._fontSizeLegend
 
+    def SetShowScrollbars(self, value):
+        """Set True to show scrollbars"""
+        if value not in [True,False]:
+            raise TypeError, "Value should be True or False"
+        if value == self.GetShowScrollbars():
+            return
+        self.sb_vert.Show(value)
+        self.sb_hor.Show(value)
+        wx.CallAfter(self.Layout)
+
+    def GetShowScrollbars(self):
+        """Set True to show scrollbars"""
+        return self.sb_vert.IsShown()
+
+    def SetEnableDrag(self, value):
+        """Set True to enable drag."""
+        if value not in [True,False]:
+            raise TypeError, "Value should be True or False"
+        if value:
+            if self.GetEnableZoom():
+                self.SetEnableZoom(False)
+            self.SetCursor(self.HandCursor)
+        else:
+            self.SetCursor(wx.CROSS_CURSOR)
+        self._dragEnabled = value
+    
+    def GetEnableDrag(self):
+        return self._dragEnabled
+    
     def SetEnableZoom(self, value):
         """Set True to enable zooming."""
         if value not in [True,False]:
             raise TypeError, "Value should be True or False"
+        if value:
+            if self.GetEnableDrag():
+                self.SetEnableDrag(False)
+            self.SetCursor(self.MagCursor)
+        else:
+            self.SetCursor(wx.CROSS_CURSOR)
         self._zoomEnabled= value
 
     def GetEnableZoom(self):
@@ -595,8 +778,8 @@ class PlotCanvas(wx.Window):
 
     def SetEnableGrid(self, value):
         """Set True to enable grid."""
-        if value not in [True,False]:
-            raise TypeError, "Value should be True or False"
+        if value not in [True,False,'Horizontal','Vertical']:
+            raise TypeError, "Value should be True, False, Horizontal or Vertical"
         self._gridEnabled= value
         self.Redraw()
 
@@ -615,31 +798,76 @@ class PlotCanvas(wx.Window):
         """True if Legend enabled."""
         return self._legendEnabled
 
+    def SetEnablePointLabel(self, value):
+        """Set True to enable pointLabel."""
+        if value not in [True,False]:
+            raise TypeError, "Value should be True or False"
+        self._pointLabelEnabled= value 
+        self.Redraw()  #will erase existing pointLabel if present
+        self.last_PointLabel = None
+
+    def GetEnablePointLabel(self):
+        """True if pointLabel enabled."""
+        return self._pointLabelEnabled
+
+    def SetPointLabelFunc(self, func):
+        """Sets the function with custom code for pointLabel drawing
+            ******** more info needed ***************
+        """
+        self._pointLabelFunc= func
+
+    def GetPointLabelFunc(self):
+        """Returns pointLabel Drawing Function"""
+        return self._pointLabelFunc
+
     def Reset(self):
         """Unzoom the plot."""
+        self.last_PointLabel = None        #reset pointLabel
         if self.last_draw is not None:
-            self.Draw(self.last_draw[0])
+            self._Draw(self.last_draw[0])
         
     def ScrollRight(self, units):          
         """Move view right number of axis units."""
+        self.last_PointLabel = None        #reset pointLabel
         if self.last_draw is not None:
             graphics, xAxis, yAxis= self.last_draw
             xAxis= (xAxis[0]+units, xAxis[1]+units)
-            self.Draw(graphics,xAxis,yAxis)
+            self._Draw(graphics,xAxis,yAxis)
 
     def ScrollUp(self, units):
         """Move view up number of axis units."""
+        self.last_PointLabel = None        #reset pointLabel
         if self.last_draw is not None:
-            graphics, xAxis, yAxis= self.last_draw
-            yAxis= (yAxis[0]+units, yAxis[1]+units)
-            self.Draw(graphics,xAxis,yAxis)
+             graphics, xAxis, yAxis= self.last_draw
+             yAxis= (yAxis[0]+units, yAxis[1]+units)
+             self._Draw(graphics,xAxis,yAxis)
+
+    def GetXY(self, event):
+        """Wrapper around _getXY, which handles log scales"""
+        x,y = self._getXY(event)
+        if self.getLogScale()[0]:
+            x = _Numeric.power(10,x)
+        if self.getLogScale()[1]:
+            y = _Numeric.power(10,y)
+        return x,y
         
-    def GetXY(self,event):
+    def _getXY(self,event):
         """Takes a mouse event and returns the XY user axis values."""
-        screenPos= Numeric.array( event.GetPosition())
-        x,y= (screenPos-self._pointShift)/self._pointScale
+        x,y= self.PositionScreenToUser(event.GetPosition())
         return x,y
 
+    def PositionUserToScreen(self, pntXY):
+        """Converts User position to Screen Coordinates"""
+        userPos= _Numeric.array(pntXY)
+        x,y= userPos * self._pointScale + self._pointShift
+        return x,y
+        
+    def PositionScreenToUser(self, pntXY):
+        """Converts Screen position to User Coordinates"""
+        screenPos= _Numeric.array(pntXY)
+        x,y= (screenPos-self._pointShift)/self._pointScale
+        return x,y
+        
     def SetXSpec(self, type= 'auto'):
         """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
         where:
@@ -667,6 +895,12 @@ class PlotCanvas(wx.Window):
         return self._ySpec
     
     def GetXMaxRange(self):
+        xAxis = self._getXMaxRange()
+        if self.getLogScale()[0]:
+            xAxis = _Numeric.power(10,xAxis)
+        return xAxis
+
+    def _getXMaxRange(self):
         """Returns (minX, maxX) x-axis range for displayed graph"""
         graphics= self.last_draw[0]
         p1, p2 = graphics.boundingBox()     # min, max points of graphics
@@ -674,6 +908,12 @@ class PlotCanvas(wx.Window):
         return xAxis
 
     def GetYMaxRange(self):
+        yAxis = self._getYMaxRange()
+        if self.getLogScale()[1]:
+            yAxis = _Numeric.power(10,yAxis)
+        return yAxis
+
+    def _getYMaxRange(self):
         """Returns (minY, maxY) y-axis range for displayed graph"""
         graphics= self.last_draw[0]
         p1, p2 = graphics.boundingBox()     # min, max points of graphics
@@ -681,38 +921,62 @@ class PlotCanvas(wx.Window):
         return yAxis
 
     def GetXCurrentRange(self):
+        xAxis = self._getXCurrentRange()
+        if self.getLogScale()[0]:
+            xAxis = _Numeric.power(10,xAxis)
+        return xAxis
+
+    def _getXCurrentRange(self):
         """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
         return self.last_draw[1]
     
     def GetYCurrentRange(self):
+        yAxis = self._getYCurrentRange()
+        if self.getLogScale()[1]:
+            yAxis = _Numeric.power(10,yAxis)
+        return yAxis
+
+    def _getYCurrentRange(self):
         """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
         return self.last_draw[2]
-        
+
     def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
-        """Draw objects in graphics with specified x and y axis.
-        graphics- instance of PlotGraphics with list of PolyXXX objects
-        xAxis - tuple with (min, max) axis range to view
-        yAxis - same as xAxis
-        dc - drawing context - doesn't have to be specified.    
-        If it's not, the offscreen buffer is used
-        """
+        """Wrapper around _Draw, which handles log axes"""
+        
+        graphics.setLogScale(self.getLogScale())
+        
         # check Axis is either tuple or none
         if type(xAxis) not in [type(None),tuple]:
-            raise TypeError, "xAxis should be None or (minX,maxX)"
+            raise TypeError, "xAxis should be None or (minX,maxX)"+str(type(xAxis))
         if type(yAxis) not in [type(None),tuple]:
-            raise TypeError, "yAxis should be None or (minY,maxY)"
+            raise TypeError, "yAxis should be None or (minY,maxY)"+str(type(xAxis))
              
         # check case for axis = (a,b) where a==b caused by improper zooms
         if xAxis != None:
             if xAxis[0] == xAxis[1]:
                 return
+            if self.getLogScale()[0]:
+                xAxis = _Numeric.log10(xAxis)
         if yAxis != None:
             if yAxis[0] == yAxis[1]:
                 return
-            
+            if self.getLogScale()[1]:
+                yAxis = _Numeric.log10(yAxis)
+        self._Draw(graphics, xAxis, yAxis, dc)
+        
+    def _Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
+        """\
+        Draw objects in graphics with specified x and y axis.
+        graphics- instance of PlotGraphics with list of PolyXXX objects
+        xAxis - tuple with (min, max) axis range to view
+        yAxis - same as xAxis
+        dc - drawing context - doesn't have to be specified.    
+        If it's not, the offscreen buffer is used
+        """
+
         if dc == None:
-            # allows using floats for certain functions 
-            dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
+            # sets new dc and clears it 
+            dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
             dc.Clear()
             
         dc.BeginDrawing()
@@ -734,24 +998,27 @@ class PlotCanvas(wx.Window):
             p2[0],p2[1] = xAxis[1], yAxis[1]     # upper right corner user scale (xmax,ymax)
         else:
             # Both axis specified in Draw
-            p1= Numeric.array([xAxis[0], yAxis[0]])    # lower left corner user scale (xmin,ymin)
-            p2= Numeric.array([xAxis[1], yAxis[1]])     # upper right corner user scale (xmax,ymax)
+            p1= _Numeric.array([xAxis[0], yAxis[0]])    # lower left corner user scale (xmin,ymin)
+            p2= _Numeric.array([xAxis[1], yAxis[1]])     # upper right corner user scale (xmax,ymax)
 
-        self.last_draw = (graphics, xAxis, yAxis)       # saves most recient values
+        self.last_draw = (graphics, _Numeric.array(xAxis), _Numeric.array(yAxis))       # saves most recient values
 
         # Get ticks and textExtents for axis if required
         if self._xSpec is not 'none':        
-            xticks = self._ticks(xAxis[0], xAxis[1])
+            xticks = self._xticks(xAxis[0], xAxis[1])
             xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis
         else:
             xticks = None
             xTextExtent= (0,0) # No text for ticks
         if self._ySpec is not 'none':
-            yticks = self._ticks(yAxis[0], yAxis[1])
-            yTextExtentBottom= dc.GetTextExtent(yticks[0][1])
-            yTextExtentTop   = dc.GetTextExtent(yticks[-1][1])
-            yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]),
-                        max(yTextExtentBottom[1],yTextExtentTop[1]))
+            yticks = self._yticks(yAxis[0], yAxis[1])
+            if self.getLogScale()[1]:
+                yTextExtent = dc.GetTextExtent('-2e-2')
+            else:
+                yTextExtentBottom = dc.GetTextExtent(yticks[0][1])
+                yTextExtentTop = dc.GetTextExtent(yticks[-1][1])
+                yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]),
+                              max(yTextExtentBottom[1],yTextExtentTop[1]))
         else:
             yticks = None
             yTextExtent= (0,0) # No text for ticks
@@ -767,8 +1034,8 @@ class PlotCanvas(wx.Window):
         lhsW= yTextExtent[0]+ yLabelWH[1]
         bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1]
         topH= yTextExtent[1]/2. + titleWH[1]
-        textSize_scale= Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size
-        textSize_shift= Numeric.array([lhsW, bottomH])          # shift plot area by this amount
+        textSize_scale= _Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size
+        textSize_shift= _Numeric.array([lhsW, bottomH])          # shift plot area by this amount
 
         # drawing title and labels text
         dc.SetFont(self._getFont(self._fontSizeTitle))
@@ -789,8 +1056,8 @@ class PlotCanvas(wx.Window):
             self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt)
 
         # allow for scaling and shifting plotted points
-        scale = (self.plotbox_size-textSize_scale) / (p2-p1)* Numeric.array((1,-1))
-        shift = -p1*scale + self.plotbox_origin + textSize_shift * Numeric.array((1,-1))
+        scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _Numeric.array((1,-1))
+        shift = -p1*scale + self.plotbox_origin + textSize_shift * _Numeric.array((1,-1))
         self._pointScale= scale  # make available for mouse events
         self._pointShift= shift        
         self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks)
@@ -802,22 +1069,25 @@ class PlotCanvas(wx.Window):
         ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2)
         dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight)
         # Draw the lines and markers
-        #start = time.clock()
+        #start = _time.clock()
         graphics.draw(dc)
-        # print "entire graphics drawing took: %f second"%(time.clock() - start)
+        # print "entire graphics drawing took: %f second"%(_time.clock() - start)
         # remove the clipping region
         dc.DestroyClippingRegion()
         dc.EndDrawing()
+
+        self._adjustScrollbars()
         
-    def Redraw(self, dc= None):
+    def Redraw(self, dc=None):
         """Redraw the existing plot."""
         if self.last_draw is not None:
             graphics, xAxis, yAxis= self.last_draw
-            self.Draw(graphics,xAxis,yAxis,dc)
+            self._Draw(graphics,xAxis,yAxis,dc)
 
     def Clear(self):
         """Erase the window."""
-        dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
+        self.last_PointLabel = None        #reset pointLabel
+        dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
         dc.Clear()
         self.last_draw = None
 
@@ -826,6 +1096,7 @@ class PlotCanvas(wx.Window):
             Centers on the X,Y coords given in Center
             Zooms by the Ratio = (Xratio, Yratio) given
         """
+        self.last_PointLabel = None   #reset maker
         x,y = Center
         if self.last_draw != None:
             (graphics, xAxis, yAxis) = self.last_draw
@@ -833,8 +1104,76 @@ class PlotCanvas(wx.Window):
             h = (yAxis[1] - yAxis[0]) * Ratio[1]
             xAxis = ( x - w/2, x + w/2 )
             yAxis = ( y - h/2, y + h/2 )
-            self.Draw(graphics, xAxis, yAxis)
+            self._Draw(graphics, xAxis, yAxis)
         
+    def GetClosestPoints(self, pntXY, pointScaled= True):
+        """Returns list with
+            [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
+            list for each curve.
+            Returns [] if no curves are being plotted.
+            
+            x, y in user coords
+            if pointScaled == True based on screen coords
+            if pointScaled == False based on user coords
+        """
+        if self.last_draw == None:
+            #no graph available
+            return []
+        graphics, xAxis, yAxis= self.last_draw
+        l = []
+        for curveNum,obj in enumerate(graphics):
+            #check there are points in the curve
+            if len(obj.points) == 0:
+                continue  #go to next obj
+            #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
+            cn = [curveNum]+ [obj.getLegend()]+ obj.getClosestPoint( pntXY, pointScaled)
+            l.append(cn)
+        return l
+
+    def GetClosetPoint(self, pntXY, pointScaled= True):
+        """Returns list with
+            [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
+            list for only the closest curve.
+            Returns [] if no curves are being plotted.
+            
+            x, y in user coords
+            if pointScaled == True based on screen coords
+            if pointScaled == False based on user coords
+        """
+        #closest points on screen based on screen scaling (pointScaled= True)
+        #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
+        closestPts= self.GetClosestPoints(pntXY, pointScaled)
+        if closestPts == []:
+            return []  #no graph present
+        #find one with least distance
+        dists = [c[-1] for c in closestPts]
+        mdist = min(dists)  #Min dist
+        i = dists.index(mdist)  #index for min dist
+        return closestPts[i]  #this is the closest point on closest curve
+
+    def UpdatePointLabel(self, mDataDict):
+        """Updates the pointLabel point on screen with data contained in
+            mDataDict.
+
+            mDataDict will be passed to your function set by
+            SetPointLabelFunc.  It can contain anything you
+            want to display on the screen at the scaledXY point
+            you specify.
+
+            This function can be called from parent window with onClick,
+            onMotion events etc.            
+        """
+        if self.last_PointLabel != None:
+            #compare pointXY
+            if _Numeric.sometrue(mDataDict["pointXY"] != self.last_PointLabel["pointXY"]):
+                #closest changed
+                self._drawPointLabel(self.last_PointLabel) #erase old
+                self._drawPointLabel(mDataDict) #plot new
+        else:
+            #just plot new with no erase
+            self._drawPointLabel(mDataDict) #plot new
+        #save for next erase
+        self.last_PointLabel = mDataDict
 
     # event handlers **********************************
     def OnMotion(self, event):
@@ -843,68 +1182,119 @@ class PlotCanvas(wx.Window):
                 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
             else:
                 self._hasDragged= True
-            self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event)
+            self._zoomCorner2[0], self._zoomCorner2[1] = self._getXY(event)
             self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new
-
+        elif self._dragEnabled and event.LeftIsDown():
+            coordinates = event.GetPosition()
+            newpos, oldpos = map(_Numeric.array, map(self.PositionScreenToUser, [coordinates, self._screenCoordinates]))
+            dist = newpos-oldpos
+            self._screenCoordinates = coordinates
+
+            if self.last_draw is not None:
+                graphics, xAxis, yAxis= self.last_draw
+                yAxis -= dist[1]
+                xAxis -= dist[0]
+                self._Draw(graphics,xAxis,yAxis)
+            
     def OnMouseLeftDown(self,event):
-        self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event)
+        self._zoomCorner1[0], self._zoomCorner1[1]= self._getXY(event)
+        self._screenCoordinates = _Numeric.array(event.GetPosition())
+        if self._dragEnabled:
+            self.SetCursor(self.GrabHandCursor)
+            self.canvas.CaptureMouse()
 
     def OnMouseLeftUp(self, event):
         if self._zoomEnabled:
             if self._hasDragged == True:
                 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
-                self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event)
+                self._zoomCorner2[0], self._zoomCorner2[1]= self._getXY(event)
                 self._hasDragged = False  # reset flag
-                minX, minY= Numeric.minimum( self._zoomCorner1, self._zoomCorner2)
-                maxX, maxY= Numeric.maximum( self._zoomCorner1, self._zoomCorner2)
+                minX, minY= _Numeric.minimum( self._zoomCorner1, self._zoomCorner2)
+                maxX, maxY= _Numeric.maximum( self._zoomCorner1, self._zoomCorner2)
+                self.last_PointLabel = None        #reset pointLabel
                 if self.last_draw != None:
-                    self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
+                    self._Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
             #else: # A box has not been drawn, zoom in on a point
             ## this interfered with the double click, so I've disables it.
-            #    X,Y = self.GetXY(event)
+            #    X,Y = self._getXY(event)
             #    self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
+        if self._dragEnabled:
+            self.SetCursor(self.HandCursor)
+            if self.canvas.HasCapture():
+                self.canvas.ReleaseMouse()
 
     def OnMouseDoubleClick(self,event):
         if self._zoomEnabled:
-            self.Reset()
+            # Give a little time for the click to be totally finished
+            # before (possibly) removing the scrollbars and trigering
+            # size events, etc.
+            wx.FutureCall(200,self.Reset)
         
     def OnMouseRightDown(self,event):
         if self._zoomEnabled:
-            X,Y = self.GetXY(event)
+            X,Y = self._getXY(event)
             self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) )
 
     def OnPaint(self, event):
         # All that is needed here is to draw the buffer to screen
-        dc = wx.BufferedPaintDC(self, self._Buffer)        
+        if self.last_PointLabel != None:
+            self._drawPointLabel(self.last_PointLabel) #erase old
+            self.last_PointLabel = None
+        dc = wx.BufferedPaintDC(self.canvas, self._Buffer)
 
     def OnSize(self,event):
         # The Buffer init is done here, to make sure the buffer is always
         # the same size as the Window
-        Size  = self.GetClientSize()
-
+        Size  = self.canvas.GetClientSize()
+        Size.width = max(1, Size.width)
+        Size.height = max(1, Size.height)
+        
         # Make new offscreen bitmap: this bitmap will always have the
         # current drawing in it, so it can be used to save the image to
         # a file, or whatever.
-        self._Buffer = wx.EmptyBitmap(Size[0],Size[1])
+        self._Buffer = wx.EmptyBitmap(Size.width, Size.height)
         self._setSize()
+
+        self.last_PointLabel = None        #reset pointLabel
+
         if self.last_draw is None:
             self.Clear()
         else:
             graphics, xSpec, ySpec = self.last_draw
-            self.Draw(graphics,xSpec,ySpec)
-
+            self._Draw(graphics,xSpec,ySpec)
+
+    def OnLeave(self, event):
+        """Used to erase pointLabel when mouse outside window"""
+        if self.last_PointLabel != None:
+            self._drawPointLabel(self.last_PointLabel) #erase old
+            self.last_PointLabel = None
+
+    def OnScroll(self, evt):
+        if not self._adjustingSB:
+            self._sb_ignore = True
+            sbpos = evt.GetPosition()
         
+            if evt.GetOrientation() == wx.VERTICAL:
+                fullrange,pagesize = self.sb_vert.GetRange(),self.sb_vert.GetPageSize()
+                sbpos = fullrange-pagesize-sbpos
+                dist = sbpos*self._sb_yunit-(self._getYCurrentRange()[0]-self._sb_yfullrange[0])
+                self.ScrollUp(dist)
+            
+            if evt.GetOrientation() == wx.HORIZONTAL:
+                dist = sbpos*self._sb_xunit-(self._getXCurrentRange()[0]-self._sb_xfullrange[0])
+                self.ScrollRight(dist)
+               
     # Private Methods **************************************************
     def _setSize(self, width=None, height=None):
         """DC width and height."""
         if width == None:
-            (self.width,self.height) = self.GetClientSize()
+            (self.width,self.height) = self.canvas.GetClientSize()
         else:
             self.width, self.height= width,height    
-        self.plotbox_size = 0.97*Numeric.array([self.width, self.height])
+        self.plotbox_size = 0.97*_Numeric.array([self.width, self.height])
         xo = 0.5*(self.width-self.plotbox_size[0])
         yo = self.height-0.5*(self.height-self.plotbox_size[1])
-        self.plotbox_origin = Numeric.array([xo, yo])
+        self.plotbox_origin = _Numeric.array([xo, yo])
     
     def _setPrinterScale(self, scale):
         """Used to thicken lines and increase marker size for print out."""
@@ -915,7 +1305,24 @@ class PlotCanvas(wx.Window):
         """Used for printing."""
         if self.last_draw != None:
             graphics, xSpec, ySpec= self.last_draw
-            self.Draw(graphics,xSpec,ySpec,printDC)
+            self._Draw(graphics,xSpec,ySpec,printDC)
+
+    def _drawPointLabel(self, mDataDict):
+        """Draws and erases pointLabels"""
+        width = self._Buffer.GetWidth()
+        height = self._Buffer.GetHeight()
+        tmp_Buffer = wx.EmptyBitmap(width,height)
+        dcs = wx.MemoryDC()
+        dcs.SelectObject(tmp_Buffer)
+        dcs.Clear()
+        dcs.BeginDrawing()
+        self._pointLabelFunc(dcs,mDataDict)  #custom user pointLabel function
+        dcs.EndDrawing()
+
+        dc = wx.ClientDC( self.canvas )
+        #this will erase if called twice
+        dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV)  #(NOT src) XOR dst
+        
 
     def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
         """Draws legend symbols and text"""
@@ -930,12 +1337,12 @@ class PlotCanvas(wx.Window):
             if isinstance(o,PolyMarker):
                 # draw marker with legend
                 pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.)
-                o.draw(dc, self.printerScale, coord= Numeric.array([pnt]))
+                o.draw(dc, self.printerScale, coord= _Numeric.array([pnt]))
             elif isinstance(o,PolyLine):
                 # draw line with legend
                 pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.)
                 pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.)
-                o.draw(dc, self.printerScale, coord= Numeric.array([pnt1,pnt2]))
+                o.draw(dc, self.printerScale, coord= _Numeric.array([pnt1,pnt2]))
             else:
                 raise TypeError, "object is neither PolyMarker or PolyLine instance"
             # draw legend txt
@@ -967,7 +1374,7 @@ class PlotCanvas(wx.Window):
             txtList= graphics.getLegendNames()
             txtExt= dc.GetTextExtent(txtList[0])
             for txt in graphics.getLegendNames()[1:]:
-                txtExt= Numeric.maximum(txtExt,dc.GetTextExtent(txt))
+                txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt))
             maxW= symExt[0]+txtExt[0]    
             maxH= max(symExt[1],txtExt[1])
             # padding .1 for lhs of legend box and space between lines
@@ -981,7 +1388,7 @@ class PlotCanvas(wx.Window):
         """Draws/erases rect box from corner1 to corner2"""
         ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2)
         # draw rectangle
-        dc = wx.ClientDC( self )
+        dc = wx.ClientDC( self.canvas )
         dc.BeginDrawing()                 
         dc.SetPen(wx.Pen(wx.BLACK))
         dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) )
@@ -1007,14 +1414,14 @@ class PlotCanvas(wx.Window):
 
     def _point2ClientCoord(self, corner1, corner2):
         """Converts user point coords to client screen int coords x,y,width,height"""
-        c1= Numeric.array(corner1)
-        c2= Numeric.array(corner2)
+        c1= _Numeric.array(corner1)
+        c2= _Numeric.array(corner2)
         # convert to screen coords
         pt1= c1*self._pointScale+self._pointShift
         pt2= c2*self._pointScale+self._pointShift
         # make height and width positive
-        pul= Numeric.minimum(pt1,pt2) # Upper left corner
-        plr= Numeric.maximum(pt1,pt2) # Lower right corner
+        pul= _Numeric.minimum(pt1,pt2) # Upper left corner
+        plr= _Numeric.maximum(pt1,pt2) # Lower right corner
         rectWidth, rectHeight= plr-pul
         ptx,pty= pul
         return ptx, pty, rectWidth, rectHeight 
@@ -1030,8 +1437,8 @@ class PlotCanvas(wx.Window):
             range = upper-lower
             if range == 0.:
                 return lower-0.5, upper+0.5
-            log = Numeric.log10(range)
-            power = Numeric.floor(log)
+            log = _Numeric.log10(range)
+            power = _Numeric.floor(log)
             fraction = log-power
             if fraction <= 0.05:
                 power = power-1
@@ -1053,13 +1460,20 @@ class PlotCanvas(wx.Window):
     def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
         
         penWidth= self.printerScale        # increases thickness for printing only
-        dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth))
+        dc.SetPen(wx.Pen(self._gridColour, penWidth))
         
         # set length of tick marks--long ones make grid
         if self._gridEnabled:
             x,y,width,height= self._point2ClientCoord(p1,p2)
-            yTickLength= width/2.0 +1
-            xTickLength= height/2.0 +1
+            if self._gridEnabled == 'Horizontal':
+                yTickLength= width/2.0 +1
+                xTickLength= 3 * self.printerScale
+            elif self._gridEnabled == 'Vertical':
+                yTickLength= 3 * self.printerScale
+                xTickLength= height/2.0 +1
+            else:
+                yTickLength= width/2.0 +1
+                xTickLength= height/2.0 +1
         else:
             yTickLength= 3 * self.printerScale  # lengthens lines for printing
             xTickLength= 3 * self.printerScale
@@ -1068,11 +1482,11 @@ class PlotCanvas(wx.Window):
             lower, upper = p1[0],p2[0]
             text = 1
             for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]:   # miny, maxy and tick lengths
-                a1 = scale*Numeric.array([lower, y])+shift
-                a2 = scale*Numeric.array([upper, y])+shift
+                a1 = scale*_Numeric.array([lower, y])+shift
+                a2 = scale*_Numeric.array([upper, y])+shift
                 dc.DrawLine(a1[0],a1[1],a2[0],a2[1])  # draws upper and lower axis line
                 for x, label in xticks:
-                    pt = scale*Numeric.array([x, y])+shift
+                    pt = scale*_Numeric.array([x, y])+shift
                     dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units
                     if text:
                         dc.DrawText(label,pt[0],pt[1])
@@ -1083,26 +1497,69 @@ class PlotCanvas(wx.Window):
             text = 1
             h = dc.GetCharHeight()
             for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]:
-                a1 = scale*Numeric.array([x, lower])+shift
-                a2 = scale*Numeric.array([x, upper])+shift
+                a1 = scale*_Numeric.array([x, lower])+shift
+                a2 = scale*_Numeric.array([x, upper])+shift
                 dc.DrawLine(a1[0],a1[1],a2[0],a2[1])
                 for y, label in yticks:
-                    pt = scale*Numeric.array([x, y])+shift
+                    pt = scale*_Numeric.array([x, y])+shift
                     dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1])
                     if text:
                         dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0],
                                     pt[1]-0.5*h)
                 text = 0    # axis values not drawn on right side
 
+    def _xticks(self, *args):
+        if self._logscale[0]:
+            return self._logticks(*args)
+        else:
+            return self._ticks(*args)
+    
+    def _yticks(self, *args):
+        if self._logscale[1]:
+            return self._logticks(*args)
+        else:
+            return self._ticks(*args)
+        
+    def _logticks(self, lower, upper):
+        #lower,upper = map(_Numeric.log10,[lower,upper])
+        #print 'logticks',lower,upper
+        ticks = []
+        mag = _Numeric.power(10,_Numeric.floor(lower))
+        if upper-lower > 6:
+            t = _Numeric.power(10,_Numeric.ceil(lower))
+            base = _Numeric.power(10,_Numeric.floor((upper-lower)/6))
+            def inc(t):
+                return t*base-t
+        else:
+            t = _Numeric.ceil(_Numeric.power(10,lower)/mag)*mag
+            def inc(t):
+                return 10**int(_Numeric.floor(_Numeric.log10(t)+1e-16))
+        majortick = int(_Numeric.log10(mag))
+        while t <= pow(10,upper):
+            if majortick != int(_Numeric.floor(_Numeric.log10(t)+1e-16)):
+                majortick = int(_Numeric.floor(_Numeric.log10(t)+1e-16))
+                ticklabel = '1e%d'%majortick
+            else:
+                if upper-lower < 2:
+                    minortick = int(t/pow(10,majortick)+.5)
+                    ticklabel = '%de%d'%(minortick,majortick)
+                else:
+                    ticklabel = ''
+            ticks.append((_Numeric.log10(t), ticklabel))
+            t += inc(t)
+        if len(ticks) == 0:
+            ticks = [(0,'')]
+        return ticks
+    
     def _ticks(self, lower, upper):
         ideal = (upper-lower)/7.
-        log = Numeric.log10(ideal)
-        power = Numeric.floor(log)
+        log = _Numeric.log10(ideal)
+        power = _Numeric.floor(log)
         fraction = log-power
         factor = 1.
         error = fraction
         for f, lf in self._multiples:
-            e = Numeric.fabs(fraction-lf)
+            e = _Numeric.fabs(fraction-lf)
             if e < error:
                 error = e
                 factor = f
@@ -1116,14 +1573,69 @@ class PlotCanvas(wx.Window):
             digits = -int(power)
             format = '%'+`digits+2`+'.'+`digits`+'f'
         ticks = []
-        t = -grid*Numeric.floor(-lower/grid)
+        t = -grid*_Numeric.floor(-lower/grid)
         while t <= upper:
             ticks.append( (t, format % (t,)) )
             t = t + grid
         return ticks
 
-    _multiples = [(2., Numeric.log10(2.)), (5., Numeric.log10(5.))]
+    _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))]
+
+
+    def _adjustScrollbars(self):
+        if self._sb_ignore:
+            self._sb_ignore = False
+            return
+
+        self._adjustingSB = True
+        needScrollbars = False
+        
+        # horizontal scrollbar
+        r_current = self._getXCurrentRange()
+        r_max = list(self._getXMaxRange())
+        sbfullrange = float(self.sb_hor.GetRange())
+
+        r_max[0] = min(r_max[0],r_current[0])
+        r_max[1] = max(r_max[1],r_current[1])
+            
+        self._sb_xfullrange = r_max
+
+        unit = (r_max[1]-r_max[0])/float(self.sb_hor.GetRange())
+        pos = int((r_current[0]-r_max[0])/unit)
+        
+        if pos >= 0:
+            pagesize = int((r_current[1]-r_current[0])/unit)
+
+            self.sb_hor.SetScrollbar(pos, pagesize, sbfullrange, pagesize)
+            self._sb_xunit = unit
+            needScrollbars = needScrollbars or (pagesize != sbfullrange)
+        else:
+            self.sb_hor.SetScrollbar(0, 1000, 1000, 1000)
+
+        # vertical scrollbar
+        r_current = self._getYCurrentRange()
+        r_max = list(self._getYMaxRange())
+        sbfullrange = float(self.sb_vert.GetRange())
+
+        r_max[0] = min(r_max[0],r_current[0])
+        r_max[1] = max(r_max[1],r_current[1])
+            
+        self._sb_yfullrange = r_max
+        
+        unit = (r_max[1]-r_max[0])/sbfullrange
+        pos = int((r_current[0]-r_max[0])/unit)
+        
+        if pos >= 0:
+            pagesize = int((r_current[1]-r_current[0])/unit)
+            pos = (sbfullrange-1-pos-pagesize)
+            self.sb_vert.SetScrollbar(pos, pagesize, sbfullrange, pagesize)
+            self._sb_yunit = unit
+            needScrollbars = needScrollbars or (pagesize != sbfullrange)
+        else:
+            self.sb_vert.SetScrollbar(0, 1000, 1000, 1000)
 
+        self.SetShowScrollbars(needScrollbars)
+        self._adjustingSB = False
 
 #-------------------------------------------------------------------------------
 # Used to layout the printer page
@@ -1198,10 +1710,88 @@ class PlotPrintout(wx.Printout):
         # rescale back to original
         self.graph._setSize()
         self.graph._setPrinterScale(1)
+        self.graph.Redraw()     #to get point label scale and shift correct
 
         return True
 
 
+#----------------------------------------------------------------------
+from wx import ImageFromStream, BitmapFromImage
+import cStringIO, zlib
+
+
+def getMagPlusData():
+    return zlib.decompress(
+'x\xda\x01*\x01\xd5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
+\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
+\x08\x08|\x08d\x88\x00\x00\x00\xe1IDATx\x9c\xb5U\xd1\x0e\xc4 \x08\xa3n\xff\
+\xff\xc5\xdb\xb8\xa7\xee<\x04\x86gFb\xb2\x88\xb6\x14\x90\x01m\x937m\x8f\x1c\
+\xd7yh\xe4k\xdb\x8e*\x01<\x05\x04\x07F\x1cU\x9d"\x19\x14\\\xe7\xa1\x1e\xf07"\
+\x90H+$?\x04\x16\x9c\xd1z\x04\x00J$m\x06\xdc\xee\x03Hku\x13\xd8C\x16\x84+"O\
+\x1b\xa2\x07\xca"\xb7\xc6sY\xbdD\x926\xf5.\xce\x06!\xd2)x\xcb^\'\x08S\xe4\
+\xe5x&5\xb4[A\xb5h\xb4j=\x9a\xc8\xf8\xecm\xd4\\\x9e\xdf\xbb?\x10\xf0P\x06\
+\x12\xed?=\xb6a\xd8=\xcd\xa2\xc8T\xd5U2t\x11\x95d\xa3"\x9aQ\x9e\x12\xb7M\x19\
+I\x9f\xff\x1e\xd8\xa63#q\xff\x07U\x8b\xd2\xd9\xa7k\xe9\xa1U\x94,\xbf\xe4\x88\
+\xe4\xf6\xaf\x12x$}\x8a\xc2Q\xf1\'\x89\xf2\x9b\xfbKE\xae\xd8\x07+\xd2\xa7c\
+\xdf\x0e\xc3D\x00\x00\x00\x00IEND\xaeB`\x82\xe2ovy' )
+
+def getMagPlusBitmap():
+    return BitmapFromImage(getMagPlusImage())
+
+def getMagPlusImage():
+    stream = cStringIO.StringIO(getMagPlusData())
+    return ImageFromStream(stream)
+
+#----------------------------------------------------------------------
+def getGrabHandData():
+    return zlib.decompress(
+'x\xda\x01Z\x01\xa5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
+\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
+\x08\x08|\x08d\x88\x00\x00\x01\x11IDATx\x9c\xb5U\xd1\x12\x830\x08Kh\xff\xff\
+\x8b7\xb3\x97\xd1C\xa4Zw\x93;\x1fJ1\t\x98VJ\x92\xb5N<\x14\x04 I\x00\x80H\xb4\
+\xbd_\x8a9_{\\\x89\xf2z\x02\x18/J\x82\xb5\xce\xed\xfd\x12\xc9\x91\x03\x00_\
+\xc7\xda\x8al\x00{\xfdW\xfex\xf2zeO\x92h\xed\x80\x05@\xa45D\xc5\xb3\x98u\x12\
+\xf7\xab.\xa9\xd0k\x1eK\x95\xbb\x1a]&0\x92\xf0\'\xc6]gI\xda\tsr\xab\x8aI\x1e\
+\\\xe3\xa4\x0e\xb4*`7"\x07\x8f\xaa"x\x05\xe0\xdfo6B\xf3\x17\xe3\x98r\xf1\xaf\
+\x07\xd1Z\'%\x95\x0erW\xac\x8c\xe3\xe0\xfd\xd8AN\xae\xb8\xa3R\x9as>\x11\x8bl\
+yD\xab\x1f\xf3\xec\x1cY\x06\x89$\xbf\x80\xfb\x14\\dw\x90x\x12\xa3+\xeeD\x16%\
+I\xe3\x1c\xb8\xc7c\'\xd5Y8S\x9f\xc3Zg\xcf\x89\xe8\xaao\'\xbbk{U\xfd\xc0\xacX\
+\xab\xbb\xe8\xae\xfa)AEr\x15g\x86(\t\xfe\x19\xa4\xb5\xe9f\xfem\xde\xdd\xbf$\
+\xf8G<>\xa2\xc7\t>\tE\xfc\x8a\xf6\x8dqc\x00\x00\x00\x00IEND\xaeB`\x82\xdb\
+\xd0\x8f\n' )
+
+def getGrabHandBitmap():
+    return BitmapFromImage(getGrabHandImage())
+
+def getGrabHandImage():
+    stream = cStringIO.StringIO(getGrabHandData())
+    return ImageFromStream(stream)
+
+#----------------------------------------------------------------------
+def getHandData():
+    return zlib.decompress(
+'x\xda\x01Y\x01\xa6\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
+\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
+\x08\x08|\x08d\x88\x00\x00\x01\x10IDATx\x9c\xad\x96\xe1\x02\xc2 \x08\x849\
+\xf5\xfd\x9fx\xdb\xf5\'\x8c!\xa8\xab\xee\x975\xe5\x83\x0b\\@\xa9\xb2\xab\xeb\
+<\xa8\xebR\x1bv\xce\xb4\'\xc1\x81OL\x92\xdc\x81\x0c\x00\x1b\x88\xa4\x94\xda\
+\xe0\x83\x8b\x88\x00\x10\x92\xcb\x8a\xca,K\x1fT\xa1\x1e\x04\xe0f_\n\x88\x02\
+\xf1:\xc3\x83>\x81\x0c\x92\x02v\xe5+\xba\xce\x83\xb7f\xb8\xd1\x9c\x8fz8\xb2*\
+\x93\xb7l\xa8\xe0\x9b\xa06\xb8]_\xe7\xc1\x01\x10U\xe1m\x98\xc9\xefm"ck\xea\
+\x1a\x80\xa0Th\xb9\xfd\x877{V*Qk\xda,\xb4\x8b\xf4;[\xa1\xcf6\xaa4\x9cd\x85X\
+\xb0\r\\j\x83\x9dd\x92\xc3 \xf6\xbd\xab\x0c2\x05\xc0p\x9a\xa7]\xf4\x14\x18]3\
+7\x80}h?\xff\xa2\xa2\xe5e\x90\xact\xaf\xe8B\x14y[4\x83|\x13\xdc\x9e\xeb\x16e\
+\x90\xa7\xf2I\rw\x91\x87d\xd7p\x96\xbd\xd70\x07\xda\xe3v\x9a\xf5\xc5\xb2\xb2\
++\xb24\xbc\xaew\xedZe\x9f\x02"\xc8J\xdb\x83\xf6oa\xf5\xb7\xa5\xbf8\x12\xffW\
+\xcf_\xbd;\xe4\x8c\x03\x10\xdb^\x00\x00\x00\x00IEND\xaeB`\x82\xd1>\x97B' )
+
+def getHandBitmap():
+    return BitmapFromImage(getHandImage())
+
+def getHandImage():
+    stream = cStringIO.StringIO(getHandData())
+    return ImageFromStream(stream)
+
 
 
 #---------------------------------------------------------------------------
@@ -1212,19 +1802,19 @@ class PlotPrintout(wx.Printout):
 
 def _draw1Objects():
     # 100 points sin function, plotted as green circles
-    data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
+    data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
     data1.shape = (100, 2)
-    data1[:,1] = Numeric.sin(data1[:,0])
+    data1[:,1] = _Numeric.sin(data1[:,0])
     markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1)
 
     # 50 points cos function, plotted as red line
-    data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
+    data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
     data1.shape = (50,2)
-    data1[:,1] = Numeric.cos(data1[:,0])
+    data1[:,1] = _Numeric.cos(data1[:,0])
     lines = PolyLine(data1, legend= 'Red Line', colour='red')
 
     # A few more points...
-    pi = Numeric.pi
+    pi = _Numeric.pi
     markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
                           (3.*pi/4., -1)], legend='Cross Legend', colour='blue',
                           marker='cross')
@@ -1233,19 +1823,19 @@ def _draw1Objects():
 
 def _draw2Objects():
     # 100 points sin function, plotted as green dots
-    data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
+    data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
     data1.shape = (100, 2)
-    data1[:,1] = Numeric.sin(data1[:,0])
+    data1[:,1] = _Numeric.sin(data1[:,0])
     line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT)
 
     # 50 points cos function, plotted as red dot-dash
-    data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
+    data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
     data1.shape = (50,2)
-    data1[:,1] = Numeric.cos(data1[:,0])
+    data1[:,1] = _Numeric.cos(data1[:,0])
     line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH)
 
     # A few more points...
-    pi = Numeric.pi
+    pi = _Numeric.pi
     markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
                           (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6,
                           fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH,
@@ -1264,7 +1854,7 @@ def _draw3Objects():
 
 def _draw4Objects():
     # 25,000 point line
-    data1 = Numeric.arange(5e5,1e6,10)
+    data1 = _Numeric.arange(5e5,1e6,10)
     data1.shape = (25000, 2)
     line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5)
 
@@ -1297,6 +1887,16 @@ def _draw6Objects():
 
     return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
                         "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
+def _draw7Objects():
+    # Empty graph with axis defined but no points/lines
+    x = _Numeric.arange(1,1000,1)
+    y1 = 4.5*x**2
+    y2 = 2.2*x**3
+    points1 = _Numeric.transpose([x,y1])
+    points2 = _Numeric.transpose([x,y2])
+    line1 = PolyLine(points1, legend='quadratic', colour='blue', width=1)
+    line2 = PolyLine(points2, legend='cubic', colour='red', width=1)
+    return PlotGraphics([line1,line2], "double log plot", "Value X", "Value Y")
 
 
 class TestFrame(wx.Frame):
@@ -1337,7 +1937,8 @@ class TestFrame(wx.Frame):
         self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210)
         menu.Append(260, 'Draw6', 'Draw plots6')
         self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260)
-       
+        menu.Append(261, 'Draw7', 'Draw plots7')
+        self.Bind(wx.EVT_MENU,self.OnPlotDraw7, id=261)
 
         menu.Append(211, '&Redraw', 'Redraw plots')
         self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211)
@@ -1349,8 +1950,13 @@ class TestFrame(wx.Frame):
         self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214) 
         menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK)
         self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215)
+        menu.Append(217, 'Enable &Drag', 'Activates dragging mode', kind=wx.ITEM_CHECK)
+        self.Bind(wx.EVT_MENU,self.OnEnableDrag, id=217)
         menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK)
-        self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220) 
+        self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220)
+        menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK)
+        self.Bind(wx.EVT_MENU,self.OnEnablePointLabel, id=222)
+       
         menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
         self.Bind(wx.EVT_MENU,self.OnScrUp, id=225) 
         menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
@@ -1371,14 +1977,58 @@ class TestFrame(wx.Frame):
         self.CreateStatusBar(1)
         
         self.client = PlotCanvas(self)
+        #define the function for drawing pointLabels
+        self.client.SetPointLabelFunc(self.DrawPointLabel)
         # Create mouse event for showing cursor coords in status bar
-        self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
+        self.client.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
+        # Show closest point when enabled
+        self.client.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
+
         self.Show(True)
 
+    def DrawPointLabel(self, dc, mDataDict):
+        """This is the fuction that defines how the pointLabels are plotted
+            dc - DC that will be passed
+            mDataDict - Dictionary of data that you want to use for the pointLabel
+
+            As an example I have decided I want a box at the curve point
+            with some text information about the curve plotted below.
+            Any wxDC method can be used.
+        """
+        # ----------
+        dc.SetPen(wx.Pen(wx.BLACK))
+        dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
+        
+        sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
+        dc.DrawRectangle( sx-5,sy-5, 10, 10)  #10by10 square centered on point
+        px,py = mDataDict["pointXY"]
+        cNum = mDataDict["curveNum"]
+        pntIn = mDataDict["pIndex"]
+        legend = mDataDict["legend"]
+        #make a string to display
+        s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
+        dc.DrawText(s, sx , sy+1)
+        # -----------
+
     def OnMouseLeftDown(self,event):
-        s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event)
+        s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client._getXY(event)
         self.SetStatusText(s)
-        event.Skip()
+        event.Skip()            #allows plotCanvas OnMouseLeftDown to be called
+
+    def OnMotion(self, event):
+        #show closest point (when enbled)
+        if self.client.GetEnablePointLabel() == True:
+            #make up dict with info for the pointLabel
+            #I've decided to mark the closest point on the closest curve
+            dlst= self.client.GetClosetPoint( self.client._getXY(event), pointScaled= True)
+            if dlst != []:    #returns [] if none
+                curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
+                #make up dictionary to pass to my user function (see DrawPointLabel) 
+                mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
+                            "pointXY":pointXY, "scaledXY":scaledXY}
+                #pass dict to update the pointLabel
+                self.client.UpdatePointLabel(mDataDict)
+        event.Skip()           #go to next handler
 
     def OnFilePageSetup(self, event):
         self.client.PageSetup()
@@ -1416,12 +2066,12 @@ class TestFrame(wx.Frame):
         self.resetDefaults()
         drawObj= _draw4Objects()
         self.client.Draw(drawObj)
-##            # profile
-##            start = time.clock()            
-##            for x in range(10):
-##                self.client.Draw(drawObj)
-##            print "10 plots of Draw4 took: %f sec."%(time.clock() - start)
-##            # profile end
+##        # profile
+##        start = _time.clock()            
+##        for x in range(10):
+##            self.client.Draw(drawObj)
+##        print "10 plots of Draw4 took: %f sec."%(_time.clock() - start)
+##        # profile end
 
     def OnPlotDraw5(self, event):
         # Empty plot with just axes
@@ -1440,6 +2090,12 @@ class TestFrame(wx.Frame):
         self.client.SetYSpec('auto')
         self.client.Draw(_draw6Objects(), xAxis= (0,7))
 
+    def OnPlotDraw7(self, event):
+        #log scale example
+        self.resetDefaults()
+        self.client.setLogScale((True,True))
+        self.client.Draw(_draw7Objects())
+
     def OnPlotRedraw(self,event):
         self.client.Redraw()
 
@@ -1453,13 +2109,21 @@ class TestFrame(wx.Frame):
 
     def OnEnableZoom(self, event):
         self.client.SetEnableZoom(event.IsChecked())
+        self.mainmenu.Check(217, not event.IsChecked())
         
     def OnEnableGrid(self, event):
         self.client.SetEnableGrid(event.IsChecked())
         
+    def OnEnableDrag(self, event):
+        self.client.SetEnableDrag(event.IsChecked())
+        self.mainmenu.Check(214, not event.IsChecked())
+        
     def OnEnableLegend(self, event):
         self.client.SetEnableLegend(event.IsChecked())
 
+    def OnEnablePointLabel(self, event):
+        self.client.SetEnablePointLabel(event.IsChecked())
+
     def OnScrUp(self, event):
         self.client.ScrollUp(1)
         
@@ -1479,9 +2143,11 @@ class TestFrame(wx.Frame):
         self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
         self.client.SetFontSizeAxis(10)
         self.client.SetFontSizeLegend(7)
+        self.client.setLogScale((False,False))
         self.client.SetXSpec('auto')
         self.client.SetYSpec('auto')
         
+        
 
 def __test():