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