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