]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/plot.py
otherwise we always fall back to blitting, even if we can provide better results
[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)
567 pdd = wx.PrintDialogData()
568 pdd.SetPrintData(self.print_data)
569 printer = wx.Printer(pdd)
570 out = PlotPrintout(self)
571 print_ok = printer.Print(self.parent, out)
572 if print_ok:
a721e6ec 573 self.print_data = wx.PrintData(printer.GetPrintDialogData().GetPrintData())
9d6685e2
RD
574 out.Destroy()
575
576 def PrintPreview(self):
577 """Print-preview current plot."""
578 printout = PlotPrintout(self)
579 printout2 = PlotPrintout(self)
580 self.preview = wx.PrintPreview(printout, printout2, self.print_data)
581 if not self.preview.Ok():
582 wx.MessageDialog(self, "Print Preview failed.\n" \
583 "Check that default printer is configured\n", \
584 "Print error", wx.OK|wx.CENTRE).ShowModal()
b31cbeb9 585 self.preview.SetZoom(40)
9d6685e2
RD
586 # search up tree to find frame instance
587 frameInst= self
588 while not isinstance(frameInst, wx.Frame):
589 frameInst= frameInst.GetParent()
590 frame = wx.PreviewFrame(self.preview, frameInst, "Preview")
591 frame.Initialize()
592 frame.SetPosition(self.GetPosition())
b31cbeb9 593 frame.SetSize((600,550))
9d6685e2
RD
594 frame.Centre(wx.BOTH)
595 frame.Show(True)
596
597 def SetFontSizeAxis(self, point= 10):
598 """Set the tick and axis label font size (default is 10 point)"""
599 self._fontSizeAxis= point
600
601 def GetFontSizeAxis(self):
602 """Get current tick and axis label font size in points"""
603 return self._fontSizeAxis
604
605 def SetFontSizeTitle(self, point= 15):
606 """Set Title font size (default is 15 point)"""
607 self._fontSizeTitle= point
608
609 def GetFontSizeTitle(self):
610 """Get current Title font size in points"""
611 return self._fontSizeTitle
612
613 def SetFontSizeLegend(self, point= 7):
614 """Set Legend font size (default is 7 point)"""
615 self._fontSizeLegend= point
616
617 def GetFontSizeLegend(self):
618 """Get current Legend font size in points"""
619 return self._fontSizeLegend
620
621 def SetEnableZoom(self, value):
622 """Set True to enable zooming."""
623 if value not in [True,False]:
624 raise TypeError, "Value should be True or False"
625 self._zoomEnabled= value
626
627 def GetEnableZoom(self):
628 """True if zooming enabled."""
629 return self._zoomEnabled
630
631 def SetEnableGrid(self, value):
632 """Set True to enable grid."""
2b9590d6
RD
633 if value not in [True,False,'Horizontal','Vertical']:
634 raise TypeError, "Value should be True, False, Horizontal or Vertical"
9d6685e2
RD
635 self._gridEnabled= value
636 self.Redraw()
637
638 def GetEnableGrid(self):
639 """True if grid enabled."""
640 return self._gridEnabled
641
642 def SetEnableLegend(self, value):
643 """Set True to enable legend."""
644 if value not in [True,False]:
645 raise TypeError, "Value should be True or False"
646 self._legendEnabled= value
647 self.Redraw()
648
649 def GetEnableLegend(self):
650 """True if Legend enabled."""
651 return self._legendEnabled
652
b31cbeb9
RD
653 def SetEnablePointLabel(self, value):
654 """Set True to enable pointLabel."""
655 if value not in [True,False]:
656 raise TypeError, "Value should be True or False"
657 self._pointLabelEnabled= value
658 self.Redraw() #will erase existing pointLabel if present
659 self.last_PointLabel = None
660
661 def GetEnablePointLabel(self):
662 """True if pointLabel enabled."""
663 return self._pointLabelEnabled
664
665 def SetPointLabelFunc(self, func):
666 """Sets the function with custom code for pointLabel drawing
667 ******** more info needed ***************
668 """
669 self._pointLabelFunc= func
670
671 def GetPointLabelFunc(self):
672 """Returns pointLabel Drawing Function"""
673 return self._pointLabelFunc
674
9d6685e2
RD
675 def Reset(self):
676 """Unzoom the plot."""
b31cbeb9 677 self.last_PointLabel = None #reset pointLabel
48023e15
RD
678 if self.last_draw is not None:
679 self.Draw(self.last_draw[0])
9d6685e2
RD
680
681 def ScrollRight(self, units):
682 """Move view right number of axis units."""
b31cbeb9 683 self.last_PointLabel = None #reset pointLabel
48023e15
RD
684 if self.last_draw is not None:
685 graphics, xAxis, yAxis= self.last_draw
686 xAxis= (xAxis[0]+units, xAxis[1]+units)
687 self.Draw(graphics,xAxis,yAxis)
9d6685e2
RD
688
689 def ScrollUp(self, units):
690 """Move view up number of axis units."""
b31cbeb9 691 self.last_PointLabel = None #reset pointLabel
ecf0b9f9
RD
692 if self.BeenDrawn():
693 self._drawCmd.scrollAxisY(units, self._ySpec)
694 self._draw()
9d6685e2
RD
695
696 def GetXY(self,event):
697 """Takes a mouse event and returns the XY user axis values."""
b31cbeb9 698 x,y= self.PositionScreenToUser(event.GetPosition())
9d6685e2
RD
699 return x,y
700
b31cbeb9
RD
701 def PositionUserToScreen(self, pntXY):
702 """Converts User position to Screen Coordinates"""
703 userPos= _Numeric.array(pntXY)
704 x,y= userPos * self._pointScale + self._pointShift
705 return x,y
706
707 def PositionScreenToUser(self, pntXY):
708 """Converts Screen position to User Coordinates"""
709 screenPos= _Numeric.array(pntXY)
710 x,y= (screenPos-self._pointShift)/self._pointScale
711 return x,y
712
9d6685e2
RD
713 def SetXSpec(self, type= 'auto'):
714 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
715 where:
716 'none' - shows no axis or tick mark values
717 'min' - shows min bounding box values
718 'auto' - rounds axis range to sensible values
719 """
720 self._xSpec= type
721
722 def SetYSpec(self, type= 'auto'):
723 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
724 where:
725 'none' - shows no axis or tick mark values
726 'min' - shows min bounding box values
727 'auto' - rounds axis range to sensible values
728 """
729 self._ySpec= type
730
731 def GetXSpec(self):
732 """Returns current XSpec for axis"""
733 return self._xSpec
734
735 def GetYSpec(self):
736 """Returns current YSpec for axis"""
737 return self._ySpec
738
739 def GetXMaxRange(self):
740 """Returns (minX, maxX) x-axis range for displayed graph"""
48023e15
RD
741 graphics= self.last_draw[0]
742 p1, p2 = graphics.boundingBox() # min, max points of graphics
743 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
744 return xAxis
9d6685e2
RD
745
746 def GetYMaxRange(self):
747 """Returns (minY, maxY) y-axis range for displayed graph"""
48023e15
RD
748 graphics= self.last_draw[0]
749 p1, p2 = graphics.boundingBox() # min, max points of graphics
750 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
751 return yAxis
9d6685e2
RD
752
753 def GetXCurrentRange(self):
754 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
48023e15 755 return self.last_draw[1]
9d6685e2
RD
756
757 def GetYCurrentRange(self):
758 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
48023e15 759 return self.last_draw[2]
9d6685e2 760
48023e15 761 def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
9d6685e2
RD
762 """Draw objects in graphics with specified x and y axis.
763 graphics- instance of PlotGraphics with list of PolyXXX objects
764 xAxis - tuple with (min, max) axis range to view
765 yAxis - same as xAxis
766 dc - drawing context - doesn't have to be specified.
48023e15 767 If it's not, the offscreen buffer is used
9d6685e2
RD
768 """
769 # check Axis is either tuple or none
770 if type(xAxis) not in [type(None),tuple]:
771 raise TypeError, "xAxis should be None or (minX,maxX)"
772 if type(yAxis) not in [type(None),tuple]:
773 raise TypeError, "yAxis should be None or (minY,maxY)"
774
775 # check case for axis = (a,b) where a==b caused by improper zooms
48023e15
RD
776 if xAxis != None:
777 if xAxis[0] == xAxis[1]:
778 return
779 if yAxis != None:
780 if yAxis[0] == yAxis[1]:
9d6685e2
RD
781 return
782
783 if dc == None:
b31cbeb9 784 # sets new dc and clears it
00b9c867 785 dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
9d6685e2
RD
786 dc.Clear()
787
788 dc.BeginDrawing()
789 # dc.Clear()
790
791 # set font size for every thing but title and legend
792 dc.SetFont(self._getFont(self._fontSizeAxis))
793
48023e15
RD
794 # sizes axis to axis type, create lower left and upper right corners of plot
795 if xAxis == None or yAxis == None:
796 # One or both axis not specified in Draw
797 p1, p2 = graphics.boundingBox() # min, max points of graphics
798 if xAxis == None:
799 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
800 if yAxis == None:
801 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
802 # Adjust bounding box for axis spec
803 p1[0],p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin,ymin)
804 p2[0],p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax,ymax)
805 else:
806 # Both axis specified in Draw
807 p1= _Numeric.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin)
808 p2= _Numeric.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax)
809
810 self.last_draw = (graphics, xAxis, yAxis) # saves most recient values
811
9d6685e2
RD
812 # Get ticks and textExtents for axis if required
813 if self._xSpec is not 'none':
48023e15 814 xticks = self._ticks(xAxis[0], xAxis[1])
9d6685e2
RD
815 xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis
816 else:
817 xticks = None
818 xTextExtent= (0,0) # No text for ticks
819 if self._ySpec is not 'none':
48023e15 820 yticks = self._ticks(yAxis[0], yAxis[1])
9d6685e2
RD
821 yTextExtentBottom= dc.GetTextExtent(yticks[0][1])
822 yTextExtentTop = dc.GetTextExtent(yticks[-1][1])
823 yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]),
824 max(yTextExtentBottom[1],yTextExtentTop[1]))
825 else:
826 yticks = None
827 yTextExtent= (0,0) # No text for ticks
828
829 # TextExtents for Title and Axis Labels
830 titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics)
831
832 # TextExtents for Legend
48023e15 833 legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
9d6685e2
RD
834
835 # room around graph area
836 rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width
837 lhsW= yTextExtent[0]+ yLabelWH[1]
838 bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1]
839 topH= yTextExtent[1]/2. + titleWH[1]
b31cbeb9
RD
840 textSize_scale= _Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size
841 textSize_shift= _Numeric.array([lhsW, bottomH]) # shift plot area by this amount
9d6685e2
RD
842
843 # drawing title and labels text
844 dc.SetFont(self._getFont(self._fontSizeTitle))
845 titlePos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- titleWH[0]/2.,
846 self.plotbox_origin[1]- self.plotbox_size[1])
847 dc.DrawText(graphics.getTitle(),titlePos[0],titlePos[1])
848 dc.SetFont(self._getFont(self._fontSizeAxis))
849 xLabelPos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- xLabelWH[0]/2.,
850 self.plotbox_origin[1]- xLabelWH[1])
851 dc.DrawText(graphics.getXLabel(),xLabelPos[0],xLabelPos[1])
852 yLabelPos= (self.plotbox_origin[0],
853 self.plotbox_origin[1]- bottomH- (self.plotbox_size[1]-bottomH-topH)/2.+ yLabelWH[0]/2.)
854 if graphics.getYLabel(): # bug fix for Linux
855 dc.DrawRotatedText(graphics.getYLabel(),yLabelPos[0],yLabelPos[1],90)
856
857 # drawing legend makers and text
858 if self._legendEnabled:
48023e15 859 self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt)
9d6685e2
RD
860
861 # allow for scaling and shifting plotted points
b31cbeb9
RD
862 scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _Numeric.array((1,-1))
863 shift = -p1*scale + self.plotbox_origin + textSize_shift * _Numeric.array((1,-1))
9d6685e2
RD
864 self._pointScale= scale # make available for mouse events
865 self._pointShift= shift
866 self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks)
867
868 graphics.scaleAndShift(scale, shift)
869 graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing
870
871 # set clipping area so drawing does not occur outside axis box
872 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2)
873 dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight)
874 # Draw the lines and markers
b31cbeb9 875 #start = _time.clock()
9d6685e2 876 graphics.draw(dc)
b31cbeb9 877 # print "entire graphics drawing took: %f second"%(_time.clock() - start)
9d6685e2
RD
878 # remove the clipping region
879 dc.DestroyClippingRegion()
880 dc.EndDrawing()
881
882 def Redraw(self, dc= None):
883 """Redraw the existing plot."""
48023e15
RD
884 if self.last_draw is not None:
885 graphics, xAxis, yAxis= self.last_draw
886 self.Draw(graphics,xAxis,yAxis,dc)
9d6685e2
RD
887
888 def Clear(self):
889 """Erase the window."""
b31cbeb9 890 self.last_PointLabel = None #reset pointLabel
9d6685e2
RD
891 dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
892 dc.Clear()
48023e15 893 self.last_draw = None
9d6685e2
RD
894
895 def Zoom(self, Center, Ratio):
896 """ Zoom on the plot
897 Centers on the X,Y coords given in Center
898 Zooms by the Ratio = (Xratio, Yratio) given
899 """
b31cbeb9 900 self.last_PointLabel = None #reset maker
48023e15
RD
901 x,y = Center
902 if self.last_draw != None:
903 (graphics, xAxis, yAxis) = self.last_draw
904 w = (xAxis[1] - xAxis[0]) * Ratio[0]
905 h = (yAxis[1] - yAxis[0]) * Ratio[1]
906 xAxis = ( x - w/2, x + w/2 )
907 yAxis = ( y - h/2, y + h/2 )
908 self.Draw(graphics, xAxis, yAxis)
9d6685e2 909
b31cbeb9
RD
910 def GetClosestPoints(self, pntXY, pointScaled= True):
911 """Returns list with
912 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
913 list for each curve.
914 Returns [] if no curves are being plotted.
915
916 x, y in user coords
917 if pointScaled == True based on screen coords
918 if pointScaled == False based on user coords
919 """
48023e15 920 if self.last_draw == None:
b31cbeb9
RD
921 #no graph available
922 return []
48023e15
RD
923 graphics, xAxis, yAxis= self.last_draw
924 l = []
925 for curveNum,obj in enumerate(graphics):
926 #check there are points in the curve
927 if len(obj.points) == 0:
928 continue #go to next obj
929 #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
930 cn = [curveNum]+ [obj.getLegend()]+ obj.getClosestPoint( pntXY, pointScaled)
931 l.append(cn)
932 return l
b31cbeb9
RD
933
934 def GetClosetPoint(self, pntXY, pointScaled= True):
935 """Returns list with
936 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
937 list for only the closest curve.
938 Returns [] if no curves are being plotted.
939
940 x, y in user coords
941 if pointScaled == True based on screen coords
942 if pointScaled == False based on user coords
943 """
944 #closest points on screen based on screen scaling (pointScaled= True)
945 #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
946 closestPts= self.GetClosestPoints(pntXY, pointScaled)
947 if closestPts == []:
948 return [] #no graph present
949 #find one with least distance
950 dists = [c[-1] for c in closestPts]
951 mdist = min(dists) #Min dist
952 i = dists.index(mdist) #index for min dist
953 return closestPts[i] #this is the closest point on closest curve
954
955 def UpdatePointLabel(self, mDataDict):
956 """Updates the pointLabel point on screen with data contained in
957 mDataDict.
958
959 mDataDict will be passed to your function set by
960 SetPointLabelFunc. It can contain anything you
961 want to display on the screen at the scaledXY point
962 you specify.
963
964 This function can be called from parent window with onClick,
965 onMotion events etc.
966 """
967 if self.last_PointLabel != None:
968 #compare pointXY
969 if mDataDict["pointXY"] != self.last_PointLabel["pointXY"]:
970 #closest changed
971 self._drawPointLabel(self.last_PointLabel) #erase old
972 self._drawPointLabel(mDataDict) #plot new
973 else:
974 #just plot new with no erase
975 self._drawPointLabel(mDataDict) #plot new
976 #save for next erase
977 self.last_PointLabel = mDataDict
9d6685e2
RD
978
979 # event handlers **********************************
980 def OnMotion(self, event):
981 if self._zoomEnabled and event.LeftIsDown():
982 if self._hasDragged:
983 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
984 else:
985 self._hasDragged= True
986 self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event)
987 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new
988
989 def OnMouseLeftDown(self,event):
48023e15 990 self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event)
9d6685e2
RD
991
992 def OnMouseLeftUp(self, event):
993 if self._zoomEnabled:
994 if self._hasDragged == True:
995 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
996 self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event)
997 self._hasDragged = False # reset flag
b31cbeb9
RD
998 minX, minY= _Numeric.minimum( self._zoomCorner1, self._zoomCorner2)
999 maxX, maxY= _Numeric.maximum( self._zoomCorner1, self._zoomCorner2)
1000 self.last_PointLabel = None #reset pointLabel
48023e15
RD
1001 if self.last_draw != None:
1002 self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
9d6685e2
RD
1003 #else: # A box has not been drawn, zoom in on a point
1004 ## this interfered with the double click, so I've disables it.
1005 # X,Y = self.GetXY(event)
1006 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
1007
1008 def OnMouseDoubleClick(self,event):
1009 if self._zoomEnabled:
1010 self.Reset()
1011
1012 def OnMouseRightDown(self,event):
1013 if self._zoomEnabled:
1014 X,Y = self.GetXY(event)
1015 self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) )
1016
1017 def OnPaint(self, event):
1018 # All that is needed here is to draw the buffer to screen
b31cbeb9
RD
1019 if self.last_PointLabel != None:
1020 self._drawPointLabel(self.last_PointLabel) #erase old
1021 self.last_PointLabel = None
1022 dc = wx.BufferedPaintDC(self, self._Buffer)
9d6685e2
RD
1023
1024 def OnSize(self,event):
1025 # The Buffer init is done here, to make sure the buffer is always
1026 # the same size as the Window
1027 Size = self.GetClientSize()
1028
1029 # Make new offscreen bitmap: this bitmap will always have the
1030 # current drawing in it, so it can be used to save the image to
1031 # a file, or whatever.
1032 self._Buffer = wx.EmptyBitmap(Size[0],Size[1])
1033 self._setSize()
b31cbeb9
RD
1034
1035 self.last_PointLabel = None #reset pointLabel
1036
48023e15 1037 if self.last_draw is None:
ecf0b9f9 1038 self.Clear()
48023e15
RD
1039 else:
1040 graphics, xSpec, ySpec = self.last_draw
1041 self.Draw(graphics,xSpec,ySpec)
9d6685e2 1042
b31cbeb9
RD
1043 def OnLeave(self, event):
1044 """Used to erase pointLabel when mouse outside window"""
1045 if self.last_PointLabel != None:
1046 self._drawPointLabel(self.last_PointLabel) #erase old
1047 self.last_PointLabel = None
1048
9d6685e2
RD
1049
1050 # Private Methods **************************************************
1051 def _setSize(self, width=None, height=None):
1052 """DC width and height."""
1053 if width == None:
1054 (self.width,self.height) = self.GetClientSize()
1055 else:
1056 self.width, self.height= width,height
b31cbeb9 1057 self.plotbox_size = 0.97*_Numeric.array([self.width, self.height])
9d6685e2
RD
1058 xo = 0.5*(self.width-self.plotbox_size[0])
1059 yo = self.height-0.5*(self.height-self.plotbox_size[1])
b31cbeb9 1060 self.plotbox_origin = _Numeric.array([xo, yo])
9d6685e2
RD
1061
1062 def _setPrinterScale(self, scale):
1063 """Used to thicken lines and increase marker size for print out."""
1064 # line thickness on printer is very thin at 600 dot/in. Markers small
1065 self.printerScale= scale
1066
1067 def _printDraw(self, printDC):
1068 """Used for printing."""
48023e15
RD
1069 if self.last_draw != None:
1070 graphics, xSpec, ySpec= self.last_draw
1071 self.Draw(graphics,xSpec,ySpec,printDC)
9d6685e2 1072
b31cbeb9
RD
1073 def _drawPointLabel(self, mDataDict):
1074 """Draws and erases pointLabels"""
1075 width = self._Buffer.GetWidth()
1076 height = self._Buffer.GetHeight()
1077 tmp_Buffer = wx.EmptyBitmap(width,height)
1078 dcs = wx.MemoryDC()
1079 dcs.SelectObject(tmp_Buffer)
1080 dcs.Clear()
1081 dcs.BeginDrawing()
1082 self._pointLabelFunc(dcs,mDataDict) #custom user pointLabel function
1083 dcs.EndDrawing()
1084
1085 dc = wx.ClientDC( self )
1086 #this will erase if called twice
1087 dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst
1088
1089
48023e15 1090 def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
9d6685e2
RD
1091 """Draws legend symbols and text"""
1092 # top right hand corner of graph box is ref corner
1093 trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1]
1094 legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box
1095 lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines
1096 dc.SetFont(self._getFont(self._fontSizeLegend))
48023e15
RD
1097 for i in range(len(graphics)):
1098 o = graphics[i]
1099 s= i*lineHeight
1100 if isinstance(o,PolyMarker):
1101 # draw marker with legend
1102 pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.)
1103 o.draw(dc, self.printerScale, coord= _Numeric.array([pnt]))
1104 elif isinstance(o,PolyLine):
1105 # draw line with legend
1106 pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.)
1107 pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.)
1108 o.draw(dc, self.printerScale, coord= _Numeric.array([pnt1,pnt2]))
1109 else:
1110 raise TypeError, "object is neither PolyMarker or PolyLine instance"
1111 # draw legend txt
1112 pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2)
1113 dc.DrawText(o.getLegend(),pnt[0],pnt[1])
9d6685e2
RD
1114 dc.SetFont(self._getFont(self._fontSizeAxis)) # reset
1115
1116 def _titleLablesWH(self, dc, graphics):
1117 """Draws Title and labels and returns width and height for each"""
1118 # TextExtents for Title and Axis Labels
1119 dc.SetFont(self._getFont(self._fontSizeTitle))
1120 title= graphics.getTitle()
1121 titleWH= dc.GetTextExtent(title)
1122 dc.SetFont(self._getFont(self._fontSizeAxis))
1123 xLabel, yLabel= graphics.getXLabel(),graphics.getYLabel()
1124 xLabelWH= dc.GetTextExtent(xLabel)
1125 yLabelWH= dc.GetTextExtent(yLabel)
1126 return titleWH, xLabelWH, yLabelWH
1127
1128 def _legendWH(self, dc, graphics):
1129 """Returns the size in screen units for legend box"""
1130 if self._legendEnabled != True:
1131 legendBoxWH= symExt= txtExt= (0,0)
1132 else:
48023e15 1133 # find max symbol size
9d6685e2
RD
1134 symExt= graphics.getSymExtent(self.printerScale)
1135 # find max legend text extent
1136 dc.SetFont(self._getFont(self._fontSizeLegend))
1137 txtList= graphics.getLegendNames()
1138 txtExt= dc.GetTextExtent(txtList[0])
1139 for txt in graphics.getLegendNames()[1:]:
b31cbeb9 1140 txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt))
48023e15 1141 maxW= symExt[0]+txtExt[0]
9d6685e2
RD
1142 maxH= max(symExt[1],txtExt[1])
1143 # padding .1 for lhs of legend box and space between lines
1144 maxW= maxW* 1.1
1145 maxH= maxH* 1.1 * len(txtList)
1146 dc.SetFont(self._getFont(self._fontSizeAxis))
1147 legendBoxWH= (maxW,maxH)
48023e15 1148 return (legendBoxWH, symExt, txtExt)
9d6685e2
RD
1149
1150 def _drawRubberBand(self, corner1, corner2):
1151 """Draws/erases rect box from corner1 to corner2"""
1152 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2)
1153 # draw rectangle
1154 dc = wx.ClientDC( self )
1155 dc.BeginDrawing()
1156 dc.SetPen(wx.Pen(wx.BLACK))
1157 dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) )
1158 dc.SetLogicalFunction(wx.INVERT)
d7403ad2 1159 dc.DrawRectangle( ptx,pty, rectWidth,rectHeight)
9d6685e2
RD
1160 dc.SetLogicalFunction(wx.COPY)
1161 dc.EndDrawing()
1162
1163 def _getFont(self,size):
1164 """Take font size, adjusts if printing and returns wx.Font"""
1165 s = size*self.printerScale
1166 of = self.GetFont()
1167 # Linux speed up to get font from cache rather than X font server
1168 key = (int(s), of.GetFamily (), of.GetStyle (), of.GetWeight ())
1169 font = self._fontCache.get (key, None)
1170 if font:
1171 return font # yeah! cache hit
1172 else:
1173 font = wx.Font(int(s), of.GetFamily(), of.GetStyle(), of.GetWeight())
1174 self._fontCache[key] = font
1175 return font
1176
1177
1178 def _point2ClientCoord(self, corner1, corner2):
1179 """Converts user point coords to client screen int coords x,y,width,height"""
b31cbeb9
RD
1180 c1= _Numeric.array(corner1)
1181 c2= _Numeric.array(corner2)
9d6685e2
RD
1182 # convert to screen coords
1183 pt1= c1*self._pointScale+self._pointShift
1184 pt2= c2*self._pointScale+self._pointShift
1185 # make height and width positive
b31cbeb9
RD
1186 pul= _Numeric.minimum(pt1,pt2) # Upper left corner
1187 plr= _Numeric.maximum(pt1,pt2) # Lower right corner
9d6685e2
RD
1188 rectWidth, rectHeight= plr-pul
1189 ptx,pty= pul
00b9c867 1190 return ptx, pty, rectWidth, rectHeight
9d6685e2
RD
1191
1192 def _axisInterval(self, spec, lower, upper):
1193 """Returns sensible axis range for given spec"""
1194 if spec == 'none' or spec == 'min':
1195 if lower == upper:
1196 return lower-0.5, upper+0.5
1197 else:
1198 return lower, upper
1199 elif spec == 'auto':
1200 range = upper-lower
1201 if range == 0.:
1202 return lower-0.5, upper+0.5
b31cbeb9
RD
1203 log = _Numeric.log10(range)
1204 power = _Numeric.floor(log)
9d6685e2
RD
1205 fraction = log-power
1206 if fraction <= 0.05:
1207 power = power-1
1208 grid = 10.**power
1209 lower = lower - lower % grid
1210 mod = upper % grid
1211 if mod != 0:
1212 upper = upper - mod + grid
1213 return lower, upper
1214 elif type(spec) == type(()):
1215 lower, upper = spec
1216 if lower <= upper:
1217 return lower, upper
1218 else:
1219 return upper, lower
1220 else:
1221 raise ValueError, str(spec) + ': illegal axis specification'
1222
1223 def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
1224
1225 penWidth= self.printerScale # increases thickness for printing only
48023e15 1226 dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth))
9d6685e2
RD
1227
1228 # set length of tick marks--long ones make grid
1229 if self._gridEnabled:
1230 x,y,width,height= self._point2ClientCoord(p1,p2)
2b9590d6
RD
1231 if self._gridEnabled == 'Horizontal':
1232 yTickLength= width/2.0 +1
1233 xTickLength= 3 * self.printerScale
1234 elif self._gridEnabled == 'Vertical':
1235 yTickLength= 3 * self.printerScale
1236 xTickLength= height/2.0 +1
1237 else:
1238 yTickLength= width/2.0 +1
1239 xTickLength= height/2.0 +1
9d6685e2
RD
1240 else:
1241 yTickLength= 3 * self.printerScale # lengthens lines for printing
1242 xTickLength= 3 * self.printerScale
1243
1244 if self._xSpec is not 'none':
1245 lower, upper = p1[0],p2[0]
1246 text = 1
1247 for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths
b31cbeb9
RD
1248 a1 = scale*_Numeric.array([lower, y])+shift
1249 a2 = scale*_Numeric.array([upper, y])+shift
9d6685e2
RD
1250 dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line
1251 for x, label in xticks:
b31cbeb9 1252 pt = scale*_Numeric.array([x, y])+shift
9d6685e2
RD
1253 dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units
1254 if text:
1255 dc.DrawText(label,pt[0],pt[1])
1256 text = 0 # axis values not drawn on top side
1257
1258 if self._ySpec is not 'none':
1259 lower, upper = p1[1],p2[1]
1260 text = 1
1261 h = dc.GetCharHeight()
1262 for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]:
b31cbeb9
RD
1263 a1 = scale*_Numeric.array([x, lower])+shift
1264 a2 = scale*_Numeric.array([x, upper])+shift
9d6685e2
RD
1265 dc.DrawLine(a1[0],a1[1],a2[0],a2[1])
1266 for y, label in yticks:
b31cbeb9 1267 pt = scale*_Numeric.array([x, y])+shift
9d6685e2
RD
1268 dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1])
1269 if text:
1270 dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0],
1271 pt[1]-0.5*h)
1272 text = 0 # axis values not drawn on right side
1273
48023e15 1274 def _ticks(self, lower, upper):
9d6685e2 1275 ideal = (upper-lower)/7.
b31cbeb9
RD
1276 log = _Numeric.log10(ideal)
1277 power = _Numeric.floor(log)
9d6685e2
RD
1278 fraction = log-power
1279 factor = 1.
1280 error = fraction
1281 for f, lf in self._multiples:
b31cbeb9 1282 e = _Numeric.fabs(fraction-lf)
9d6685e2
RD
1283 if e < error:
1284 error = e
1285 factor = f
1286 grid = factor * 10.**power
1287 if power > 4 or power < -4:
1288 format = '%+7.1e'
1289 elif power >= 0:
1290 digits = max(1, int(power))
1291 format = '%' + `digits`+'.0f'
1292 else:
1293 digits = -int(power)
1294 format = '%'+`digits+2`+'.'+`digits`+'f'
48023e15
RD
1295 ticks = []
1296 t = -grid*_Numeric.floor(-lower/grid)
1297 while t <= upper:
1298 ticks.append( (t, format % (t,)) )
1299 t = t + grid
1300 return ticks
9d6685e2 1301
b31cbeb9 1302 _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))]
9d6685e2
RD
1303
1304
1305#-------------------------------------------------------------------------------
1306# Used to layout the printer page
1307
1308class PlotPrintout(wx.Printout):
1309 """Controls how the plot is made in printing and previewing"""
1310 # Do not change method names in this class,
1311 # we have to override wx.Printout methods here!
1312 def __init__(self, graph):
1313 """graph is instance of plotCanvas to be printed or previewed"""
1314 wx.Printout.__init__(self)
1315 self.graph = graph
1316
1317 def HasPage(self, page):
1318 if page == 1:
1319 return True
1320 else:
1321 return False
1322
1323 def GetPageInfo(self):
e5ce86d8 1324 return (1, 1, 1, 1) # disable page numbers
9d6685e2
RD
1325
1326 def OnPrintPage(self, page):
00b9c867 1327 dc = self.GetDC() # allows using floats for certain functions
9d6685e2
RD
1328## print "PPI Printer",self.GetPPIPrinter()
1329## print "PPI Screen", self.GetPPIScreen()
1330## print "DC GetSize", dc.GetSize()
1331## print "GetPageSizePixels", self.GetPageSizePixels()
1332 # Note PPIScreen does not give the correct number
1333 # Calulate everything for printer and then scale for preview
1334 PPIPrinter= self.GetPPIPrinter() # printer dots/inch (w,h)
1335 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1336 dcSize= dc.GetSize() # DC size
1337 pageSize= self.GetPageSizePixels() # page size in terms of pixcels
1338 clientDcSize= self.graph.GetClientSize()
1339
1340 # find what the margins are (mm)
1341 margLeftSize,margTopSize= self.graph.pageSetupData.GetMarginTopLeft()
1342 margRightSize, margBottomSize= self.graph.pageSetupData.GetMarginBottomRight()
1343
1344 # calculate offset and scale for dc
1345 pixLeft= margLeftSize*PPIPrinter[0]/25.4 # mm*(dots/in)/(mm/in)
1346 pixRight= margRightSize*PPIPrinter[0]/25.4
1347 pixTop= margTopSize*PPIPrinter[1]/25.4
1348 pixBottom= margBottomSize*PPIPrinter[1]/25.4
1349
1350 plotAreaW= pageSize[0]-(pixLeft+pixRight)
1351 plotAreaH= pageSize[1]-(pixTop+pixBottom)
1352
1353 # ratio offset and scale to screen size if preview
1354 if self.IsPreview():
1355 ratioW= float(dcSize[0])/pageSize[0]
1356 ratioH= float(dcSize[1])/pageSize[1]
1357 pixLeft *= ratioW
1358 pixTop *= ratioH
1359 plotAreaW *= ratioW
1360 plotAreaH *= ratioH
1361
1362 # rescale plot to page or preview plot area
1363 self.graph._setSize(plotAreaW,plotAreaH)
1364
1365 # Set offset and scale
1366 dc.SetDeviceOrigin(pixLeft,pixTop)
1367
1368 # Thicken up pens and increase marker size for printing
1369 ratioW= float(plotAreaW)/clientDcSize[0]
1370 ratioH= float(plotAreaH)/clientDcSize[1]
1371 aveScale= (ratioW+ratioH)/2
1372 self.graph._setPrinterScale(aveScale) # tickens up pens for printing
1373
1374 self.graph._printDraw(dc)
1375 # rescale back to original
1376 self.graph._setSize()
1377 self.graph._setPrinterScale(1)
b31cbeb9 1378 self.graph.Redraw() #to get point label scale and shift correct
9d6685e2
RD
1379
1380 return True
1381
9d6685e2
RD
1382
1383
1384
1385#---------------------------------------------------------------------------
1386# if running standalone...
1387#
1388# ...a sample implementation using the above
1389#
1390
1391def _draw1Objects():
1392 # 100 points sin function, plotted as green circles
b31cbeb9 1393 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
9d6685e2 1394 data1.shape = (100, 2)
b31cbeb9 1395 data1[:,1] = _Numeric.sin(data1[:,0])
9d6685e2
RD
1396 markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1)
1397
1398 # 50 points cos function, plotted as red line
b31cbeb9 1399 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
9d6685e2 1400 data1.shape = (50,2)
b31cbeb9 1401 data1[:,1] = _Numeric.cos(data1[:,0])
9d6685e2
RD
1402 lines = PolyLine(data1, legend= 'Red Line', colour='red')
1403
1404 # A few more points...
b31cbeb9 1405 pi = _Numeric.pi
9d6685e2
RD
1406 markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1407 (3.*pi/4., -1)], legend='Cross Legend', colour='blue',
1408 marker='cross')
1409
1410 return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis")
1411
1412def _draw2Objects():
1413 # 100 points sin function, plotted as green dots
b31cbeb9 1414 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
9d6685e2 1415 data1.shape = (100, 2)
b31cbeb9 1416 data1[:,1] = _Numeric.sin(data1[:,0])
9d6685e2
RD
1417 line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT)
1418
1419 # 50 points cos function, plotted as red dot-dash
b31cbeb9 1420 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
9d6685e2 1421 data1.shape = (50,2)
b31cbeb9 1422 data1[:,1] = _Numeric.cos(data1[:,0])
9d6685e2
RD
1423 line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH)
1424
1425 # A few more points...
b31cbeb9 1426 pi = _Numeric.pi
9d6685e2
RD
1427 markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1428 (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6,
1429 fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH,
1430 marker='square')
1431
1432 return PlotGraphics([markers1, line1, line2], "Big Markers with Different Line Styles")
1433
1434def _draw3Objects():
1435 markerList= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1436 'cross', 'plus', 'circle']
1437 m=[]
1438 for i in range(len(markerList)):
1439 m.append(PolyMarker([(2*i+.5,i+.5)], legend=markerList[i], colour='blue',
1440 marker=markerList[i]))
1441 return PlotGraphics(m, "Selection of Markers", "Minimal Axis", "No Axis")
1442
1443def _draw4Objects():
1444 # 25,000 point line
b31cbeb9 1445 data1 = _Numeric.arange(5e5,1e6,10)
9d6685e2
RD
1446 data1.shape = (25000, 2)
1447 line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5)
1448
1449 # A few more points...
1450 markers2 = PolyMarker(data1, legend='Square', colour='blue',
1451 marker='square')
1452 return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "")
1453
1454def _draw5Objects():
1455 # Empty graph with axis defined but no points/lines
1456 points=[]
1457 line1 = PolyLine(points, legend='Wide Line', colour='green', width=5)
1458 return PlotGraphics([line1], "Empty Plot With Just Axes", "Value X", "Value Y")
1459
00b9c867
RD
1460def _draw6Objects():
1461 # Bar graph
1462 points1=[(1,0), (1,10)]
1463 line1 = PolyLine(points1, colour='green', legend='Feb.', width=10)
1464 points1g=[(2,0), (2,4)]
1465 line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10)
1466 points1b=[(3,0), (3,6)]
1467 line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10)
1468
1469 points2=[(4,0), (4,12)]
1470 line2 = PolyLine(points2, colour='Yellow', legend='May', width=10)
1471 points2g=[(5,0), (5,8)]
1472 line2g = PolyLine(points2g, colour='orange', legend='June', width=10)
1473 points2b=[(6,0), (6,4)]
1474 line2b = PolyLine(points2b, colour='brown', legend='July', width=10)
1475
1476 return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
1477 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1478
9d6685e2
RD
1479
1480class TestFrame(wx.Frame):
1481 def __init__(self, parent, id, title):
1482 wx.Frame.__init__(self, parent, id, title,
1483 wx.DefaultPosition, (600, 400))
1484
1485 # Now Create the menu bar and items
1486 self.mainmenu = wx.MenuBar()
1487
1488 menu = wx.Menu()
1489 menu.Append(200, 'Page Setup...', 'Setup the printer page')
1490 self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200)
1491
1492 menu.Append(201, 'Print Preview...', 'Show the current plot on page')
1493 self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201)
1494
1495 menu.Append(202, 'Print...', 'Print the current plot')
1496 self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202)
1497
1498 menu.Append(203, 'Save Plot...', 'Save current plot')
1499 self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203)
1500
1501 menu.Append(205, 'E&xit', 'Enough of this already!')
1502 self.Bind(wx.EVT_MENU, self.OnFileExit, id=205)
1503 self.mainmenu.Append(menu, '&File')
1504
1505 menu = wx.Menu()
1506 menu.Append(206, 'Draw1', 'Draw plots1')
1507 self.Bind(wx.EVT_MENU,self.OnPlotDraw1, id=206)
1508 menu.Append(207, 'Draw2', 'Draw plots2')
1509 self.Bind(wx.EVT_MENU,self.OnPlotDraw2, id=207)
1510 menu.Append(208, 'Draw3', 'Draw plots3')
1511 self.Bind(wx.EVT_MENU,self.OnPlotDraw3, id=208)
1512 menu.Append(209, 'Draw4', 'Draw plots4')
1513 self.Bind(wx.EVT_MENU,self.OnPlotDraw4, id=209)
1514 menu.Append(210, 'Draw5', 'Draw plots5')
1515 self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210)
00b9c867
RD
1516 menu.Append(260, 'Draw6', 'Draw plots6')
1517 self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260)
1518
9d6685e2
RD
1519
1520 menu.Append(211, '&Redraw', 'Redraw plots')
1521 self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211)
1522 menu.Append(212, '&Clear', 'Clear canvas')
1523 self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212)
1524 menu.Append(213, '&Scale', 'Scale canvas')
1525 self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213)
1526 menu.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK)
1527 self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214)
1528 menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK)
1529 self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215)
1530 menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK)
b31cbeb9
RD
1531 self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220)
1532 menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK)
1533 self.Bind(wx.EVT_MENU,self.OnEnablePointLabel, id=222)
1534
9d6685e2
RD
1535 menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1536 self.Bind(wx.EVT_MENU,self.OnScrUp, id=225)
1537 menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1538 self.Bind(wx.EVT_MENU,self.OnScrRt, id=230)
1539 menu.Append(235, '&Plot Reset', 'Reset to original plot')
1540 self.Bind(wx.EVT_MENU,self.OnReset, id=235)
1541
1542 self.mainmenu.Append(menu, '&Plot')
1543
1544 menu = wx.Menu()
1545 menu.Append(300, '&About', 'About this thing...')
1546 self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300)
1547 self.mainmenu.Append(menu, '&Help')
1548
1549 self.SetMenuBar(self.mainmenu)
1550
1551 # A status bar to tell people what's happening
1552 self.CreateStatusBar(1)
1553
1554 self.client = PlotCanvas(self)
b31cbeb9
RD
1555 #define the function for drawing pointLabels
1556 self.client.SetPointLabelFunc(self.DrawPointLabel)
9d6685e2
RD
1557 # Create mouse event for showing cursor coords in status bar
1558 self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
b31cbeb9
RD
1559 # Show closest point when enabled
1560 self.client.Bind(wx.EVT_MOTION, self.OnMotion)
1561
9d6685e2
RD
1562 self.Show(True)
1563
b31cbeb9
RD
1564 def DrawPointLabel(self, dc, mDataDict):
1565 """This is the fuction that defines how the pointLabels are plotted
1566 dc - DC that will be passed
1567 mDataDict - Dictionary of data that you want to use for the pointLabel
1568
1569 As an example I have decided I want a box at the curve point
1570 with some text information about the curve plotted below.
1571 Any wxDC method can be used.
1572 """
1573 # ----------
1574 dc.SetPen(wx.Pen(wx.BLACK))
1575 dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
1576
1577 sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
1578 dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point
1579 px,py = mDataDict["pointXY"]
1580 cNum = mDataDict["curveNum"]
1581 pntIn = mDataDict["pIndex"]
1582 legend = mDataDict["legend"]
1583 #make a string to display
1584 s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
1585 dc.DrawText(s, sx , sy+1)
1586 # -----------
1587
9d6685e2
RD
1588 def OnMouseLeftDown(self,event):
1589 s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event)
1590 self.SetStatusText(s)
b31cbeb9
RD
1591 event.Skip() #allows plotCanvas OnMouseLeftDown to be called
1592
1593 def OnMotion(self, event):
1594 #show closest point (when enbled)
1595 if self.client.GetEnablePointLabel() == True:
1596 #make up dict with info for the pointLabel
1597 #I've decided to mark the closest point on the closest curve
1598 dlst= self.client.GetClosetPoint( self.client.GetXY(event), pointScaled= True)
1599 if dlst != []: #returns [] if none
1600 curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
1601 #make up dictionary to pass to my user function (see DrawPointLabel)
1602 mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
1603 "pointXY":pointXY, "scaledXY":scaledXY}
1604 #pass dict to update the pointLabel
1605 self.client.UpdatePointLabel(mDataDict)
1606 event.Skip() #go to next handler
9d6685e2
RD
1607
1608 def OnFilePageSetup(self, event):
1609 self.client.PageSetup()
1610
1611 def OnFilePrintPreview(self, event):
1612 self.client.PrintPreview()
1613
1614 def OnFilePrint(self, event):
1615 self.client.Printout()
1616
1617 def OnSaveFile(self, event):
1618 self.client.SaveFile()
1619
1620 def OnFileExit(self, event):
1621 self.Close()
1622
1623 def OnPlotDraw1(self, event):
1624 self.resetDefaults()
1625 self.client.Draw(_draw1Objects())
1626
1627 def OnPlotDraw2(self, event):
1628 self.resetDefaults()
1629 self.client.Draw(_draw2Objects())
1630
1631 def OnPlotDraw3(self, event):
1632 self.resetDefaults()
1633 self.client.SetFont(wx.Font(10,wx.SCRIPT,wx.NORMAL,wx.NORMAL))
1634 self.client.SetFontSizeAxis(20)
1635 self.client.SetFontSizeLegend(12)
1636 self.client.SetXSpec('min')
1637 self.client.SetYSpec('none')
1638 self.client.Draw(_draw3Objects())
1639
1640 def OnPlotDraw4(self, event):
1641 self.resetDefaults()
1642 drawObj= _draw4Objects()
1643 self.client.Draw(drawObj)
b31cbeb9
RD
1644## # profile
1645## start = _time.clock()
1646## for x in range(10):
1647## self.client.Draw(drawObj)
1648## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start)
1649## # profile end
9d6685e2
RD
1650
1651 def OnPlotDraw5(self, event):
1652 # Empty plot with just axes
1653 self.resetDefaults()
1654 drawObj= _draw5Objects()
1655 # make the axis X= (0,5), Y=(0,10)
1656 # (default with None is X= (-1,1), Y= (-1,1))
1657 self.client.Draw(drawObj, xAxis= (0,5), yAxis= (0,10))
1658
00b9c867
RD
1659 def OnPlotDraw6(self, event):
1660 #Bar Graph Example
1661 self.resetDefaults()
1662 #self.client.SetEnableLegend(True) #turn on Legend
1663 #self.client.SetEnableGrid(True) #turn on Grid
1664 self.client.SetXSpec('none') #turns off x-axis scale
1665 self.client.SetYSpec('auto')
1666 self.client.Draw(_draw6Objects(), xAxis= (0,7))
1667
9d6685e2
RD
1668 def OnPlotRedraw(self,event):
1669 self.client.Redraw()
1670
1671 def OnPlotClear(self,event):
1672 self.client.Clear()
1673
1674 def OnPlotScale(self, event):
48023e15
RD
1675 if self.client.last_draw != None:
1676 graphics, xAxis, yAxis= self.client.last_draw
1677 self.client.Draw(graphics,(1,3.05),(0,1))
9d6685e2
RD
1678
1679 def OnEnableZoom(self, event):
1680 self.client.SetEnableZoom(event.IsChecked())
1681
1682 def OnEnableGrid(self, event):
1683 self.client.SetEnableGrid(event.IsChecked())
1684
1685 def OnEnableLegend(self, event):
1686 self.client.SetEnableLegend(event.IsChecked())
1687
b31cbeb9
RD
1688 def OnEnablePointLabel(self, event):
1689 self.client.SetEnablePointLabel(event.IsChecked())
1690
9d6685e2
RD
1691 def OnScrUp(self, event):
1692 self.client.ScrollUp(1)
1693
1694 def OnScrRt(self,event):
1695 self.client.ScrollRight(2)
1696
1697 def OnReset(self,event):
1698 self.client.Reset()
1699
1700 def OnHelpAbout(self, event):
33785d9f
RD
1701 from wx.lib.dialogs import ScrolledMessageDialog
1702 about = ScrolledMessageDialog(self, __doc__, "About...")
9d6685e2
RD
1703 about.ShowModal()
1704
1705 def resetDefaults(self):
1706 """Just to reset the fonts back to the PlotCanvas defaults"""
1707 self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
1708 self.client.SetFontSizeAxis(10)
1709 self.client.SetFontSizeLegend(7)
1710 self.client.SetXSpec('auto')
1711 self.client.SetYSpec('auto')
1712
1713
1714def __test():
1715
1716 class MyApp(wx.App):
1717 def OnInit(self):
1718 wx.InitAllImageHandlers()
1719 frame = TestFrame(None, -1, "PlotCanvas")
1720 #frame.Show(True)
1721 self.SetTopWindow(frame)
1722 return True
1723
1724
1725 app = MyApp(0)
1726 app.MainLoop()
1727
1728if __name__ == '__main__':
1729 __test()