]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/plot.py
Applied patch [ 1281902 ] fix for wxJoystick under Linux
[wxWidgets.git] / wxPython / wx / lib / plot.py
CommitLineData
9d6685e2
RD
1#-----------------------------------------------------------------------------
2# Name: wx.lib.plot.py
00b9c867 3# Purpose: Line, Bar and Scatter Graphs
9d6685e2
RD
4#
5# Author: Gordon Williams
6#
7# Created: 2003/11/03
8# RCS-ID: $Id$
9# Copyright: (c) 2002
10# Licence: Use as you wish.
11#-----------------------------------------------------------------------------
12# 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net)
13#
14# o 2.5 compatability update.
15# o Renamed to plot.py in the wx.lib directory.
16# o Reworked test frame to work with wx demo framework. This saves a bit
17# of tedious cut and paste, and the test app is excellent.
18#
33785d9f
RD
19# 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net)
20#
21# o wxScrolledMessageDialog -> ScrolledMessageDialog
22#
00b9c867
RD
23# Oct 6, 2004 Gordon Williams (g_will@cyberus.ca)
24# - Added bar graph demo
25# - Modified line end shape from round to square.
26# - Removed FloatDCWrapper for conversion to ints and ints in arguments
27#
b31cbeb9
RD
28# Oct 15, 2004 Gordon Williams (g_will@cyberus.ca)
29# - Imported modules given leading underscore to name.
30# - Added Cursor Line Tracking and User Point Labels.
31# - Demo for Cursor Line Tracking and Point Labels.
32# - Size of plot preview frame adjusted to show page better.
33# - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas.
34# - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve)
35# can be in either user coords or screen coords.
36#
37#
9d6685e2
RD
38
39"""
40This is a simple light weight plotting module that can be used with
41Boa or easily integrated into your own wxPython application. The
42emphasis is on small size and fast plotting for large data sets. It
43has a reasonable number of features to do line and scatter graphs
00b9c867
RD
44easily as well as simple bar graphs. It is not as sophisticated or
45as powerful as SciPy Plt or Chaco. Both of these are great packages
46but consume huge amounts of computer resources for simple plots.
47They can be found at http://scipy.com
9d6685e2
RD
48
49This file contains two parts; first the re-usable library stuff, then,
50after a "if __name__=='__main__'" test, a simple frame and a few default
51plots for examples and testing.
52
53Based on wxPlotCanvas
54Written by K.Hinsen, R. Srinivasan;
55Ported to wxPython Harm van der Heijden, feb 1999
56
57Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
58 -More style options
59 -Zooming using mouse "rubber band"
60 -Scroll left, right
61 -Grid(graticule)
62 -Printing, preview, and page set up (margins)
63 -Axis and title labels
64 -Cursor xy axis values
65 -Doc strings and lots of comments
66 -Optimizations for large number of points
67 -Legends
68
69Did a lot of work here to speed markers up. Only a factor of 4
70improvement though. Lines are much faster than markers, especially
71filled markers. Stay away from circles and triangles unless you
72only have a few thousand points.
73
74Times for 25,000 points
75Line - 0.078 sec
76Markers
77Square - 0.22 sec
78dot - 0.10
79circle - 0.87
80cross,plus - 0.28
81triangle, triangle_down - 0.90
82
83Thanks to Chris Barker for getting this version working on Linux.
84
85Zooming controls with mouse (when enabled):
86 Left mouse drag - Zoom box.
87 Left mouse double click - reset zoom.
88 Right mouse click - zoom out centred on click location.
89"""
90
b31cbeb9
RD
91import string as _string
92import time as _time
9d6685e2
RD
93import wx
94
00b9c867 95# Needs Numeric or numarray
9d6685e2 96try:
b31cbeb9 97 import Numeric as _Numeric
9d6685e2
RD
98except:
99 try:
b31cbeb9 100 import numarray as _Numeric #if numarray is used it is renamed Numeric
9d6685e2
RD
101 except:
102 msg= """
103 This module requires the Numeric or numarray module,
104 which could not be imported. It probably is not installed
105 (it's not part of the standard Python distribution). See the
106 Python site (http://www.python.org) for information on
107 downloading source or binaries."""
108 raise ImportError, "Numeric or numarray not found. \n" + msg
109
110
111
112#
113# Plotting classes...
114#
115class PolyPoints:
116 """Base Class for lines and markers
117 - All methods are private.
118 """
119
120 def __init__(self, points, attr):
b31cbeb9 121 self.points = _Numeric.array(points)
9d6685e2
RD
122 self.currentScale= (1,1)
123 self.currentShift= (0,0)
124 self.scaled = self.points
125 self.attributes = {}
126 self.attributes.update(self._attributes)
127 for name, value in attr.items():
128 if name not in self._attributes.keys():
129 raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys()
130 self.attributes[name] = value
131
132 def boundingBox(self):
133 if len(self.points) == 0:
134 # no curves to draw
135 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
b31cbeb9
RD
136 minXY= _Numeric.array([-1,-1])
137 maxXY= _Numeric.array([ 1, 1])
9d6685e2 138 else:
b31cbeb9
RD
139 minXY= _Numeric.minimum.reduce(self.points)
140 maxXY= _Numeric.maximum.reduce(self.points)
9d6685e2
RD
141 return minXY, maxXY
142
143 def scaleAndShift(self, scale=(1,1), shift=(0,0)):
144 if len(self.points) == 0:
145 # no curves to draw
146 return
147 if (scale is not self.currentScale) or (shift is not self.currentShift):
148 # update point scaling
149 self.scaled = scale*self.points+shift
150 self.currentScale= scale
151 self.currentShift= shift
152 # else unchanged use the current scaling
153
154 def getLegend(self):
155 return self.attributes['legend']
156
b31cbeb9
RD
157 def getClosestPoint(self, pntXY, pointScaled= True):
158 """Returns the index of closest point on the curve, pointXY, scaledXY, distance
159 x, y in user coords
160 if pointScaled == True based on screen coords
161 if pointScaled == False based on user coords
162 """
163 if pointScaled == True:
164 #Using screen coords
165 p = self.scaled
166 pxy = self.currentScale * _Numeric.array(pntXY)+ self.currentShift
167 else:
168 #Using user coords
169 p = self.points
170 pxy = _Numeric.array(pntXY)
171 #determine distance for each point
172 d= _Numeric.sqrt(_Numeric.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2)
173 pntIndex = _Numeric.argmin(d)
174 dist = d[pntIndex]
175 return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist]
176
177
9d6685e2
RD
178class PolyLine(PolyPoints):
179 """Class to define line type and style
180 - All methods except __init__ are private.
181 """
182
183 _attributes = {'colour': 'black',
184 'width': 1,
185 'style': wx.SOLID,
186 'legend': ''}
187
188 def __init__(self, points, **attr):
189 """Creates PolyLine object
190 points - sequence (array, tuple or list) of (x,y) points making up line
191 **attr - key word attributes
192 Defaults:
193 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
194 'width'= 1, - Pen width
195 'style'= wx.SOLID, - wx.Pen style
196 'legend'= '' - Line Legend to display
197 """
198 PolyPoints.__init__(self, points, attr)
199
200 def draw(self, dc, printerScale, coord= None):
201 colour = self.attributes['colour']
202 width = self.attributes['width'] * printerScale
203 style= self.attributes['style']
48023e15 204 pen = wx.Pen(wx.NamedColour(colour), width, style)
00b9c867
RD
205 pen.SetCap(wx.CAP_BUTT)
206 dc.SetPen(pen)
9d6685e2
RD
207 if coord == None:
208 dc.DrawLines(self.scaled)
209 else:
210 dc.DrawLines(coord) # draw legend line
211
212 def getSymExtent(self, printerScale):
213 """Width and Height of Marker"""
214 h= self.attributes['width'] * printerScale
215 w= 5 * h
216 return (w,h)
217
218
219class PolyMarker(PolyPoints):
220 """Class to define marker type and style
221 - All methods except __init__ are private.
222 """
223
224 _attributes = {'colour': 'black',
225 'width': 1,
226 'size': 2,
227 'fillcolour': None,
228 'fillstyle': wx.SOLID,
229 'marker': 'circle',
230 'legend': ''}
231
232 def __init__(self, points, **attr):
233 """Creates PolyMarker object
234 points - sequence (array, tuple or list) of (x,y) points
235 **attr - key word attributes
236 Defaults:
237 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
238 'width'= 1, - Pen width
239 'size'= 2, - Marker size
48023e15 240 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
9d6685e2
RD
241 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
242 'marker'= 'circle' - Marker shape
243 'legend'= '' - Marker Legend to display
244
245 Marker Shapes:
246 - 'circle'
247 - 'dot'
248 - 'square'
249 - 'triangle'
250 - 'triangle_down'
251 - 'cross'
252 - 'plus'
253 """
254
255 PolyPoints.__init__(self, points, attr)
256
257 def draw(self, dc, printerScale, coord= None):
258 colour = self.attributes['colour']
259 width = self.attributes['width'] * printerScale
260 size = self.attributes['size'] * printerScale
261 fillcolour = self.attributes['fillcolour']
262 fillstyle = self.attributes['fillstyle']
263 marker = self.attributes['marker']
264
48023e15 265 dc.SetPen(wx.Pen(wx.NamedColour(colour), width))
9d6685e2 266 if fillcolour:
48023e15 267 dc.SetBrush(wx.Brush(wx.NamedColour(fillcolour),fillstyle))
9d6685e2 268 else:
48023e15 269 dc.SetBrush(wx.Brush(wx.NamedColour(colour), fillstyle))
9d6685e2
RD
270 if coord == None:
271 self._drawmarkers(dc, self.scaled, marker, size)
272 else:
273 self._drawmarkers(dc, coord, marker, size) # draw legend marker
274
275 def getSymExtent(self, printerScale):
276 """Width and Height of Marker"""
277 s= 5*self.attributes['size'] * printerScale
278 return (s,s)
279
280 def _drawmarkers(self, dc, coords, marker,size=1):
281 f = eval('self._' +marker)
282 f(dc, coords, size)
283
284 def _circle(self, dc, coords, size=1):
285 fact= 2.5*size
286 wh= 5.0*size
b31cbeb9 287 rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
9d6685e2 288 rect[:,0:2]= coords-[fact,fact]
b31cbeb9 289 dc.DrawEllipseList(rect.astype(_Numeric.Int32))
9d6685e2
RD
290
291 def _dot(self, dc, coords, size=1):
292 dc.DrawPointList(coords)
293
294 def _square(self, dc, coords, size=1):
295 fact= 2.5*size
296 wh= 5.0*size
b31cbeb9 297 rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
9d6685e2 298 rect[:,0:2]= coords-[fact,fact]
b31cbeb9 299 dc.DrawRectangleList(rect.astype(_Numeric.Int32))
9d6685e2
RD
300
301 def _triangle(self, dc, coords, size=1):
302 shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)]
b31cbeb9 303 poly= _Numeric.repeat(coords,3)
9d6685e2
RD
304 poly.shape= (len(coords),3,2)
305 poly += shape
b31cbeb9 306 dc.DrawPolygonList(poly.astype(_Numeric.Int32))
9d6685e2
RD
307
308 def _triangle_down(self, dc, coords, size=1):
309 shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)]
b31cbeb9 310 poly= _Numeric.repeat(coords,3)
9d6685e2
RD
311 poly.shape= (len(coords),3,2)
312 poly += shape
b31cbeb9 313 dc.DrawPolygonList(poly.astype(_Numeric.Int32))
9d6685e2
RD
314
315 def _cross(self, dc, coords, size=1):
316 fact= 2.5*size
317 for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]:
b31cbeb9
RD
318 lines= _Numeric.concatenate((coords,coords),axis=1)+f
319 dc.DrawLineList(lines.astype(_Numeric.Int32))
9d6685e2
RD
320
321 def _plus(self, dc, coords, size=1):
322 fact= 2.5*size
323 for f in [[-fact,0,fact,0],[0,-fact,0,fact]]:
b31cbeb9
RD
324 lines= _Numeric.concatenate((coords,coords),axis=1)+f
325 dc.DrawLineList(lines.astype(_Numeric.Int32))
9d6685e2
RD
326
327class PlotGraphics:
328 """Container to hold PolyXXX objects and graph labels
329 - All methods except __init__ are private.
330 """
331
332 def __init__(self, objects, title='', xLabel='', yLabel= ''):
333 """Creates PlotGraphics object
334 objects - list of PolyXXX objects to make graph
335 title - title shown at top of graph
336 xLabel - label shown on x-axis
337 yLabel - label shown on y-axis
338 """
339 if type(objects) not in [list,tuple]:
340 raise TypeError, "objects argument should be list or tuple"
341 self.objects = objects
342 self.title= title
343 self.xLabel= xLabel
344 self.yLabel= yLabel
345
346 def boundingBox(self):
347 p1, p2 = self.objects[0].boundingBox()
348 for o in self.objects[1:]:
349 p1o, p2o = o.boundingBox()
b31cbeb9
RD
350 p1 = _Numeric.minimum(p1, p1o)
351 p2 = _Numeric.maximum(p2, p2o)
9d6685e2
RD
352 return p1, p2
353
354 def scaleAndShift(self, scale=(1,1), shift=(0,0)):
355 for o in self.objects:
356 o.scaleAndShift(scale, shift)
357
358 def setPrinterScale(self, scale):
359 """Thickens up lines and markers only for printing"""
360 self.printerScale= scale
361
362 def setXLabel(self, xLabel= ''):
363 """Set the X axis label on the graph"""
364 self.xLabel= xLabel
365
366 def setYLabel(self, yLabel= ''):
367 """Set the Y axis label on the graph"""
368 self.yLabel= yLabel
369
370 def setTitle(self, title= ''):
371 """Set the title at the top of graph"""
372 self.title= title
373
374 def getXLabel(self):
375 """Get x axis label string"""
376 return self.xLabel
377
378 def getYLabel(self):
379 """Get y axis label string"""
380 return self.yLabel
381
382 def getTitle(self, title= ''):
383 """Get the title at the top of graph"""
384 return self.title
385
386 def draw(self, dc):
387 for o in self.objects:
b31cbeb9 388 #t=_time.clock() # profile info
9d6685e2 389 o.draw(dc, self.printerScale)
b31cbeb9 390 #dt= _time.clock()-t
9d6685e2
RD
391 #print o, "time=", dt
392
393 def getSymExtent(self, printerScale):
394 """Get max width and height of lines and markers symbols for legend"""
395 symExt = self.objects[0].getSymExtent(printerScale)
396 for o in self.objects[1:]:
397 oSymExt = o.getSymExtent(printerScale)
b31cbeb9 398 symExt = _Numeric.maximum(symExt, oSymExt)
9d6685e2
RD
399 return symExt
400
401 def getLegendNames(self):
402 """Returns list of legend names"""
403 lst = [None]*len(self)
404 for i in range(len(self)):
405 lst[i]= self.objects[i].getLegend()
406 return lst
407
408 def __len__(self):
409 return len(self.objects)
410
411 def __getitem__(self, item):
412 return self.objects[item]
413
414
415#-------------------------------------------------------------------------------
416# Main window that you will want to import into your application.
417
418class PlotCanvas(wx.Window):
419 """Subclass of a wx.Window to allow simple general plotting
420 of data with zoom, labels, and automatic axis scaling."""
421
422 def __init__(self, parent, id = -1, pos=wx.DefaultPosition,
423 size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""):
424 """Constucts a window, which can be a child of a frame, dialog or
425 any other non-control window"""
426
427 wx.Window.__init__(self, parent, id, pos, size, style, name)
428 self.border = (1,1)
429
430 self.SetBackgroundColour("white")
431
9d6685e2
RD
432 # Create some mouse events for zooming
433 self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
434 self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
435 self.Bind(wx.EVT_MOTION, self.OnMotion)
436 self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick)
437 self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
438
439 # set curser as cross-hairs
440 self.SetCursor(wx.CROSS_CURSOR)
441
442 # Things for printing
443 self.print_data = wx.PrintData()
444 self.print_data.SetPaperId(wx.PAPER_LETTER)
445 self.print_data.SetOrientation(wx.LANDSCAPE)
446 self.pageSetupData= wx.PageSetupDialogData()
447 self.pageSetupData.SetMarginBottomRight((25,25))
448 self.pageSetupData.SetMarginTopLeft((25,25))
449 self.pageSetupData.SetPrintData(self.print_data)
450 self.printerScale = 1
451 self.parent= parent
452
453 # Zooming variables
454 self._zoomInFactor = 0.5
455 self._zoomOutFactor = 2
b31cbeb9
RD
456 self._zoomCorner1= _Numeric.array([0.0, 0.0]) # left mouse down corner
457 self._zoomCorner2= _Numeric.array([0.0, 0.0]) # left mouse up corner
9d6685e2
RD
458 self._zoomEnabled= False
459 self._hasDragged= False
460
461 # Drawing Variables
48023e15 462 self.last_draw = None
9d6685e2
RD
463 self._pointScale= 1
464 self._pointShift= 0
465 self._xSpec= 'auto'
466 self._ySpec= 'auto'
467 self._gridEnabled= False
468 self._legendEnabled= False
469
470 # Fonts
471 self._fontCache = {}
472 self._fontSizeAxis= 10
473 self._fontSizeTitle= 15
474 self._fontSizeLegend= 7
475
b31cbeb9
RD
476 # pointLabels
477 self._pointLabelEnabled= False
478 self.last_PointLabel= None
479 self._pointLabelFunc= None
480 self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
481
da8d6ffa
RD
482 self.Bind(wx.EVT_PAINT, self.OnPaint)
483 self.Bind(wx.EVT_SIZE, self.OnSize)
9d6685e2
RD
484 # OnSize called to make sure the buffer is initialized.
485 # This might result in OnSize getting called twice on some
486 # platforms at initialization, but little harm done.
b31cbeb9
RD
487 if wx.Platform != "__WXMAC__":
488 self.OnSize(None) # sets the initial size based on client size
9d6685e2
RD
489
490
491 # SaveFile
492 def SaveFile(self, fileName= ''):
493 """Saves the file to the type specified in the extension. If no file
494 name is specified a dialog box is provided. Returns True if sucessful,
495 otherwise False.
496
497 .bmp Save a Windows bitmap file.
498 .xbm Save an X bitmap file.
499 .xpm Save an XPM bitmap file.
500 .png Save a Portable Network Graphics file.
501 .jpg Save a Joint Photographic Experts Group file.
502 """
b31cbeb9 503 if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
9d6685e2
RD
504 dlg1 = wx.FileDialog(
505 self,
506 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
507 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
508 wx.SAVE|wx.OVERWRITE_PROMPT
509 )
510 try:
511 while 1:
512 if dlg1.ShowModal() == wx.ID_OK:
513 fileName = dlg1.GetPath()
514 # Check for proper exension
b31cbeb9 515 if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
9d6685e2
RD
516 dlg2 = wx.MessageDialog(self, 'File name extension\n'
517 'must be one of\n'
518 'bmp, xbm, xpm, png, or jpg',
519 'File Name Error', wx.OK | wx.ICON_ERROR)
520 try:
521 dlg2.ShowModal()
522 finally:
523 dlg2.Destroy()
524 else:
525 break # now save file
526 else: # exit without saving
527 return False
528 finally:
529 dlg1.Destroy()
530
531 # File name has required extension
b31cbeb9 532 fType = _string.lower(fileName[-3:])
9d6685e2
RD
533 if fType == "bmp":
534 tp= wx.BITMAP_TYPE_BMP # Save a Windows bitmap file.
535 elif fType == "xbm":
536 tp= wx.BITMAP_TYPE_XBM # Save an X bitmap file.
537 elif fType == "xpm":
538 tp= wx.BITMAP_TYPE_XPM # Save an XPM bitmap file.
539 elif fType == "jpg":
540 tp= wx.BITMAP_TYPE_JPEG # Save a JPG file.
541 else:
542 tp= wx.BITMAP_TYPE_PNG # Save a PNG file.
543 # Save Bitmap
544 res= self._Buffer.SaveFile(fileName, tp)
545 return res
546
547 def PageSetup(self):
548 """Brings up the page setup dialog"""
549 data = self.pageSetupData
550 data.SetPrintData(self.print_data)
551 dlg = wx.PageSetupDialog(self.parent, data)
552 try:
553 if dlg.ShowModal() == wx.ID_OK:
554 data = dlg.GetPageSetupData() # returns wx.PageSetupDialogData
555 # updates page parameters from dialog
556 self.pageSetupData.SetMarginBottomRight(data.GetMarginBottomRight())
557 self.pageSetupData.SetMarginTopLeft(data.GetMarginTopLeft())
558 self.pageSetupData.SetPrintData(data.GetPrintData())
a721e6ec 559 self.print_data=wx.PrintData(data.GetPrintData()) # updates print_data
9d6685e2
RD
560 finally:
561 dlg.Destroy()
562
563 def Printout(self, paper=None):
564 """Print current plot."""
565 if paper != None:
566 self.print_data.SetPaperId(paper)
467f4d3a 567 pdd = wx.PrintDialogData(self.print_data)
9d6685e2
RD
568 printer = wx.Printer(pdd)
569 out = PlotPrintout(self)
570 print_ok = printer.Print(self.parent, out)
571 if print_ok:
a721e6ec 572 self.print_data = wx.PrintData(printer.GetPrintDialogData().GetPrintData())
9d6685e2
RD
573 out.Destroy()
574
575 def PrintPreview(self):
576 """Print-preview current plot."""
577 printout = PlotPrintout(self)
578 printout2 = PlotPrintout(self)
579 self.preview = wx.PrintPreview(printout, printout2, self.print_data)
580 if not self.preview.Ok():
581 wx.MessageDialog(self, "Print Preview failed.\n" \
582 "Check that default printer is configured\n", \
583 "Print error", wx.OK|wx.CENTRE).ShowModal()
b31cbeb9 584 self.preview.SetZoom(40)
9d6685e2
RD
585 # search up tree to find frame instance
586 frameInst= self
587 while not isinstance(frameInst, wx.Frame):
588 frameInst= frameInst.GetParent()
589 frame = wx.PreviewFrame(self.preview, frameInst, "Preview")
590 frame.Initialize()
591 frame.SetPosition(self.GetPosition())
b31cbeb9 592 frame.SetSize((600,550))
9d6685e2
RD
593 frame.Centre(wx.BOTH)
594 frame.Show(True)
595
596 def SetFontSizeAxis(self, point= 10):
597 """Set the tick and axis label font size (default is 10 point)"""
598 self._fontSizeAxis= point
599
600 def GetFontSizeAxis(self):
601 """Get current tick and axis label font size in points"""
602 return self._fontSizeAxis
603
604 def SetFontSizeTitle(self, point= 15):
605 """Set Title font size (default is 15 point)"""
606 self._fontSizeTitle= point
607
608 def GetFontSizeTitle(self):
609 """Get current Title font size in points"""
610 return self._fontSizeTitle
611
612 def SetFontSizeLegend(self, point= 7):
613 """Set Legend font size (default is 7 point)"""
614 self._fontSizeLegend= point
615
616 def GetFontSizeLegend(self):
617 """Get current Legend font size in points"""
618 return self._fontSizeLegend
619
620 def SetEnableZoom(self, value):
621 """Set True to enable zooming."""
622 if value not in [True,False]:
623 raise TypeError, "Value should be True or False"
624 self._zoomEnabled= value
625
626 def GetEnableZoom(self):
627 """True if zooming enabled."""
628 return self._zoomEnabled
629
630 def SetEnableGrid(self, value):
631 """Set True to enable grid."""
2b9590d6
RD
632 if value not in [True,False,'Horizontal','Vertical']:
633 raise TypeError, "Value should be True, False, Horizontal or Vertical"
9d6685e2
RD
634 self._gridEnabled= value
635 self.Redraw()
636
637 def GetEnableGrid(self):
638 """True if grid enabled."""
639 return self._gridEnabled
640
641 def SetEnableLegend(self, value):
642 """Set True to enable legend."""
643 if value not in [True,False]:
644 raise TypeError, "Value should be True or False"
645 self._legendEnabled= value
646 self.Redraw()
647
648 def GetEnableLegend(self):
649 """True if Legend enabled."""
650 return self._legendEnabled
651
b31cbeb9
RD
652 def SetEnablePointLabel(self, value):
653 """Set True to enable pointLabel."""
654 if value not in [True,False]:
655 raise TypeError, "Value should be True or False"
656 self._pointLabelEnabled= value
657 self.Redraw() #will erase existing pointLabel if present
658 self.last_PointLabel = None
659
660 def GetEnablePointLabel(self):
661 """True if pointLabel enabled."""
662 return self._pointLabelEnabled
663
664 def SetPointLabelFunc(self, func):
665 """Sets the function with custom code for pointLabel drawing
666 ******** more info needed ***************
667 """
668 self._pointLabelFunc= func
669
670 def GetPointLabelFunc(self):
671 """Returns pointLabel Drawing Function"""
672 return self._pointLabelFunc
673
9d6685e2
RD
674 def Reset(self):
675 """Unzoom the plot."""
b31cbeb9 676 self.last_PointLabel = None #reset pointLabel
48023e15
RD
677 if self.last_draw is not None:
678 self.Draw(self.last_draw[0])
9d6685e2
RD
679
680 def ScrollRight(self, units):
681 """Move view right number of axis units."""
b31cbeb9 682 self.last_PointLabel = None #reset pointLabel
48023e15
RD
683 if self.last_draw is not None:
684 graphics, xAxis, yAxis= self.last_draw
685 xAxis= (xAxis[0]+units, xAxis[1]+units)
686 self.Draw(graphics,xAxis,yAxis)
9d6685e2
RD
687
688 def ScrollUp(self, units):
689 """Move view up number of axis units."""
b31cbeb9 690 self.last_PointLabel = None #reset pointLabel
8bb039c8
RD
691 if self.last_draw is not None:
692 graphics, xAxis, yAxis= self.last_draw
693 yAxis= (yAxis[0]+units, yAxis[1]+units)
694 self.Draw(graphics,xAxis,yAxis)
695
9d6685e2
RD
696
697 def GetXY(self,event):
698 """Takes a mouse event and returns the XY user axis values."""
b31cbeb9 699 x,y= self.PositionScreenToUser(event.GetPosition())
9d6685e2
RD
700 return x,y
701
b31cbeb9
RD
702 def PositionUserToScreen(self, pntXY):
703 """Converts User position to Screen Coordinates"""
704 userPos= _Numeric.array(pntXY)
705 x,y= userPos * self._pointScale + self._pointShift
706 return x,y
707
708 def PositionScreenToUser(self, pntXY):
709 """Converts Screen position to User Coordinates"""
710 screenPos= _Numeric.array(pntXY)
711 x,y= (screenPos-self._pointShift)/self._pointScale
712 return x,y
713
9d6685e2
RD
714 def SetXSpec(self, type= 'auto'):
715 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
716 where:
717 'none' - shows no axis or tick mark values
718 'min' - shows min bounding box values
719 'auto' - rounds axis range to sensible values
720 """
721 self._xSpec= type
722
723 def SetYSpec(self, type= 'auto'):
724 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
725 where:
726 'none' - shows no axis or tick mark values
727 'min' - shows min bounding box values
728 'auto' - rounds axis range to sensible values
729 """
730 self._ySpec= type
731
732 def GetXSpec(self):
733 """Returns current XSpec for axis"""
734 return self._xSpec
735
736 def GetYSpec(self):
737 """Returns current YSpec for axis"""
738 return self._ySpec
739
740 def GetXMaxRange(self):
741 """Returns (minX, maxX) x-axis range for displayed graph"""
48023e15
RD
742 graphics= self.last_draw[0]
743 p1, p2 = graphics.boundingBox() # min, max points of graphics
744 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
745 return xAxis
9d6685e2
RD
746
747 def GetYMaxRange(self):
748 """Returns (minY, maxY) y-axis range for displayed graph"""
48023e15
RD
749 graphics= self.last_draw[0]
750 p1, p2 = graphics.boundingBox() # min, max points of graphics
751 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
752 return yAxis
9d6685e2
RD
753
754 def GetXCurrentRange(self):
755 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
48023e15 756 return self.last_draw[1]
9d6685e2
RD
757
758 def GetYCurrentRange(self):
759 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
48023e15 760 return self.last_draw[2]
9d6685e2 761
48023e15 762 def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
9d6685e2
RD
763 """Draw objects in graphics with specified x and y axis.
764 graphics- instance of PlotGraphics with list of PolyXXX objects
765 xAxis - tuple with (min, max) axis range to view
766 yAxis - same as xAxis
767 dc - drawing context - doesn't have to be specified.
48023e15 768 If it's not, the offscreen buffer is used
9d6685e2
RD
769 """
770 # check Axis is either tuple or none
771 if type(xAxis) not in [type(None),tuple]:
772 raise TypeError, "xAxis should be None or (minX,maxX)"
773 if type(yAxis) not in [type(None),tuple]:
774 raise TypeError, "yAxis should be None or (minY,maxY)"
775
776 # check case for axis = (a,b) where a==b caused by improper zooms
48023e15
RD
777 if xAxis != None:
778 if xAxis[0] == xAxis[1]:
779 return
780 if yAxis != None:
781 if yAxis[0] == yAxis[1]:
9d6685e2
RD
782 return
783
784 if dc == None:
b31cbeb9 785 # sets new dc and clears it
00b9c867 786 dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
9d6685e2
RD
787 dc.Clear()
788
789 dc.BeginDrawing()
790 # dc.Clear()
791
792 # set font size for every thing but title and legend
793 dc.SetFont(self._getFont(self._fontSizeAxis))
794
48023e15
RD
795 # sizes axis to axis type, create lower left and upper right corners of plot
796 if xAxis == None or yAxis == None:
797 # One or both axis not specified in Draw
798 p1, p2 = graphics.boundingBox() # min, max points of graphics
799 if xAxis == None:
800 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
801 if yAxis == None:
802 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
803 # Adjust bounding box for axis spec
804 p1[0],p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin,ymin)
805 p2[0],p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax,ymax)
806 else:
807 # Both axis specified in Draw
808 p1= _Numeric.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin)
809 p2= _Numeric.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax)
810
811 self.last_draw = (graphics, xAxis, yAxis) # saves most recient values
812
9d6685e2
RD
813 # Get ticks and textExtents for axis if required
814 if self._xSpec is not 'none':
48023e15 815 xticks = self._ticks(xAxis[0], xAxis[1])
9d6685e2
RD
816 xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis
817 else:
818 xticks = None
819 xTextExtent= (0,0) # No text for ticks
820 if self._ySpec is not 'none':
48023e15 821 yticks = self._ticks(yAxis[0], yAxis[1])
9d6685e2
RD
822 yTextExtentBottom= dc.GetTextExtent(yticks[0][1])
823 yTextExtentTop = dc.GetTextExtent(yticks[-1][1])
824 yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]),
825 max(yTextExtentBottom[1],yTextExtentTop[1]))
826 else:
827 yticks = None
828 yTextExtent= (0,0) # No text for ticks
829
830 # TextExtents for Title and Axis Labels
831 titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics)
832
833 # TextExtents for Legend
48023e15 834 legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
9d6685e2
RD
835
836 # room around graph area
837 rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width
838 lhsW= yTextExtent[0]+ yLabelWH[1]
839 bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1]
840 topH= yTextExtent[1]/2. + titleWH[1]
b31cbeb9
RD
841 textSize_scale= _Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size
842 textSize_shift= _Numeric.array([lhsW, bottomH]) # shift plot area by this amount
9d6685e2
RD
843
844 # drawing title and labels text
845 dc.SetFont(self._getFont(self._fontSizeTitle))
846 titlePos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- titleWH[0]/2.,
847 self.plotbox_origin[1]- self.plotbox_size[1])
848 dc.DrawText(graphics.getTitle(),titlePos[0],titlePos[1])
849 dc.SetFont(self._getFont(self._fontSizeAxis))
850 xLabelPos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- xLabelWH[0]/2.,
851 self.plotbox_origin[1]- xLabelWH[1])
852 dc.DrawText(graphics.getXLabel(),xLabelPos[0],xLabelPos[1])
853 yLabelPos= (self.plotbox_origin[0],
854 self.plotbox_origin[1]- bottomH- (self.plotbox_size[1]-bottomH-topH)/2.+ yLabelWH[0]/2.)
855 if graphics.getYLabel(): # bug fix for Linux
856 dc.DrawRotatedText(graphics.getYLabel(),yLabelPos[0],yLabelPos[1],90)
857
858 # drawing legend makers and text
859 if self._legendEnabled:
48023e15 860 self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt)
9d6685e2
RD
861
862 # allow for scaling and shifting plotted points
b31cbeb9
RD
863 scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _Numeric.array((1,-1))
864 shift = -p1*scale + self.plotbox_origin + textSize_shift * _Numeric.array((1,-1))
9d6685e2
RD
865 self._pointScale= scale # make available for mouse events
866 self._pointShift= shift
867 self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks)
868
869 graphics.scaleAndShift(scale, shift)
870 graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing
871
872 # set clipping area so drawing does not occur outside axis box
873 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2)
874 dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight)
875 # Draw the lines and markers
b31cbeb9 876 #start = _time.clock()
9d6685e2 877 graphics.draw(dc)
b31cbeb9 878 # print "entire graphics drawing took: %f second"%(_time.clock() - start)
9d6685e2
RD
879 # remove the clipping region
880 dc.DestroyClippingRegion()
881 dc.EndDrawing()
882
883 def Redraw(self, dc= None):
884 """Redraw the existing plot."""
48023e15
RD
885 if self.last_draw is not None:
886 graphics, xAxis, yAxis= self.last_draw
887 self.Draw(graphics,xAxis,yAxis,dc)
9d6685e2
RD
888
889 def Clear(self):
890 """Erase the window."""
b31cbeb9 891 self.last_PointLabel = None #reset pointLabel
9d6685e2
RD
892 dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
893 dc.Clear()
48023e15 894 self.last_draw = None
9d6685e2
RD
895
896 def Zoom(self, Center, Ratio):
897 """ Zoom on the plot
898 Centers on the X,Y coords given in Center
899 Zooms by the Ratio = (Xratio, Yratio) given
900 """
b31cbeb9 901 self.last_PointLabel = None #reset maker
48023e15
RD
902 x,y = Center
903 if self.last_draw != None:
904 (graphics, xAxis, yAxis) = self.last_draw
905 w = (xAxis[1] - xAxis[0]) * Ratio[0]
906 h = (yAxis[1] - yAxis[0]) * Ratio[1]
907 xAxis = ( x - w/2, x + w/2 )
908 yAxis = ( y - h/2, y + h/2 )
909 self.Draw(graphics, xAxis, yAxis)
9d6685e2 910
b31cbeb9
RD
911 def GetClosestPoints(self, pntXY, pointScaled= True):
912 """Returns list with
913 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
914 list for each curve.
915 Returns [] if no curves are being plotted.
916
917 x, y in user coords
918 if pointScaled == True based on screen coords
919 if pointScaled == False based on user coords
920 """
48023e15 921 if self.last_draw == None:
b31cbeb9
RD
922 #no graph available
923 return []
48023e15
RD
924 graphics, xAxis, yAxis= self.last_draw
925 l = []
926 for curveNum,obj in enumerate(graphics):
927 #check there are points in the curve
928 if len(obj.points) == 0:
929 continue #go to next obj
930 #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
931 cn = [curveNum]+ [obj.getLegend()]+ obj.getClosestPoint( pntXY, pointScaled)
932 l.append(cn)
933 return l
b31cbeb9
RD
934
935 def GetClosetPoint(self, pntXY, pointScaled= True):
936 """Returns list with
937 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
938 list for only the closest curve.
939 Returns [] if no curves are being plotted.
940
941 x, y in user coords
942 if pointScaled == True based on screen coords
943 if pointScaled == False based on user coords
944 """
945 #closest points on screen based on screen scaling (pointScaled= True)
946 #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
947 closestPts= self.GetClosestPoints(pntXY, pointScaled)
948 if closestPts == []:
949 return [] #no graph present
950 #find one with least distance
951 dists = [c[-1] for c in closestPts]
952 mdist = min(dists) #Min dist
953 i = dists.index(mdist) #index for min dist
954 return closestPts[i] #this is the closest point on closest curve
955
956 def UpdatePointLabel(self, mDataDict):
957 """Updates the pointLabel point on screen with data contained in
958 mDataDict.
959
960 mDataDict will be passed to your function set by
961 SetPointLabelFunc. It can contain anything you
962 want to display on the screen at the scaledXY point
963 you specify.
964
965 This function can be called from parent window with onClick,
966 onMotion events etc.
967 """
968 if self.last_PointLabel != None:
969 #compare pointXY
970 if mDataDict["pointXY"] != self.last_PointLabel["pointXY"]:
971 #closest changed
972 self._drawPointLabel(self.last_PointLabel) #erase old
973 self._drawPointLabel(mDataDict) #plot new
974 else:
975 #just plot new with no erase
976 self._drawPointLabel(mDataDict) #plot new
977 #save for next erase
978 self.last_PointLabel = mDataDict
9d6685e2
RD
979
980 # event handlers **********************************
981 def OnMotion(self, event):
982 if self._zoomEnabled and event.LeftIsDown():
983 if self._hasDragged:
984 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
985 else:
986 self._hasDragged= True
987 self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event)
988 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new
989
990 def OnMouseLeftDown(self,event):
48023e15 991 self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event)
9d6685e2
RD
992
993 def OnMouseLeftUp(self, event):
994 if self._zoomEnabled:
995 if self._hasDragged == True:
996 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
997 self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event)
998 self._hasDragged = False # reset flag
b31cbeb9
RD
999 minX, minY= _Numeric.minimum( self._zoomCorner1, self._zoomCorner2)
1000 maxX, maxY= _Numeric.maximum( self._zoomCorner1, self._zoomCorner2)
1001 self.last_PointLabel = None #reset pointLabel
48023e15
RD
1002 if self.last_draw != None:
1003 self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
9d6685e2
RD
1004 #else: # A box has not been drawn, zoom in on a point
1005 ## this interfered with the double click, so I've disables it.
1006 # X,Y = self.GetXY(event)
1007 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
1008
1009 def OnMouseDoubleClick(self,event):
1010 if self._zoomEnabled:
1011 self.Reset()
1012
1013 def OnMouseRightDown(self,event):
1014 if self._zoomEnabled:
1015 X,Y = self.GetXY(event)
1016 self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) )
1017
1018 def OnPaint(self, event):
1019 # All that is needed here is to draw the buffer to screen
b31cbeb9
RD
1020 if self.last_PointLabel != None:
1021 self._drawPointLabel(self.last_PointLabel) #erase old
1022 self.last_PointLabel = None
1023 dc = wx.BufferedPaintDC(self, self._Buffer)
9d6685e2
RD
1024
1025 def OnSize(self,event):
1026 # The Buffer init is done here, to make sure the buffer is always
1027 # the same size as the Window
1028 Size = self.GetClientSize()
7a9ca9fc
RD
1029 if Size.width <= 0 or Size.height <= 0:
1030 return
1031
9d6685e2
RD
1032 # Make new offscreen bitmap: this bitmap will always have the
1033 # current drawing in it, so it can be used to save the image to
1034 # a file, or whatever.
1035 self._Buffer = wx.EmptyBitmap(Size[0],Size[1])
1036 self._setSize()
b31cbeb9
RD
1037
1038 self.last_PointLabel = None #reset pointLabel
1039
48023e15 1040 if self.last_draw is None:
ecf0b9f9 1041 self.Clear()
48023e15
RD
1042 else:
1043 graphics, xSpec, ySpec = self.last_draw
1044 self.Draw(graphics,xSpec,ySpec)
9d6685e2 1045
b31cbeb9
RD
1046 def OnLeave(self, event):
1047 """Used to erase pointLabel when mouse outside window"""
1048 if self.last_PointLabel != None:
1049 self._drawPointLabel(self.last_PointLabel) #erase old
1050 self.last_PointLabel = None
1051
9d6685e2
RD
1052
1053 # Private Methods **************************************************
1054 def _setSize(self, width=None, height=None):
1055 """DC width and height."""
1056 if width == None:
1057 (self.width,self.height) = self.GetClientSize()
1058 else:
1059 self.width, self.height= width,height
b31cbeb9 1060 self.plotbox_size = 0.97*_Numeric.array([self.width, self.height])
9d6685e2
RD
1061 xo = 0.5*(self.width-self.plotbox_size[0])
1062 yo = self.height-0.5*(self.height-self.plotbox_size[1])
b31cbeb9 1063 self.plotbox_origin = _Numeric.array([xo, yo])
9d6685e2
RD
1064
1065 def _setPrinterScale(self, scale):
1066 """Used to thicken lines and increase marker size for print out."""
1067 # line thickness on printer is very thin at 600 dot/in. Markers small
1068 self.printerScale= scale
1069
1070 def _printDraw(self, printDC):
1071 """Used for printing."""
48023e15
RD
1072 if self.last_draw != None:
1073 graphics, xSpec, ySpec= self.last_draw
1074 self.Draw(graphics,xSpec,ySpec,printDC)
9d6685e2 1075
b31cbeb9
RD
1076 def _drawPointLabel(self, mDataDict):
1077 """Draws and erases pointLabels"""
1078 width = self._Buffer.GetWidth()
1079 height = self._Buffer.GetHeight()
1080 tmp_Buffer = wx.EmptyBitmap(width,height)
1081 dcs = wx.MemoryDC()
1082 dcs.SelectObject(tmp_Buffer)
1083 dcs.Clear()
1084 dcs.BeginDrawing()
1085 self._pointLabelFunc(dcs,mDataDict) #custom user pointLabel function
1086 dcs.EndDrawing()
1087
1088 dc = wx.ClientDC( self )
1089 #this will erase if called twice
1090 dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst
1091
1092
48023e15 1093 def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
9d6685e2
RD
1094 """Draws legend symbols and text"""
1095 # top right hand corner of graph box is ref corner
1096 trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1]
1097 legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box
1098 lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines
1099 dc.SetFont(self._getFont(self._fontSizeLegend))
48023e15
RD
1100 for i in range(len(graphics)):
1101 o = graphics[i]
1102 s= i*lineHeight
1103 if isinstance(o,PolyMarker):
1104 # draw marker with legend
1105 pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.)
1106 o.draw(dc, self.printerScale, coord= _Numeric.array([pnt]))
1107 elif isinstance(o,PolyLine):
1108 # draw line with legend
1109 pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.)
1110 pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.)
1111 o.draw(dc, self.printerScale, coord= _Numeric.array([pnt1,pnt2]))
1112 else:
1113 raise TypeError, "object is neither PolyMarker or PolyLine instance"
1114 # draw legend txt
1115 pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2)
1116 dc.DrawText(o.getLegend(),pnt[0],pnt[1])
9d6685e2
RD
1117 dc.SetFont(self._getFont(self._fontSizeAxis)) # reset
1118
1119 def _titleLablesWH(self, dc, graphics):
1120 """Draws Title and labels and returns width and height for each"""
1121 # TextExtents for Title and Axis Labels
1122 dc.SetFont(self._getFont(self._fontSizeTitle))
1123 title= graphics.getTitle()
1124 titleWH= dc.GetTextExtent(title)
1125 dc.SetFont(self._getFont(self._fontSizeAxis))
1126 xLabel, yLabel= graphics.getXLabel(),graphics.getYLabel()
1127 xLabelWH= dc.GetTextExtent(xLabel)
1128 yLabelWH= dc.GetTextExtent(yLabel)
1129 return titleWH, xLabelWH, yLabelWH
1130
1131 def _legendWH(self, dc, graphics):
1132 """Returns the size in screen units for legend box"""
1133 if self._legendEnabled != True:
1134 legendBoxWH= symExt= txtExt= (0,0)
1135 else:
48023e15 1136 # find max symbol size
9d6685e2
RD
1137 symExt= graphics.getSymExtent(self.printerScale)
1138 # find max legend text extent
1139 dc.SetFont(self._getFont(self._fontSizeLegend))
1140 txtList= graphics.getLegendNames()
1141 txtExt= dc.GetTextExtent(txtList[0])
1142 for txt in graphics.getLegendNames()[1:]:
b31cbeb9 1143 txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt))
48023e15 1144 maxW= symExt[0]+txtExt[0]
9d6685e2
RD
1145 maxH= max(symExt[1],txtExt[1])
1146 # padding .1 for lhs of legend box and space between lines
1147 maxW= maxW* 1.1
1148 maxH= maxH* 1.1 * len(txtList)
1149 dc.SetFont(self._getFont(self._fontSizeAxis))
1150 legendBoxWH= (maxW,maxH)
48023e15 1151 return (legendBoxWH, symExt, txtExt)
9d6685e2
RD
1152
1153 def _drawRubberBand(self, corner1, corner2):
1154 """Draws/erases rect box from corner1 to corner2"""
1155 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2)
1156 # draw rectangle
1157 dc = wx.ClientDC( self )
1158 dc.BeginDrawing()
1159 dc.SetPen(wx.Pen(wx.BLACK))
1160 dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) )
1161 dc.SetLogicalFunction(wx.INVERT)
d7403ad2 1162 dc.DrawRectangle( ptx,pty, rectWidth,rectHeight)
9d6685e2
RD
1163 dc.SetLogicalFunction(wx.COPY)
1164 dc.EndDrawing()
1165
1166 def _getFont(self,size):
1167 """Take font size, adjusts if printing and returns wx.Font"""
1168 s = size*self.printerScale
1169 of = self.GetFont()
1170 # Linux speed up to get font from cache rather than X font server
1171 key = (int(s), of.GetFamily (), of.GetStyle (), of.GetWeight ())
1172 font = self._fontCache.get (key, None)
1173 if font:
1174 return font # yeah! cache hit
1175 else:
1176 font = wx.Font(int(s), of.GetFamily(), of.GetStyle(), of.GetWeight())
1177 self._fontCache[key] = font
1178 return font
1179
1180
1181 def _point2ClientCoord(self, corner1, corner2):
1182 """Converts user point coords to client screen int coords x,y,width,height"""
b31cbeb9
RD
1183 c1= _Numeric.array(corner1)
1184 c2= _Numeric.array(corner2)
9d6685e2
RD
1185 # convert to screen coords
1186 pt1= c1*self._pointScale+self._pointShift
1187 pt2= c2*self._pointScale+self._pointShift
1188 # make height and width positive
b31cbeb9
RD
1189 pul= _Numeric.minimum(pt1,pt2) # Upper left corner
1190 plr= _Numeric.maximum(pt1,pt2) # Lower right corner
9d6685e2
RD
1191 rectWidth, rectHeight= plr-pul
1192 ptx,pty= pul
00b9c867 1193 return ptx, pty, rectWidth, rectHeight
9d6685e2
RD
1194
1195 def _axisInterval(self, spec, lower, upper):
1196 """Returns sensible axis range for given spec"""
1197 if spec == 'none' or spec == 'min':
1198 if lower == upper:
1199 return lower-0.5, upper+0.5
1200 else:
1201 return lower, upper
1202 elif spec == 'auto':
1203 range = upper-lower
1204 if range == 0.:
1205 return lower-0.5, upper+0.5
b31cbeb9
RD
1206 log = _Numeric.log10(range)
1207 power = _Numeric.floor(log)
9d6685e2
RD
1208 fraction = log-power
1209 if fraction <= 0.05:
1210 power = power-1
1211 grid = 10.**power
1212 lower = lower - lower % grid
1213 mod = upper % grid
1214 if mod != 0:
1215 upper = upper - mod + grid
1216 return lower, upper
1217 elif type(spec) == type(()):
1218 lower, upper = spec
1219 if lower <= upper:
1220 return lower, upper
1221 else:
1222 return upper, lower
1223 else:
1224 raise ValueError, str(spec) + ': illegal axis specification'
1225
1226 def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
1227
1228 penWidth= self.printerScale # increases thickness for printing only
48023e15 1229 dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth))
9d6685e2
RD
1230
1231 # set length of tick marks--long ones make grid
1232 if self._gridEnabled:
1233 x,y,width,height= self._point2ClientCoord(p1,p2)
2b9590d6
RD
1234 if self._gridEnabled == 'Horizontal':
1235 yTickLength= width/2.0 +1
1236 xTickLength= 3 * self.printerScale
1237 elif self._gridEnabled == 'Vertical':
1238 yTickLength= 3 * self.printerScale
1239 xTickLength= height/2.0 +1
1240 else:
1241 yTickLength= width/2.0 +1
1242 xTickLength= height/2.0 +1
9d6685e2
RD
1243 else:
1244 yTickLength= 3 * self.printerScale # lengthens lines for printing
1245 xTickLength= 3 * self.printerScale
1246
1247 if self._xSpec is not 'none':
1248 lower, upper = p1[0],p2[0]
1249 text = 1
1250 for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths
b31cbeb9
RD
1251 a1 = scale*_Numeric.array([lower, y])+shift
1252 a2 = scale*_Numeric.array([upper, y])+shift
9d6685e2
RD
1253 dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line
1254 for x, label in xticks:
b31cbeb9 1255 pt = scale*_Numeric.array([x, y])+shift
9d6685e2
RD
1256 dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units
1257 if text:
1258 dc.DrawText(label,pt[0],pt[1])
1259 text = 0 # axis values not drawn on top side
1260
1261 if self._ySpec is not 'none':
1262 lower, upper = p1[1],p2[1]
1263 text = 1
1264 h = dc.GetCharHeight()
1265 for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]:
b31cbeb9
RD
1266 a1 = scale*_Numeric.array([x, lower])+shift
1267 a2 = scale*_Numeric.array([x, upper])+shift
9d6685e2
RD
1268 dc.DrawLine(a1[0],a1[1],a2[0],a2[1])
1269 for y, label in yticks:
b31cbeb9 1270 pt = scale*_Numeric.array([x, y])+shift
9d6685e2
RD
1271 dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1])
1272 if text:
1273 dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0],
1274 pt[1]-0.5*h)
1275 text = 0 # axis values not drawn on right side
1276
48023e15 1277 def _ticks(self, lower, upper):
9d6685e2 1278 ideal = (upper-lower)/7.
b31cbeb9
RD
1279 log = _Numeric.log10(ideal)
1280 power = _Numeric.floor(log)
9d6685e2
RD
1281 fraction = log-power
1282 factor = 1.
1283 error = fraction
1284 for f, lf in self._multiples:
b31cbeb9 1285 e = _Numeric.fabs(fraction-lf)
9d6685e2
RD
1286 if e < error:
1287 error = e
1288 factor = f
1289 grid = factor * 10.**power
1290 if power > 4 or power < -4:
1291 format = '%+7.1e'
1292 elif power >= 0:
1293 digits = max(1, int(power))
1294 format = '%' + `digits`+'.0f'
1295 else:
1296 digits = -int(power)
1297 format = '%'+`digits+2`+'.'+`digits`+'f'
48023e15
RD
1298 ticks = []
1299 t = -grid*_Numeric.floor(-lower/grid)
1300 while t <= upper:
1301 ticks.append( (t, format % (t,)) )
1302 t = t + grid
1303 return ticks
9d6685e2 1304
b31cbeb9 1305 _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))]
9d6685e2
RD
1306
1307
1308#-------------------------------------------------------------------------------
1309# Used to layout the printer page
1310
1311class PlotPrintout(wx.Printout):
1312 """Controls how the plot is made in printing and previewing"""
1313 # Do not change method names in this class,
1314 # we have to override wx.Printout methods here!
1315 def __init__(self, graph):
1316 """graph is instance of plotCanvas to be printed or previewed"""
1317 wx.Printout.__init__(self)
1318 self.graph = graph
1319
1320 def HasPage(self, page):
1321 if page == 1:
1322 return True
1323 else:
1324 return False
1325
1326 def GetPageInfo(self):
e5ce86d8 1327 return (1, 1, 1, 1) # disable page numbers
9d6685e2
RD
1328
1329 def OnPrintPage(self, page):
00b9c867 1330 dc = self.GetDC() # allows using floats for certain functions
9d6685e2
RD
1331## print "PPI Printer",self.GetPPIPrinter()
1332## print "PPI Screen", self.GetPPIScreen()
1333## print "DC GetSize", dc.GetSize()
1334## print "GetPageSizePixels", self.GetPageSizePixels()
1335 # Note PPIScreen does not give the correct number
1336 # Calulate everything for printer and then scale for preview
1337 PPIPrinter= self.GetPPIPrinter() # printer dots/inch (w,h)
1338 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1339 dcSize= dc.GetSize() # DC size
1340 pageSize= self.GetPageSizePixels() # page size in terms of pixcels
1341 clientDcSize= self.graph.GetClientSize()
1342
1343 # find what the margins are (mm)
1344 margLeftSize,margTopSize= self.graph.pageSetupData.GetMarginTopLeft()
1345 margRightSize, margBottomSize= self.graph.pageSetupData.GetMarginBottomRight()
1346
1347 # calculate offset and scale for dc
1348 pixLeft= margLeftSize*PPIPrinter[0]/25.4 # mm*(dots/in)/(mm/in)
1349 pixRight= margRightSize*PPIPrinter[0]/25.4
1350 pixTop= margTopSize*PPIPrinter[1]/25.4
1351 pixBottom= margBottomSize*PPIPrinter[1]/25.4
1352
1353 plotAreaW= pageSize[0]-(pixLeft+pixRight)
1354 plotAreaH= pageSize[1]-(pixTop+pixBottom)
1355
1356 # ratio offset and scale to screen size if preview
1357 if self.IsPreview():
1358 ratioW= float(dcSize[0])/pageSize[0]
1359 ratioH= float(dcSize[1])/pageSize[1]
1360 pixLeft *= ratioW
1361 pixTop *= ratioH
1362 plotAreaW *= ratioW
1363 plotAreaH *= ratioH
1364
1365 # rescale plot to page or preview plot area
1366 self.graph._setSize(plotAreaW,plotAreaH)
1367
1368 # Set offset and scale
1369 dc.SetDeviceOrigin(pixLeft,pixTop)
1370
1371 # Thicken up pens and increase marker size for printing
1372 ratioW= float(plotAreaW)/clientDcSize[0]
1373 ratioH= float(plotAreaH)/clientDcSize[1]
1374 aveScale= (ratioW+ratioH)/2
1375 self.graph._setPrinterScale(aveScale) # tickens up pens for printing
1376
1377 self.graph._printDraw(dc)
1378 # rescale back to original
1379 self.graph._setSize()
1380 self.graph._setPrinterScale(1)
b31cbeb9 1381 self.graph.Redraw() #to get point label scale and shift correct
9d6685e2
RD
1382
1383 return True
1384
9d6685e2
RD
1385
1386
1387
1388#---------------------------------------------------------------------------
1389# if running standalone...
1390#
1391# ...a sample implementation using the above
1392#
1393
1394def _draw1Objects():
1395 # 100 points sin function, plotted as green circles
b31cbeb9 1396 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
9d6685e2 1397 data1.shape = (100, 2)
b31cbeb9 1398 data1[:,1] = _Numeric.sin(data1[:,0])
9d6685e2
RD
1399 markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1)
1400
1401 # 50 points cos function, plotted as red line
b31cbeb9 1402 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
9d6685e2 1403 data1.shape = (50,2)
b31cbeb9 1404 data1[:,1] = _Numeric.cos(data1[:,0])
9d6685e2
RD
1405 lines = PolyLine(data1, legend= 'Red Line', colour='red')
1406
1407 # A few more points...
b31cbeb9 1408 pi = _Numeric.pi
9d6685e2
RD
1409 markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1410 (3.*pi/4., -1)], legend='Cross Legend', colour='blue',
1411 marker='cross')
1412
1413 return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis")
1414
1415def _draw2Objects():
1416 # 100 points sin function, plotted as green dots
b31cbeb9 1417 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
9d6685e2 1418 data1.shape = (100, 2)
b31cbeb9 1419 data1[:,1] = _Numeric.sin(data1[:,0])
9d6685e2
RD
1420 line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT)
1421
1422 # 50 points cos function, plotted as red dot-dash
b31cbeb9 1423 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
9d6685e2 1424 data1.shape = (50,2)
b31cbeb9 1425 data1[:,1] = _Numeric.cos(data1[:,0])
9d6685e2
RD
1426 line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH)
1427
1428 # A few more points...
b31cbeb9 1429 pi = _Numeric.pi
9d6685e2
RD
1430 markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1431 (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6,
1432 fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH,
1433 marker='square')
1434
1435 return PlotGraphics([markers1, line1, line2], "Big Markers with Different Line Styles")
1436
1437def _draw3Objects():
1438 markerList= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1439 'cross', 'plus', 'circle']
1440 m=[]
1441 for i in range(len(markerList)):
1442 m.append(PolyMarker([(2*i+.5,i+.5)], legend=markerList[i], colour='blue',
1443 marker=markerList[i]))
1444 return PlotGraphics(m, "Selection of Markers", "Minimal Axis", "No Axis")
1445
1446def _draw4Objects():
1447 # 25,000 point line
b31cbeb9 1448 data1 = _Numeric.arange(5e5,1e6,10)
9d6685e2
RD
1449 data1.shape = (25000, 2)
1450 line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5)
1451
1452 # A few more points...
1453 markers2 = PolyMarker(data1, legend='Square', colour='blue',
1454 marker='square')
1455 return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "")
1456
1457def _draw5Objects():
1458 # Empty graph with axis defined but no points/lines
1459 points=[]
1460 line1 = PolyLine(points, legend='Wide Line', colour='green', width=5)
1461 return PlotGraphics([line1], "Empty Plot With Just Axes", "Value X", "Value Y")
1462
00b9c867
RD
1463def _draw6Objects():
1464 # Bar graph
1465 points1=[(1,0), (1,10)]
1466 line1 = PolyLine(points1, colour='green', legend='Feb.', width=10)
1467 points1g=[(2,0), (2,4)]
1468 line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10)
1469 points1b=[(3,0), (3,6)]
1470 line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10)
1471
1472 points2=[(4,0), (4,12)]
1473 line2 = PolyLine(points2, colour='Yellow', legend='May', width=10)
1474 points2g=[(5,0), (5,8)]
1475 line2g = PolyLine(points2g, colour='orange', legend='June', width=10)
1476 points2b=[(6,0), (6,4)]
1477 line2b = PolyLine(points2b, colour='brown', legend='July', width=10)
1478
1479 return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
1480 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1481
9d6685e2
RD
1482
1483class TestFrame(wx.Frame):
1484 def __init__(self, parent, id, title):
1485 wx.Frame.__init__(self, parent, id, title,
1486 wx.DefaultPosition, (600, 400))
1487
1488 # Now Create the menu bar and items
1489 self.mainmenu = wx.MenuBar()
1490
1491 menu = wx.Menu()
1492 menu.Append(200, 'Page Setup...', 'Setup the printer page')
1493 self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200)
1494
1495 menu.Append(201, 'Print Preview...', 'Show the current plot on page')
1496 self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201)
1497
1498 menu.Append(202, 'Print...', 'Print the current plot')
1499 self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202)
1500
1501 menu.Append(203, 'Save Plot...', 'Save current plot')
1502 self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203)
1503
1504 menu.Append(205, 'E&xit', 'Enough of this already!')
1505 self.Bind(wx.EVT_MENU, self.OnFileExit, id=205)
1506 self.mainmenu.Append(menu, '&File')
1507
1508 menu = wx.Menu()
1509 menu.Append(206, 'Draw1', 'Draw plots1')
1510 self.Bind(wx.EVT_MENU,self.OnPlotDraw1, id=206)
1511 menu.Append(207, 'Draw2', 'Draw plots2')
1512 self.Bind(wx.EVT_MENU,self.OnPlotDraw2, id=207)
1513 menu.Append(208, 'Draw3', 'Draw plots3')
1514 self.Bind(wx.EVT_MENU,self.OnPlotDraw3, id=208)
1515 menu.Append(209, 'Draw4', 'Draw plots4')
1516 self.Bind(wx.EVT_MENU,self.OnPlotDraw4, id=209)
1517 menu.Append(210, 'Draw5', 'Draw plots5')
1518 self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210)
00b9c867
RD
1519 menu.Append(260, 'Draw6', 'Draw plots6')
1520 self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260)
1521
9d6685e2
RD
1522
1523 menu.Append(211, '&Redraw', 'Redraw plots')
1524 self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211)
1525 menu.Append(212, '&Clear', 'Clear canvas')
1526 self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212)
1527 menu.Append(213, '&Scale', 'Scale canvas')
1528 self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213)
1529 menu.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK)
1530 self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214)
1531 menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK)
1532 self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215)
1533 menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK)
b31cbeb9
RD
1534 self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220)
1535 menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK)
1536 self.Bind(wx.EVT_MENU,self.OnEnablePointLabel, id=222)
1537
9d6685e2
RD
1538 menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1539 self.Bind(wx.EVT_MENU,self.OnScrUp, id=225)
1540 menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1541 self.Bind(wx.EVT_MENU,self.OnScrRt, id=230)
1542 menu.Append(235, '&Plot Reset', 'Reset to original plot')
1543 self.Bind(wx.EVT_MENU,self.OnReset, id=235)
1544
1545 self.mainmenu.Append(menu, '&Plot')
1546
1547 menu = wx.Menu()
1548 menu.Append(300, '&About', 'About this thing...')
1549 self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300)
1550 self.mainmenu.Append(menu, '&Help')
1551
1552 self.SetMenuBar(self.mainmenu)
1553
1554 # A status bar to tell people what's happening
1555 self.CreateStatusBar(1)
1556
1557 self.client = PlotCanvas(self)
b31cbeb9
RD
1558 #define the function for drawing pointLabels
1559 self.client.SetPointLabelFunc(self.DrawPointLabel)
9d6685e2
RD
1560 # Create mouse event for showing cursor coords in status bar
1561 self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
b31cbeb9
RD
1562 # Show closest point when enabled
1563 self.client.Bind(wx.EVT_MOTION, self.OnMotion)
1564
9d6685e2
RD
1565 self.Show(True)
1566
b31cbeb9
RD
1567 def DrawPointLabel(self, dc, mDataDict):
1568 """This is the fuction that defines how the pointLabels are plotted
1569 dc - DC that will be passed
1570 mDataDict - Dictionary of data that you want to use for the pointLabel
1571
1572 As an example I have decided I want a box at the curve point
1573 with some text information about the curve plotted below.
1574 Any wxDC method can be used.
1575 """
1576 # ----------
1577 dc.SetPen(wx.Pen(wx.BLACK))
1578 dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
1579
1580 sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
1581 dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point
1582 px,py = mDataDict["pointXY"]
1583 cNum = mDataDict["curveNum"]
1584 pntIn = mDataDict["pIndex"]
1585 legend = mDataDict["legend"]
1586 #make a string to display
1587 s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
1588 dc.DrawText(s, sx , sy+1)
1589 # -----------
1590
9d6685e2
RD
1591 def OnMouseLeftDown(self,event):
1592 s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event)
1593 self.SetStatusText(s)
b31cbeb9
RD
1594 event.Skip() #allows plotCanvas OnMouseLeftDown to be called
1595
1596 def OnMotion(self, event):
1597 #show closest point (when enbled)
1598 if self.client.GetEnablePointLabel() == True:
1599 #make up dict with info for the pointLabel
1600 #I've decided to mark the closest point on the closest curve
1601 dlst= self.client.GetClosetPoint( self.client.GetXY(event), pointScaled= True)
1602 if dlst != []: #returns [] if none
1603 curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
1604 #make up dictionary to pass to my user function (see DrawPointLabel)
1605 mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
1606 "pointXY":pointXY, "scaledXY":scaledXY}
1607 #pass dict to update the pointLabel
1608 self.client.UpdatePointLabel(mDataDict)
1609 event.Skip() #go to next handler
9d6685e2
RD
1610
1611 def OnFilePageSetup(self, event):
1612 self.client.PageSetup()
1613
1614 def OnFilePrintPreview(self, event):
1615 self.client.PrintPreview()
1616
1617 def OnFilePrint(self, event):
1618 self.client.Printout()
1619
1620 def OnSaveFile(self, event):
1621 self.client.SaveFile()
1622
1623 def OnFileExit(self, event):
1624 self.Close()
1625
1626 def OnPlotDraw1(self, event):
1627 self.resetDefaults()
1628 self.client.Draw(_draw1Objects())
1629
1630 def OnPlotDraw2(self, event):
1631 self.resetDefaults()
1632 self.client.Draw(_draw2Objects())
1633
1634 def OnPlotDraw3(self, event):
1635 self.resetDefaults()
1636 self.client.SetFont(wx.Font(10,wx.SCRIPT,wx.NORMAL,wx.NORMAL))
1637 self.client.SetFontSizeAxis(20)
1638 self.client.SetFontSizeLegend(12)
1639 self.client.SetXSpec('min')
1640 self.client.SetYSpec('none')
1641 self.client.Draw(_draw3Objects())
1642
1643 def OnPlotDraw4(self, event):
1644 self.resetDefaults()
1645 drawObj= _draw4Objects()
1646 self.client.Draw(drawObj)
b31cbeb9
RD
1647## # profile
1648## start = _time.clock()
1649## for x in range(10):
1650## self.client.Draw(drawObj)
1651## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start)
1652## # profile end
9d6685e2
RD
1653
1654 def OnPlotDraw5(self, event):
1655 # Empty plot with just axes
1656 self.resetDefaults()
1657 drawObj= _draw5Objects()
1658 # make the axis X= (0,5), Y=(0,10)
1659 # (default with None is X= (-1,1), Y= (-1,1))
1660 self.client.Draw(drawObj, xAxis= (0,5), yAxis= (0,10))
1661
00b9c867
RD
1662 def OnPlotDraw6(self, event):
1663 #Bar Graph Example
1664 self.resetDefaults()
1665 #self.client.SetEnableLegend(True) #turn on Legend
1666 #self.client.SetEnableGrid(True) #turn on Grid
1667 self.client.SetXSpec('none') #turns off x-axis scale
1668 self.client.SetYSpec('auto')
1669 self.client.Draw(_draw6Objects(), xAxis= (0,7))
1670
9d6685e2
RD
1671 def OnPlotRedraw(self,event):
1672 self.client.Redraw()
1673
1674 def OnPlotClear(self,event):
1675 self.client.Clear()
1676
1677 def OnPlotScale(self, event):
48023e15
RD
1678 if self.client.last_draw != None:
1679 graphics, xAxis, yAxis= self.client.last_draw
1680 self.client.Draw(graphics,(1,3.05),(0,1))
9d6685e2
RD
1681
1682 def OnEnableZoom(self, event):
1683 self.client.SetEnableZoom(event.IsChecked())
1684
1685 def OnEnableGrid(self, event):
1686 self.client.SetEnableGrid(event.IsChecked())
1687
1688 def OnEnableLegend(self, event):
1689 self.client.SetEnableLegend(event.IsChecked())
1690
b31cbeb9
RD
1691 def OnEnablePointLabel(self, event):
1692 self.client.SetEnablePointLabel(event.IsChecked())
1693
9d6685e2
RD
1694 def OnScrUp(self, event):
1695 self.client.ScrollUp(1)
1696
1697 def OnScrRt(self,event):
1698 self.client.ScrollRight(2)
1699
1700 def OnReset(self,event):
1701 self.client.Reset()
1702
1703 def OnHelpAbout(self, event):
33785d9f
RD
1704 from wx.lib.dialogs import ScrolledMessageDialog
1705 about = ScrolledMessageDialog(self, __doc__, "About...")
9d6685e2
RD
1706 about.ShowModal()
1707
1708 def resetDefaults(self):
1709 """Just to reset the fonts back to the PlotCanvas defaults"""
1710 self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
1711 self.client.SetFontSizeAxis(10)
1712 self.client.SetFontSizeLegend(7)
1713 self.client.SetXSpec('auto')
1714 self.client.SetYSpec('auto')
1715
1716
1717def __test():
1718
1719 class MyApp(wx.App):
1720 def OnInit(self):
1721 wx.InitAllImageHandlers()
1722 frame = TestFrame(None, -1, "PlotCanvas")
1723 #frame.Show(True)
1724 self.SetTopWindow(frame)
1725 return True
1726
1727
1728 app = MyApp(0)
1729 app.MainLoop()
1730
1731if __name__ == '__main__':
1732 __test()