]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/plot.py
check for wxHAS_HUGE_FILES
[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#
28
9d6685e2
RD
29
30"""
31This is a simple light weight plotting module that can be used with
32Boa or easily integrated into your own wxPython application. The
33emphasis is on small size and fast plotting for large data sets. It
34has a reasonable number of features to do line and scatter graphs
00b9c867
RD
35easily as well as simple bar graphs. It is not as sophisticated or
36as powerful as SciPy Plt or Chaco. Both of these are great packages
37but consume huge amounts of computer resources for simple plots.
38They can be found at http://scipy.com
9d6685e2
RD
39
40This file contains two parts; first the re-usable library stuff, then,
41after a "if __name__=='__main__'" test, a simple frame and a few default
42plots for examples and testing.
43
44Based on wxPlotCanvas
45Written by K.Hinsen, R. Srinivasan;
46Ported to wxPython Harm van der Heijden, feb 1999
47
48Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
49 -More style options
50 -Zooming using mouse "rubber band"
51 -Scroll left, right
52 -Grid(graticule)
53 -Printing, preview, and page set up (margins)
54 -Axis and title labels
55 -Cursor xy axis values
56 -Doc strings and lots of comments
57 -Optimizations for large number of points
58 -Legends
59
60Did a lot of work here to speed markers up. Only a factor of 4
61improvement though. Lines are much faster than markers, especially
62filled markers. Stay away from circles and triangles unless you
63only have a few thousand points.
64
65Times for 25,000 points
66Line - 0.078 sec
67Markers
68Square - 0.22 sec
69dot - 0.10
70circle - 0.87
71cross,plus - 0.28
72triangle, triangle_down - 0.90
73
74Thanks to Chris Barker for getting this version working on Linux.
75
76Zooming controls with mouse (when enabled):
77 Left mouse drag - Zoom box.
78 Left mouse double click - reset zoom.
79 Right mouse click - zoom out centred on click location.
80"""
81
82import string
83import time
84import wx
85
00b9c867 86# Needs Numeric or numarray
9d6685e2
RD
87try:
88 import Numeric
89except:
90 try:
91 import numarray as Numeric #if numarray is used it is renamed Numeric
92 except:
93 msg= """
94 This module requires the Numeric or numarray module,
95 which could not be imported. It probably is not installed
96 (it's not part of the standard Python distribution). See the
97 Python site (http://www.python.org) for information on
98 downloading source or binaries."""
99 raise ImportError, "Numeric or numarray not found. \n" + msg
100
101
102
103#
104# Plotting classes...
105#
106class PolyPoints:
107 """Base Class for lines and markers
108 - All methods are private.
109 """
110
111 def __init__(self, points, attr):
112 self.points = Numeric.array(points)
113 self.currentScale= (1,1)
114 self.currentShift= (0,0)
115 self.scaled = self.points
116 self.attributes = {}
117 self.attributes.update(self._attributes)
118 for name, value in attr.items():
119 if name not in self._attributes.keys():
120 raise KeyError, "Style attribute incorrect. Should be one of %s" % self._attributes.keys()
121 self.attributes[name] = value
122
123 def boundingBox(self):
124 if len(self.points) == 0:
125 # no curves to draw
126 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
127 minXY= Numeric.array([-1,-1])
128 maxXY= Numeric.array([ 1, 1])
129 else:
130 minXY= Numeric.minimum.reduce(self.points)
131 maxXY= Numeric.maximum.reduce(self.points)
132 return minXY, maxXY
133
134 def scaleAndShift(self, scale=(1,1), shift=(0,0)):
135 if len(self.points) == 0:
136 # no curves to draw
137 return
138 if (scale is not self.currentScale) or (shift is not self.currentShift):
139 # update point scaling
140 self.scaled = scale*self.points+shift
141 self.currentScale= scale
142 self.currentShift= shift
143 # else unchanged use the current scaling
144
145 def getLegend(self):
146 return self.attributes['legend']
147
148
149class PolyLine(PolyPoints):
150 """Class to define line type and style
151 - All methods except __init__ are private.
152 """
153
154 _attributes = {'colour': 'black',
155 'width': 1,
156 'style': wx.SOLID,
157 'legend': ''}
158
159 def __init__(self, points, **attr):
160 """Creates PolyLine object
161 points - sequence (array, tuple or list) of (x,y) points making up line
162 **attr - key word attributes
163 Defaults:
164 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
165 'width'= 1, - Pen width
166 'style'= wx.SOLID, - wx.Pen style
167 'legend'= '' - Line Legend to display
168 """
169 PolyPoints.__init__(self, points, attr)
170
171 def draw(self, dc, printerScale, coord= None):
172 colour = self.attributes['colour']
173 width = self.attributes['width'] * printerScale
174 style= self.attributes['style']
00b9c867
RD
175 pen = wx.Pen(wx.NamedColour(colour), width, style)
176 pen.SetCap(wx.CAP_BUTT)
177 dc.SetPen(pen)
9d6685e2
RD
178 if coord == None:
179 dc.DrawLines(self.scaled)
180 else:
181 dc.DrawLines(coord) # draw legend line
182
183 def getSymExtent(self, printerScale):
184 """Width and Height of Marker"""
185 h= self.attributes['width'] * printerScale
186 w= 5 * h
187 return (w,h)
188
189
190class PolyMarker(PolyPoints):
191 """Class to define marker type and style
192 - All methods except __init__ are private.
193 """
194
195 _attributes = {'colour': 'black',
196 'width': 1,
197 'size': 2,
198 'fillcolour': None,
199 'fillstyle': wx.SOLID,
200 'marker': 'circle',
201 'legend': ''}
202
203 def __init__(self, points, **attr):
204 """Creates PolyMarker object
205 points - sequence (array, tuple or list) of (x,y) points
206 **attr - key word attributes
207 Defaults:
208 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
209 'width'= 1, - Pen width
210 'size'= 2, - Marker size
211 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
212 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
213 'marker'= 'circle' - Marker shape
214 'legend'= '' - Marker Legend to display
215
216 Marker Shapes:
217 - 'circle'
218 - 'dot'
219 - 'square'
220 - 'triangle'
221 - 'triangle_down'
222 - 'cross'
223 - 'plus'
224 """
225
226 PolyPoints.__init__(self, points, attr)
227
228 def draw(self, dc, printerScale, coord= None):
229 colour = self.attributes['colour']
230 width = self.attributes['width'] * printerScale
231 size = self.attributes['size'] * printerScale
232 fillcolour = self.attributes['fillcolour']
233 fillstyle = self.attributes['fillstyle']
234 marker = self.attributes['marker']
235
00b9c867 236 dc.SetPen(wx.Pen(wx.NamedColour(colour), width))
9d6685e2
RD
237 if fillcolour:
238 dc.SetBrush(wx.Brush(wx.NamedColour(fillcolour),fillstyle))
239 else:
240 dc.SetBrush(wx.Brush(wx.NamedColour(colour), fillstyle))
241 if coord == None:
242 self._drawmarkers(dc, self.scaled, marker, size)
243 else:
244 self._drawmarkers(dc, coord, marker, size) # draw legend marker
245
246 def getSymExtent(self, printerScale):
247 """Width and Height of Marker"""
248 s= 5*self.attributes['size'] * printerScale
249 return (s,s)
250
251 def _drawmarkers(self, dc, coords, marker,size=1):
252 f = eval('self._' +marker)
253 f(dc, coords, size)
254
255 def _circle(self, dc, coords, size=1):
256 fact= 2.5*size
257 wh= 5.0*size
258 rect= Numeric.zeros((len(coords),4),Numeric.Float)+[0.0,0.0,wh,wh]
259 rect[:,0:2]= coords-[fact,fact]
260 dc.DrawEllipseList(rect.astype(Numeric.Int32))
261
262 def _dot(self, dc, coords, size=1):
263 dc.DrawPointList(coords)
264
265 def _square(self, dc, coords, size=1):
266 fact= 2.5*size
267 wh= 5.0*size
268 rect= Numeric.zeros((len(coords),4),Numeric.Float)+[0.0,0.0,wh,wh]
269 rect[:,0:2]= coords-[fact,fact]
270 dc.DrawRectangleList(rect.astype(Numeric.Int32))
271
272 def _triangle(self, dc, coords, size=1):
273 shape= [(-2.5*size,1.44*size), (2.5*size,1.44*size), (0.0,-2.88*size)]
274 poly= Numeric.repeat(coords,3)
275 poly.shape= (len(coords),3,2)
276 poly += shape
277 dc.DrawPolygonList(poly.astype(Numeric.Int32))
278
279 def _triangle_down(self, dc, coords, size=1):
280 shape= [(-2.5*size,-1.44*size), (2.5*size,-1.44*size), (0.0,2.88*size)]
281 poly= Numeric.repeat(coords,3)
282 poly.shape= (len(coords),3,2)
283 poly += shape
284 dc.DrawPolygonList(poly.astype(Numeric.Int32))
285
286 def _cross(self, dc, coords, size=1):
287 fact= 2.5*size
288 for f in [[-fact,-fact,fact,fact],[-fact,fact,fact,-fact]]:
289 lines= Numeric.concatenate((coords,coords),axis=1)+f
290 dc.DrawLineList(lines.astype(Numeric.Int32))
291
292 def _plus(self, dc, coords, size=1):
293 fact= 2.5*size
294 for f in [[-fact,0,fact,0],[0,-fact,0,fact]]:
295 lines= Numeric.concatenate((coords,coords),axis=1)+f
296 dc.DrawLineList(lines.astype(Numeric.Int32))
297
298class PlotGraphics:
299 """Container to hold PolyXXX objects and graph labels
300 - All methods except __init__ are private.
301 """
302
303 def __init__(self, objects, title='', xLabel='', yLabel= ''):
304 """Creates PlotGraphics object
305 objects - list of PolyXXX objects to make graph
306 title - title shown at top of graph
307 xLabel - label shown on x-axis
308 yLabel - label shown on y-axis
309 """
310 if type(objects) not in [list,tuple]:
311 raise TypeError, "objects argument should be list or tuple"
312 self.objects = objects
313 self.title= title
314 self.xLabel= xLabel
315 self.yLabel= yLabel
316
317 def boundingBox(self):
318 p1, p2 = self.objects[0].boundingBox()
319 for o in self.objects[1:]:
320 p1o, p2o = o.boundingBox()
321 p1 = Numeric.minimum(p1, p1o)
322 p2 = Numeric.maximum(p2, p2o)
323 return p1, p2
324
325 def scaleAndShift(self, scale=(1,1), shift=(0,0)):
326 for o in self.objects:
327 o.scaleAndShift(scale, shift)
328
329 def setPrinterScale(self, scale):
330 """Thickens up lines and markers only for printing"""
331 self.printerScale= scale
332
333 def setXLabel(self, xLabel= ''):
334 """Set the X axis label on the graph"""
335 self.xLabel= xLabel
336
337 def setYLabel(self, yLabel= ''):
338 """Set the Y axis label on the graph"""
339 self.yLabel= yLabel
340
341 def setTitle(self, title= ''):
342 """Set the title at the top of graph"""
343 self.title= title
344
345 def getXLabel(self):
346 """Get x axis label string"""
347 return self.xLabel
348
349 def getYLabel(self):
350 """Get y axis label string"""
351 return self.yLabel
352
353 def getTitle(self, title= ''):
354 """Get the title at the top of graph"""
355 return self.title
356
357 def draw(self, dc):
358 for o in self.objects:
359 #t=time.clock() # profile info
360 o.draw(dc, self.printerScale)
361 #dt= time.clock()-t
362 #print o, "time=", dt
363
364 def getSymExtent(self, printerScale):
365 """Get max width and height of lines and markers symbols for legend"""
366 symExt = self.objects[0].getSymExtent(printerScale)
367 for o in self.objects[1:]:
368 oSymExt = o.getSymExtent(printerScale)
369 symExt = Numeric.maximum(symExt, oSymExt)
370 return symExt
371
372 def getLegendNames(self):
373 """Returns list of legend names"""
374 lst = [None]*len(self)
375 for i in range(len(self)):
376 lst[i]= self.objects[i].getLegend()
377 return lst
378
379 def __len__(self):
380 return len(self.objects)
381
382 def __getitem__(self, item):
383 return self.objects[item]
384
385
386#-------------------------------------------------------------------------------
387# Main window that you will want to import into your application.
388
389class PlotCanvas(wx.Window):
390 """Subclass of a wx.Window to allow simple general plotting
391 of data with zoom, labels, and automatic axis scaling."""
392
393 def __init__(self, parent, id = -1, pos=wx.DefaultPosition,
394 size=wx.DefaultSize, style= wx.DEFAULT_FRAME_STYLE, name= ""):
395 """Constucts a window, which can be a child of a frame, dialog or
396 any other non-control window"""
397
398 wx.Window.__init__(self, parent, id, pos, size, style, name)
399 self.border = (1,1)
400
401 self.SetBackgroundColour("white")
402
9d6685e2
RD
403 # Create some mouse events for zooming
404 self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
405 self.Bind(wx.EVT_LEFT_UP, self.OnMouseLeftUp)
406 self.Bind(wx.EVT_MOTION, self.OnMotion)
407 self.Bind(wx.EVT_LEFT_DCLICK, self.OnMouseDoubleClick)
408 self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseRightDown)
409
410 # set curser as cross-hairs
411 self.SetCursor(wx.CROSS_CURSOR)
412
413 # Things for printing
414 self.print_data = wx.PrintData()
415 self.print_data.SetPaperId(wx.PAPER_LETTER)
416 self.print_data.SetOrientation(wx.LANDSCAPE)
417 self.pageSetupData= wx.PageSetupDialogData()
418 self.pageSetupData.SetMarginBottomRight((25,25))
419 self.pageSetupData.SetMarginTopLeft((25,25))
420 self.pageSetupData.SetPrintData(self.print_data)
421 self.printerScale = 1
422 self.parent= parent
423
424 # Zooming variables
425 self._zoomInFactor = 0.5
426 self._zoomOutFactor = 2
427 self._zoomCorner1= Numeric.array([0.0, 0.0]) # left mouse down corner
428 self._zoomCorner2= Numeric.array([0.0, 0.0]) # left mouse up corner
429 self._zoomEnabled= False
430 self._hasDragged= False
431
432 # Drawing Variables
433 self.last_draw = None
434 self._pointScale= 1
435 self._pointShift= 0
436 self._xSpec= 'auto'
437 self._ySpec= 'auto'
438 self._gridEnabled= False
439 self._legendEnabled= False
440
441 # Fonts
442 self._fontCache = {}
443 self._fontSizeAxis= 10
444 self._fontSizeTitle= 15
445 self._fontSizeLegend= 7
446
da8d6ffa
RD
447 self.Bind(wx.EVT_PAINT, self.OnPaint)
448 self.Bind(wx.EVT_SIZE, self.OnSize)
9d6685e2
RD
449 # OnSize called to make sure the buffer is initialized.
450 # This might result in OnSize getting called twice on some
451 # platforms at initialization, but little harm done.
da8d6ffa
RD
452 if wx.Platform != "__WXMAC__":
453 self.OnSize(None) # sets the initial size based on client size
9d6685e2
RD
454
455
456 # SaveFile
457 def SaveFile(self, fileName= ''):
458 """Saves the file to the type specified in the extension. If no file
459 name is specified a dialog box is provided. Returns True if sucessful,
460 otherwise False.
461
462 .bmp Save a Windows bitmap file.
463 .xbm Save an X bitmap file.
464 .xpm Save an XPM bitmap file.
465 .png Save a Portable Network Graphics file.
466 .jpg Save a Joint Photographic Experts Group file.
467 """
468 if string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
469 dlg1 = wx.FileDialog(
470 self,
471 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
472 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
473 wx.SAVE|wx.OVERWRITE_PROMPT
474 )
475 try:
476 while 1:
477 if dlg1.ShowModal() == wx.ID_OK:
478 fileName = dlg1.GetPath()
479 # Check for proper exension
480 if string.lower(fileName[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
481 dlg2 = wx.MessageDialog(self, 'File name extension\n'
482 'must be one of\n'
483 'bmp, xbm, xpm, png, or jpg',
484 'File Name Error', wx.OK | wx.ICON_ERROR)
485 try:
486 dlg2.ShowModal()
487 finally:
488 dlg2.Destroy()
489 else:
490 break # now save file
491 else: # exit without saving
492 return False
493 finally:
494 dlg1.Destroy()
495
496 # File name has required extension
497 fType = string.lower(fileName[-3:])
498 if fType == "bmp":
499 tp= wx.BITMAP_TYPE_BMP # Save a Windows bitmap file.
500 elif fType == "xbm":
501 tp= wx.BITMAP_TYPE_XBM # Save an X bitmap file.
502 elif fType == "xpm":
503 tp= wx.BITMAP_TYPE_XPM # Save an XPM bitmap file.
504 elif fType == "jpg":
505 tp= wx.BITMAP_TYPE_JPEG # Save a JPG file.
506 else:
507 tp= wx.BITMAP_TYPE_PNG # Save a PNG file.
508 # Save Bitmap
509 res= self._Buffer.SaveFile(fileName, tp)
510 return res
511
512 def PageSetup(self):
513 """Brings up the page setup dialog"""
514 data = self.pageSetupData
515 data.SetPrintData(self.print_data)
516 dlg = wx.PageSetupDialog(self.parent, data)
517 try:
518 if dlg.ShowModal() == wx.ID_OK:
519 data = dlg.GetPageSetupData() # returns wx.PageSetupDialogData
520 # updates page parameters from dialog
521 self.pageSetupData.SetMarginBottomRight(data.GetMarginBottomRight())
522 self.pageSetupData.SetMarginTopLeft(data.GetMarginTopLeft())
523 self.pageSetupData.SetPrintData(data.GetPrintData())
524 self.print_data=data.GetPrintData() # updates print_data
525 finally:
526 dlg.Destroy()
527
528 def Printout(self, paper=None):
529 """Print current plot."""
530 if paper != None:
531 self.print_data.SetPaperId(paper)
532 pdd = wx.PrintDialogData()
533 pdd.SetPrintData(self.print_data)
534 printer = wx.Printer(pdd)
535 out = PlotPrintout(self)
536 print_ok = printer.Print(self.parent, out)
537 if print_ok:
538 self.print_data = printer.GetPrintDialogData().GetPrintData()
539 out.Destroy()
540
541 def PrintPreview(self):
542 """Print-preview current plot."""
543 printout = PlotPrintout(self)
544 printout2 = PlotPrintout(self)
545 self.preview = wx.PrintPreview(printout, printout2, self.print_data)
546 if not self.preview.Ok():
547 wx.MessageDialog(self, "Print Preview failed.\n" \
548 "Check that default printer is configured\n", \
549 "Print error", wx.OK|wx.CENTRE).ShowModal()
550 self.preview.SetZoom(30)
551 # search up tree to find frame instance
552 frameInst= self
553 while not isinstance(frameInst, wx.Frame):
554 frameInst= frameInst.GetParent()
555 frame = wx.PreviewFrame(self.preview, frameInst, "Preview")
556 frame.Initialize()
557 frame.SetPosition(self.GetPosition())
558 frame.SetSize((500,400))
559 frame.Centre(wx.BOTH)
560 frame.Show(True)
561
562 def SetFontSizeAxis(self, point= 10):
563 """Set the tick and axis label font size (default is 10 point)"""
564 self._fontSizeAxis= point
565
566 def GetFontSizeAxis(self):
567 """Get current tick and axis label font size in points"""
568 return self._fontSizeAxis
569
570 def SetFontSizeTitle(self, point= 15):
571 """Set Title font size (default is 15 point)"""
572 self._fontSizeTitle= point
573
574 def GetFontSizeTitle(self):
575 """Get current Title font size in points"""
576 return self._fontSizeTitle
577
578 def SetFontSizeLegend(self, point= 7):
579 """Set Legend font size (default is 7 point)"""
580 self._fontSizeLegend= point
581
582 def GetFontSizeLegend(self):
583 """Get current Legend font size in points"""
584 return self._fontSizeLegend
585
586 def SetEnableZoom(self, value):
587 """Set True to enable zooming."""
588 if value not in [True,False]:
589 raise TypeError, "Value should be True or False"
590 self._zoomEnabled= value
591
592 def GetEnableZoom(self):
593 """True if zooming enabled."""
594 return self._zoomEnabled
595
596 def SetEnableGrid(self, value):
597 """Set True to enable grid."""
598 if value not in [True,False]:
599 raise TypeError, "Value should be True or False"
600 self._gridEnabled= value
601 self.Redraw()
602
603 def GetEnableGrid(self):
604 """True if grid enabled."""
605 return self._gridEnabled
606
607 def SetEnableLegend(self, value):
608 """Set True to enable legend."""
609 if value not in [True,False]:
610 raise TypeError, "Value should be True or False"
611 self._legendEnabled= value
612 self.Redraw()
613
614 def GetEnableLegend(self):
615 """True if Legend enabled."""
616 return self._legendEnabled
617
618 def Reset(self):
619 """Unzoom the plot."""
620 if self.last_draw is not None:
621 self.Draw(self.last_draw[0])
622
623 def ScrollRight(self, units):
624 """Move view right number of axis units."""
625 if self.last_draw is not None:
626 graphics, xAxis, yAxis= self.last_draw
627 xAxis= (xAxis[0]+units, xAxis[1]+units)
628 self.Draw(graphics,xAxis,yAxis)
629
630 def ScrollUp(self, units):
631 """Move view up number of axis units."""
632 if self.last_draw is not None:
633 graphics, xAxis, yAxis= self.last_draw
634 yAxis= (yAxis[0]+units, yAxis[1]+units)
635 self.Draw(graphics,xAxis,yAxis)
636
637 def GetXY(self,event):
638 """Takes a mouse event and returns the XY user axis values."""
639 screenPos= Numeric.array( event.GetPosition())
640 x,y= (screenPos-self._pointShift)/self._pointScale
641 return x,y
642
643 def SetXSpec(self, type= 'auto'):
644 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
645 where:
646 'none' - shows no axis or tick mark values
647 'min' - shows min bounding box values
648 'auto' - rounds axis range to sensible values
649 """
650 self._xSpec= type
651
652 def SetYSpec(self, type= 'auto'):
653 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
654 where:
655 'none' - shows no axis or tick mark values
656 'min' - shows min bounding box values
657 'auto' - rounds axis range to sensible values
658 """
659 self._ySpec= type
660
661 def GetXSpec(self):
662 """Returns current XSpec for axis"""
663 return self._xSpec
664
665 def GetYSpec(self):
666 """Returns current YSpec for axis"""
667 return self._ySpec
668
669 def GetXMaxRange(self):
670 """Returns (minX, maxX) x-axis range for displayed graph"""
671 graphics= self.last_draw[0]
672 p1, p2 = graphics.boundingBox() # min, max points of graphics
673 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
674 return xAxis
675
676 def GetYMaxRange(self):
677 """Returns (minY, maxY) y-axis range for displayed graph"""
678 graphics= self.last_draw[0]
679 p1, p2 = graphics.boundingBox() # min, max points of graphics
680 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
681 return yAxis
682
683 def GetXCurrentRange(self):
684 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
685 return self.last_draw[1]
686
687 def GetYCurrentRange(self):
688 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
689 return self.last_draw[2]
690
691 def Draw(self, graphics, xAxis = None, yAxis = None, dc = None):
692 """Draw objects in graphics with specified x and y axis.
693 graphics- instance of PlotGraphics with list of PolyXXX objects
694 xAxis - tuple with (min, max) axis range to view
695 yAxis - same as xAxis
696 dc - drawing context - doesn't have to be specified.
697 If it's not, the offscreen buffer is used
698 """
699 # check Axis is either tuple or none
700 if type(xAxis) not in [type(None),tuple]:
701 raise TypeError, "xAxis should be None or (minX,maxX)"
702 if type(yAxis) not in [type(None),tuple]:
703 raise TypeError, "yAxis should be None or (minY,maxY)"
704
705 # check case for axis = (a,b) where a==b caused by improper zooms
706 if xAxis != None:
707 if xAxis[0] == xAxis[1]:
708 return
709 if yAxis != None:
710 if yAxis[0] == yAxis[1]:
711 return
712
713 if dc == None:
714 # allows using floats for certain functions
00b9c867 715 dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
9d6685e2
RD
716 dc.Clear()
717
718 dc.BeginDrawing()
719 # dc.Clear()
720
721 # set font size for every thing but title and legend
722 dc.SetFont(self._getFont(self._fontSizeAxis))
723
724 # sizes axis to axis type, create lower left and upper right corners of plot
725 if xAxis == None or yAxis == None:
726 # One or both axis not specified in Draw
727 p1, p2 = graphics.boundingBox() # min, max points of graphics
728 if xAxis == None:
729 xAxis = self._axisInterval(self._xSpec, p1[0], p2[0]) # in user units
730 if yAxis == None:
731 yAxis = self._axisInterval(self._ySpec, p1[1], p2[1])
732 # Adjust bounding box for axis spec
733 p1[0],p1[1] = xAxis[0], yAxis[0] # lower left corner user scale (xmin,ymin)
734 p2[0],p2[1] = xAxis[1], yAxis[1] # upper right corner user scale (xmax,ymax)
735 else:
736 # Both axis specified in Draw
737 p1= Numeric.array([xAxis[0], yAxis[0]]) # lower left corner user scale (xmin,ymin)
738 p2= Numeric.array([xAxis[1], yAxis[1]]) # upper right corner user scale (xmax,ymax)
739
740 self.last_draw = (graphics, xAxis, yAxis) # saves most recient values
741
742 # Get ticks and textExtents for axis if required
743 if self._xSpec is not 'none':
744 xticks = self._ticks(xAxis[0], xAxis[1])
745 xTextExtent = dc.GetTextExtent(xticks[-1][1])# w h of x axis text last number on axis
746 else:
747 xticks = None
748 xTextExtent= (0,0) # No text for ticks
749 if self._ySpec is not 'none':
750 yticks = self._ticks(yAxis[0], yAxis[1])
751 yTextExtentBottom= dc.GetTextExtent(yticks[0][1])
752 yTextExtentTop = dc.GetTextExtent(yticks[-1][1])
753 yTextExtent= (max(yTextExtentBottom[0],yTextExtentTop[0]),
754 max(yTextExtentBottom[1],yTextExtentTop[1]))
755 else:
756 yticks = None
757 yTextExtent= (0,0) # No text for ticks
758
759 # TextExtents for Title and Axis Labels
760 titleWH, xLabelWH, yLabelWH= self._titleLablesWH(dc, graphics)
761
762 # TextExtents for Legend
763 legendBoxWH, legendSymExt, legendTextExt = self._legendWH(dc, graphics)
764
765 # room around graph area
766 rhsW= max(xTextExtent[0], legendBoxWH[0]) # use larger of number width or legend width
767 lhsW= yTextExtent[0]+ yLabelWH[1]
768 bottomH= max(xTextExtent[1], yTextExtent[1]/2.)+ xLabelWH[1]
769 topH= yTextExtent[1]/2. + titleWH[1]
770 textSize_scale= Numeric.array([rhsW+lhsW,bottomH+topH]) # make plot area smaller by text size
771 textSize_shift= Numeric.array([lhsW, bottomH]) # shift plot area by this amount
772
773 # drawing title and labels text
774 dc.SetFont(self._getFont(self._fontSizeTitle))
775 titlePos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- titleWH[0]/2.,
776 self.plotbox_origin[1]- self.plotbox_size[1])
777 dc.DrawText(graphics.getTitle(),titlePos[0],titlePos[1])
778 dc.SetFont(self._getFont(self._fontSizeAxis))
779 xLabelPos= (self.plotbox_origin[0]+ lhsW + (self.plotbox_size[0]-lhsW-rhsW)/2.- xLabelWH[0]/2.,
780 self.plotbox_origin[1]- xLabelWH[1])
781 dc.DrawText(graphics.getXLabel(),xLabelPos[0],xLabelPos[1])
782 yLabelPos= (self.plotbox_origin[0],
783 self.plotbox_origin[1]- bottomH- (self.plotbox_size[1]-bottomH-topH)/2.+ yLabelWH[0]/2.)
784 if graphics.getYLabel(): # bug fix for Linux
785 dc.DrawRotatedText(graphics.getYLabel(),yLabelPos[0],yLabelPos[1],90)
786
787 # drawing legend makers and text
788 if self._legendEnabled:
789 self._drawLegend(dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt)
790
791 # allow for scaling and shifting plotted points
792 scale = (self.plotbox_size-textSize_scale) / (p2-p1)* Numeric.array((1,-1))
793 shift = -p1*scale + self.plotbox_origin + textSize_shift * Numeric.array((1,-1))
794 self._pointScale= scale # make available for mouse events
795 self._pointShift= shift
796 self._drawAxes(dc, p1, p2, scale, shift, xticks, yticks)
797
798 graphics.scaleAndShift(scale, shift)
799 graphics.setPrinterScale(self.printerScale) # thicken up lines and markers if printing
800
801 # set clipping area so drawing does not occur outside axis box
802 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(p1, p2)
803 dc.SetClippingRegion(ptx,pty,rectWidth,rectHeight)
804 # Draw the lines and markers
805 #start = time.clock()
806 graphics.draw(dc)
807 # print "entire graphics drawing took: %f second"%(time.clock() - start)
808 # remove the clipping region
809 dc.DestroyClippingRegion()
810 dc.EndDrawing()
811
812 def Redraw(self, dc= None):
813 """Redraw the existing plot."""
814 if self.last_draw is not None:
815 graphics, xAxis, yAxis= self.last_draw
816 self.Draw(graphics,xAxis,yAxis,dc)
817
818 def Clear(self):
819 """Erase the window."""
820 dc = wx.BufferedDC(wx.ClientDC(self), self._Buffer)
821 dc.Clear()
822 self.last_draw = None
823
824 def Zoom(self, Center, Ratio):
825 """ Zoom on the plot
826 Centers on the X,Y coords given in Center
827 Zooms by the Ratio = (Xratio, Yratio) given
828 """
829 x,y = Center
830 if self.last_draw != None:
831 (graphics, xAxis, yAxis) = self.last_draw
832 w = (xAxis[1] - xAxis[0]) * Ratio[0]
833 h = (yAxis[1] - yAxis[0]) * Ratio[1]
834 xAxis = ( x - w/2, x + w/2 )
835 yAxis = ( y - h/2, y + h/2 )
836 self.Draw(graphics, xAxis, yAxis)
837
838
839 # event handlers **********************************
840 def OnMotion(self, event):
841 if self._zoomEnabled and event.LeftIsDown():
842 if self._hasDragged:
843 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
844 else:
845 self._hasDragged= True
846 self._zoomCorner2[0], self._zoomCorner2[1] = self.GetXY(event)
847 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # add new
848
849 def OnMouseLeftDown(self,event):
850 self._zoomCorner1[0], self._zoomCorner1[1]= self.GetXY(event)
851
852 def OnMouseLeftUp(self, event):
853 if self._zoomEnabled:
854 if self._hasDragged == True:
855 self._drawRubberBand(self._zoomCorner1, self._zoomCorner2) # remove old
856 self._zoomCorner2[0], self._zoomCorner2[1]= self.GetXY(event)
857 self._hasDragged = False # reset flag
858 minX, minY= Numeric.minimum( self._zoomCorner1, self._zoomCorner2)
859 maxX, maxY= Numeric.maximum( self._zoomCorner1, self._zoomCorner2)
860 if self.last_draw != None:
861 self.Draw(self.last_draw[0], xAxis = (minX,maxX), yAxis = (minY,maxY), dc = None)
862 #else: # A box has not been drawn, zoom in on a point
863 ## this interfered with the double click, so I've disables it.
864 # X,Y = self.GetXY(event)
865 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
866
867 def OnMouseDoubleClick(self,event):
868 if self._zoomEnabled:
869 self.Reset()
870
871 def OnMouseRightDown(self,event):
872 if self._zoomEnabled:
873 X,Y = self.GetXY(event)
874 self.Zoom( (X,Y), (self._zoomOutFactor, self._zoomOutFactor) )
875
876 def OnPaint(self, event):
877 # All that is needed here is to draw the buffer to screen
878 dc = wx.BufferedPaintDC(self, self._Buffer)
879
880 def OnSize(self,event):
881 # The Buffer init is done here, to make sure the buffer is always
882 # the same size as the Window
883 Size = self.GetClientSize()
884
885 # Make new offscreen bitmap: this bitmap will always have the
886 # current drawing in it, so it can be used to save the image to
887 # a file, or whatever.
888 self._Buffer = wx.EmptyBitmap(Size[0],Size[1])
889 self._setSize()
890 if self.last_draw is None:
891 self.Clear()
892 else:
893 graphics, xSpec, ySpec = self.last_draw
894 self.Draw(graphics,xSpec,ySpec)
895
896
897 # Private Methods **************************************************
898 def _setSize(self, width=None, height=None):
899 """DC width and height."""
900 if width == None:
901 (self.width,self.height) = self.GetClientSize()
902 else:
903 self.width, self.height= width,height
904 self.plotbox_size = 0.97*Numeric.array([self.width, self.height])
905 xo = 0.5*(self.width-self.plotbox_size[0])
906 yo = self.height-0.5*(self.height-self.plotbox_size[1])
907 self.plotbox_origin = Numeric.array([xo, yo])
908
909 def _setPrinterScale(self, scale):
910 """Used to thicken lines and increase marker size for print out."""
911 # line thickness on printer is very thin at 600 dot/in. Markers small
912 self.printerScale= scale
913
914 def _printDraw(self, printDC):
915 """Used for printing."""
916 if self.last_draw != None:
917 graphics, xSpec, ySpec= self.last_draw
918 self.Draw(graphics,xSpec,ySpec,printDC)
919
920 def _drawLegend(self,dc,graphics,rhsW,topH,legendBoxWH, legendSymExt, legendTextExt):
921 """Draws legend symbols and text"""
922 # top right hand corner of graph box is ref corner
923 trhc= self.plotbox_origin+ (self.plotbox_size-[rhsW,topH])*[1,-1]
924 legendLHS= .091* legendBoxWH[0] # border space between legend sym and graph box
925 lineHeight= max(legendSymExt[1], legendTextExt[1]) * 1.1 #1.1 used as space between lines
926 dc.SetFont(self._getFont(self._fontSizeLegend))
927 for i in range(len(graphics)):
928 o = graphics[i]
929 s= i*lineHeight
930 if isinstance(o,PolyMarker):
931 # draw marker with legend
932 pnt= (trhc[0]+legendLHS+legendSymExt[0]/2., trhc[1]+s+lineHeight/2.)
933 o.draw(dc, self.printerScale, coord= Numeric.array([pnt]))
934 elif isinstance(o,PolyLine):
935 # draw line with legend
936 pnt1= (trhc[0]+legendLHS, trhc[1]+s+lineHeight/2.)
937 pnt2= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.)
938 o.draw(dc, self.printerScale, coord= Numeric.array([pnt1,pnt2]))
939 else:
940 raise TypeError, "object is neither PolyMarker or PolyLine instance"
941 # draw legend txt
942 pnt= (trhc[0]+legendLHS+legendSymExt[0], trhc[1]+s+lineHeight/2.-legendTextExt[1]/2)
943 dc.DrawText(o.getLegend(),pnt[0],pnt[1])
944 dc.SetFont(self._getFont(self._fontSizeAxis)) # reset
945
946 def _titleLablesWH(self, dc, graphics):
947 """Draws Title and labels and returns width and height for each"""
948 # TextExtents for Title and Axis Labels
949 dc.SetFont(self._getFont(self._fontSizeTitle))
950 title= graphics.getTitle()
951 titleWH= dc.GetTextExtent(title)
952 dc.SetFont(self._getFont(self._fontSizeAxis))
953 xLabel, yLabel= graphics.getXLabel(),graphics.getYLabel()
954 xLabelWH= dc.GetTextExtent(xLabel)
955 yLabelWH= dc.GetTextExtent(yLabel)
956 return titleWH, xLabelWH, yLabelWH
957
958 def _legendWH(self, dc, graphics):
959 """Returns the size in screen units for legend box"""
960 if self._legendEnabled != True:
961 legendBoxWH= symExt= txtExt= (0,0)
962 else:
963 # find max symbol size
964 symExt= graphics.getSymExtent(self.printerScale)
965 # find max legend text extent
966 dc.SetFont(self._getFont(self._fontSizeLegend))
967 txtList= graphics.getLegendNames()
968 txtExt= dc.GetTextExtent(txtList[0])
969 for txt in graphics.getLegendNames()[1:]:
970 txtExt= Numeric.maximum(txtExt,dc.GetTextExtent(txt))
971 maxW= symExt[0]+txtExt[0]
972 maxH= max(symExt[1],txtExt[1])
973 # padding .1 for lhs of legend box and space between lines
974 maxW= maxW* 1.1
975 maxH= maxH* 1.1 * len(txtList)
976 dc.SetFont(self._getFont(self._fontSizeAxis))
977 legendBoxWH= (maxW,maxH)
978 return (legendBoxWH, symExt, txtExt)
979
980 def _drawRubberBand(self, corner1, corner2):
981 """Draws/erases rect box from corner1 to corner2"""
982 ptx,pty,rectWidth,rectHeight= self._point2ClientCoord(corner1, corner2)
983 # draw rectangle
984 dc = wx.ClientDC( self )
985 dc.BeginDrawing()
986 dc.SetPen(wx.Pen(wx.BLACK))
987 dc.SetBrush(wx.Brush( wx.WHITE, wx.TRANSPARENT ) )
988 dc.SetLogicalFunction(wx.INVERT)
d7403ad2 989 dc.DrawRectangle( ptx,pty, rectWidth,rectHeight)
9d6685e2
RD
990 dc.SetLogicalFunction(wx.COPY)
991 dc.EndDrawing()
992
993 def _getFont(self,size):
994 """Take font size, adjusts if printing and returns wx.Font"""
995 s = size*self.printerScale
996 of = self.GetFont()
997 # Linux speed up to get font from cache rather than X font server
998 key = (int(s), of.GetFamily (), of.GetStyle (), of.GetWeight ())
999 font = self._fontCache.get (key, None)
1000 if font:
1001 return font # yeah! cache hit
1002 else:
1003 font = wx.Font(int(s), of.GetFamily(), of.GetStyle(), of.GetWeight())
1004 self._fontCache[key] = font
1005 return font
1006
1007
1008 def _point2ClientCoord(self, corner1, corner2):
1009 """Converts user point coords to client screen int coords x,y,width,height"""
1010 c1= Numeric.array(corner1)
1011 c2= Numeric.array(corner2)
1012 # convert to screen coords
1013 pt1= c1*self._pointScale+self._pointShift
1014 pt2= c2*self._pointScale+self._pointShift
1015 # make height and width positive
1016 pul= Numeric.minimum(pt1,pt2) # Upper left corner
1017 plr= Numeric.maximum(pt1,pt2) # Lower right corner
1018 rectWidth, rectHeight= plr-pul
1019 ptx,pty= pul
00b9c867 1020 return ptx, pty, rectWidth, rectHeight
9d6685e2
RD
1021
1022 def _axisInterval(self, spec, lower, upper):
1023 """Returns sensible axis range for given spec"""
1024 if spec == 'none' or spec == 'min':
1025 if lower == upper:
1026 return lower-0.5, upper+0.5
1027 else:
1028 return lower, upper
1029 elif spec == 'auto':
1030 range = upper-lower
1031 if range == 0.:
1032 return lower-0.5, upper+0.5
1033 log = Numeric.log10(range)
1034 power = Numeric.floor(log)
1035 fraction = log-power
1036 if fraction <= 0.05:
1037 power = power-1
1038 grid = 10.**power
1039 lower = lower - lower % grid
1040 mod = upper % grid
1041 if mod != 0:
1042 upper = upper - mod + grid
1043 return lower, upper
1044 elif type(spec) == type(()):
1045 lower, upper = spec
1046 if lower <= upper:
1047 return lower, upper
1048 else:
1049 return upper, lower
1050 else:
1051 raise ValueError, str(spec) + ': illegal axis specification'
1052
1053 def _drawAxes(self, dc, p1, p2, scale, shift, xticks, yticks):
1054
1055 penWidth= self.printerScale # increases thickness for printing only
00b9c867 1056 dc.SetPen(wx.Pen(wx.NamedColour('BLACK'), penWidth))
9d6685e2
RD
1057
1058 # set length of tick marks--long ones make grid
1059 if self._gridEnabled:
1060 x,y,width,height= self._point2ClientCoord(p1,p2)
1061 yTickLength= width/2.0 +1
1062 xTickLength= height/2.0 +1
1063 else:
1064 yTickLength= 3 * self.printerScale # lengthens lines for printing
1065 xTickLength= 3 * self.printerScale
1066
1067 if self._xSpec is not 'none':
1068 lower, upper = p1[0],p2[0]
1069 text = 1
1070 for y, d in [(p1[1], -xTickLength), (p2[1], xTickLength)]: # miny, maxy and tick lengths
1071 a1 = scale*Numeric.array([lower, y])+shift
1072 a2 = scale*Numeric.array([upper, y])+shift
1073 dc.DrawLine(a1[0],a1[1],a2[0],a2[1]) # draws upper and lower axis line
1074 for x, label in xticks:
1075 pt = scale*Numeric.array([x, y])+shift
1076 dc.DrawLine(pt[0],pt[1],pt[0],pt[1] + d) # draws tick mark d units
1077 if text:
1078 dc.DrawText(label,pt[0],pt[1])
1079 text = 0 # axis values not drawn on top side
1080
1081 if self._ySpec is not 'none':
1082 lower, upper = p1[1],p2[1]
1083 text = 1
1084 h = dc.GetCharHeight()
1085 for x, d in [(p1[0], -yTickLength), (p2[0], yTickLength)]:
1086 a1 = scale*Numeric.array([x, lower])+shift
1087 a2 = scale*Numeric.array([x, upper])+shift
1088 dc.DrawLine(a1[0],a1[1],a2[0],a2[1])
1089 for y, label in yticks:
1090 pt = scale*Numeric.array([x, y])+shift
1091 dc.DrawLine(pt[0],pt[1],pt[0]-d,pt[1])
1092 if text:
1093 dc.DrawText(label,pt[0]-dc.GetTextExtent(label)[0],
1094 pt[1]-0.5*h)
1095 text = 0 # axis values not drawn on right side
1096
1097 def _ticks(self, lower, upper):
1098 ideal = (upper-lower)/7.
1099 log = Numeric.log10(ideal)
1100 power = Numeric.floor(log)
1101 fraction = log-power
1102 factor = 1.
1103 error = fraction
1104 for f, lf in self._multiples:
1105 e = Numeric.fabs(fraction-lf)
1106 if e < error:
1107 error = e
1108 factor = f
1109 grid = factor * 10.**power
1110 if power > 4 or power < -4:
1111 format = '%+7.1e'
1112 elif power >= 0:
1113 digits = max(1, int(power))
1114 format = '%' + `digits`+'.0f'
1115 else:
1116 digits = -int(power)
1117 format = '%'+`digits+2`+'.'+`digits`+'f'
1118 ticks = []
1119 t = -grid*Numeric.floor(-lower/grid)
1120 while t <= upper:
1121 ticks.append( (t, format % (t,)) )
1122 t = t + grid
1123 return ticks
1124
1125 _multiples = [(2., Numeric.log10(2.)), (5., Numeric.log10(5.))]
1126
1127
1128#-------------------------------------------------------------------------------
1129# Used to layout the printer page
1130
1131class PlotPrintout(wx.Printout):
1132 """Controls how the plot is made in printing and previewing"""
1133 # Do not change method names in this class,
1134 # we have to override wx.Printout methods here!
1135 def __init__(self, graph):
1136 """graph is instance of plotCanvas to be printed or previewed"""
1137 wx.Printout.__init__(self)
1138 self.graph = graph
1139
1140 def HasPage(self, page):
1141 if page == 1:
1142 return True
1143 else:
1144 return False
1145
1146 def GetPageInfo(self):
e5ce86d8 1147 return (1, 1, 1, 1) # disable page numbers
9d6685e2
RD
1148
1149 def OnPrintPage(self, page):
00b9c867 1150 dc = self.GetDC() # allows using floats for certain functions
9d6685e2
RD
1151## print "PPI Printer",self.GetPPIPrinter()
1152## print "PPI Screen", self.GetPPIScreen()
1153## print "DC GetSize", dc.GetSize()
1154## print "GetPageSizePixels", self.GetPageSizePixels()
1155 # Note PPIScreen does not give the correct number
1156 # Calulate everything for printer and then scale for preview
1157 PPIPrinter= self.GetPPIPrinter() # printer dots/inch (w,h)
1158 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1159 dcSize= dc.GetSize() # DC size
1160 pageSize= self.GetPageSizePixels() # page size in terms of pixcels
1161 clientDcSize= self.graph.GetClientSize()
1162
1163 # find what the margins are (mm)
1164 margLeftSize,margTopSize= self.graph.pageSetupData.GetMarginTopLeft()
1165 margRightSize, margBottomSize= self.graph.pageSetupData.GetMarginBottomRight()
1166
1167 # calculate offset and scale for dc
1168 pixLeft= margLeftSize*PPIPrinter[0]/25.4 # mm*(dots/in)/(mm/in)
1169 pixRight= margRightSize*PPIPrinter[0]/25.4
1170 pixTop= margTopSize*PPIPrinter[1]/25.4
1171 pixBottom= margBottomSize*PPIPrinter[1]/25.4
1172
1173 plotAreaW= pageSize[0]-(pixLeft+pixRight)
1174 plotAreaH= pageSize[1]-(pixTop+pixBottom)
1175
1176 # ratio offset and scale to screen size if preview
1177 if self.IsPreview():
1178 ratioW= float(dcSize[0])/pageSize[0]
1179 ratioH= float(dcSize[1])/pageSize[1]
1180 pixLeft *= ratioW
1181 pixTop *= ratioH
1182 plotAreaW *= ratioW
1183 plotAreaH *= ratioH
1184
1185 # rescale plot to page or preview plot area
1186 self.graph._setSize(plotAreaW,plotAreaH)
1187
1188 # Set offset and scale
1189 dc.SetDeviceOrigin(pixLeft,pixTop)
1190
1191 # Thicken up pens and increase marker size for printing
1192 ratioW= float(plotAreaW)/clientDcSize[0]
1193 ratioH= float(plotAreaH)/clientDcSize[1]
1194 aveScale= (ratioW+ratioH)/2
1195 self.graph._setPrinterScale(aveScale) # tickens up pens for printing
1196
1197 self.graph._printDraw(dc)
1198 # rescale back to original
1199 self.graph._setSize()
1200 self.graph._setPrinterScale(1)
1201
1202 return True
1203
9d6685e2
RD
1204
1205
1206
1207#---------------------------------------------------------------------------
1208# if running standalone...
1209#
1210# ...a sample implementation using the above
1211#
1212
1213def _draw1Objects():
1214 # 100 points sin function, plotted as green circles
1215 data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
1216 data1.shape = (100, 2)
1217 data1[:,1] = Numeric.sin(data1[:,0])
1218 markers1 = PolyMarker(data1, legend='Green Markers', colour='green', marker='circle',size=1)
1219
1220 # 50 points cos function, plotted as red line
1221 data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
1222 data1.shape = (50,2)
1223 data1[:,1] = Numeric.cos(data1[:,0])
1224 lines = PolyLine(data1, legend= 'Red Line', colour='red')
1225
1226 # A few more points...
1227 pi = Numeric.pi
1228 markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1229 (3.*pi/4., -1)], legend='Cross Legend', colour='blue',
1230 marker='cross')
1231
1232 return PlotGraphics([markers1, lines, markers2],"Graph Title", "X Axis", "Y Axis")
1233
1234def _draw2Objects():
1235 # 100 points sin function, plotted as green dots
1236 data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
1237 data1.shape = (100, 2)
1238 data1[:,1] = Numeric.sin(data1[:,0])
1239 line1 = PolyLine(data1, legend='Green Line', colour='green', width=6, style=wx.DOT)
1240
1241 # 50 points cos function, plotted as red dot-dash
1242 data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
1243 data1.shape = (50,2)
1244 data1[:,1] = Numeric.cos(data1[:,0])
1245 line2 = PolyLine(data1, legend='Red Line', colour='red', width=3, style= wx.DOT_DASH)
1246
1247 # A few more points...
1248 pi = Numeric.pi
1249 markers1 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
1250 (3.*pi/4., -1)], legend='Cross Hatch Square', colour='blue', width= 3, size= 6,
1251 fillcolour= 'red', fillstyle= wx.CROSSDIAG_HATCH,
1252 marker='square')
1253
1254 return PlotGraphics([markers1, line1, line2], "Big Markers with Different Line Styles")
1255
1256def _draw3Objects():
1257 markerList= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1258 'cross', 'plus', 'circle']
1259 m=[]
1260 for i in range(len(markerList)):
1261 m.append(PolyMarker([(2*i+.5,i+.5)], legend=markerList[i], colour='blue',
1262 marker=markerList[i]))
1263 return PlotGraphics(m, "Selection of Markers", "Minimal Axis", "No Axis")
1264
1265def _draw4Objects():
1266 # 25,000 point line
1267 data1 = Numeric.arange(5e5,1e6,10)
1268 data1.shape = (25000, 2)
1269 line1 = PolyLine(data1, legend='Wide Line', colour='green', width=5)
1270
1271 # A few more points...
1272 markers2 = PolyMarker(data1, legend='Square', colour='blue',
1273 marker='square')
1274 return PlotGraphics([line1, markers2], "25,000 Points", "Value X", "")
1275
1276def _draw5Objects():
1277 # Empty graph with axis defined but no points/lines
1278 points=[]
1279 line1 = PolyLine(points, legend='Wide Line', colour='green', width=5)
1280 return PlotGraphics([line1], "Empty Plot With Just Axes", "Value X", "Value Y")
1281
00b9c867
RD
1282def _draw6Objects():
1283 # Bar graph
1284 points1=[(1,0), (1,10)]
1285 line1 = PolyLine(points1, colour='green', legend='Feb.', width=10)
1286 points1g=[(2,0), (2,4)]
1287 line1g = PolyLine(points1g, colour='red', legend='Mar.', width=10)
1288 points1b=[(3,0), (3,6)]
1289 line1b = PolyLine(points1b, colour='blue', legend='Apr.', width=10)
1290
1291 points2=[(4,0), (4,12)]
1292 line2 = PolyLine(points2, colour='Yellow', legend='May', width=10)
1293 points2g=[(5,0), (5,8)]
1294 line2g = PolyLine(points2g, colour='orange', legend='June', width=10)
1295 points2b=[(6,0), (6,4)]
1296 line2b = PolyLine(points2b, colour='brown', legend='July', width=10)
1297
1298 return PlotGraphics([line1, line1g, line1b, line2, line2g, line2b],
1299 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1300
9d6685e2
RD
1301
1302class TestFrame(wx.Frame):
1303 def __init__(self, parent, id, title):
1304 wx.Frame.__init__(self, parent, id, title,
1305 wx.DefaultPosition, (600, 400))
1306
1307 # Now Create the menu bar and items
1308 self.mainmenu = wx.MenuBar()
1309
1310 menu = wx.Menu()
1311 menu.Append(200, 'Page Setup...', 'Setup the printer page')
1312 self.Bind(wx.EVT_MENU, self.OnFilePageSetup, id=200)
1313
1314 menu.Append(201, 'Print Preview...', 'Show the current plot on page')
1315 self.Bind(wx.EVT_MENU, self.OnFilePrintPreview, id=201)
1316
1317 menu.Append(202, 'Print...', 'Print the current plot')
1318 self.Bind(wx.EVT_MENU, self.OnFilePrint, id=202)
1319
1320 menu.Append(203, 'Save Plot...', 'Save current plot')
1321 self.Bind(wx.EVT_MENU, self.OnSaveFile, id=203)
1322
1323 menu.Append(205, 'E&xit', 'Enough of this already!')
1324 self.Bind(wx.EVT_MENU, self.OnFileExit, id=205)
1325 self.mainmenu.Append(menu, '&File')
1326
1327 menu = wx.Menu()
1328 menu.Append(206, 'Draw1', 'Draw plots1')
1329 self.Bind(wx.EVT_MENU,self.OnPlotDraw1, id=206)
1330 menu.Append(207, 'Draw2', 'Draw plots2')
1331 self.Bind(wx.EVT_MENU,self.OnPlotDraw2, id=207)
1332 menu.Append(208, 'Draw3', 'Draw plots3')
1333 self.Bind(wx.EVT_MENU,self.OnPlotDraw3, id=208)
1334 menu.Append(209, 'Draw4', 'Draw plots4')
1335 self.Bind(wx.EVT_MENU,self.OnPlotDraw4, id=209)
1336 menu.Append(210, 'Draw5', 'Draw plots5')
1337 self.Bind(wx.EVT_MENU,self.OnPlotDraw5, id=210)
00b9c867
RD
1338 menu.Append(260, 'Draw6', 'Draw plots6')
1339 self.Bind(wx.EVT_MENU,self.OnPlotDraw6, id=260)
1340
9d6685e2
RD
1341
1342 menu.Append(211, '&Redraw', 'Redraw plots')
1343 self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211)
1344 menu.Append(212, '&Clear', 'Clear canvas')
1345 self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212)
1346 menu.Append(213, '&Scale', 'Scale canvas')
1347 self.Bind(wx.EVT_MENU,self.OnPlotScale, id=213)
1348 menu.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind=wx.ITEM_CHECK)
1349 self.Bind(wx.EVT_MENU,self.OnEnableZoom, id=214)
1350 menu.Append(215, 'Enable &Grid', 'Turn on Grid', kind=wx.ITEM_CHECK)
1351 self.Bind(wx.EVT_MENU,self.OnEnableGrid, id=215)
1352 menu.Append(220, 'Enable &Legend', 'Turn on Legend', kind=wx.ITEM_CHECK)
1353 self.Bind(wx.EVT_MENU,self.OnEnableLegend, id=220)
1354 menu.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1355 self.Bind(wx.EVT_MENU,self.OnScrUp, id=225)
1356 menu.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1357 self.Bind(wx.EVT_MENU,self.OnScrRt, id=230)
1358 menu.Append(235, '&Plot Reset', 'Reset to original plot')
1359 self.Bind(wx.EVT_MENU,self.OnReset, id=235)
1360
1361 self.mainmenu.Append(menu, '&Plot')
1362
1363 menu = wx.Menu()
1364 menu.Append(300, '&About', 'About this thing...')
1365 self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=300)
1366 self.mainmenu.Append(menu, '&Help')
1367
1368 self.SetMenuBar(self.mainmenu)
1369
1370 # A status bar to tell people what's happening
1371 self.CreateStatusBar(1)
1372
1373 self.client = PlotCanvas(self)
1374 # Create mouse event for showing cursor coords in status bar
1375 self.client.Bind(wx.EVT_LEFT_DOWN, self.OnMouseLeftDown)
1376 self.Show(True)
1377
1378 def OnMouseLeftDown(self,event):
1379 s= "Left Mouse Down at Point: (%.4f, %.4f)" % self.client.GetXY(event)
1380 self.SetStatusText(s)
1381 event.Skip()
1382
1383 def OnFilePageSetup(self, event):
1384 self.client.PageSetup()
1385
1386 def OnFilePrintPreview(self, event):
1387 self.client.PrintPreview()
1388
1389 def OnFilePrint(self, event):
1390 self.client.Printout()
1391
1392 def OnSaveFile(self, event):
1393 self.client.SaveFile()
1394
1395 def OnFileExit(self, event):
1396 self.Close()
1397
1398 def OnPlotDraw1(self, event):
1399 self.resetDefaults()
1400 self.client.Draw(_draw1Objects())
1401
1402 def OnPlotDraw2(self, event):
1403 self.resetDefaults()
1404 self.client.Draw(_draw2Objects())
1405
1406 def OnPlotDraw3(self, event):
1407 self.resetDefaults()
1408 self.client.SetFont(wx.Font(10,wx.SCRIPT,wx.NORMAL,wx.NORMAL))
1409 self.client.SetFontSizeAxis(20)
1410 self.client.SetFontSizeLegend(12)
1411 self.client.SetXSpec('min')
1412 self.client.SetYSpec('none')
1413 self.client.Draw(_draw3Objects())
1414
1415 def OnPlotDraw4(self, event):
1416 self.resetDefaults()
1417 drawObj= _draw4Objects()
1418 self.client.Draw(drawObj)
1419## # profile
1420## start = time.clock()
1421## for x in range(10):
1422## self.client.Draw(drawObj)
1423## print "10 plots of Draw4 took: %f sec."%(time.clock() - start)
1424## # profile end
1425
1426 def OnPlotDraw5(self, event):
1427 # Empty plot with just axes
1428 self.resetDefaults()
1429 drawObj= _draw5Objects()
1430 # make the axis X= (0,5), Y=(0,10)
1431 # (default with None is X= (-1,1), Y= (-1,1))
1432 self.client.Draw(drawObj, xAxis= (0,5), yAxis= (0,10))
1433
00b9c867
RD
1434 def OnPlotDraw6(self, event):
1435 #Bar Graph Example
1436 self.resetDefaults()
1437 #self.client.SetEnableLegend(True) #turn on Legend
1438 #self.client.SetEnableGrid(True) #turn on Grid
1439 self.client.SetXSpec('none') #turns off x-axis scale
1440 self.client.SetYSpec('auto')
1441 self.client.Draw(_draw6Objects(), xAxis= (0,7))
1442
9d6685e2
RD
1443 def OnPlotRedraw(self,event):
1444 self.client.Redraw()
1445
1446 def OnPlotClear(self,event):
1447 self.client.Clear()
1448
1449 def OnPlotScale(self, event):
1450 if self.client.last_draw != None:
1451 graphics, xAxis, yAxis= self.client.last_draw
1452 self.client.Draw(graphics,(1,3.05),(0,1))
1453
1454 def OnEnableZoom(self, event):
1455 self.client.SetEnableZoom(event.IsChecked())
1456
1457 def OnEnableGrid(self, event):
1458 self.client.SetEnableGrid(event.IsChecked())
1459
1460 def OnEnableLegend(self, event):
1461 self.client.SetEnableLegend(event.IsChecked())
1462
1463 def OnScrUp(self, event):
1464 self.client.ScrollUp(1)
1465
1466 def OnScrRt(self,event):
1467 self.client.ScrollRight(2)
1468
1469 def OnReset(self,event):
1470 self.client.Reset()
1471
1472 def OnHelpAbout(self, event):
33785d9f
RD
1473 from wx.lib.dialogs import ScrolledMessageDialog
1474 about = ScrolledMessageDialog(self, __doc__, "About...")
9d6685e2
RD
1475 about.ShowModal()
1476
1477 def resetDefaults(self):
1478 """Just to reset the fonts back to the PlotCanvas defaults"""
1479 self.client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
1480 self.client.SetFontSizeAxis(10)
1481 self.client.SetFontSizeLegend(7)
1482 self.client.SetXSpec('auto')
1483 self.client.SetYSpec('auto')
1484
1485
1486def __test():
1487
1488 class MyApp(wx.App):
1489 def OnInit(self):
1490 wx.InitAllImageHandlers()
1491 frame = TestFrame(None, -1, "PlotCanvas")
1492 #frame.Show(True)
1493 self.SetTopWindow(frame)
1494 return True
1495
1496
1497 app = MyApp(0)
1498 app.MainLoop()
1499
1500if __name__ == '__main__':
1501 __test()