]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/plot.py
2a5be751207b3d98a4d306000d5593fe0abe30da
[wxWidgets.git] / wxPython / wx / lib / plot.py
1 #-----------------------------------------------------------------------------
2 # Name: wx.lib.plot.py
3 # Purpose: Line, Bar and Scatter Graphs
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 #
19 # 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net)
20 #
21 # o wxScrolledMessageDialog -> ScrolledMessageDialog
22 #
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 #
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 #
38
39 """
40 This is a simple light weight plotting module that can be used with
41 Boa or easily integrated into your own wxPython application. The
42 emphasis is on small size and fast plotting for large data sets. It
43 has a reasonable number of features to do line and scatter graphs
44 easily as well as simple bar graphs. It is not as sophisticated or
45 as powerful as SciPy Plt or Chaco. Both of these are great packages
46 but consume huge amounts of computer resources for simple plots.
47 They can be found at http://scipy.com
48
49 This file contains two parts; first the re-usable library stuff, then,
50 after a "if __name__=='__main__'" test, a simple frame and a few default
51 plots for examples and testing.
52
53 Based on wxPlotCanvas
54 Written by K.Hinsen, R. Srinivasan;
55 Ported to wxPython Harm van der Heijden, feb 1999
56
57 Major 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
69 Did a lot of work here to speed markers up. Only a factor of 4
70 improvement though. Lines are much faster than markers, especially
71 filled markers. Stay away from circles and triangles unless you
72 only have a few thousand points.
73
74 Times for 25,000 points
75 Line - 0.078 sec
76 Markers
77 Square - 0.22 sec
78 dot - 0.10
79 circle - 0.87
80 cross,plus - 0.28
81 triangle, triangle_down - 0.90
82
83 Thanks to Chris Barker for getting this version working on Linux.
84
85 Zooming 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
91 import string as _string
92 import time as _time
93 import wx
94
95 # Needs Numeric or numarray or NumPy
96 try:
97 import numpy as _Numeric
98 except:
99 try:
100 import numarray as _Numeric #if numarray is used it is renamed Numeric
101 except:
102 try:
103 import Numeric as _Numeric
104 except:
105 msg= """
106 This module requires the Numeric/numarray or NumPy module,
107 which could not be imported. It probably is not installed
108 (it's not part of the standard Python distribution). See the
109 Numeric Python site (http://numpy.scipy.org) for information on
110 downloading source or binaries."""
111 raise ImportError, "Numeric,numarray or NumPy not found. \n" + msg
112
113
114
115 #
116 # Plotting classes...
117 #
118 class PolyPoints:
119 """Base Class for lines and markers
120 - All methods are private.
121 """
122
123 def __init__(self, points, attr):
124 self.points = _Numeric.array(points)
125 self.currentScale= (1,1)
126 self.currentShift= (0,0)
127 self.scaled = self.points
128 self.attributes = {}
129 self.attributes.update(self._attributes)
130 for name, value in attr.items():
131 if name not in self._attributes.keys():
132 raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys()
133 self.attributes[name] = value
134
135 def boundingBox(self):
136 if len(self.points) == 0:
137 # no curves to draw
138 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
139 minXY= _Numeric.array([-1,-1])
140 maxXY= _Numeric.array([ 1, 1])
141 else:
142 minXY= _Numeric.minimum.reduce(self.points)
143 maxXY= _Numeric.maximum.reduce(self.points)
144 return minXY, maxXY
145
146 def scaleAndShift(self, scale=(1,1), shift=(0,0)):
147 if len(self.points) == 0:
148 # no curves to draw
149 return
150 if (scale is not self.currentScale) or (shift is not self.currentShift):
151 # update point scaling
152 self.scaled = scale*self.points+shift
153 self.currentScale= scale
154 self.currentShift= shift
155 # else unchanged use the current scaling
156
157 def getLegend(self):
158 return self.attributes['legend']
159
160 def getClosestPoint(self, pntXY, pointScaled= True):
161 """Returns the index of closest point on the curve, pointXY, scaledXY, distance
162 x, y in user coords
163 if pointScaled == True based on screen coords
164 if pointScaled == False based on user coords
165 """
166 if pointScaled == True:
167 #Using screen coords
168 p = self.scaled
169 pxy = self.currentScale * _Numeric.array(pntXY)+ self.currentShift
170 else:
171 #Using user coords
172 p = self.points
173 pxy = _Numeric.array(pntXY)
174 #determine distance for each point
175 d= _Numeric.sqrt(_Numeric.add.reduce((p-pxy)**2,1)) #sqrt(dx^2+dy^2)
176 pntIndex = _Numeric.argmin(d)
177 dist = d[pntIndex]
178 return [pntIndex, self.points[pntIndex], self.scaled[pntIndex], dist]
179
180
181 class PolyLine(PolyPoints):
182 """Class to define line type and style
183 - All methods except __init__ are private.
184 """
185
186 _attributes = {'colour': 'black',
187 'width': 1,
188 'style': wx.SOLID,
189 'legend': ''}
190
191 def __init__(self, points, **attr):
192 """Creates PolyLine object
193 points - sequence (array, tuple or list) of (x,y) points making up line
194 **attr - key word attributes
195 Defaults:
196 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
197 'width'= 1, - Pen width
198 'style'= wx.SOLID, - wx.Pen style
199 'legend'= '' - Line Legend to display
200 """
201 PolyPoints.__init__(self, points, attr)
202
203 def draw(self, dc, printerScale, coord= None):
204 colour = self.attributes['colour']
205 width = self.attributes['width'] * printerScale
206 style= self.attributes['style']
207 if not isinstance(colour, wx.Colour):
208 colour = wx.NamedColour(colour)
209 pen = wx.Pen(colour, width, style)
210 pen.SetCap(wx.CAP_BUTT)
211 dc.SetPen(pen)
212 if coord == None:
213 dc.DrawLines(self.scaled)
214 else:
215 dc.DrawLines(coord) # draw legend line
216
217 def getSymExtent(self, printerScale):
218 """Width and Height of Marker"""
219 h= self.attributes['width'] * printerScale
220 w= 5 * h
221 return (w,h)
222
223
224 class PolyMarker(PolyPoints):
225 """Class to define marker type and style
226 - All methods except __init__ are private.
227 """
228
229 _attributes = {'colour': 'black',
230 'width': 1,
231 'size': 2,
232 'fillcolour': None,
233 'fillstyle': wx.SOLID,
234 'marker': 'circle',
235 'legend': ''}
236
237 def __init__(self, points, **attr):
238 """Creates PolyMarker object
239 points - sequence (array, tuple or list) of (x,y) points
240 **attr - key word attributes
241 Defaults:
242 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
243 'width'= 1, - Pen width
244 'size'= 2, - Marker size
245 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
246 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
247 'marker'= 'circle' - Marker shape
248 'legend'= '' - Marker Legend to display
249
250 Marker Shapes:
251 - 'circle'
252 - 'dot'
253 - 'square'
254 - 'triangle'
255 - 'triangle_down'
256 - 'cross'
257 - 'plus'
258 """
259
260 PolyPoints.__init__(self, points, attr)
261
262 def draw(self, dc, printerScale, coord= None):
263 colour = self.attributes['colour']
264 width = self.attributes['width'] * printerScale
265 size = self.attributes['size'] * printerScale
266 fillcolour = self.attributes['fillcolour']
267 fillstyle = self.attributes['fillstyle']
268 marker = self.attributes['marker']
269
270 if colour and not isinstance(colour, wx.Colour):
271 colour = wx.NamedColour(colour)
272 if fillcolour and not isinstance(fillcolour, wx.Colour):
273 fillcolour = wx.NamedColour(fillcolour)
274
275 dc.SetPen(wx.Pen(colour, width))
276 if fillcolour:
277 dc.SetBrush(wx.Brush(fillcolour,fillstyle))
278 else:
279 dc.SetBrush(wx.Brush(colour, fillstyle))
280 if coord == None:
281 self._drawmarkers(dc, self.scaled, marker, size)
282 else:
283 self._drawmarkers(dc, coord, marker, size) # draw legend marker
284
285 def getSymExtent(self, printerScale):
286 """Width and Height of Marker"""
287 s= 5*self.attributes['size'] * printerScale
288 return (s,s)
289
290 def _drawmarkers(self, dc, coords, marker,size=1):
291 f = eval('self._' +marker)
292 f(dc, coords, size)
293
294 def _circle(self, dc, coords, size=1):
295 fact= 2.5*size
296 wh= 5.0*size
297 rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
298 rect[:,0:2]= coords-[fact,fact]
299 dc.DrawEllipseList(rect.astype(_Numeric.Int32))
300
301 def _dot(self, dc, coords, size=1):
302 dc.DrawPointList(coords)
303
304 def _square(self, dc, coords, size=1):
305 fact= 2.5*size
306 wh= 5.0*size
307 rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
308 rect[:,0:2]= coords-[fact,fact]
309 dc.DrawRectangleList(rect.astype(_Numeric.Int32))
310
311 def _triangle(self, dc, coords, size=1):
312 shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)]
313 poly= _Numeric.repeat(coords,3)
314 poly.shape= (len(coords),3,2)
315 poly += shape
316 dc.DrawPolygonList(poly.astype(_Numeric.Int32))
317
318 def _triangle_down(self, dc, coords, size=1):
319 shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)]
320 poly= _Numeric.repeat(coords,3)
321 poly.shape= (len(coords),3,2)
322 poly += shape
323 dc.DrawPolygonList(poly.astype(_Numeric.Int32))
324
325 def _cross(self, dc, coords, size=1):
326 fact= 2.5*size
327 for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]:
328 lines= _Numeric.concatenate((coords,coords),axis=1)+f
329 dc.DrawLineList(lines.astype(_Numeric.Int32))
330
331 def _plus(self, dc, coords, size=1):
332 fact= 2.5*size
333 for f in [[-fact,0,fact,0],[0,-fact,0,fact]]:
334 lines= _Numeric.concatenate((coords,coords),axis=1)+f
335 dc.DrawLineList(lines.astype(_Numeric.Int32))
336
337 class PlotGraphics:
338 """Container to hold PolyXXX objects and graph labels
339 - All methods except __init__ are private.
340 """
341
342 def __init__(self, objects, title='', xLabel='', yLabel= ''):
343 """Creates PlotGraphics object
344 objects - list of PolyXXX objects to make graph
345 title - title shown at top of graph
346 xLabel - label shown on x-axis
347 yLabel - label shown on y-axis
348 """
349 if type(objects) not in [list,tuple]:
350 raise TypeError, "objects argument should be list or tuple"
351 self.objects = objects
352 self.title= title
353 self.xLabel= xLabel
354 self.yLabel= yLabel
355
356 def boundingBox(self):
357 p1, p2 = self.objects[0].boundingBox()
358 for o in self.objects[1:]:
359 p1o, p2o = o.boundingBox()
360 p1 = _Numeric.minimum(p1, p1o)
361 p2 = _Numeric.maximum(p2, p2o)
362 return p1, p2
363
364 def scaleAndShift(self, scale=(1,1), shift=(0,0)):
365 for o in self.objects:
366 o.scaleAndShift(scale, shift)
367
368 def setPrinterScale(self, scale):
369 """Thickens up lines and markers only for printing"""
370 self.printerScale= scale
371
372 def setXLabel(self, xLabel= ''):
373 """Set the X axis label on the graph"""
374 self.xLabel= xLabel
375
376 def setYLabel(self, yLabel= ''):
377 """Set the Y axis label on the graph"""
378 self.yLabel= yLabel
379
380 def setTitle(self, title= ''):
381 """Set the title at the top of graph"""
382 self.title= title
383
384 def getXLabel(self):
385 """Get x axis label string"""
386 return self.xLabel
387
388 def getYLabel(self):
389 """Get y axis label string"""
390 return self.yLabel
391
392 def getTitle(self, title= ''):
393 """Get the title at the top of graph"""
394 return self.title
395
396 def draw(self, dc):
397 for o in self.objects:
398 #t=_time.clock() # profile info
399 o.draw(dc, self.printerScale)
400 #dt= _time.clock()-t
401 #print o, "time=", dt
402
403 def getSymExtent(self, printerScale):
404 """Get max width and height of lines and markers symbols for legend"""
405 symExt = self.objects[0].getSymExtent(printerScale)
406 for o in self.objects[1:]:
407 oSymExt = o.getSymExtent(printerScale)
408 symExt = _Numeric.maximum(symExt, oSymExt)
409 return symExt
410
411 def getLegendNames(self):
412 """Returns list of legend names"""
413 lst = [None]*len(self)
414 for i in range(len(self)):
415 lst[i]= self.objects[i].getLegend()
416 return lst
417
418 def __len__(self):
419 return len(self.objects)
420
421 def __getitem__(self, item):
422 return self.objects[item]
423
424
425 #-------------------------------------------------------------------------------
426 # Main window that you will want to import into your application.
427
428 class PlotCanvas(wx.Panel):
429 """
430 Subclass of a wx.Panel which holds two scrollbars and the actual
431 plotting canvas (self.canvas). It allows for simple general plotting
432 of data with zoom, labels, and automatic axis scaling."""
433
434 def __init__(self, parent):
435 """Constructs a panel, which can be a child of a frame or
436 any other non-control window"""
437
438 wx.Panel.__init__(self, parent)
439
440 sizer = wx.FlexGridSizer(2,2,0,0)
441 self.canvas = wx.Window(self, -1)
442 #self.canvas.SetMinSize((10,10))
443 self.sb_vert = wx.ScrollBar(self, -1, style=wx.SB_VERTICAL)
444 self.sb_vert.SetScrollbar(0,1000,1000,1000)
445 self.sb_hor = wx.ScrollBar(self, -1, style=wx.SB_HORIZONTAL)
446 self.sb_hor.SetScrollbar(0,1000,1000,1000)
447
448 sizer.Add(self.canvas, 1, wx.EXPAND)
449 sizer.Add(self.sb_vert, 0, wx.EXPAND)
450 sizer.Add(self.sb_hor, 0, wx.EXPAND)
451 sizer.Add((0,0))
452 #corner = wx.Window(self)
453 #corner.SetMinSize((0,0))
454 #sizer.Add(corner, 0, wx.EXPAND)
455
456 sizer.AddGrowableRow(0, 1)
457 sizer.AddGrowableCol(0, 1)
458 self.sb_vert.Show(False)
459 self.sb_hor.Show(False)
460 self.SetSizer(sizer)
461 self.Fit()
462
463 self.SetBackgroundColour("white")
464
465 # Create some mouse events for zooming
466 self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
467 self.canvas.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
468 self.canvas.Bind(wx.EVT_MOTION, self.OnMotion)
469 self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick)
470 self.canvas.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
471
472 # scrollbar events
473 self.Bind(wx.EVT_SCROLL_THUMBTRACK, self.OnScroll)
474 self.Bind(wx.EVT_SCROLL_PAGEUP, self.OnScroll)
475 self.Bind(wx.EVT_SCROLL_PAGEDOWN, self.OnScroll)
476 self.Bind(wx.EVT_SCROLL_LINEUP, self.OnScroll)
477 self.Bind(wx.EVT_SCROLL_LINEDOWN, self.OnScroll)
478
479 # set curser as cross-hairs
480 self.canvas.SetCursor(wx.CROSS_CURSOR)
481 self.HandCursor = wx.CursorFromImage(getHandImage())
482 self.GrabHandCursor = wx.CursorFromImage(getGrabHandImage())
483 self.MagCursor = wx.CursorFromImage(getMagPlusImage())
484
485 # Things for printing
486 self.print_data = wx.PrintData()
487 self.print_data.SetPaperId(wx.PAPER_LETTER)
488 self.print_data.SetOrientation(wx.LANDSCAPE)
489 self.pageSetupData= wx.PageSetupDialogData()
490 self.pageSetupData.SetMarginBottomRight((25,25))
491 self.pageSetupData.SetMarginTopLeft((25,25))
492 self.pageSetupData.SetPrintData(self.print_data)
493 self.printerScale = 1
494 self.parent= parent
495
496 # scrollbar variables
497 self._sb_ignore = False
498 self._adjustingSB = False
499 self._sb_xfullrange = 0
500 self._sb_yfullrange = 0
501 self._sb_xunit = 0
502 self._sb_yunit = 0
503
504 self._dragEnabled = False
505 self._screenCoordinates = _Numeric.array([0.0, 0.0])
506
507 # Zooming variables
508 self._zoomInFactor = 0.5
509 self._zoomOutFactor = 2
510 self._zoomCorner1= _Numeric.array([0.0, 0.0]) # left mouse down corner
511 self._zoomCorner2= _Numeric.array([0.0, 0.0]) # left mouse up corner
512 self._zoomEnabled= False
513 self._hasDragged= False
514
515 # Drawing Variables
516 self.last_draw = None
517 self._pointScale= 1
518 self._pointShift= 0
519 self._xSpec= 'auto'
520 self._ySpec= 'auto'
521 self._gridEnabled= False
522 self._legendEnabled= False
523
524 # Fonts
525 self._fontCache = {}
526 self._fontSizeAxis= 10
527 self._fontSizeTitle= 15
528 self._fontSizeLegend= 7
529
530 # pointLabels
531 self._pointLabelEnabled= False
532 self.last_PointLabel= None
533 self._pointLabelFunc= None
534 self.canvas.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
535
536 self.canvas.Bind(wx.EVT_PAINT, self.OnPaint)
537 self.canvas.Bind(wx.EVT_SIZE, self.OnSize)
538 # OnSize called to make sure the buffer is initialized.
539 # This might result in OnSize getting called twice on some
540 # platforms at initialization, but little harm done.
541 self.OnSize(None) # sets the initial size based on client size
542
543 self._gridColour = wx.NamedColour('black')
544
545 def SetCursor(self, cursor):
546 self.canvas.SetCursor(cursor)
547
548 def GetGridColour(self):
549 return self._gridColour
550
551 def SetGridColour(self, colour):
552 if isinstance(colour, wx.Colour):
553 self._gridColour = colour
554 else:
555 self._gridColour = wx.NamedColour(colour)
556
557
558 # SaveFile
559 def SaveFile(self, fileName= ''):
560 """Saves the file to the type specified in the extension. If no file
561 name is specified a dialog box is provided. Returns True if sucessful,
562 otherwise False.
563
564 .bmp Save a Windows bitmap file.
565 .xbm Save an X bitmap file.
566 .xpm Save an XPM bitmap file.
567 .png Save a Portable Network Graphics file.
568 .jpg Save a Joint Photographic Experts Group file.
569 """
570 if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
571 dlg1 = wx.FileDialog(
572 self,
573 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
574 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
575 wx.SAVE|wx.OVERWRITE_PROMPT
576 )
577 try:
578 while 1:
579 if dlg1.ShowModal() == wx.ID_OK:
580 fileName = dlg1.GetPath()
581 # Check for proper exension
582 if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
583 dlg2 = wx.MessageDialog(self, 'File name extension\n'
584 'must be one of\n'
585 'bmp, xbm, xpm, png, or jpg',
586 'File Name Error', wx.OK | wx.ICON_ERROR)
587 try:
588 dlg2.ShowModal()
589 finally:
590 dlg2.Destroy()
591 else:
592 break # now save file
593 else: # exit without saving
594 return False
595 finally:
596 dlg1.Destroy()
597
598 # File name has required extension
599 fType = _string.lower(fileName[-3:])
600 if fType == "bmp":
601 tp= wx.BITMAP_TYPE_BMP # Save a Windows bitmap file.
602 elif fType == "xbm":
603 tp= wx.BITMAP_TYPE_XBM # Save an X bitmap file.
604 elif fType == "xpm":
605 tp= wx.BITMAP_TYPE_XPM # Save an XPM bitmap file.
606 elif fType == "jpg":
607 tp= wx.BITMAP_TYPE_JPEG # Save a JPG file.
608 else:
609 tp= wx.BITMAP_TYPE_PNG # Save a PNG file.
610 # Save Bitmap
611 res= self._Buffer.SaveFile(fileName, tp)
612 return res
613
614 def PageSetup(self):
615 """Brings up the page setup dialog"""
616 data = self.pageSetupData
617 data.SetPrintData(self.print_data)
618 dlg = wx.PageSetupDialog(self.parent, data)
619 try:
620 if dlg.ShowModal() == wx.ID_OK:
621 data = dlg.GetPageSetupData() # returns wx.PageSetupDialogData
622 # updates page parameters from dialog
623 self.pageSetupData.SetMarginBottomRight(data.GetMarginBottomRight())
624 self.pageSetupData.SetMarginTopLeft(data.GetMarginTopLeft())
625 self.pageSetupData.SetPrintData(data.GetPrintData())
626 self.print_data=wx.PrintData(data.GetPrintData()) # updates print_data
627 finally:
628 dlg.Destroy()
629
630 def Printout(self, paper=None):
631 """Print current plot."""
632 if paper != None:
633 self.print_data.SetPaperId(paper)
634 pdd = wx.PrintDialogData(self.print_data)
635 printer = wx.Printer(pdd)
636 out = PlotPrintout(self)
637 print_ok = printer.Print(self.parent, out)
638 if print_ok:
639 self.print_data = wx.PrintData(printer.GetPrintDialogData().GetPrintData())
640 out.Destroy()
641
642 def PrintPreview(self):
643 """Print-preview current plot."""
644 printout = PlotPrintout(self)
645 printout2 = PlotPrintout(self)
646 self.preview = wx.PrintPreview(printout, printout2, self.print_data)
647 if not self.preview.Ok():
648 wx.MessageDialog(self, "Print Preview failed.\n" \
649 "Check that default printer is configured\n", \
650 "Print error", wx.OK|wx.CENTRE).ShowModal()
651 self.preview.SetZoom(40)
652 # search up tree to find frame instance
653 frameInst= self
654 while not isinstance(frameInst, wx.Frame):
655 frameInst= frameInst.GetParent()
656 frame = wx.PreviewFrame(self.preview, frameInst, "Preview")
657 frame.Initialize()
658 frame.SetPosition(self.GetPosition())
659 frame.SetSize((600,550))
660 frame.Centre(wx.BOTH)
661 frame.Show(True)
662
663 def SetFontSizeAxis(self, point= 10):
664 """Set the tick and axis label font size (default is 10 point)"""
665 self._fontSizeAxis= point
666
667 def GetFontSizeAxis(self):
668 """Get current tick and axis label font size in points"""
669 return self._fontSizeAxis
670
671 def SetFontSizeTitle(self, point= 15):
672 """Set Title font size (default is 15 point)"""
673 self._fontSizeTitle= point
674
675 def GetFontSizeTitle(self):
676 """Get current Title font size in points"""
677 return self._fontSizeTitle
678
679 def SetFontSizeLegend(self, point= 7):
680 """Set Legend font size (default is 7 point)"""
681 self._fontSizeLegend= point
682
683 def GetFontSizeLegend(self):
684 """Get current Legend font size in points"""
685 return self._fontSizeLegend
686
687 def SetShowScrollbars(self, value):
688 """Set True to show scrollbars"""
689 if value not in [True,False]:
690 raise TypeError, "Value should be True or False"
691 if value == self.GetShowScrollbars():
692 return
693 self.sb_vert.Show(value)
694 self.sb_hor.Show(value)
695 wx.CallAfter(self.Layout)
696
697 def GetShowScrollbars(self):
698 """Set True to show scrollbars"""
699 return self.sb_vert.IsShown()
700
701 def SetEnableDrag(self, value):
702 """Set True to enable drag."""
703 if value not in [True,False]:
704 raise TypeError, "Value should be True or False"
705 if value:
706 if self.GetEnableZoom():
707 self.SetEnableZoom(False)
708 self.SetCursor(self.HandCursor)
709 else:
710 self.SetCursor(wx.CROSS_CURSOR)
711 self._dragEnabled = value
712
713 def GetEnableDrag(self):
714 return self._dragEnabled
715
716 def SetEnableZoom(self, value):
717 """Set True to enable zooming."""
718 if value not in [True,False]:
719 raise TypeError, "Value should be True or False"
720 if value:
721 if self.GetEnableDrag():
722 self.SetEnableDrag(False)
723 self.SetCursor(self.MagCursor)
724 else:
725 self.SetCursor(wx.CROSS_CURSOR)
726 self._zoomEnabled= value
727
728 def GetEnableZoom(self):
729 """True if zooming enabled."""
730 return self._zoomEnabled
731
732 def SetEnableGrid(self, value):
733 """Set True to enable grid."""
734 if value not in [True,False,'Horizontal','Vertical']:
735 raise TypeError, "Value should be True, False, Horizontal or Vertical"
736 self._gridEnabled= value
737 self.Redraw()
738
739 def GetEnableGrid(self):
740 """True if grid enabled."""
741 return self._gridEnabled
742
743 def SetEnableLegend(self, value):
744 """Set True to enable legend."""
745 if value not in [True,False]:
746 raise TypeError, "Value should be True or False"
747 self._legendEnabled= value
748 self.Redraw()
749
750 def GetEnableLegend(self):
751 """True if Legend enabled."""
752 return self._legendEnabled
753
754 def SetEnablePointLabel(self, value):
755 """Set True to enable pointLabel."""
756 if value not in [True,False]:
757 raise TypeError, "Value should be True or False"
758 self._pointLabelEnabled= value
759 self.Redraw() #will erase existing pointLabel if present
760 self.last_PointLabel = None
761
762 def GetEnablePointLabel(self):
763 """True if pointLabel enabled."""
764 return self._pointLabelEnabled
765
766 def SetPointLabelFunc(self, func):
767 """Sets the function with custom code for pointLabel drawing
768 ******** more info needed ***************
769 """
770 self._pointLabelFunc= func
771
772 def GetPointLabelFunc(self):
773 """Returns pointLabel Drawing Function"""
774 return self._pointLabelFunc
775
776 def Reset(self):
777 """Unzoom the plot."""
778 self.last_PointLabel = None #reset pointLabel
779 if self.last_draw is not None:
780 self.Draw(self.last_draw[0])
781
782 def ScrollRight(self, units):
783 """Move view right number of axis units."""
784 self.last_PointLabel = None #reset pointLabel
785 if self.last_draw is not None:
786 graphics, xAxis, yAxis= self.last_draw
787 xAxis= (xAxis[0]+units, xAxis[1]+units)
788 self.Draw(graphics,xAxis,yAxis)
789
790 def ScrollUp(self, units):
791 """Move view up number of axis units."""
792 self.last_PointLabel = None #reset pointLabel
793 if self.last_draw is not None:
794 graphics, xAxis, yAxis= self.last_draw
795 yAxis= (yAxis[0]+units, yAxis[1]+units)
796 self.Draw(graphics,xAxis,yAxis)
797
798
799 def GetXY(self,event):
800 """Takes a mouse event and returns the XY user axis values."""
801 x,y= self.PositionScreenToUser(event.GetPosition())
802 return x,y
803
804 def PositionUserToScreen(self, pntXY):
805 """Converts User position to Screen Coordinates"""
806 userPos= _Numeric.array(pntXY)
807 x,y= userPos * self._pointScale + self._pointShift
808 return x,y
809
810 def PositionScreenToUser(self, pntXY):
811 """Converts Screen position to User Coordinates"""
812 screenPos= _Numeric.array(pntXY)
813 x,y= (screenPos-self._pointShift)/self._pointScale
814 return x,y
815
816 def SetXSpec(self, type= 'auto'):
817 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
818 where:
819 'none' - shows no axis or tick mark values
820 'min' - shows min bounding box values
821 'auto' - rounds axis range to sensible values
822 """
823 self._xSpec= type
824
825 def SetYSpec(self, type= 'auto'):
826 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
827 where:
828 'none' - shows no axis or tick mark values
829 'min' - shows min bounding box values
830 'auto' - rounds axis range to sensible values
831 """
832 self._ySpec= type
833
834 def GetXSpec(self):
835 """Returns current XSpec for axis"""
836 return self._xSpec
837
838 def GetYSpec(self):
839 """Returns current YSpec for axis"""
840 return self._ySpec
841
842 def GetXMaxRange(self):
843 """Returns (minX, maxX) x-axis range for displayed graph"""
844 graphics= self.last_draw[0]
845 p1, p2 = graphics.boundingBox() # min, max points of graphics
846 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
847 return xAxis
848
849 def GetYMaxRange(self):
850 """Returns (minY, maxY) y-axis range for displayed graph"""
851 graphics= self.last_draw[0]
852 p1, p2 = graphics.boundingBox() # min, max points of graphics
853 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
854 return yAxis
855
856 def GetXCurrentRange(self):
857 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
858 return self.last_draw[1]
859
860 def GetYCurrentRange(self):
861 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
862 return self.last_draw[2]
863
864 def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
865 """Draw objects in graphics with specified x and y axis.
866 graphics- instance of PlotGraphics with list of PolyXXX objects
867 xAxis - tuple with (min, max) axis range to view
868 yAxis - same as xAxis
869 dc - drawing context - doesn't have to be specified.
870 If it's not, the offscreen buffer is used
871 """
872 # check Axis is either tuple or none
873 if type(xAxis) not in [type(None),tuple]:
874 raise TypeError, "xAxis should be None or (minX,maxX)"
875 if type(yAxis) not in [type(None),tuple]:
876 raise TypeError, "yAxis should be None or (minY,maxY)"
877
878 # check case for axis = (a,b) where a==b caused by improper zooms
879 if xAxis != None:
880 if xAxis[0] == xAxis[1]:
881 return
882 if yAxis != None:
883 if yAxis[0] == yAxis[1]:
884 return
885
886 if dc == None:
887 # sets new dc and clears it
888 dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
889 dc.Clear()
890
891 dc.BeginDrawing()
892 # dc.Clear()
893
894 # set font size for every thing but title and legend
895 dc.SetFont(self._getFont(self._fontSizeAxis))
896
897 # sizes axis to axis type, create lower left and upper right corners of plot
898 if xAxis == None or yAxis == None:
899 # One or both axis not specified in Draw
900 p1, p2 = graphics.boundingBox() # min, max points of graphics
901 if xAxis == None:
902 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
903 if yAxis == None:
904 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
905 # Adjust bounding box for axis spec
906 p1[0],p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin,ymin)
907 p2[0],p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax,ymax)
908 else:
909 # Both axis specified in Draw
910 p1= _Numeric.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin)
911 p2= _Numeric.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax)
912
913 self.last_draw = (graphics, xAxis, yAxis) # saves most recient values
914
915 # Get ticks and textExtents for axis if required
916 if self._xSpec is not 'none':
917 xticks = self._ticks(xAxis[0], xAxis[1])
918 xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis
919 else:
920 xticks = None
921 xTextExtent= (0,0) # No text for ticks
922 if self._ySpec is not 'none':
923 yticks = self._ticks(yAxis[0], yAxis[1])
924 yTextExtentBottom= dc.GetTextExtent(yticks[0][1])
925 yTextExtentTop = dc.GetTextExtent(yticks[-1][1])
926 yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]),
927 max(yTextExtentBottom[1],yTextExtentTop[1]))
928 else:
929 yticks = None
930 yTextExtent= (0,0) # No text for ticks
931
932 # TextExtents for Title and Axis Labels
933 titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics)
934
935 # TextExtents for Legend
936 legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
937
938 # room around graph area
939 rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width
940 lhsW= yTextExtent[0]+ yLabelWH[1]
941 bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1]
942 topH= yTextExtent[1]/2. + titleWH[1]
943 textSize_scale= _Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size
944 textSize_shift= _Numeric.array([lhsW, bottomH]) # shift plot area by this amount
945
946 # drawing title and labels text
947 dc.SetFont(self._getFont(self._fontSizeTitle))
948 titlePos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- titleWH[0]/2.,
949 self.plotbox_origin[1]- self.plotbox_size[1])
950 dc.DrawText(graphics.getTitle(),titlePos[0],titlePos[1])
951 dc.SetFont(self._getFont(self._fontSizeAxis))
952 xLabelPos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- xLabelWH[0]/2.,
953 self.plotbox_origin[1]- xLabelWH[1])
954 dc.DrawText(graphics.getXLabel(),xLabelPos[0],xLabelPos[1])
955 yLabelPos= (self.plotbox_origin[0],
956 self.plotbox_origin[1]- bottomH- (self.plotbox_size[1]-bottomH-topH)/2.+ yLabelWH[0]/2.)
957 if graphics.getYLabel(): # bug fix for Linux
958 dc.DrawRotatedText(graphics.getYLabel(),yLabelPos[0],yLabelPos[1],90)
959
960 # drawing legend makers and text
961 if self._legendEnabled:
962 self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt)
963
964 # allow for scaling and shifting plotted points
965 scale = (self.plotbox_size-textSize_scale) / (p2-p1)* _Numeric.array((1,-1))
966 shift = -p1*scale + self.plotbox_origin + textSize_shift * _Numeric.array((1,-1))
967 self._pointScale= scale # make available for mouse events
968 self._pointShift= shift
969 self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks)
970
971 graphics.scaleAndShift(scale, shift)
972 graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing
973
974 # set clipping area so drawing does not occur outside axis box
975 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2)
976 dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight)
977 # Draw the lines and markers
978 #start = _time.clock()
979 graphics.draw(dc)
980 # print "entire graphics drawing took: %f second"%(_time.clock() - start)
981 # remove the clipping region
982 dc.DestroyClippingRegion()
983 dc.EndDrawing()
984 self._adjustScrollbars()
985
986 def Redraw(self, dc= None):
987 """Redraw the existing plot."""
988 if self.last_draw is not None:
989 graphics, xAxis, yAxis= self.last_draw
990 self.Draw(graphics,xAxis,yAxis,dc)
991
992 def Clear(self):
993 """Erase the window."""
994 self.last_PointLabel = None #reset pointLabel
995 dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
996 dc.Clear()
997 self.last_draw = None
998
999 def Zoom(self, Center, Ratio):
1000 """ Zoom on the plot
1001 Centers on the X,Y coords given in Center
1002 Zooms by the Ratio = (Xratio, Yratio) given
1003 """
1004 self.last_PointLabel = None #reset maker
1005 x,y = Center
1006 if self.last_draw != None:
1007 (graphics, xAxis, yAxis) = self.last_draw
1008 w = (xAxis[1] - xAxis[0]) * Ratio[0]
1009 h = (yAxis[1] - yAxis[0]) * Ratio[1]
1010 xAxis = ( x - w/2, x + w/2 )
1011 yAxis = ( y - h/2, y + h/2 )
1012 self.Draw(graphics, xAxis, yAxis)
1013
1014 def GetClosestPoints(self, pntXY, pointScaled= True):
1015 """Returns list with
1016 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1017 list for each curve.
1018 Returns [] if no curves are being plotted.
1019
1020 x, y in user coords
1021 if pointScaled == True based on screen coords
1022 if pointScaled == False based on user coords
1023 """
1024 if self.last_draw == None:
1025 #no graph available
1026 return []
1027 graphics, xAxis, yAxis= self.last_draw
1028 l = []
1029 for curveNum,obj in enumerate(graphics):
1030 #check there are points in the curve
1031 if len(obj.points) == 0:
1032 continue #go to next obj
1033 #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1034 cn = [curveNum]+ [obj.getLegend()]+ obj.getClosestPoint( pntXY, pointScaled)
1035 l.append(cn)
1036 return l
1037
1038 def GetClosetPoint(self, pntXY, pointScaled= True):
1039 """Returns list with
1040 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1041 list for only the closest curve.
1042 Returns [] if no curves are being plotted.
1043
1044 x, y in user coords
1045 if pointScaled == True based on screen coords
1046 if pointScaled == False based on user coords
1047 """
1048 #closest points on screen based on screen scaling (pointScaled= True)
1049 #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
1050 closestPts= self.GetClosestPoints(pntXY, pointScaled)
1051 if closestPts == []:
1052 return [] #no graph present
1053 #find one with least distance
1054 dists = [c[-1] for c in closestPts]
1055 mdist = min(dists) #Min dist
1056 i = dists.index(mdist) #index for min dist
1057 return closestPts[i] #this is the closest point on closest curve
1058
1059 def UpdatePointLabel(self, mDataDict):
1060 """Updates the pointLabel point on screen with data contained in
1061 mDataDict.
1062
1063 mDataDict will be passed to your function set by
1064 SetPointLabelFunc. It can contain anything you
1065 want to display on the screen at the scaledXY point
1066 you specify.
1067
1068 This function can be called from parent window with onClick,
1069 onMotion events etc.
1070 """
1071 if self.last_PointLabel != None:
1072 #compare pointXY
1073 if mDataDict["pointXY"] != self.last_PointLabel["pointXY"]:
1074 #closest changed
1075 self._drawPointLabel(self.last_PointLabel) #erase old
1076 self._drawPointLabel(mDataDict) #plot new
1077 else:
1078 #just plot new with no erase
1079 self._drawPointLabel(mDataDict) #plot new
1080 #save for next erase
1081 self.last_PointLabel = mDataDict
1082
1083 # event handlers **********************************
1084 def OnMotion(self, event):
1085 if self._zoomEnabled and event.LeftIsDown():
1086 if self._hasDragged:
1087 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
1088 else:
1089 self._hasDragged= True
1090 self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event)
1091 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new
1092 elif self._dragEnabled and event.LeftIsDown():
1093 coordinates = event.GetPosition()
1094 newpos, oldpos = map(_Numeric.array, map(self.PositionScreenToUser, [coordinates, self._screenCoordinates]))
1095 dist = newpos-oldpos
1096 self._screenCoordinates = coordinates
1097
1098 self.ScrollUp(-dist[1])
1099 self.ScrollRight(-dist[0])
1100
1101 def OnMouseLeftDown(self,event):
1102 self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event)
1103 self._screenCoordinates = _Numeric.array(event.GetPosition())
1104 if self._dragEnabled:
1105 self.SetCursor(self.GrabHandCursor)
1106 self.canvas.CaptureMouse()
1107
1108 def OnMouseLeftUp(self, event):
1109 if self._zoomEnabled:
1110 if self._hasDragged == True:
1111 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
1112 self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event)
1113 self._hasDragged = False # reset flag
1114 minX, minY= _Numeric.minimum( self._zoomCorner1, self._zoomCorner2)
1115 maxX, maxY= _Numeric.maximum( self._zoomCorner1, self._zoomCorner2)
1116 self.last_PointLabel = None #reset pointLabel
1117 if self.last_draw != None:
1118 self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
1119 #else: # A box has not been drawn, zoom in on a point
1120 ## this interfered with the double click, so I've disables it.
1121 # X,Y = self.GetXY(event)
1122 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
1123 if self._dragEnabled:
1124 self.SetCursor(self.HandCursor)
1125 if self.canvas.HasCapture():
1126 self.canvas.ReleaseMouse()
1127
1128 def OnMouseDoubleClick(self,event):
1129 if self._zoomEnabled:
1130 # Give a little time for the click to be totally finished
1131 # before (possibly) removing the scrollbars and trigering
1132 # size events, etc.
1133 wx.FutureCall(200,self.Reset)
1134
1135 def OnMouseRightDown(self,event):
1136 if self._zoomEnabled:
1137 X,Y = self.GetXY(event)
1138 self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) )
1139
1140 def OnPaint(self, event):
1141 # All that is needed here is to draw the buffer to screen
1142 if self.last_PointLabel != None:
1143 self._drawPointLabel(self.last_PointLabel) #erase old
1144 self.last_PointLabel = None
1145 dc = wx.BufferedPaintDC(self.canvas, self._Buffer)
1146
1147 def OnSize(self,event):
1148 # The Buffer init is done here, to make sure the buffer is always
1149 # the same size as the Window
1150 Size = self.canvas.GetClientSize()
1151 if Size.width <= 0 or Size.height <= 0:
1152 return
1153
1154 # Make new offscreen bitmap: this bitmap will always have the
1155 # current drawing in it, so it can be used to save the image to
1156 # a file, or whatever.
1157 self._Buffer = wx.EmptyBitmap(Size[0],Size[1])
1158 self._setSize()
1159
1160 self.last_PointLabel = None #reset pointLabel
1161
1162 if self.last_draw is None:
1163 self.Clear()
1164 else:
1165 graphics, xSpec, ySpec = self.last_draw
1166 self.Draw(graphics,xSpec,ySpec)
1167
1168 def OnLeave(self, event):
1169 """Used to erase pointLabel when mouse outside window"""
1170 if self.last_PointLabel != None:
1171 self._drawPointLabel(self.last_PointLabel) #erase old
1172 self.last_PointLabel = None
1173
1174 def OnScroll(self, evt):
1175 if not self._adjustingSB:
1176 self._sb_ignore = True
1177 sbpos = evt.GetPosition()
1178
1179 if evt.GetOrientation() == wx.VERTICAL:
1180 fullrange,pagesize = self.sb_vert.GetRange(),self.sb_vert.GetPageSize()
1181 sbpos = fullrange-pagesize-sbpos
1182 dist = sbpos*self._sb_yunit-(self.GetYCurrentRange()[0]-self._sb_yfullrange[0])
1183 self.ScrollUp(dist)
1184
1185 if evt.GetOrientation() == wx.HORIZONTAL:
1186 dist = sbpos*self._sb_xunit-(self.GetXCurrentRange()[0]-self._sb_xfullrange[0])
1187 self.ScrollRight(dist)
1188
1189 # Private Methods **************************************************
1190 def _setSize(self, width=None, height=None):
1191 """DC width and height."""
1192 if width == None:
1193 (self.width,self.height) = self.canvas.GetClientSize()
1194 else:
1195 self.width, self.height= width,height
1196 self.plotbox_size = 0.97*_Numeric.array([self.width, self.height])
1197 xo = 0.5*(self.width-self.plotbox_size[0])
1198 yo = self.height-0.5*(self.height-self.plotbox_size[1])
1199 self.plotbox_origin = _Numeric.array([xo, yo])
1200
1201 def _setPrinterScale(self, scale):
1202 """Used to thicken lines and increase marker size for print out."""
1203 # line thickness on printer is very thin at 600 dot/in. Markers small
1204 self.printerScale= scale
1205
1206 def _printDraw(self, printDC):
1207 """Used for printing."""
1208 if self.last_draw != None:
1209 graphics, xSpec, ySpec= self.last_draw
1210 self.Draw(graphics,xSpec,ySpec,printDC)
1211
1212 def _drawPointLabel(self, mDataDict):
1213 """Draws and erases pointLabels"""
1214 width = self._Buffer.GetWidth()
1215 height = self._Buffer.GetHeight()
1216 tmp_Buffer = wx.EmptyBitmap(width,height)
1217 dcs = wx.MemoryDC()
1218 dcs.SelectObject(tmp_Buffer)
1219 dcs.Clear()
1220 dcs.BeginDrawing()
1221 self._pointLabelFunc(dcs,mDataDict) #custom user pointLabel function
1222 dcs.EndDrawing()
1223
1224 dc = wx.ClientDC( self.canvas )
1225 #this will erase if called twice
1226 dc.Blit(0, 0, width, height, dcs, 0, 0, wx.EQUIV) #(NOT src) XOR dst
1227
1228
1229 def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
1230 """Draws legend symbols and text"""
1231 # top right hand corner of graph box is ref corner
1232 trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1]
1233 legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box
1234 lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines
1235 dc.SetFont(self._getFont(self._fontSizeLegend))
1236 for i in range(len(graphics)):
1237 o = graphics[i]
1238 s= i*lineHeight
1239 if isinstance(o,PolyMarker):
1240 # draw marker with legend
1241 pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.)
1242 o.draw(dc, self.printerScale, coord= _Numeric.array([pnt]))
1243 elif isinstance(o,PolyLine):
1244 # draw line with legend
1245 pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.)
1246 pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.)
1247 o.draw(dc, self.printerScale, coord= _Numeric.array([pnt1,pnt2]))
1248 else:
1249 raise TypeError, "object is neither PolyMarker or PolyLine instance"
1250 # draw legend txt
1251 pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2)
1252 dc.DrawText(o.getLegend(),pnt[0],pnt[1])
1253 dc.SetFont(self._getFont(self._fontSizeAxis)) # reset
1254
1255 def _titleLablesWH(self, dc, graphics):
1256 """Draws Title and labels and returns width and height for each"""
1257 # TextExtents for Title and Axis Labels
1258 dc.SetFont(self._getFont(self._fontSizeTitle))
1259 title= graphics.getTitle()
1260 titleWH= dc.GetTextExtent(title)
1261 dc.SetFont(self._getFont(self._fontSizeAxis))
1262 xLabel, yLabel= graphics.getXLabel(),graphics.getYLabel()
1263 xLabelWH= dc.GetTextExtent(xLabel)
1264 yLabelWH= dc.GetTextExtent(yLabel)
1265 return titleWH, xLabelWH, yLabelWH
1266
1267 def _legendWH(self, dc, graphics):
1268 """Returns the size in screen units for legend box"""
1269 if self._legendEnabled != True:
1270 legendBoxWH= symExt= txtExt= (0,0)
1271 else:
1272 # find max symbol size
1273 symExt= graphics.getSymExtent(self.printerScale)
1274 # find max legend text extent
1275 dc.SetFont(self._getFont(self._fontSizeLegend))
1276 txtList= graphics.getLegendNames()
1277 txtExt= dc.GetTextExtent(txtList[0])
1278 for txt in graphics.getLegendNames()[1:]:
1279 txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt))
1280 maxW= symExt[0]+txtExt[0]
1281 maxH= max(symExt[1],txtExt[1])
1282 # padding .1 for lhs of legend box and space between lines
1283 maxW= maxW* 1.1
1284 maxH= maxH* 1.1 * len(txtList)
1285 dc.SetFont(self._getFont(self._fontSizeAxis))
1286 legendBoxWH= (maxW,maxH)
1287 return (legendBoxWH, symExt, txtExt)
1288
1289 def _drawRubberBand(self, corner1, corner2):
1290 """Draws/erases rect box from corner1 to corner2"""
1291 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2)
1292 # draw rectangle
1293 dc = wx.ClientDC( self.canvas )
1294 dc.BeginDrawing()
1295 dc.SetPen(wx.Pen(wx.BLACK))
1296 dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) )
1297 dc.SetLogicalFunction(wx.INVERT)
1298 dc.DrawRectangle( ptx,pty, rectWidth,rectHeight)
1299 dc.SetLogicalFunction(wx.COPY)
1300 dc.EndDrawing()
1301
1302 def _getFont(self,size):
1303 """Take font size, adjusts if printing and returns wx.Font"""
1304 s = size*self.printerScale
1305 of = self.GetFont()
1306 # Linux speed up to get font from cache rather than X font server
1307 key = (int(s), of.GetFamily (), of.GetStyle (), of.GetWeight ())
1308 font = self._fontCache.get (key, None)
1309 if font:
1310 return font # yeah! cache hit
1311 else:
1312 font = wx.Font(int(s), of.GetFamily(), of.GetStyle(), of.GetWeight())
1313 self._fontCache[key] = font
1314 return font
1315
1316
1317 def _point2ClientCoord(self, corner1, corner2):
1318 """Converts user point coords to client screen int coords x,y,width,height"""
1319 c1= _Numeric.array(corner1)
1320 c2= _Numeric.array(corner2)
1321 # convert to screen coords
1322 pt1= c1*self._pointScale+self._pointShift
1323 pt2= c2*self._pointScale+self._pointShift
1324 # make height and width positive
1325 pul= _Numeric.minimum(pt1,pt2) # Upper left corner
1326 plr= _Numeric.maximum(pt1,pt2) # Lower right corner
1327 rectWidth, rectHeight= plr-pul
1328 ptx,pty= pul
1329 return ptx, pty, rectWidth, rectHeight
1330
1331 def _axisInterval(self, spec, lower, upper):
1332 """Returns sensible axis range for given spec"""
1333 if spec == 'none' or spec == 'min':
1334 if lower == upper:
1335 return lower-0.5, upper+0.5
1336 else:
1337 return lower, upper
1338 elif spec == 'auto':
1339 range = upper-lower
1340 if range == 0.:
1341 return lower-0.5, upper+0.5
1342 log = _Numeric.log10(range)
1343 power = _Numeric.floor(log)
1344 fraction = log-power
1345 if fraction <= 0.05:
1346 power = power-1
1347 grid = 10.**power
1348 lower = lower - lower % grid
1349 mod = upper % grid
1350 if mod != 0:
1351 upper = upper - mod + grid
1352 return lower, upper
1353 elif type(spec) == type(()):
1354 lower, upper = spec
1355 if lower <= upper:
1356 return lower, upper
1357 else:
1358 return upper, lower
1359 else:
1360 raise ValueError, str(spec) + ': illegal axis specification'
1361
1362 def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
1363
1364 penWidth= self.printerScale # increases thickness for printing only
1365 dc.SetPen(wx.Pen(self._gridColour, penWidth))
1366
1367 # set length of tick marks--long ones make grid
1368 if self._gridEnabled:
1369 x,y,width,height= self._point2ClientCoord(p1,p2)
1370 if self._gridEnabled == 'Horizontal':
1371 yTickLength= width/2.0 +1
1372 xTickLength= 3 * self.printerScale
1373 elif self._gridEnabled == 'Vertical':
1374 yTickLength= 3 * self.printerScale
1375 xTickLength= height/2.0 +1
1376 else:
1377 yTickLength= width/2.0 +1
1378 xTickLength= height/2.0 +1
1379 else:
1380 yTickLength= 3 * self.printerScale # lengthens lines for printing
1381 xTickLength= 3 * self.printerScale
1382
1383 if self._xSpec is not 'none':
1384 lower, upper = p1[0],p2[0]
1385 text = 1
1386 for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths
1387 a1 = scale*_Numeric.array([lower, y])+shift
1388 a2 = scale*_Numeric.array([upper, y])+shift
1389 dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line
1390 for x, label in xticks:
1391 pt = scale*_Numeric.array([x, y])+shift
1392 dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units
1393 if text:
1394 dc.DrawText(label,pt[0],pt[1])
1395 text = 0 # axis values not drawn on top side
1396
1397 if self._ySpec is not 'none':
1398 lower, upper = p1[1],p2[1]
1399 text = 1
1400 h = dc.GetCharHeight()
1401 for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]:
1402 a1 = scale*_Numeric.array([x, lower])+shift
1403 a2 = scale*_Numeric.array([x, upper])+shift
1404 dc.DrawLine(a1[0],a1[1],a2[0],a2[1])
1405 for y, label in yticks:
1406 pt = scale*_Numeric.array([x, y])+shift
1407 dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1])
1408 if text:
1409 dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0],
1410 pt[1]-0.5*h)
1411 text = 0 # axis values not drawn on right side
1412
1413 def _ticks(self, lower, upper):
1414 ideal = (upper-lower)/7.
1415 log = _Numeric.log10(ideal)
1416 power = _Numeric.floor(log)
1417 fraction = log-power
1418 factor = 1.
1419 error = fraction
1420 for f, lf in self._multiples:
1421 e = _Numeric.fabs(fraction-lf)
1422 if e < error:
1423 error = e
1424 factor = f
1425 grid = factor * 10.**power
1426 if power > 4 or power < -4:
1427 format = '%+7.1e'
1428 elif power >= 0:
1429 digits = max(1, int(power))
1430 format = '%' + `digits`+'.0f'
1431 else:
1432 digits = -int(power)
1433 format = '%'+`digits+2`+'.'+`digits`+'f'
1434 ticks = []
1435 t = -grid*_Numeric.floor(-lower/grid)
1436 while t <= upper:
1437 ticks.append( (t, format % (t,)) )
1438 t = t + grid
1439 return ticks
1440
1441 _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))]
1442
1443
1444 def _adjustScrollbars(self):
1445 if self._sb_ignore:
1446 self._sb_ignore = False
1447 return
1448
1449 self._adjustingSB = True
1450 needScrollbars = False
1451
1452 # horizontal scrollbar
1453 r_current = self.GetXCurrentRange()
1454 r_max = list(self.GetXMaxRange())
1455 sbfullrange = float(self.sb_hor.GetRange())
1456
1457 r_max[0] = min(r_max[0],r_current[0])
1458 r_max[1] = max(r_max[1],r_current[1])
1459
1460 self._sb_xfullrange = r_max
1461
1462 unit = (r_max[1]-r_max[0])/float(self.sb_hor.GetRange())
1463 pos = int((r_current[0]-r_max[0])/unit)
1464
1465 if pos >= 0:
1466 pagesize = int((r_current[1]-r_current[0])/unit)
1467
1468 self.sb_hor.SetScrollbar(pos, pagesize, sbfullrange, pagesize)
1469 self._sb_xunit = unit
1470 needScrollbars = needScrollbars or (pagesize != sbfullrange)
1471 else:
1472 self.sb_hor.SetScrollbar(0, 1000, 1000, 1000)
1473
1474 # vertical scrollbar
1475 r_current = self.GetYCurrentRange()
1476 r_max = list(self.GetYMaxRange())
1477 sbfullrange = float(self.sb_vert.GetRange())
1478
1479 r_max[0] = min(r_max[0],r_current[0])
1480 r_max[1] = max(r_max[1],r_current[1])
1481
1482 self._sb_yfullrange = r_max
1483
1484 unit = (r_max[1]-r_max[0])/sbfullrange
1485 pos = int((r_current[0]-r_max[0])/unit)
1486
1487 if pos >= 0:
1488 pagesize = int((r_current[1]-r_current[0])/unit)
1489 pos = (sbfullrange-1-pos-pagesize)
1490 self.sb_vert.SetScrollbar(pos, pagesize, sbfullrange, pagesize)
1491 self._sb_yunit = unit
1492 needScrollbars = needScrollbars or (pagesize != sbfullrange)
1493 else:
1494 self.sb_vert.SetScrollbar(0, 1000, 1000, 1000)
1495
1496 self.SetShowScrollbars(needScrollbars)
1497 self._adjustingSB = False
1498
1499 #-------------------------------------------------------------------------------
1500 # Used to layout the printer page
1501
1502 class PlotPrintout(wx.Printout):
1503 """Controls how the plot is made in printing and previewing"""
1504 # Do not change method names in this class,
1505 # we have to override wx.Printout methods here!
1506 def __init__(self, graph):
1507 """graph is instance of plotCanvas to be printed or previewed"""
1508 wx.Printout.__init__(self)
1509 self.graph = graph
1510
1511 def HasPage(self, page):
1512 if page == 1:
1513 return True
1514 else:
1515 return False
1516
1517 def GetPageInfo(self):
1518 return (1, 1, 1, 1) # disable page numbers
1519
1520 def OnPrintPage(self, page):
1521 dc = self.GetDC() # allows using floats for certain functions
1522 ## print "PPI Printer",self.GetPPIPrinter()
1523 ## print "PPI Screen", self.GetPPIScreen()
1524 ## print "DC GetSize", dc.GetSize()
1525 ## print "GetPageSizePixels", self.GetPageSizePixels()
1526 # Note PPIScreen does not give the correct number
1527 # Calulate everything for printer and then scale for preview
1528 PPIPrinter= self.GetPPIPrinter() # printer dots/inch (w,h)
1529 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1530 dcSize= dc.GetSize() # DC size
1531 pageSize= self.GetPageSizePixels() # page size in terms of pixcels
1532 clientDcSize= self.graph.GetClientSize()
1533
1534 # find what the margins are (mm)
1535 margLeftSize,margTopSize= self.graph.pageSetupData.GetMarginTopLeft()
1536 margRightSize, margBottomSize= self.graph.pageSetupData.GetMarginBottomRight()
1537
1538 # calculate offset and scale for dc
1539 pixLeft= margLeftSize*PPIPrinter[0]/25.4 # mm*(dots/in)/(mm/in)
1540 pixRight= margRightSize*PPIPrinter[0]/25.4
1541 pixTop= margTopSize*PPIPrinter[1]/25.4
1542 pixBottom= margBottomSize*PPIPrinter[1]/25.4
1543
1544 plotAreaW= pageSize[0]-(pixLeft+pixRight)
1545 plotAreaH= pageSize[1]-(pixTop+pixBottom)
1546
1547 # ratio offset and scale to screen size if preview
1548 if self.IsPreview():
1549 ratioW= float(dcSize[0])/pageSize[0]
1550 ratioH= float(dcSize[1])/pageSize[1]
1551 pixLeft *= ratioW
1552 pixTop *= ratioH
1553 plotAreaW *= ratioW
1554 plotAreaH *= ratioH
1555
1556 # rescale plot to page or preview plot area
1557 self.graph._setSize(plotAreaW,plotAreaH)
1558
1559 # Set offset and scale
1560 dc.SetDeviceOrigin(pixLeft,pixTop)
1561
1562 # Thicken up pens and increase marker size for printing
1563 ratioW= float(plotAreaW)/clientDcSize[0]
1564 ratioH= float(plotAreaH)/clientDcSize[1]
1565 aveScale= (ratioW+ratioH)/2
1566 self.graph._setPrinterScale(aveScale) # tickens up pens for printing
1567
1568 self.graph._printDraw(dc)
1569 # rescale back to original
1570 self.graph._setSize()
1571 self.graph._setPrinterScale(1)
1572 self.graph.Redraw() #to get point label scale and shift correct
1573
1574 return True
1575
1576
1577 #----------------------------------------------------------------------
1578 from wx import ImageFromStream, BitmapFromImage
1579 import cStringIO, zlib
1580
1581
1582 def getMagPlusData():
1583 return zlib.decompress(
1584 'x\xda\x01*\x01\xd5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1585 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1586 \x08\x08|\x08d\x88\x00\x00\x00\xe1IDATx\x9c\xb5U\xd1\x0e\xc4 \x08\xa3n\xff\
1587 \xff\xc5\xdb\xb8\xa7\xee<\x04\x86gFb\xb2\x88\xb6\x14\x90\x01m\x937m\x8f\x1c\
1588 \xd7yh\xe4k\xdb\x8e*\x01<\x05\x04\x07F\x1cU\x9d"\x19\x14\\\xe7\xa1\x1e\xf07"\
1589 \x90H+$?\x04\x16\x9c\xd1z\x04\x00J$m\x06\xdc\xee\x03Hku\x13\xd8C\x16\x84+"O\
1590 \x1b\xa2\x07\xca"\xb7\xc6sY\xbdD\x926\xf5.\xce\x06!\xd2)x\xcb^\'\x08S\xe4\
1591 \xe5x&5\xb4[A\xb5h\xb4j=\x9a\xc8\xf8\xecm\xd4\\\x9e\xdf\xbb?\x10\xf0P\x06\
1592 \x12\xed?=\xb6a\xd8=\xcd\xa2\xc8T\xd5U2t\x11\x95d\xa3"\x9aQ\x9e\x12\xb7M\x19\
1593 I\x9f\xff\x1e\xd8\xa63#q\xff\x07U\x8b\xd2\xd9\xa7k\xe9\xa1U\x94,\xbf\xe4\x88\
1594 \xe4\xf6\xaf\x12x$}\x8a\xc2Q\xf1\'\x89\xf2\x9b\xfbKE\xae\xd8\x07+\xd2\xa7c\
1595 \xdf\x0e\xc3D\x00\x00\x00\x00IEND\xaeB`\x82\xe2ovy' )
1596
1597 def getMagPlusBitmap():
1598 return BitmapFromImage(getMagPlusImage())
1599
1600 def getMagPlusImage():
1601 stream = cStringIO.StringIO(getMagPlusData())
1602 return ImageFromStream(stream)
1603
1604 #----------------------------------------------------------------------
1605 def getGrabHandData():
1606 return zlib.decompress(
1607 'x\xda\x01Z\x01\xa5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1608 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1609 \x08\x08|\x08d\x88\x00\x00\x01\x11IDATx\x9c\xb5U\xd1\x12\x830\x08Kh\xff\xff\
1610 \x8b7\xb3\x97\xd1C\xa4Zw\x93;\x1fJ1\t\x98VJ\x92\xb5N<\x14\x04 I\x00\x80H\xb4\
1611 \xbd_\x8a9_{\\\x89\xf2z\x02\x18/J\x82\xb5\xce\xed\xfd\x12\xc9\x91\x03\x00_\
1612 \xc7\xda\x8al\x00{\xfdW\xfex\xf2zeO\x92h\xed\x80\x05@\xa45D\xc5\xb3\x98u\x12\
1613 \xf7\xab.\xa9\xd0k\x1eK\x95\xbb\x1a]&0\x92\xf0\'\xc6]gI\xda\tsr\xab\x8aI\x1e\
1614 \\\xe3\xa4\x0e\xb4*`7"\x07\x8f\xaa"x\x05\xe0\xdfo6B\xf3\x17\xe3\x98r\xf1\xaf\
1615 \x07\xd1Z\'%\x95\x0erW\xac\x8c\xe3\xe0\xfd\xd8AN\xae\xb8\xa3R\x9as>\x11\x8bl\
1616 yD\xab\x1f\xf3\xec\x1cY\x06\x89$\xbf\x80\xfb\x14\\dw\x90x\x12\xa3+\xeeD\x16%\
1617 I\xe3\x1c\xb8\xc7c\'\xd5Y8S\x9f\xc3Zg\xcf\x89\xe8\xaao\'\xbbk{U\xfd\xc0\xacX\
1618 \xab\xbb\xe8\xae\xfa)AEr\x15g\x86(\t\xfe\x19\xa4\xb5\xe9f\xfem\xde\xdd\xbf$\
1619 \xf8G<>\xa2\xc7\t>\tE\xfc\x8a\xf6\x8dqc\x00\x00\x00\x00IEND\xaeB`\x82\xdb\
1620 \xd0\x8f\n' )
1621
1622 def getGrabHandBitmap():
1623 return BitmapFromImage(getGrabHandImage())
1624
1625 def getGrabHandImage():
1626 stream = cStringIO.StringIO(getGrabHandData())
1627 return ImageFromStream(stream)
1628
1629 #----------------------------------------------------------------------
1630 def getHandData():
1631 return zlib.decompress(
1632 'x\xda\x01Y\x01\xa6\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1633 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1634 \x08\x08|\x08d\x88\x00\x00\x01\x10IDATx\x9c\xad\x96\xe1\x02\xc2 \x08\x849\
1635 \xf5\xfd\x9fx\xdb\xf5\'\x8c!\xa8\xab\xee\x975\xe5\x83\x0b\\@\xa9\xb2\xab\xeb\
1636 <\xa8\xebR\x1bv\xce\xb4\'\xc1\x81OL\x92\xdc\x81\x0c\x00\x1b\x88\xa4\x94\xda\
1637 \xe0\x83\x8b\x88\x00\x10\x92\xcb\x8a\xca,K\x1fT\xa1\x1e\x04\xe0f_\n\x88\x02\
1638 \xf1:\xc3\x83>\x81\x0c\x92\x02v\xe5+\xba\xce\x83\xb7f\xb8\xd1\x9c\x8fz8\xb2*\
1639 \x93\xb7l\xa8\xe0\x9b\xa06\xb8]_\xe7\xc1\x01\x10U\xe1m\x98\xc9\xefm"ck\xea\
1640 \x1a\x80\xa0Th\xb9\xfd\x877{V*Qk\xda,\xb4\x8b\xf4;[\xa1\xcf6\xaa4\x9cd\x85X\
1641 \xb0\r\\j\x83\x9dd\x92\xc3 \xf6\xbd\xab\x0c2\x05\xc0p\x9a\xa7]\xf4\x14\x18]3\
1642 7\x80}h?\xff\xa2\xa2\xe5e\x90\xact\xaf\xe8B\x14y[4\x83|\x13\xdc\x9e\xeb\x16e\
1643 \x90\xa7\xf2I\rw\x91\x87d\xd7p\x96\xbd\xd70\x07\xda\xe3v\x9a\xf5\xc5\xb2\xb2\
1644 +\xb24\xbc\xaew\xedZe\x9f\x02"\xc8J\xdb\x83\xf6oa\xf5\xb7\xa5\xbf8\x12\xffW\
1645 \xcf_\xbd;\xe4\x8c\x03\x10\xdb^\x00\x00\x00\x00IEND\xaeB`\x82\xd1>\x97B' )
1646
1647 def getHandBitmap():
1648 return BitmapFromImage(getHandImage())
1649
1650 def getHandImage():
1651 stream = cStringIO.StringIO(getHandData())
1652 return ImageFromStream(stream)
1653
1654
1655
1656 #---------------------------------------------------------------------------
1657 # if running standalone...
1658 #
1659 # ...a sample implementation using the above
1660 #
1661
1662 def _draw1Objects():
1663 # 100 points sin function, plotted as green circles
1664 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
1665 data1.shape = (100, 2)
1666 data1[:,1] = _Numeric.sin(data1[:,0])
1667 markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1)
1668
1669 # 50 points cos function, plotted as red line
1670 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
1671 data1.shape = (50,2)
1672 data1[:,1] = _Numeric.cos(data1[:,0])
1673 lines = PolyLine(data1, legend= 'Red Line', colour='red')
1674
1675 # A few more points...
1676 pi = _Numeric.pi
1677 markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1678 (3.*pi/4., -1)], legend='Cross Legend', colour='blue',
1679 marker='cross')
1680
1681 return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis")
1682
1683 def _draw2Objects():
1684 # 100 points sin function, plotted as green dots
1685 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
1686 data1.shape = (100, 2)
1687 data1[:,1] = _Numeric.sin(data1[:,0])
1688 line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT)
1689
1690 # 50 points cos function, plotted as red dot-dash
1691 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
1692 data1.shape = (50,2)
1693 data1[:,1] = _Numeric.cos(data1[:,0])
1694 line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH)
1695
1696 # A few more points...
1697 pi = _Numeric.pi
1698 markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1699 (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6,
1700 fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH,
1701 marker='square')
1702
1703 return PlotGraphics([markers1, line1, line2], "Big Markers with Different Line Styles")
1704
1705 def _draw3Objects():
1706 markerList= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1707 'cross', 'plus', 'circle']
1708 m=[]
1709 for i in range(len(markerList)):
1710 m.append(PolyMarker([(2*i+.5,i+.5)], legend=markerList[i], colour='blue',
1711 marker=markerList[i]))
1712 return PlotGraphics(m, "Selection of Markers", "Minimal Axis", "No Axis")
1713
1714 def _draw4Objects():
1715 # 25,000 point line
1716 data1 = _Numeric.arange(5e5,1e6,10)
1717 data1.shape = (25000, 2)
1718 line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5)
1719
1720 # A few more points...
1721 markers2 = PolyMarker(data1, legend='Square', colour='blue',
1722 marker='square')
1723 return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "")
1724
1725 def _draw5Objects():
1726 # Empty graph with axis defined but no points/lines
1727 points=[]
1728 line1 = PolyLine(points, legend='Wide Line', colour='green', width=5)
1729 return PlotGraphics([line1], "Empty Plot With Just Axes", "Value X", "Value Y")
1730
1731 def _draw6Objects():
1732 # Bar graph
1733 points1=[(1,0), (1,10)]
1734 line1 = PolyLine(points1, colour='green', legend='Feb.', width=10)
1735 points1g=[(2,0), (2,4)]
1736 line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10)
1737 points1b=[(3,0), (3,6)]
1738 line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10)
1739
1740 points2=[(4,0), (4,12)]
1741 line2 = PolyLine(points2, colour='Yellow', legend='May', width=10)
1742 points2g=[(5,0), (5,8)]
1743 line2g = PolyLine(points2g, colour='orange', legend='June', width=10)
1744 points2b=[(6,0), (6,4)]
1745 line2b = PolyLine(points2b, colour='brown', legend='July', width=10)
1746
1747 return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
1748 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1749
1750
1751 class TestFrame(wx.Frame):
1752 def __init__(self, parent, id, title):
1753 wx.Frame.__init__(self, parent, id, title,
1754 wx.DefaultPosition, (600, 400))
1755
1756 # Now Create the menu bar and items
1757 self.mainmenu = wx.MenuBar()
1758
1759 menu = wx.Menu()
1760 menu.Append(200, 'Page Setup...', 'Setup the printer page')
1761 self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200)
1762
1763 menu.Append(201, 'Print Preview...', 'Show the current plot on page')
1764 self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201)
1765
1766 menu.Append(202, 'Print...', 'Print the current plot')
1767 self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202)
1768
1769 menu.Append(203, 'Save Plot...', 'Save current plot')
1770 self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203)
1771
1772 menu.Append(205, 'E&xit', 'Enough of this already!')
1773 self.Bind(wx.EVT_MENU, self.OnFileExit, id=205)
1774 self.mainmenu.Append(menu, '&File')
1775
1776 menu = wx.Menu()
1777 menu.Append(206, 'Draw1', 'Draw plots1')
1778 self.Bind(wx.EVT_MENU,self.OnPlotDraw1, id=206)
1779 menu.Append(207, 'Draw2', 'Draw plots2')
1780 self.Bind(wx.EVT_MENU,self.OnPlotDraw2, id=207)
1781 menu.Append(208, 'Draw3', 'Draw plots3')
1782 self.Bind(wx.EVT_MENU,self.OnPlotDraw3, id=208)
1783 menu.Append(209, 'Draw4', 'Draw plots4')
1784 self.Bind(wx.EVT_MENU,self.OnPlotDraw4, id=209)
1785 menu.Append(210, 'Draw5', 'Draw plots5')
1786 self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210)
1787 menu.Append(260, 'Draw6', 'Draw plots6')
1788 self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260)
1789
1790
1791 menu.Append(211, '&Redraw', 'Redraw plots')
1792 self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211)
1793 menu.Append(212, '&Clear', 'Clear canvas')
1794 self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212)
1795 menu.Append(213, '&Scale', 'Scale canvas')
1796 self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213)
1797 menu.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK)
1798 self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214)
1799 menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK)
1800 self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215)
1801 menu.Append(217, 'Enable &Drag', 'Activates dragging mode', kind=wx.ITEM_CHECK)
1802 self.Bind(wx.EVT_MENU,self.OnEnableDrag, id=217)
1803 menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK)
1804 self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220)
1805 menu.Append(222, 'Enable &Point Label', 'Show Closest Point', kind=wx.ITEM_CHECK)
1806 self.Bind(wx.EVT_MENU,self.OnEnablePointLabel, id=222)
1807
1808 menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1809 self.Bind(wx.EVT_MENU,self.OnScrUp, id=225)
1810 menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1811 self.Bind(wx.EVT_MENU,self.OnScrRt, id=230)
1812 menu.Append(235, '&Plot Reset', 'Reset to original plot')
1813 self.Bind(wx.EVT_MENU,self.OnReset, id=235)
1814
1815 self.mainmenu.Append(menu, '&Plot')
1816
1817 menu = wx.Menu()
1818 menu.Append(300, '&About', 'About this thing...')
1819 self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300)
1820 self.mainmenu.Append(menu, '&Help')
1821
1822 self.SetMenuBar(self.mainmenu)
1823
1824 # A status bar to tell people what's happening
1825 self.CreateStatusBar(1)
1826
1827 self.client = PlotCanvas(self)
1828 #define the function for drawing pointLabels
1829 self.client.SetPointLabelFunc(self.DrawPointLabel)
1830 # Create mouse event for showing cursor coords in status bar
1831 self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
1832 # Show closest point when enabled
1833 self.client.Bind(wx.EVT_MOTION, self.OnMotion)
1834
1835 self.Show()
1836
1837
1838 def DrawPointLabel(self, dc, mDataDict):
1839 """This is the fuction that defines how the pointLabels are plotted
1840 dc - DC that will be passed
1841 mDataDict - Dictionary of data that you want to use for the pointLabel
1842
1843 As an example I have decided I want a box at the curve point
1844 with some text information about the curve plotted below.
1845 Any wxDC method can be used.
1846 """
1847 # ----------
1848 dc.SetPen(wx.Pen(wx.BLACK))
1849 dc.SetBrush(wx.Brush( wx.BLACK, wx.SOLID ) )
1850
1851 sx, sy = mDataDict["scaledXY"] #scaled x,y of closest point
1852 dc.DrawRectangle( sx-5,sy-5, 10, 10) #10by10 square centered on point
1853 px,py = mDataDict["pointXY"]
1854 cNum = mDataDict["curveNum"]
1855 pntIn = mDataDict["pIndex"]
1856 legend = mDataDict["legend"]
1857 #make a string to display
1858 s = "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum, legend, px, py, pntIn)
1859 dc.DrawText(s, sx , sy+1)
1860 # -----------
1861
1862 def OnMouseLeftDown(self,event):
1863 s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event)
1864 self.SetStatusText(s)
1865 event.Skip() #allows plotCanvas OnMouseLeftDown to be called
1866
1867 def OnMotion(self, event):
1868 #show closest point (when enbled)
1869 if self.client.GetEnablePointLabel() == True:
1870 #make up dict with info for the pointLabel
1871 #I've decided to mark the closest point on the closest curve
1872 dlst= self.client.GetClosetPoint( self.client.GetXY(event), pointScaled= True)
1873 if dlst != []: #returns [] if none
1874 curveNum, legend, pIndex, pointXY, scaledXY, distance = dlst
1875 #make up dictionary to pass to my user function (see DrawPointLabel)
1876 mDataDict= {"curveNum":curveNum, "legend":legend, "pIndex":pIndex,\
1877 "pointXY":pointXY, "scaledXY":scaledXY}
1878 #pass dict to update the pointLabel
1879 self.client.UpdatePointLabel(mDataDict)
1880 event.Skip() #go to next handler
1881
1882 def OnFilePageSetup(self, event):
1883 self.client.PageSetup()
1884
1885 def OnFilePrintPreview(self, event):
1886 self.client.PrintPreview()
1887
1888 def OnFilePrint(self, event):
1889 self.client.Printout()
1890
1891 def OnSaveFile(self, event):
1892 self.client.SaveFile()
1893
1894 def OnFileExit(self, event):
1895 self.Close()
1896
1897 def OnPlotDraw1(self, event):
1898 self.resetDefaults()
1899 self.client.Draw(_draw1Objects())
1900
1901 def OnPlotDraw2(self, event):
1902 self.resetDefaults()
1903 self.client.Draw(_draw2Objects())
1904
1905 def OnPlotDraw3(self, event):
1906 self.resetDefaults()
1907 self.client.SetFont(wx.Font(10,wx.SCRIPT,wx.NORMAL,wx.NORMAL))
1908 self.client.SetFontSizeAxis(20)
1909 self.client.SetFontSizeLegend(12)
1910 self.client.SetXSpec('min')
1911 self.client.SetYSpec('none')
1912 self.client.Draw(_draw3Objects())
1913
1914 def OnPlotDraw4(self, event):
1915 self.resetDefaults()
1916 drawObj= _draw4Objects()
1917 self.client.Draw(drawObj)
1918 ## # profile
1919 ## start = _time.clock()
1920 ## for x in range(10):
1921 ## self.client.Draw(drawObj)
1922 ## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start)
1923 ## # profile end
1924
1925 def OnPlotDraw5(self, event):
1926 # Empty plot with just axes
1927 self.resetDefaults()
1928 drawObj= _draw5Objects()
1929 # make the axis X= (0,5), Y=(0,10)
1930 # (default with None is X= (-1,1), Y= (-1,1))
1931 self.client.Draw(drawObj, xAxis= (0,5), yAxis= (0,10))
1932
1933 def OnPlotDraw6(self, event):
1934 #Bar Graph Example
1935 self.resetDefaults()
1936 #self.client.SetEnableLegend(True) #turn on Legend
1937 #self.client.SetEnableGrid(True) #turn on Grid
1938 self.client.SetXSpec('none') #turns off x-axis scale
1939 self.client.SetYSpec('auto')
1940 self.client.Draw(_draw6Objects(), xAxis= (0,7))
1941
1942 def OnPlotRedraw(self,event):
1943 self.client.Redraw()
1944
1945 def OnPlotClear(self,event):
1946 self.client.Clear()
1947
1948 def OnPlotScale(self, event):
1949 if self.client.last_draw != None:
1950 graphics, xAxis, yAxis= self.client.last_draw
1951 self.client.Draw(graphics,(1,3.05),(0,1))
1952
1953 def OnEnableZoom(self, event):
1954 self.client.SetEnableZoom(event.IsChecked())
1955 self.mainmenu.Check(217, not event.IsChecked())
1956
1957 def OnEnableGrid(self, event):
1958 self.client.SetEnableGrid(event.IsChecked())
1959
1960 def OnEnableDrag(self, event):
1961 self.client.SetEnableDrag(event.IsChecked())
1962 self.mainmenu.Check(214, not event.IsChecked())
1963
1964 def OnEnableLegend(self, event):
1965 self.client.SetEnableLegend(event.IsChecked())
1966
1967 def OnEnablePointLabel(self, event):
1968 self.client.SetEnablePointLabel(event.IsChecked())
1969
1970 def OnScrUp(self, event):
1971 self.client.ScrollUp(1)
1972
1973 def OnScrRt(self,event):
1974 self.client.ScrollRight(2)
1975
1976 def OnReset(self,event):
1977 self.client.Reset()
1978
1979 def OnHelpAbout(self, event):
1980 from wx.lib.dialogs import ScrolledMessageDialog
1981 about = ScrolledMessageDialog(self, __doc__, "About...")
1982 about.ShowModal()
1983
1984 def resetDefaults(self):
1985 """Just to reset the fonts back to the PlotCanvas defaults"""
1986 self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
1987 self.client.SetFontSizeAxis(10)
1988 self.client.SetFontSizeLegend(7)
1989 self.client.SetXSpec('auto')
1990 self.client.SetYSpec('auto')
1991
1992
1993 def __test():
1994
1995 class MyApp(wx.App):
1996 def OnInit(self):
1997 wx.InitAllImageHandlers()
1998 frame = TestFrame(None, -1, "PlotCanvas")
1999 #frame.Show(True)
2000 self.SetTopWindow(frame)
2001 return True
2002
2003
2004 app = MyApp(0)
2005 app.MainLoop()
2006
2007 if __name__ == '__main__':
2008 __test()