]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/plot.py
Using buffered drawing won't let the themed background show through,
[wxWidgets.git] / wxPython / wx / lib / plot.py
CommitLineData
9d6685e2
RD
1#-----------------------------------------------------------------------------
2# Name: wx.lib.plot.py
00b9c867 3# Purpose: Line, Bar and Scatter Graphs
9d6685e2
RD
4#
5# Author: Gordon Williams
6#
7# Created: 2003/11/03
8# RCS-ID: $Id$
9# Copyright: (c) 2002
10# Licence: Use as you wish.
11#-----------------------------------------------------------------------------
12# 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net)
13#
14# o 2.5 compatability update.
15# o Renamed to plot.py in the wx.lib directory.
16# o Reworked test frame to work with wx demo framework. This saves a bit
17# of tedious cut and paste, and the test app is excellent.
18#
33785d9f
RD
19# 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net)
20#
21# o wxScrolledMessageDialog -> ScrolledMessageDialog
22#
00b9c867
RD
23# Oct 6, 2004 Gordon Williams (g_will@cyberus.ca)
24# - Added bar graph demo
25# - Modified line end shape from round to square.
26# - Removed FloatDCWrapper for conversion to ints and ints in arguments
27#
b31cbeb9
RD
28# Oct 15, 2004 Gordon Williams (g_will@cyberus.ca)
29# - Imported modules given leading underscore to name.
30# - Added Cursor Line Tracking and User Point Labels.
31# - Demo for Cursor Line Tracking and Point Labels.
32# - Size of plot preview frame adjusted to show page better.
33# - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas.
34# - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve)
35# can be in either user coords or screen coords.
36#
37#
9d6685e2
RD
38
39"""
40This is a simple light weight plotting module that can be used with
41Boa or easily integrated into your own wxPython application. The
42emphasis is on small size and fast plotting for large data sets. It
43has a reasonable number of features to do line and scatter graphs
00b9c867
RD
44easily as well as simple bar graphs. It is not as sophisticated or
45as powerful as SciPy Plt or Chaco. Both of these are great packages
46but consume huge amounts of computer resources for simple plots.
47They can be found at http://scipy.com
9d6685e2
RD
48
49This file contains two parts; first the re-usable library stuff, then,
50after a "if __name__=='__main__'" test, a simple frame and a few default
51plots for examples and testing.
52
53Based on wxPlotCanvas
54Written by K.Hinsen, R. Srinivasan;
55Ported to wxPython Harm van der Heijden, feb 1999
56
57Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
58 -More style options
59 -Zooming using mouse "rubber band"
60 -Scroll left, right
61 -Grid(graticule)
62 -Printing, preview, and page set up (margins)
63 -Axis and title labels
64 -Cursor xy axis values
65 -Doc strings and lots of comments
66 -Optimizations for large number of points
67 -Legends
68
69Did a lot of work here to speed markers up. Only a factor of 4
70improvement though. Lines are much faster than markers, especially
71filled markers. Stay away from circles and triangles unless you
72only have a few thousand points.
73
74Times for 25,000 points
75Line - 0.078 sec
76Markers
77Square - 0.22 sec
78dot - 0.10
79circle - 0.87
80cross,plus - 0.28
81triangle, triangle_down - 0.90
82
83Thanks to Chris Barker for getting this version working on Linux.
84
85Zooming controls with mouse (when enabled):
86 Left mouse drag - Zoom box.
87 Left mouse double click - reset zoom.
88 Right mouse click - zoom out centred on click location.
89"""
90
b31cbeb9
RD
91import string as _string
92import time as _time
9d6685e2
RD
93import wx
94
71caeaf3 95# Needs Numeric or numarray or NumPy
9d6685e2 96try:
71caeaf3 97 import numpy as _Numeric
9d6685e2
RD
98except:
99 try:
b31cbeb9 100 import numarray as _Numeric #if numarray is used it is renamed Numeric
9d6685e2 101 except:
71caeaf3
RD
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
9d6685e2
RD
112
113
114
115#
116# Plotting classes...
117#
118class PolyPoints:
119 """Base Class for lines and markers
120 - All methods are private.
121 """
122
123 def __init__(self, points, attr):
b31cbeb9 124 self.points = _Numeric.array(points)
9d6685e2
RD
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
b31cbeb9
RD
139 minXY= _Numeric.array([-1,-1])
140 maxXY= _Numeric.array([ 1, 1])
9d6685e2 141 else:
b31cbeb9
RD
142 minXY= _Numeric.minimum.reduce(self.points)
143 maxXY= _Numeric.maximum.reduce(self.points)
9d6685e2
RD
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
b31cbeb9
RD
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
9d6685e2
RD
181class 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']
b0d45269
RD
207 if not isinstance(colour, wx.Colour):
208 colour = wx.NamedColour(colour)
209 pen = wx.Pen(colour, width, style)
00b9c867
RD
210 pen.SetCap(wx.CAP_BUTT)
211 dc.SetPen(pen)
9d6685e2
RD
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
224class 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
48023e15 245 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
9d6685e2
RD
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
b0d45269
RD
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))
9d6685e2 276 if fillcolour:
b0d45269 277 dc.SetBrush(wx.Brush(fillcolour,fillstyle))
9d6685e2 278 else:
b0d45269 279 dc.SetBrush(wx.Brush(colour, fillstyle))
9d6685e2
RD
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
b31cbeb9 297 rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
9d6685e2 298 rect[:,0:2]= coords-[fact,fact]
b31cbeb9 299 dc.DrawEllipseList(rect.astype(_Numeric.Int32))
9d6685e2
RD
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
b31cbeb9 307 rect= _Numeric.zeros((len(coords),4),_Numeric.Float)+[0.0,0.0,wh,wh]
9d6685e2 308 rect[:,0:2]= coords-[fact,fact]
b31cbeb9 309 dc.DrawRectangleList(rect.astype(_Numeric.Int32))
9d6685e2
RD
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)]
b31cbeb9 313 poly= _Numeric.repeat(coords,3)
9d6685e2
RD
314 poly.shape= (len(coords),3,2)
315 poly += shape
b31cbeb9 316 dc.DrawPolygonList(poly.astype(_Numeric.Int32))
9d6685e2
RD
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)]
b31cbeb9 320 poly= _Numeric.repeat(coords,3)
9d6685e2
RD
321 poly.shape= (len(coords),3,2)
322 poly += shape
b31cbeb9 323 dc.DrawPolygonList(poly.astype(_Numeric.Int32))
9d6685e2
RD
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]]:
b31cbeb9
RD
328 lines= _Numeric.concatenate((coords,coords),axis=1)+f
329 dc.DrawLineList(lines.astype(_Numeric.Int32))
9d6685e2
RD
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]]:
b31cbeb9
RD
334 lines= _Numeric.concatenate((coords,coords),axis=1)+f
335 dc.DrawLineList(lines.astype(_Numeric.Int32))
9d6685e2
RD
336
337class 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()
b31cbeb9
RD
360 p1 = _Numeric.minimum(p1, p1o)
361 p2 = _Numeric.maximum(p2, p2o)
9d6685e2
RD
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:
b31cbeb9 398 #t=_time.clock() # profile info
9d6685e2 399 o.draw(dc, self.printerScale)
b31cbeb9 400 #dt= _time.clock()-t
9d6685e2
RD
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)
b31cbeb9 408 symExt = _Numeric.maximum(symExt, oSymExt)
9d6685e2
RD
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
71caeaf3
RD
428class 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
9d6685e2
RD
432 of data with zoom, labels, and automatic axis scaling."""
433
71caeaf3
RD
434 def __init__(self, parent):
435 """Constructs a panel, which can be a child of a frame or
9d6685e2
RD
436 any other non-control window"""
437
71caeaf3
RD
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()
9d6685e2
RD
462
463 self.SetBackgroundColour("white")
464
9d6685e2 465 # Create some mouse events for zooming
71caeaf3
RD
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)
9d6685e2
RD
478
479 # set curser as cross-hairs
71caeaf3
RD
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
9d6685e2
RD
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
71caeaf3
RD
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
9d6685e2
RD
507 # Zooming variables
508 self._zoomInFactor = 0.5
509 self._zoomOutFactor = 2
b31cbeb9
RD
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
9d6685e2
RD
512 self._zoomEnabled= False
513 self._hasDragged= False
514
515 # Drawing Variables
48023e15 516 self.last_draw = None
9d6685e2
RD
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
b31cbeb9
RD
530 # pointLabels
531 self._pointLabelEnabled= False
532 self.last_PointLabel= None
533 self._pointLabelFunc= None
71caeaf3 534 self.canvas.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
b31cbeb9 535
71caeaf3
RD
536 self.canvas.Bind(wx.EVT_PAINT, self.OnPaint)
537 self.canvas.Bind(wx.EVT_SIZE, self.OnSize)
9d6685e2
RD
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.
71caeaf3 541 self.OnSize(None) # sets the initial size based on client size
9d6685e2 542
b0d45269
RD
543 self._gridColour = wx.NamedColour('black')
544
71caeaf3
RD
545 def SetCursor(self, cursor):
546 self.canvas.SetCursor(cursor)
547
b0d45269
RD
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
9d6685e2
RD
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 """
b31cbeb9 570 if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
9d6685e2
RD
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
b31cbeb9 582 if _string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
9d6685e2
RD
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
b31cbeb9 599 fType = _string.lower(fileName[-3:])
9d6685e2
RD
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())
a721e6ec 626 self.print_data=wx.PrintData(data.GetPrintData()) # updates print_data
9d6685e2
RD
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)
467f4d3a 634 pdd = wx.PrintDialogData(self.print_data)
9d6685e2
RD
635 printer = wx.Printer(pdd)
636 out = PlotPrintout(self)
637 print_ok = printer.Print(self.parent, out)
638 if print_ok:
a721e6ec 639 self.print_data = wx.PrintData(printer.GetPrintDialogData().GetPrintData())
9d6685e2
RD
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()
b31cbeb9 651 self.preview.SetZoom(40)
9d6685e2
RD
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())
b31cbeb9 659 frame.SetSize((600,550))
9d6685e2
RD
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
71caeaf3
RD
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
9d6685e2
RD
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"
71caeaf3
RD
720 if value:
721 if self.GetEnableDrag():
722 self.SetEnableDrag(False)
723 self.SetCursor(self.MagCursor)
724 else:
725 self.SetCursor(wx.CROSS_CURSOR)
9d6685e2
RD
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."""
2b9590d6
RD
734 if value not in [True,False,'Horizontal','Vertical']:
735 raise TypeError, "Value should be True, False, Horizontal or Vertical"
9d6685e2
RD
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
b31cbeb9
RD
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
9d6685e2
RD
776 def Reset(self):
777 """Unzoom the plot."""
b31cbeb9 778 self.last_PointLabel = None #reset pointLabel
48023e15
RD
779 if self.last_draw is not None:
780 self.Draw(self.last_draw[0])
9d6685e2
RD
781
782 def ScrollRight(self, units):
783 """Move view right number of axis units."""
b31cbeb9 784 self.last_PointLabel = None #reset pointLabel
48023e15
RD
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)
9d6685e2
RD
789
790 def ScrollUp(self, units):
791 """Move view up number of axis units."""
b31cbeb9 792 self.last_PointLabel = None #reset pointLabel
2458faeb 793 if self.last_draw is not None:
8bb039c8
RD
794 graphics, xAxis, yAxis= self.last_draw
795 yAxis= (yAxis[0]+units, yAxis[1]+units)
796 self.Draw(graphics,xAxis,yAxis)
797
9d6685e2
RD
798
799 def GetXY(self,event):
800 """Takes a mouse event and returns the XY user axis values."""
b31cbeb9 801 x,y= self.PositionScreenToUser(event.GetPosition())
9d6685e2
RD
802 return x,y
803
b31cbeb9
RD
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
9d6685e2
RD
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"""
48023e15
RD
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
9d6685e2
RD
848
849 def GetYMaxRange(self):
850 """Returns (minY, maxY) y-axis range for displayed graph"""
48023e15
RD
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
9d6685e2
RD
855
856 def GetXCurrentRange(self):
857 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
48023e15 858 return self.last_draw[1]
9d6685e2
RD
859
860 def GetYCurrentRange(self):
861 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
48023e15 862 return self.last_draw[2]
9d6685e2 863
48023e15 864 def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
9d6685e2
RD
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.
48023e15 870 If it's not, the offscreen buffer is used
9d6685e2
RD
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
48023e15
RD
879 if xAxis != None:
880 if xAxis[0] == xAxis[1]:
881 return
882 if yAxis != None:
883 if yAxis[0] == yAxis[1]:
9d6685e2
RD
884 return
885
886 if dc == None:
b31cbeb9 887 # sets new dc and clears it
71caeaf3 888 dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
9d6685e2
RD
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
48023e15
RD
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
9d6685e2
RD
915 # Get ticks and textExtents for axis if required
916 if self._xSpec is not 'none':
48023e15 917 xticks = self._ticks(xAxis[0], xAxis[1])
9d6685e2
RD
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':
48023e15 923 yticks = self._ticks(yAxis[0], yAxis[1])
9d6685e2
RD
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
48023e15 936 legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
9d6685e2
RD
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]
b31cbeb9
RD
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
9d6685e2
RD
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:
48023e15 962 self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt)
9d6685e2
RD
963
964 # allow for scaling and shifting plotted points
b31cbeb9
RD
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))
9d6685e2
RD
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
b31cbeb9 978 #start = _time.clock()
9d6685e2 979 graphics.draw(dc)
b31cbeb9 980 # print "entire graphics drawing took: %f second"%(_time.clock() - start)
9d6685e2
RD
981 # remove the clipping region
982 dc.DestroyClippingRegion()
983 dc.EndDrawing()
71caeaf3 984 self._adjustScrollbars()
9d6685e2
RD
985
986 def Redraw(self, dc= None):
987 """Redraw the existing plot."""
48023e15
RD
988 if self.last_draw is not None:
989 graphics, xAxis, yAxis= self.last_draw
990 self.Draw(graphics,xAxis,yAxis,dc)
9d6685e2
RD
991
992 def Clear(self):
993 """Erase the window."""
b31cbeb9 994 self.last_PointLabel = None #reset pointLabel
71caeaf3 995 dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
9d6685e2 996 dc.Clear()
48023e15 997 self.last_draw = None
9d6685e2
RD
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 """
b31cbeb9 1004 self.last_PointLabel = None #reset maker
48023e15
RD
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)
9d6685e2 1013
b31cbeb9
RD
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 """
48023e15 1024 if self.last_draw == None:
b31cbeb9
RD
1025 #no graph available
1026 return []
48023e15
RD
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
b31cbeb9
RD
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
9d6685e2
RD
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
71caeaf3
RD
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
9d6685e2 1101 def OnMouseLeftDown(self,event):
48023e15 1102 self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event)
71caeaf3
RD
1103 self._screenCoordinates = _Numeric.array(event.GetPosition())
1104 if self._dragEnabled:
1105 self.SetCursor(self.GrabHandCursor)
1106 self.canvas.CaptureMouse()
9d6685e2
RD
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
b31cbeb9
RD
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
48023e15
RD
1117 if self.last_draw != None:
1118 self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
9d6685e2
RD
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) )
71caeaf3
RD
1123 if self._dragEnabled:
1124 self.SetCursor(self.HandCursor)
1125 if self.canvas.HasCapture():
1126 self.canvas.ReleaseMouse()
9d6685e2
RD
1127
1128 def OnMouseDoubleClick(self,event):
1129 if self._zoomEnabled:
71caeaf3
RD
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)
9d6685e2
RD
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
b31cbeb9
RD
1142 if self.last_PointLabel != None:
1143 self._drawPointLabel(self.last_PointLabel) #erase old
1144 self.last_PointLabel = None
71caeaf3 1145 dc = wx.BufferedPaintDC(self.canvas, self._Buffer)
9d6685e2
RD
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
71caeaf3 1150 Size = self.canvas.GetClientSize()
7a9ca9fc
RD
1151 if Size.width <= 0 or Size.height <= 0:
1152 return
1153
9d6685e2
RD
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()
b31cbeb9
RD
1159
1160 self.last_PointLabel = None #reset pointLabel
1161
48023e15 1162 if self.last_draw is None:
ecf0b9f9 1163 self.Clear()
48023e15
RD
1164 else:
1165 graphics, xSpec, ySpec = self.last_draw
1166 self.Draw(graphics,xSpec,ySpec)
9d6685e2 1167
b31cbeb9
RD
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
71caeaf3
RD
1174 def OnScroll(self, evt):
1175 if not self._adjustingSB:
1176 self._sb_ignore = True
1177 sbpos = evt.GetPosition()
9d6685e2 1178
71caeaf3
RD
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
9d6685e2
RD
1189 # Private Methods **************************************************
1190 def _setSize(self, width=None, height=None):
1191 """DC width and height."""
1192 if width == None:
71caeaf3 1193 (self.width,self.height) = self.canvas.GetClientSize()
9d6685e2
RD
1194 else:
1195 self.width, self.height= width,height
b31cbeb9 1196 self.plotbox_size = 0.97*_Numeric.array([self.width, self.height])
9d6685e2
RD
1197 xo = 0.5*(self.width-self.plotbox_size[0])
1198 yo = self.height-0.5*(self.height-self.plotbox_size[1])
b31cbeb9 1199 self.plotbox_origin = _Numeric.array([xo, yo])
9d6685e2
RD
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."""
48023e15
RD
1208 if self.last_draw != None:
1209 graphics, xSpec, ySpec= self.last_draw
1210 self.Draw(graphics,xSpec,ySpec,printDC)
9d6685e2 1211
b31cbeb9
RD
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
71caeaf3 1224 dc = wx.ClientDC( self.canvas )
b31cbeb9
RD
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
48023e15 1229 def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
9d6685e2
RD
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))
48023e15
RD
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])
9d6685e2
RD
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:
48023e15 1272 # find max symbol size
9d6685e2
RD
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:]:
b31cbeb9 1279 txtExt= _Numeric.maximum(txtExt,dc.GetTextExtent(txt))
48023e15 1280 maxW= symExt[0]+txtExt[0]
9d6685e2
RD
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)
48023e15 1287 return (legendBoxWH, symExt, txtExt)
9d6685e2
RD
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
71caeaf3 1293 dc = wx.ClientDC( self.canvas )
9d6685e2
RD
1294 dc.BeginDrawing()
1295 dc.SetPen(wx.Pen(wx.BLACK))
1296 dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) )
1297 dc.SetLogicalFunction(wx.INVERT)
d7403ad2 1298 dc.DrawRectangle( ptx,pty, rectWidth,rectHeight)
9d6685e2
RD
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"""
b31cbeb9
RD
1319 c1= _Numeric.array(corner1)
1320 c2= _Numeric.array(corner2)
9d6685e2
RD
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
b31cbeb9
RD
1325 pul= _Numeric.minimum(pt1,pt2) # Upper left corner
1326 plr= _Numeric.maximum(pt1,pt2) # Lower right corner
9d6685e2
RD
1327 rectWidth, rectHeight= plr-pul
1328 ptx,pty= pul
00b9c867 1329 return ptx, pty, rectWidth, rectHeight
9d6685e2
RD
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
b31cbeb9
RD
1342 log = _Numeric.log10(range)
1343 power = _Numeric.floor(log)
9d6685e2
RD
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
b0d45269 1365 dc.SetPen(wx.Pen(self._gridColour, penWidth))
9d6685e2
RD
1366
1367 # set length of tick marks--long ones make grid
1368 if self._gridEnabled:
1369 x,y,width,height= self._point2ClientCoord(p1,p2)
2b9590d6
RD
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
9d6685e2
RD
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
b31cbeb9
RD
1387 a1 = scale*_Numeric.array([lower, y])+shift
1388 a2 = scale*_Numeric.array([upper, y])+shift
9d6685e2
RD
1389 dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line
1390 for x, label in xticks:
b31cbeb9 1391 pt = scale*_Numeric.array([x, y])+shift
9d6685e2
RD
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)]:
b31cbeb9
RD
1402 a1 = scale*_Numeric.array([x, lower])+shift
1403 a2 = scale*_Numeric.array([x, upper])+shift
9d6685e2
RD
1404 dc.DrawLine(a1[0],a1[1],a2[0],a2[1])
1405 for y, label in yticks:
b31cbeb9 1406 pt = scale*_Numeric.array([x, y])+shift
9d6685e2
RD
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
48023e15 1413 def _ticks(self, lower, upper):
9d6685e2 1414 ideal = (upper-lower)/7.
b31cbeb9
RD
1415 log = _Numeric.log10(ideal)
1416 power = _Numeric.floor(log)
9d6685e2
RD
1417 fraction = log-power
1418 factor = 1.
1419 error = fraction
1420 for f, lf in self._multiples:
b31cbeb9 1421 e = _Numeric.fabs(fraction-lf)
9d6685e2
RD
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'
48023e15
RD
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
9d6685e2 1440
b31cbeb9 1441 _multiples = [(2., _Numeric.log10(2.)), (5., _Numeric.log10(5.))]
9d6685e2
RD
1442
1443
71caeaf3
RD
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
9d6685e2
RD
1499#-------------------------------------------------------------------------------
1500# Used to layout the printer page
1501
1502class 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):
e5ce86d8 1518 return (1, 1, 1, 1) # disable page numbers
9d6685e2
RD
1519
1520 def OnPrintPage(self, page):
00b9c867 1521 dc = self.GetDC() # allows using floats for certain functions
9d6685e2
RD
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)
b31cbeb9 1572 self.graph.Redraw() #to get point label scale and shift correct
9d6685e2
RD
1573
1574 return True
1575
9d6685e2 1576
71caeaf3
RD
1577#----------------------------------------------------------------------
1578from wx import ImageFromStream, BitmapFromImage
1579import cStringIO, zlib
1580
1581
1582def 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\
1593I\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
1597def getMagPlusBitmap():
1598 return BitmapFromImage(getMagPlusImage())
1599
1600def getMagPlusImage():
1601 stream = cStringIO.StringIO(getMagPlusData())
1602 return ImageFromStream(stream)
1603
1604#----------------------------------------------------------------------
1605def 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\
1616yD\xab\x1f\xf3\xec\x1cY\x06\x89$\xbf\x80\xfb\x14\\dw\x90x\x12\xa3+\xeeD\x16%\
1617I\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
1622def getGrabHandBitmap():
1623 return BitmapFromImage(getGrabHandImage())
1624
1625def getGrabHandImage():
1626 stream = cStringIO.StringIO(getGrabHandData())
1627 return ImageFromStream(stream)
1628
1629#----------------------------------------------------------------------
1630def 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\
16427\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
1647def getHandBitmap():
1648 return BitmapFromImage(getHandImage())
1649
1650def getHandImage():
1651 stream = cStringIO.StringIO(getHandData())
1652 return ImageFromStream(stream)
1653
9d6685e2
RD
1654
1655
1656#---------------------------------------------------------------------------
1657# if running standalone...
1658#
1659# ...a sample implementation using the above
1660#
1661
1662def _draw1Objects():
1663 # 100 points sin function, plotted as green circles
b31cbeb9 1664 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
9d6685e2 1665 data1.shape = (100, 2)
b31cbeb9 1666 data1[:,1] = _Numeric.sin(data1[:,0])
9d6685e2
RD
1667 markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1)
1668
1669 # 50 points cos function, plotted as red line
b31cbeb9 1670 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
9d6685e2 1671 data1.shape = (50,2)
b31cbeb9 1672 data1[:,1] = _Numeric.cos(data1[:,0])
9d6685e2
RD
1673 lines = PolyLine(data1, legend= 'Red Line', colour='red')
1674
1675 # A few more points...
b31cbeb9 1676 pi = _Numeric.pi
9d6685e2
RD
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
1683def _draw2Objects():
1684 # 100 points sin function, plotted as green dots
b31cbeb9 1685 data1 = 2.*_Numeric.pi*_Numeric.arange(200)/200.
9d6685e2 1686 data1.shape = (100, 2)
b31cbeb9 1687 data1[:,1] = _Numeric.sin(data1[:,0])
9d6685e2
RD
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
b31cbeb9 1691 data1 = 2.*_Numeric.pi*_Numeric.arange(100)/100.
9d6685e2 1692 data1.shape = (50,2)
b31cbeb9 1693 data1[:,1] = _Numeric.cos(data1[:,0])
9d6685e2
RD
1694 line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH)
1695
1696 # A few more points...
b31cbeb9 1697 pi = _Numeric.pi
9d6685e2
RD
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
1705def _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
1714def _draw4Objects():
1715 # 25,000 point line
b31cbeb9 1716 data1 = _Numeric.arange(5e5,1e6,10)
9d6685e2
RD
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
1725def _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
00b9c867
RD
1731def _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
9d6685e2
RD
1750
1751class 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)
00b9c867
RD
1787 menu.Append(260, 'Draw6', 'Draw plots6')
1788 self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260)
1789
9d6685e2
RD
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)
71caeaf3
RD
1801 menu.Append(217, 'Enable &Drag', 'Activates dragging mode', kind=wx.ITEM_CHECK)
1802 self.Bind(wx.EVT_MENU,self.OnEnableDrag, id=217)
9d6685e2 1803 menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK)
b31cbeb9
RD
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
9d6685e2
RD
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)
b31cbeb9
RD
1828 #define the function for drawing pointLabels
1829 self.client.SetPointLabelFunc(self.DrawPointLabel)
9d6685e2
RD
1830 # Create mouse event for showing cursor coords in status bar
1831 self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
b31cbeb9
RD
1832 # Show closest point when enabled
1833 self.client.Bind(wx.EVT_MOTION, self.OnMotion)
1834
71caeaf3
RD
1835 self.Show()
1836
9d6685e2 1837
b31cbeb9
RD
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
9d6685e2
RD
1862 def OnMouseLeftDown(self,event):
1863 s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event)
1864 self.SetStatusText(s)
b31cbeb9
RD
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
9d6685e2
RD
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)
b31cbeb9
RD
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
9d6685e2
RD
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
00b9c867
RD
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
9d6685e2
RD
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):
48023e15
RD
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))
9d6685e2
RD
1952
1953 def OnEnableZoom(self, event):
1954 self.client.SetEnableZoom(event.IsChecked())
71caeaf3 1955 self.mainmenu.Check(217, not event.IsChecked())
9d6685e2
RD
1956
1957 def OnEnableGrid(self, event):
1958 self.client.SetEnableGrid(event.IsChecked())
1959
71caeaf3
RD
1960 def OnEnableDrag(self, event):
1961 self.client.SetEnableDrag(event.IsChecked())
1962 self.mainmenu.Check(214, not event.IsChecked())
1963
9d6685e2
RD
1964 def OnEnableLegend(self, event):
1965 self.client.SetEnableLegend(event.IsChecked())
1966
b31cbeb9
RD
1967 def OnEnablePointLabel(self, event):
1968 self.client.SetEnablePointLabel(event.IsChecked())
1969
9d6685e2
RD
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):
33785d9f
RD
1980 from wx.lib.dialogs import ScrolledMessageDialog
1981 about = ScrolledMessageDialog(self, __doc__, "About...")
9d6685e2
RD
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
1993def __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
2007if __name__ == '__main__':
2008 __test()