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