# - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve)
# can be in either user coords or screen coords.
#
-# Nov 2004 Oliver Schoenborn (oliver.schoenborn@utoronto.ca) with
-# valuable input from and testing by Gary)
-# - Factored out "draw command" so extensions easier to implement: clean
-# separation b/w user's "draw" (Draw) and internal "draw" (_draw)
-# - Added ability to define your own ticks at PlotCanvas.Draw
-# - Added better bar charts: PolyBar class and getBarTicksGen()
-# - Put legend writing for a Poly* into Poly* class where it belongs
-# - If no legend specified or exists, no gap created
#
"""
import time as _time
import wx
-# Needs Numeric or numarray
+# Needs Numeric or numarray or NumPy
try:
- import Numeric as _Numeric
+ import numpy.oldnumeric as _Numeric
except:
try:
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
"""
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
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)
self.currentShift= shift
# else unchanged use the current scaling
- def drawLegend(self, dc, printerScale, symWidth, hgap, textHeight, x0, y0):
- legend = self.getLegend()
- if legend != '':
- self._drawLegendSym(dc, printerScale, symWidth, x0, y0)
- dc.DrawText(legend, x0+symWidth+hgap, y0-textHeight/2)
- return True
-
- return False
-
- def _drawLegendSym(self, dc, printerScale, symWidth, x0, y0):
- # default:
- pnt= (x0+symWidth/2., y0)
- self.draw(dc, printerScale, coord= _Numeric.array([pnt]))
-
def getLegend(self):
return self.attributes['legend']
colour = self.attributes['colour']
width = self.attributes['width'] * printerScale
style= self.attributes['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)
else:
dc.DrawLines(coord) # draw legend line
- def _drawLegendSym(self, dc, printerScale, symWidth, x0, y0):
- pnt1= (x0, y0)
- pnt2= (x0+symWidth/1.5, y0)
- self.draw(dc, printerScale, coord= _Numeric.array([pnt1,pnt2]))
-
def getSymExtent(self, printerScale):
"""Width and Height of Marker"""
h= self.attributes['width'] * printerScale
'colour'= 'black', - wx.Pen Colour any wx.NamedColour
'width'= 1, - Pen width
'size'= 2, - Marker size
- 'fillcolour'= same as colour, - wx.Brush Colour
+ 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
'marker'= 'circle' - Marker shape
'legend'= '' - Marker Legend to display
fillstyle = self.attributes['fillstyle']
marker = self.attributes['marker']
+ 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(fillcolour,fillstyle))
lines= _Numeric.concatenate((coords,coords),axis=1)+f
dc.DrawLineList(lines.astype(_Numeric.Int32))
-class PolyBar(PolyPoints):
- """Class to define one or more bars for bar chart. All bars in a
- PolyBar share same style etc.
- - All methods except __init__ are private.
- """
-
- _attributes = {'colour': 'black',
- 'width': 1,
- 'size': 2,
- 'legend': '',
- 'fillcolour': None,
- 'fillstyle': wx.SOLID}
-
- def __init__(self, points, **attr):
- """Creates several bars.
- points - sequence (array, tuple or list) of (x,y) points
- indicating *top left* corner of bar. Can also be
- just one (x,y) tuple if labels attribute is just
- a string (not a list)
- **attr - key word attributes
- Defaults:
- 'colour'= wx.BLACK, - wx.Pen Colour
- 'width'= 1, - Pen width
- 'size'= 2, - Bar size
- 'fillcolour'= same as colour, - wx.Brush Colour
- 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
- 'legend'= '' - string used for PolyBar legend
- 'labels'= '' - string if only one point, or list of labels, one per point
- Note that if no legend is given the label is used.
- """
- barPoints = [(0,0)] # needed so height of bar can be determined
- self.labels = []
- # add labels and points to above data members:
- try:
- labels = attr['labels']
- del attr['labels']
- except:
- labels = None
- if labels is None: # figure out if we have 1 point or many
- try:
- points[0][0] # ok: we have a seq of points
- barPoints.extend(points)
- except: # failed, so have just one point:
- barPoints.append(points)
- elif isinstance(labels, list) or isinstance(labels, tuple):
- # labels is a sequence so points must be too, with same length:
- self.labels.extend(labels)
- barPoints.extend(points)
- if len(labels) != len(points):
- msg = "%d bar labels missing" % (len(points)-len(labels))
- raise ValueError, msg
- else: # label given, but only one, so must be only one point:
- barPoints.append(points)
- self.labels.append(labels)
-
- PolyPoints.__init__(self, barPoints, attr)
-
- def draw(self, dc, printerScale, coord= None):
- colour = self.attributes['colour']
- width = self.attributes['width'] * printerScale
- size = self.attributes['size'] * printerScale
- fillcolour = self.attributes['fillcolour']
- fillstyle = self.attributes['fillstyle']
-
- dc.SetPen(wx.Pen(colour,int(width)))
- if fillcolour:
- dc.SetBrush(wx.Brush(fillcolour,fillstyle))
- else:
- dc.SetBrush(wx.Brush(colour, fillstyle))
- if coord == None:
- self._drawbar(dc, self.scaled, size)
- else:
- self._drawLegendMarker(dc, coord, size) #draw legend marker
-
- def _drawLegendMarker(self, dc, coord, size):
- fact= 10
- wh= 2*fact
- rect= _Numeric.zeros((len(coord),4),_Numeric.Float)+[0.0,0.0,wh,wh]
- rect[:,0:2]= coord-[fact,fact]
- dc.DrawRectangleList(rect.astype(_Numeric.Int32))
-
- def offset(self, heights, howMany = 1, shift=0.25):
- """Return a list of points, where x's are those of this bar,
- shifted by shift*howMany (in caller's units, not pixel units), and
- heights are given in heights."""
- points = [(point[0][0]+shift*howMany,point[1]) for point
- in zip(self.points[1:], heights)]
- return points
-
- def getSymExtent(self, printerScale):
- """Width and Height of bar symbol"""
- s= 5*self.attributes['size'] * printerScale
- return (s,s)
-
- def getLegend(self):
- """This returns a comma-separated list of bar labels (one string)"""
- try: legendItem = self.attributes['legend']
- except:
- legendItem = ", ".join(self.labels)
- return legendItem
-
- def _drawbar(self, dc, coords, size=1):
- width = 5.0*size
- y0 = coords[0][1]
- bars = []
- for coord in coords[1:]:
- x = coord[0] # - width/2 not as good
- y = coord[1]
- height = int(y0) - int(y)
- bars.append((int(x),int(y),int(width),int(height)))
- #print x,y,width, height
- dc.DrawRectangleList(bars)
-
- def getTicks(self):
- """Get the list of (tick pos, label) pairs for this PolyBar.
- It is unlikely you need this, but it is used by
- getBarTicksGen."""
- ticks = []
- # remember to skip first point:
- for point, label in zip(self.points[1:], self.labels):
- ticks.append((point[0], label))
- return ticks
-
-def getBarTicksGen(*polyBars):
- """Get a function that can be given as xTicks argument when
- calling PolyCanvas.Draw() when plotting one or more PolyBar.
- The returned function allows the bar chart to have the bars
- labelled at the x axis. """
- ticks = []
- for polyBar in polyBars:
- ticks.extend(polyBar.getTicks())
-
- def tickGenerator(lower,upper,gg,ff):
- tickPairs = []
- for tick in ticks:
- if lower <= tick[0] < upper:
- tickPairs.append(tick)
- return tickPairs
-
- return tickGenerator
-
-
class PlotGraphics:
"""Container to hold PolyXXX objects and graph labels
- All methods except __init__ are private.
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:]:
#-------------------------------------------------------------------------------
# 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, id=wx.ID_ANY, pos=wx.DefaultPosition,
+ size=wx.DefaultSize, style=0, name="plotCanvas"):
+ """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, id, pos, size, style, name)
+
+ 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)
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._hasDragged= False
# Drawing Variables
- self._drawCmd = None
+ self.last_draw = None
self._pointScale= 1
self._pointShift= 0
self._xSpec= 'auto'
self._pointLabelEnabled= False
self.last_PointLabel= None
self._pointLabelFunc= None
- self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
+ self.canvas.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
- self.Bind(wx.EVT_PAINT, self.OnPaint)
- self.Bind(wx.EVT_SIZE, self.OnSize)
+ 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
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()
"""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):
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
"""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):
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()
def Reset(self):
"""Unzoom the plot."""
self.last_PointLabel = None #reset pointLabel
- if self.BeenDrawn():
- self._drawCmd.resetAxes(self._xSpec, self._ySpec)
- self._draw()
+ if self.last_draw is not None:
+ 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.BeenDrawn():
- self._drawCmd.scrollAxisX(units, self._xSpec)
- self._draw()
+ 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)
def ScrollUp(self, units):
"""Move view up number of axis units."""
self.last_PointLabel = None #reset pointLabel
- if self.BeenDrawn():
- self._drawCmd.scrollAxisY(units, self._ySpec)
- self._draw()
+ 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)
+
+ 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."""
x,y= self.PositionScreenToUser(event.GetPosition())
return x,y
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"""
- return self._drawCmd.getXMaxRange()
+ graphics= self.last_draw[0]
+ p1, p2 = graphics.boundingBox() # min, max points of graphics
+ xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
+ 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"""
- return self._drawCmd.getYMaxRange()
+ graphics= self.last_draw[0]
+ p1, p2 = graphics.boundingBox() # min, max points of graphics
+ yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
+ 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._drawCmd.xAxis
+ 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._drawCmd.yAxis
+ return self.last_draw[2]
+
+ def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
+ """Wrapper around _Draw, which handles log axes"""
+
+ graphics.setLogScale(self.getLogScale())
- def BeenDrawn(self):
- """Return true if Draw() has been called once, false otherwise."""
- return self._drawCmd is not None
-
- def Draw(self, graphics, xAxis = None, yAxis = None, dc = None,
- xTicks = None, yTicks = 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.
- xTicks and yTicks - can be either
- - a list of floats where the ticks should be, only visible
- ticks will be used;
- - a function that generates a list of (tick, label) pairs;
- The function must be of form func(lower, upper, grid,
- format), and the pairs must contain only ticks between lower and
- upper. Function can use grid and format that are computed by
- PlotCanvas, if desired, where grid is the spacing between ticks,
- and format is the format string for how tick labels
- will appear. See tickGeneratorDefault and
- tickGeneratorFromList for examples of such functions.
- """
# 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))
- self._drawCmd = DrawCmd(graphics, xAxis, yAxis,
- self._xSpec, self._ySpec,
- xTicks, yTicks)
- self._draw(dc)
-
- def _draw(self, dc=None):
- """Implement the draw command, with dc if given, using any new
- settings (legend toggled, axis specs, zoom, etc). """
- assert self._drawCmd is not None
-
# check case for axis = (a,b) where a==b caused by improper zooms
- if self._drawCmd.isEmpty():
+ 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:
# sets new dc and clears it
- dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
+ dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
dc.Clear()
dc.BeginDrawing()
# set font size for every thing but title and legend
dc.SetFont(self._getFont(self._fontSizeAxis))
+ # sizes axis to axis type, create lower left and upper right corners of plot
+ if xAxis == None or yAxis == None:
+ # One or both axis not specified in Draw
+ p1, p2 = graphics.boundingBox() # min, max points of graphics
+ if xAxis == None:
+ xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
+ if yAxis == None:
+ yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
+ # Adjust bounding box for axis spec
+ p1[0],p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin,ymin)
+ 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)
+
+ 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._drawCmd.getTicksX()
+ 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._drawCmd.getTicksY()
- 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
# TextExtents for Title and Axis Labels
- graphics = self._drawCmd.graphics
titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics)
# TextExtents for Legend
- legendBoxWH, legendSymExt, legendHGap, legendTextExt \
- = self._legendWH(dc, graphics)
+ legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
# room around graph area
rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width
# drawing legend makers and text
if self._legendEnabled:
- self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH,
- legendSymExt, legendHGap, legendTextExt)
-
- #sizes axis to axis type, create lower left and upper right corners of plot
- p1, p2 = self._drawCmd.getBoundingBox()
+ 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))
# 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.BeenDrawn():
- self._draw(dc)
+ if self.last_draw is not None:
+ graphics, xAxis, yAxis= self.last_draw
+ self._Draw(graphics,xAxis,yAxis,dc)
def Clear(self):
"""Erase the window."""
self.last_PointLabel = None #reset pointLabel
- dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
+ dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
dc.Clear()
- self._drawCmd = None
+ self.last_draw = None
def Zoom(self, Center, Ratio):
""" Zoom on the plot
Zooms by the Ratio = (Xratio, Yratio) given
"""
self.last_PointLabel = None #reset maker
- if self.BeenDrawn():
- self._drawCmd.zoom(Center, Ratio)
- self._draw()
+ x,y = Center
+ if self.last_draw != None:
+ (graphics, xAxis, yAxis) = self.last_draw
+ w = (xAxis[1] - xAxis[0]) * Ratio[0]
+ 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)
- def ChangeAxes(self, xAxis, yAxis):
- """Change axes specified at last Draw() command. If
- Draw() never called yet, RuntimeError raised. """
- if not self.BeenDrawn():
- raise RuntimeError, "Must call Draw() at least once"
- self._drawCmd.changeAxisX(xAxis, self._xSpec)
- self._drawCmd.changeAxisY(yAxis, self._ySpec)
- self._draw()
-
def GetClosestPoints(self, pntXY, pointScaled= True):
"""Returns list with
[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
if pointScaled == True based on screen coords
if pointScaled == False based on user coords
"""
- if self.BeenDrawn():
- return self._drawCmd.getClosestPoints(pntXY, pointScaled)
- else:
+ 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
"""
if self.last_PointLabel != None:
#compare pointXY
- if mDataDict["pointXY"] != self.last_PointLabel["pointXY"]:
+ if _Numeric.sometrue(mDataDict["pointXY"] != self.last_PointLabel["pointXY"]):
#closest changed
self._drawPointLabel(self.last_PointLabel) #erase old
self._drawPointLabel(mDataDict) #plot new
# event handlers **********************************
def OnMotion(self, event):
- if not self.BeenDrawn(): return
if self._zoomEnabled and event.LeftIsDown():
if self._hasDragged:
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):
- if self.BeenDrawn():
- 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 not self.BeenDrawn(): return
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)
self.last_PointLabel = None #reset pointLabel
- self._drawCmd.changeAxisX((minX,maxX))
- self._drawCmd.changeAxisY((minY,maxY))
- self._draw()
+ if self.last_draw != 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):
if self.last_PointLabel != None:
self._drawPointLabel(self.last_PointLabel) #erase old
self.last_PointLabel = None
- dc = wx.BufferedPaintDC(self, self._Buffer)
+ 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.BeenDrawn():
- self._draw()
- else:
+ if self.last_draw is None:
self.Clear()
+ else:
+ graphics, xSpec, ySpec = self.last_draw
+ self._Draw(graphics,xSpec,ySpec)
def OnLeave(self, event):
"""Used to erase pointLabel when mouse outside window"""
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])
def _printDraw(self, printDC):
"""Used for printing."""
- if self.BeenDrawn():
- self._draw(printDC)
+ if self.last_draw != None:
+ graphics, xSpec, ySpec= self.last_draw
+ self._Draw(graphics,xSpec,ySpec,printDC)
def _drawPointLabel(self, mDataDict):
"""Draws and erases pointLabels"""
self._pointLabelFunc(dcs,mDataDict) #custom user pointLabel function
dcs.EndDrawing()
- dc = wx.ClientDC( self )
+ 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, hgap, legendTextExt):
+ def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
"""Draws legend symbols and text"""
# top right hand corner of graph box is ref corner
trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1]
legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box
lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines
dc.SetFont(self._getFont(self._fontSizeLegend))
- legendIdx = 0
- for gr in graphics:
- s = legendIdx * lineHeight
- drew = gr.drawLegend(dc, self.printerScale,
- legendSymExt[0], hgap, legendTextExt[1],
- trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.)
- if drew:
- legendIdx += 1
+ for i in range(len(graphics)):
+ o = graphics[i]
+ s= i*lineHeight
+ 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]))
+ 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]))
+ else:
+ raise TypeError, "object is neither PolyMarker or PolyLine instance"
+ # draw legend txt
+ pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2)
+ dc.DrawText(o.getLegend(),pnt[0],pnt[1])
dc.SetFont(self._getFont(self._fontSizeAxis)) # reset
def _titleLablesWH(self, dc, graphics):
"""Returns the size in screen units for legend box"""
if self._legendEnabled != True:
legendBoxWH= symExt= txtExt= (0,0)
- hgap= 0
else:
- #find max symbol size and gap b/w symbol and text
+ # find max symbol size
symExt= graphics.getSymExtent(self.printerScale)
- hgap = symExt[0]*0.1
# find max legend text extent
dc.SetFont(self._getFont(self._fontSizeLegend))
txtList= graphics.getLegendNames()
txtExt= dc.GetTextExtent(txtList[0])
for txt in graphics.getLegendNames()[1:]:
txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt))
- maxW= symExt[0]+hgap+txtExt[0]
+ maxW= symExt[0]+txtExt[0]
maxH= max(symExt[1],txtExt[1])
# padding .1 for lhs of legend box and space between lines
maxW= maxW* 1.1
maxH= maxH* 1.1 * len(txtList)
dc.SetFont(self._getFont(self._fontSizeAxis))
legendBoxWH= (maxW,maxH)
- return (legendBoxWH, symExt, hgap, txtExt)
+ return (legendBoxWH, symExt, txtExt)
def _drawRubberBand(self, corner1, corner2):
"""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 ) )
def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
penWidth= self.printerScale # increases thickness for printing only
- dc.SetPen(wx.Pen(wx.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
pt[1]-0.5*h)
text = 0 # axis values not drawn on right side
-
-class DrawCmd:
- """Represent a "draw" command, i.e. the one given in call to
- PlotCanvas.Draw(). The axis specs are not saved to preserve
- backward compatibility: they could be specified before the
- first Draw() command."""
- def __init__(self, graphics, xAxis, yAxis, xSpec, ySpec, xTicks, yTicks):
- """Same args as PlotCanvas.Draw(), plus axis specs."""
- self.graphics = graphics
- self.xTickGen = xTicks
- self.yTickGen = yTicks
- self.xAxisInit, self.yAxisInit = xAxis, yAxis
-
- self.xAxis = None
- self.yAxis = None
- self.changeAxisX(xAxis, xSpec)
- self.changeAxisY(yAxis, ySpec)
- assert self.xAxis is not None
- assert self.yAxis is not None
-
- def isEmpty(self):
- """Return true if either axis has 0 size."""
- if self.xAxis[0] == self.xAxis[1]:
- return True
- if self.yAxis[0] == self.yAxis[1]:
- return True
-
- return False
-
- def resetAxes(self, xSpec, ySpec):
- """Reset the axes to what they were initially, using axes specs given."""
- self.changeAxisX(self.xAxisInit, xSpec)
- self.changeAxisY(self.yAxisInit, ySpec)
-
- def getBoundingBox(self):
- """Returns p1, p2 (two pairs)"""
- p1 = _Numeric.array((self.xAxis[0], self.yAxis[0]))
- p2 = _Numeric.array((self.xAxis[1], self.yAxis[1]))
- return p1, p2
+ def _xticks(self, *args):
+ if self._logscale[0]:
+ return self._logticks(*args)
+ else:
+ return self._ticks(*args)
- def getClosestPoints(self, pntXY, pointScaled=True):
- ll = []
- for curveNum,obj in enumerate(self.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)
- ll.append(cn)
- return ll
-
- def scrollAxisX(self, units, xSpec):
- """Scroll y axis by units, using ySpec for axis spec."""
- self.changeAxisX((self.xAxis[0]+units, self.xAxis[1]+units), xSpec)
-
- def scrollAxisY(self, units, ySpec):
- """Scroll y axis by units, using ySpec for axis spec."""
- self.changeAxisY((self.yAxis[0]+units, self.yAxis[1]+units), ySpec)
-
- def changeAxisX(self, xAxis=None, xSpec=None):
- """Change the x axis to new given, or if None use ySpec to get it. """
- assert type(xAxis) in [type(None),tuple]
- if xAxis is None:
- p1, p2 = self.graphics.boundingBox() #min, max points of graphics
- self.xAxis = self._axisInterval(xSpec, p1[0], p2[0]) #in user units
- else:
- self.xAxis = xAxis
-
- def changeAxisY(self, yAxis=None, ySpec=None):
- """Change the y axis to new given, or if None use ySpec to get it. """
- assert type(yAxis) in [type(None),tuple]
- if yAxis is None:
- p1, p2 = self.graphics.boundingBox() #min, max points of graphics
- self.yAxis = self._axisInterval(ySpec, p1[1], p2[1])
- else:
- self.yAxis = yAxis
-
- def zoom(self, center, ratio):
- """Zoom to center and ratio."""
- x,y = Center
- w = (self.xAxis[1] - self.xAxis[0]) * Ratio[0]
- h = (self.yAxis[1] - self.yAxis[0]) * Ratio[1]
- self.xAxis = ( x - w/2, x + w/2 )
- self.yAxis = ( y - h/2, y + h/2 )
-
- def getXMaxRange(self):
- p1, p2 = self.graphics.boundingBox() #min, max points of graphics
- return self._axisInterval(self._xSpec, p1[0], p2[0]) #in user units
-
- def getYMaxRange(self):
- p1, p2 = self.graphics.boundingBox() #min, max points of graphics
- return self._axisInterval(self._ySpec, p1[1], p2[1]) #in user units
+ def _yticks(self, *args):
+ if self._logscale[1]:
+ return self._logticks(*args)
+ else:
+ return self._ticks(*args)
- def getTicksX(self):
- """Get the ticks along y axis. Actually pairs of (t, str)."""
- xticks = self._ticks(self.xAxis[0], self.xAxis[1], self.xTickGen)
- if xticks == []: # try without the generator
- xticks = self._ticks(self.xAxis[0], self.xAxis[1])
- return xticks
-
- def getTicksY(self):
- """Get the ticks along y axis. Actually pairs of (t, str)."""
- yticks = self._ticks(self.yAxis[0], self.yAxis[1], self.yTickGen)
- if yticks == []: # try without the generator
- yticks = self._ticks(self.yAxis[0], self.yAxis[1])
- return yticks
-
- def _axisInterval(self, spec, lower, upper):
- """Returns sensible axis range for given spec."""
- if spec == 'none' or spec == 'min':
- if lower == upper:
- return lower-0.5, upper+0.5
- else:
- return lower, upper
- elif spec == 'auto':
- range = upper-lower
- if range == 0.:
- return lower-0.5, upper+0.5
- log = _Numeric.log10(range)
- power = _Numeric.floor(log)
- fraction = log-power
- if fraction <= 0.05:
- power = power-1
- grid = 10.**power
- lower = lower - lower % grid
- mod = upper % grid
- if mod != 0:
- upper = upper - mod + grid
- return lower, upper
- elif type(spec) == type(()):
- lower, upper = spec
- if lower <= upper:
- return lower, upper
- else:
- return upper, lower
+ 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:
- raise ValueError, str(spec) + ': illegal axis specification'
-
- def _ticks(self, lower, upper, generator=None):
- """Get ticks between lower and upper, using generator if given. """
+ 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)
else:
digits = -int(power)
format = '%'+`digits+2`+'.'+`digits`+'f'
+ ticks = []
+ 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.))]
+
+
+ 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 generator is None:
- return tickGeneratorDefault(lower, upper, grid, format)
- elif isinstance(generator, list):
- return tickGeneratorFromList(generator, lower, upper, format)
+ 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:
- return generator(lower, upper, grid, format)
+ self.sb_hor.SetScrollbar(0, 1000, 1000, 1000)
- _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))]
+ # 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)
-def tickGeneratorDefault(lower, upper, grid, format):
- """Default tick generator used by PlotCanvas.Draw() if not specified."""
- ticks = []
- t = -grid*_Numeric.floor(-lower/grid)
- while t <= upper:
- ticks.append( (t, format % (t,)) )
- t = t + grid
- return ticks
-
-def tickGeneratorFromList(ticks, lower, upper, format):
- """Tick generator used by PlotCanvas.Draw() if
- a list is given for xTicks or yTicks. """
- tickPairs = []
- for tick in ticks:
- if lower <= tick <= upper:
- tickPairs.append((tick, format % tick))
- return tickPairs
+ self.SetShowScrollbars(needScrollbars)
+ self._adjustingSB = False
#-------------------------------------------------------------------------------
# Used to layout the printer page
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)
+
#---------------------------------------------------------------------------
return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
"Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
-
def _draw7Objects():
- # four bars of various styles colors and sizes
- bar1 = PolyBar((0,1), colour='green', size=4, labels='Green bar')
- bar2 = PolyBar((2,1), colour='red', size=7, labels='Red bar',
- fillstyle=wx.CROSSDIAG_HATCH)
- bar3 = PolyBar([(1,3),(3,4)], colour='blue',
- labels=['Blue bar 1', 'Blue bar 2'],
- fillstyle=wx.TRANSPARENT)
- bars = [bar1, bar2, bar3]
- return PlotGraphics(bars,
- "Graph Title", "Bar names", "Bar height"), getBarTicksGen(*bars)
-
-def _draw8Objects():
- # four bars in two groups, overlayed to a line plot
- x1,x2 = 0.0,0.5
- bar1 = PolyBar([(x1,1.),(x1+2,2.)], colour='green', size=4,
- legend="1998", fillstyle=wx.CROSSDIAG_HATCH)
- bar2 = PolyBar(bar1.offset([1.2,2.5]), colour='red', size=7,
- legend="2000", labels=['cars','trucks'])
- line1 = PolyLine([(x1,1.5), (x2,1.2), (x1+2,1), (x2+2,2)], colour='blue')
- return PlotGraphics([bar1, bar2, line1],
- "Graph Title", "Bar names", "Bar height"), getBarTicksGen(bar1, bar2)
+ # 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):
def __init__(self, parent, id, title):
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(262, 'Draw8', 'Draw plots8')
- self.Bind(wx.EVT_MENU,self.OnPlotDraw8, id=262)
-
menu.Append(211, '&Redraw', 'Redraw plots')
self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211)
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)
menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK)
#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.Bind(wx.EVT_MOTION, self.OnMotion)
+ self.client.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
self.Show(True)
# -----------
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() #allows plotCanvas OnMouseLeftDown to be called
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)
+ 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)
self.client.Draw(_draw6Objects(), xAxis= (0,7))
def OnPlotDraw7(self, event):
- #Bar Graph and custom ticks Example, using PolyBar
+ #log scale example
self.resetDefaults()
- bars, tickGenerator = _draw7Objects()
- #note that yTicks is not necessary here, used only
- #to show off custom ticks
- self.client.Draw(bars, xAxis=(0,4),yAxis = (0,5),
- xTicks=tickGenerator, yTicks=[0,1,3])
-
- def OnPlotDraw8(self, event):
- #Bar Graph and custom ticks Example, using PolyBar
- self.resetDefaults()
- plot, tickGenerator = _draw8Objects()
- self.client.Draw(plot, xAxis=(0,4),yAxis = (0,5), xTicks=tickGenerator)
-
+ self.client.setLogScale((True,True))
+ self.client.Draw(_draw7Objects())
def OnPlotRedraw(self,event):
self.client.Redraw()
self.client.Clear()
def OnPlotScale(self, event):
- if self.client.BeenDrawn():
- self.client.ChangeAxes((1,3.05),(0,1))
-
+ if self.client.last_draw != None:
+ graphics, xAxis, yAxis= self.client.last_draw
+ self.client.Draw(graphics,(1,3.05),(0,1))
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())
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():