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