1 #-----------------------------------------------------------------------------
3 # Purpose: Line, Bar and Scatter Graphs
5 # Author: Gordon Williams
10 # Licence: Use as you wish.
11 #-----------------------------------------------------------------------------
12 # 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net)
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.
19 # 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net)
21 # o wxScrolledMessageDialog -> ScrolledMessageDialog
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
31 This is a simple light weight plotting module that can be used with
32 Boa or easily integrated into your own wxPython application. The
33 emphasis is on small size and fast plotting for large data sets. It
34 has a reasonable number of features to do line and scatter graphs
35 easily as well as simple bar graphs. It is not as sophisticated or
36 as powerful as SciPy Plt or Chaco. Both of these are great packages
37 but consume huge amounts of computer resources for simple plots.
38 They can be found at http://scipy.com
40 This file contains two parts; first the re-usable library stuff, then,
41 after a "if __name__=='__main__'" test, a simple frame and a few default
42 plots for examples and testing.
45 Written by K.Hinsen, R. Srinivasan;
46 Ported to wxPython Harm van der Heijden, feb 1999
48 Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
50 -Zooming using mouse "rubber band"
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
60 Did a lot of work here to speed markers up. Only a factor of 4
61 improvement though. Lines are much faster than markers, especially
62 filled markers. Stay away from circles and triangles unless you
63 only have a few thousand points.
65 Times for 25,000 points
72 triangle, triangle_down - 0.90
74 Thanks to Chris Barker for getting this version working on Linux.
76 Zooming 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.
86 # Needs Numeric or numarray
91 import numarray
as Numeric
#if numarray is used it is renamed Numeric
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
104 # Plotting classes...
107 """Base Class for lines and markers
108 - All methods are private.
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
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
123 def boundingBox(self
):
124 if len(self
.points
) == 0:
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])
130 minXY
= Numeric
.minimum
.reduce(self
.points
)
131 maxXY
= Numeric
.maximum
.reduce(self
.points
)
134 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
135 if len(self
.points
) == 0:
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
146 return self
.attributes
['legend']
149 class PolyLine(PolyPoints
):
150 """Class to define line type and style
151 - All methods except __init__ are private.
154 _attributes
= {'colour': 'black',
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
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
169 PolyPoints
.__init
__(self
, points
, attr
)
171 def draw(self
, dc
, printerScale
, coord
= None):
172 colour
= self
.attributes
['colour']
173 width
= self
.attributes
['width'] * printerScale
174 style
= self
.attributes
['style']
175 pen
= wx
.Pen(wx
.NamedColour(colour
), width
, style
)
176 pen
.SetCap(wx
.CAP_BUTT
)
179 dc
.DrawLines(self
.scaled
)
181 dc
.DrawLines(coord
) # draw legend line
183 def getSymExtent(self
, printerScale
):
184 """Width and Height of Marker"""
185 h
= self
.attributes
['width'] * printerScale
190 class PolyMarker(PolyPoints
):
191 """Class to define marker type and style
192 - All methods except __init__ are private.
195 _attributes
= {'colour': 'black',
199 'fillstyle': wx
.SOLID
,
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
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
226 PolyPoints
.__init
__(self
, points
, attr
)
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']
236 dc
.SetPen(wx
.Pen(wx
.NamedColour(colour
), width
))
238 dc
.SetBrush(wx
.Brush(wx
.NamedColour(fillcolour
),fillstyle
))
240 dc
.SetBrush(wx
.Brush(wx
.NamedColour(colour
), fillstyle
))
242 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
244 self
._drawmarkers
(dc
, coord
, marker
, size
) # draw legend marker
246 def getSymExtent(self
, printerScale
):
247 """Width and Height of Marker"""
248 s
= 5*self
.attributes
['size'] * printerScale
251 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
252 f
= eval('self._' +marker
)
255 def _circle(self
, dc
, coords
, size
=1):
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
))
262 def _dot(self
, dc
, coords
, size
=1):
263 dc
.DrawPointList(coords
)
265 def _square(self
, dc
, coords
, size
=1):
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
))
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)
277 dc
.DrawPolygonList(poly
.astype(Numeric
.Int32
))
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)
284 dc
.DrawPolygonList(poly
.astype(Numeric
.Int32
))
286 def _cross(self
, dc
, coords
, size
=1):
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
))
292 def _plus(self
, dc
, coords
, size
=1):
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
))
299 """Container to hold PolyXXX objects and graph labels
300 - All methods except __init__ are private.
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
310 if type(objects
) not in [list,tuple]:
311 raise TypeError, "objects argument should be list or tuple"
312 self
.objects
= objects
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
)
325 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
326 for o
in self
.objects
:
327 o
.scaleAndShift(scale
, shift
)
329 def setPrinterScale(self
, scale
):
330 """Thickens up lines and markers only for printing"""
331 self
.printerScale
= scale
333 def setXLabel(self
, xLabel
= ''):
334 """Set the X axis label on the graph"""
337 def setYLabel(self
, yLabel
= ''):
338 """Set the Y axis label on the graph"""
341 def setTitle(self
, title
= ''):
342 """Set the title at the top of graph"""
346 """Get x axis label string"""
350 """Get y axis label string"""
353 def getTitle(self
, title
= ''):
354 """Get the title at the top of graph"""
358 for o
in self
.objects
:
359 #t=time.clock() # profile info
360 o
.draw(dc
, self
.printerScale
)
362 #print o, "time=", dt
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
)
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()
380 return len(self
.objects
)
382 def __getitem__(self
, item
):
383 return self
.objects
[item
]
386 #-------------------------------------------------------------------------------
387 # Main window that you will want to import into your application.
389 class PlotCanvas(wx
.Window
):
390 """Subclass of a wx.Window to allow simple general plotting
391 of data with zoom, labels, and automatic axis scaling."""
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"""
398 wx
.Window
.__init
__(self
, parent
, id, pos
, size
, style
, name
)
401 self
.SetBackgroundColour("white")
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
)
410 # set curser as cross-hairs
411 self
.SetCursor(wx
.CROSS_CURSOR
)
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
425 self
._zoomInFactor
= 0.5
426 self
._zoomOutFactor
= 2
427 self
._zoomCorner
1= Numeric
.array([0.0, 0.0]) # left mouse down corner
428 self
._zoomCorner
2= Numeric
.array([0.0, 0.0]) # left mouse up corner
429 self
._zoomEnabled
= False
430 self
._hasDragged
= False
433 self
.last_draw
= None
438 self
._gridEnabled
= False
439 self
._legendEnabled
= False
443 self
._fontSizeAxis
= 10
444 self
._fontSizeTitle
= 15
445 self
._fontSizeLegend
= 7
447 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
448 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
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.
452 if wx
.Platform
!= "__WXMAC__":
453 self
.OnSize(None) # sets the initial size based on client size
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,
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.
468 if string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
469 dlg1
= wx
.FileDialog(
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
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'
483 'bmp, xbm, xpm, png, or jpg',
484 'File Name Error', wx
.OK | wx
.ICON_ERROR
)
490 break # now save file
491 else: # exit without saving
496 # File name has required extension
497 fType
= string
.lower(fileName
[-3:])
499 tp
= wx
.BITMAP_TYPE_BMP
# Save a Windows bitmap file.
501 tp
= wx
.BITMAP_TYPE_XBM
# Save an X bitmap file.
503 tp
= wx
.BITMAP_TYPE_XPM
# Save an XPM bitmap file.
505 tp
= wx
.BITMAP_TYPE_JPEG
# Save a JPG file.
507 tp
= wx
.BITMAP_TYPE_PNG
# Save a PNG file.
509 res
= self
._Buffer
.SaveFile(fileName
, tp
)
513 """Brings up the page setup dialog"""
514 data
= self
.pageSetupData
515 data
.SetPrintData(self
.print_data
)
516 dlg
= wx
.PageSetupDialog(self
.parent
, data
)
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
528 def Printout(self
, paper
=None):
529 """Print current plot."""
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
)
538 self
.print_data
= printer
.GetPrintDialogData().GetPrintData()
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
553 while not isinstance(frameInst
, wx
.Frame
):
554 frameInst
= frameInst
.GetParent()
555 frame
= wx
.PreviewFrame(self
.preview
, frameInst
, "Preview")
557 frame
.SetPosition(self
.GetPosition())
558 frame
.SetSize((500,400))
559 frame
.Centre(wx
.BOTH
)
562 def SetFontSizeAxis(self
, point
= 10):
563 """Set the tick and axis label font size (default is 10 point)"""
564 self
._fontSizeAxis
= point
566 def GetFontSizeAxis(self
):
567 """Get current tick and axis label font size in points"""
568 return self
._fontSizeAxis
570 def SetFontSizeTitle(self
, point
= 15):
571 """Set Title font size (default is 15 point)"""
572 self
._fontSizeTitle
= point
574 def GetFontSizeTitle(self
):
575 """Get current Title font size in points"""
576 return self
._fontSizeTitle
578 def SetFontSizeLegend(self
, point
= 7):
579 """Set Legend font size (default is 7 point)"""
580 self
._fontSizeLegend
= point
582 def GetFontSizeLegend(self
):
583 """Get current Legend font size in points"""
584 return self
._fontSizeLegend
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
592 def GetEnableZoom(self
):
593 """True if zooming enabled."""
594 return self
._zoomEnabled
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
603 def GetEnableGrid(self
):
604 """True if grid enabled."""
605 return self
._gridEnabled
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
614 def GetEnableLegend(self
):
615 """True if Legend enabled."""
616 return self
._legendEnabled
619 """Unzoom the plot."""
620 if self
.last_draw
is not None:
621 self
.Draw(self
.last_draw
[0])
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
)
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
)
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
643 def SetXSpec(self
, type= 'auto'):
644 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
646 'none' - shows no axis or tick mark values
647 'min' - shows min bounding box values
648 'auto' - rounds axis range to sensible values
652 def SetYSpec(self
, type= 'auto'):
653 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
655 'none' - shows no axis or tick mark values
656 'min' - shows min bounding box values
657 'auto' - rounds axis range to sensible values
662 """Returns current XSpec for axis"""
666 """Returns current YSpec for axis"""
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
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])
683 def GetXCurrentRange(self
):
684 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
685 return self
.last_draw
[1]
687 def GetYCurrentRange(self
):
688 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
689 return self
.last_draw
[2]
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
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)"
705 # check case for axis = (a,b) where a==b caused by improper zooms
707 if xAxis
[0] == xAxis
[1]:
710 if yAxis
[0] == yAxis
[1]:
714 # allows using floats for certain functions
715 dc
= wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
)
721 # set font size for every thing but title and legend
722 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
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
729 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
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)
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)
740 self
.last_draw
= (graphics
, xAxis
, yAxis
) # saves most recient values
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
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]))
757 yTextExtent
= (0,0) # No text for ticks
759 # TextExtents for Title and Axis Labels
760 titleWH
, xLabelWH
, yLabelWH
= self
._titleLablesWH
(dc
, graphics
)
762 # TextExtents for Legend
763 legendBoxWH
, legendSymExt
, legendTextExt
= self
._legendWH
(dc
, graphics
)
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
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)
787 # drawing legend makers and text
788 if self
._legendEnabled
:
789 self
._drawLegend
(dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
)
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
)
798 graphics
.scaleAndShift(scale
, shift
)
799 graphics
.setPrinterScale(self
.printerScale
) # thicken up lines and markers if printing
801 # set clipping area so drawing does not occur outside axis box
802 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(p1
, p2
)
803 dc
.SetClippingRegion(ptx
,pty
,rectWidth
,rectHeight
)
804 # Draw the lines and markers
805 #start = time.clock()
807 # print "entire graphics drawing took: %f second"%(time.clock() - start)
808 # remove the clipping region
809 dc
.DestroyClippingRegion()
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
)
819 """Erase the window."""
820 dc
= wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
)
822 self
.last_draw
= None
824 def Zoom(self
, Center
, Ratio
):
826 Centers on the X,Y coords given in Center
827 Zooms by the Ratio = (Xratio, Yratio) given
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
)
839 # event handlers **********************************
840 def OnMotion(self
, event
):
841 if self
._zoomEnabled
and event
.LeftIsDown():
843 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
845 self
._hasDragged
= True
846 self
._zoomCorner
2[0], self
._zoomCorner
2[1] = self
.GetXY(event
)
847 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # add new
849 def OnMouseLeftDown(self
,event
):
850 self
._zoomCorner
1[0], self
._zoomCorner
1[1]= self
.GetXY(event
)
852 def OnMouseLeftUp(self
, event
):
853 if self
._zoomEnabled
:
854 if self
._hasDragged
== True:
855 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
856 self
._zoomCorner
2[0], self
._zoomCorner
2[1]= self
.GetXY(event
)
857 self
._hasDragged
= False # reset flag
858 minX
, minY
= Numeric
.minimum( self
._zoomCorner
1, self
._zoomCorner
2)
859 maxX
, maxY
= Numeric
.maximum( self
._zoomCorner
1, self
._zoomCorner
2)
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) )
867 def OnMouseDoubleClick(self
,event
):
868 if self
._zoomEnabled
:
871 def OnMouseRightDown(self
,event
):
872 if self
._zoomEnabled
:
873 X
,Y
= self
.GetXY(event
)
874 self
.Zoom( (X
,Y
), (self
._zoomOutFactor
, self
._zoomOutFactor
) )
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
)
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()
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])
890 if self
.last_draw
is None:
893 graphics
, xSpec
, ySpec
= self
.last_draw
894 self
.Draw(graphics
,xSpec
,ySpec
)
897 # Private Methods **************************************************
898 def _setSize(self
, width
=None, height
=None):
899 """DC width and height."""
901 (self
.width
,self
.height
) = self
.GetClientSize()
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
])
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
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
)
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
)):
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
]))
940 raise TypeError, "object is neither PolyMarker or PolyLine instance"
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
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
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)
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
975 maxH
= maxH
* 1.1 * len(txtList
)
976 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
977 legendBoxWH
= (maxW
,maxH
)
978 return (legendBoxWH
, symExt
, txtExt
)
980 def _drawRubberBand(self
, corner1
, corner2
):
981 """Draws/erases rect box from corner1 to corner2"""
982 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(corner1
, corner2
)
984 dc
= wx
.ClientDC( self
)
986 dc
.SetPen(wx
.Pen(wx
.BLACK
))
987 dc
.SetBrush(wx
.Brush( wx
.WHITE
, wx
.TRANSPARENT
) )
988 dc
.SetLogicalFunction(wx
.INVERT
)
989 dc
.DrawRectangle( ptx
,pty
, rectWidth
,rectHeight
)
990 dc
.SetLogicalFunction(wx
.COPY
)
993 def _getFont(self
,size
):
994 """Take font size, adjusts if printing and returns wx.Font"""
995 s
= size
*self
.printerScale
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)
1001 return font
# yeah! cache hit
1003 font
= wx
.Font(int(s
), of
.GetFamily(), of
.GetStyle(), of
.GetWeight())
1004 self
._fontCache
[key
] = font
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
1020 return ptx
, pty
, rectWidth
, rectHeight
1022 def _axisInterval(self
, spec
, lower
, upper
):
1023 """Returns sensible axis range for given spec"""
1024 if spec
== 'none' or spec
== 'min':
1026 return lower
-0.5, upper
+0.5
1029 elif spec
== 'auto':
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:
1039 lower
= lower
- lower
% grid
1042 upper
= upper
- mod
+ grid
1044 elif type(spec
) == type(()):
1051 raise ValueError, str(spec
) + ': illegal axis specification'
1053 def _drawAxes(self
, dc
, p1
, p2
, scale
, shift
, xticks
, yticks
):
1055 penWidth
= self
.printerScale
# increases thickness for printing only
1056 dc
.SetPen(wx
.Pen(wx
.NamedColour('BLACK'), penWidth
))
1058 # set length of tick marks--long ones make grid
1059 if self
._gridEnabled
:
1060 x
,y
,width
,height
= self
._point
2ClientCoord
(p1
,p2
)
1061 yTickLength
= width
/2.0 +1
1062 xTickLength
= height
/2.0 +1
1064 yTickLength
= 3 * self
.printerScale
# lengthens lines for printing
1065 xTickLength
= 3 * self
.printerScale
1067 if self
._xSpec
is not 'none':
1068 lower
, upper
= p1
[0],p2
[0]
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
1078 dc
.DrawText(label
,pt
[0],pt
[1])
1079 text
= 0 # axis values not drawn on top side
1081 if self
._ySpec
is not 'none':
1082 lower
, upper
= p1
[1],p2
[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])
1093 dc
.DrawText(label
,pt
[0]-dc
.GetTextExtent(label
)[0],
1095 text
= 0 # axis values not drawn on right side
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
1104 for f
, lf
in self
._multiples
:
1105 e
= Numeric
.fabs(fraction
-lf
)
1109 grid
= factor
* 10.**power
1110 if power
> 4 or power
< -4:
1113 digits
= max(1, int(power
))
1114 format
= '%' + `digits`
+'.0f'
1116 digits
= -int(power
)
1117 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
1119 t
= -grid
*Numeric
.floor(-lower
/grid
)
1121 ticks
.append( (t
, format
% (t
,)) )
1125 _multiples
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))]
1128 #-------------------------------------------------------------------------------
1129 # Used to layout the printer page
1131 class 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
)
1140 def HasPage(self
, page
):
1146 def GetPageInfo(self
):
1147 return (1, 1, 1, 1) # disable page numbers
1149 def OnPrintPage(self
, page
):
1150 dc
= self
.GetDC() # allows using floats for certain functions
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()
1163 # find what the margins are (mm)
1164 margLeftSize
,margTopSize
= self
.graph
.pageSetupData
.GetMarginTopLeft()
1165 margRightSize
, margBottomSize
= self
.graph
.pageSetupData
.GetMarginBottomRight()
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
1173 plotAreaW
= pageSize
[0]-(pixLeft
+pixRight
)
1174 plotAreaH
= pageSize
[1]-(pixTop
+pixBottom
)
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]
1185 # rescale plot to page or preview plot area
1186 self
.graph
._setSize
(plotAreaW
,plotAreaH
)
1188 # Set offset and scale
1189 dc
.SetDeviceOrigin(pixLeft
,pixTop
)
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
1197 self
.graph
._printDraw
(dc
)
1198 # rescale back to original
1199 self
.graph
._setSize
()
1200 self
.graph
._setPrinterScale
(1)
1207 #---------------------------------------------------------------------------
1208 # if running standalone...
1210 # ...a sample implementation using the above
1213 def _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)
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')
1226 # A few more points...
1228 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1229 (3.*pi
/4., -1)], legend
='Cross Legend', colour
='blue',
1232 return PlotGraphics([markers1
, lines
, markers2
],"Graph Title", "X Axis", "Y Axis")
1234 def _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
)
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
)
1247 # A few more points...
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
,
1254 return PlotGraphics([markers1
, line1
, line2
], "Big Markers with Different Line Styles")
1256 def _draw3Objects():
1257 markerList
= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1258 'cross', 'plus', 'circle']
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")
1265 def _draw4Objects():
1267 data1
= Numeric
.arange(5e5
,1e6
,10)
1268 data1
.shape
= (25000, 2)
1269 line1
= PolyLine(data1
, legend
='Wide Line', colour
='green', width
=5)
1271 # A few more points...
1272 markers2
= PolyMarker(data1
, legend
='Square', colour
='blue',
1274 return PlotGraphics([line1
, markers2
], "25,000 Points", "Value X", "")
1276 def _draw5Objects():
1277 # Empty graph with axis defined but no points/lines
1279 line1
= PolyLine(points
, legend
='Wide Line', colour
='green', width
=5)
1280 return PlotGraphics([line1
], "Empty Plot With Just Axes", "Value X", "Value Y")
1282 def _draw6Objects():
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)
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)
1298 return PlotGraphics([line1
, line1g
, line1b
, line2
, line2g
, line2b
],
1299 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1302 class TestFrame(wx
.Frame
):
1303 def __init__(self
, parent
, id, title
):
1304 wx
.Frame
.__init
__(self
, parent
, id, title
,
1305 wx
.DefaultPosition
, (600, 400))
1307 # Now Create the menu bar and items
1308 self
.mainmenu
= wx
.MenuBar()
1311 menu
.Append(200, 'Page Setup...', 'Setup the printer page')
1312 self
.Bind(wx
.EVT_MENU
, self
.OnFilePageSetup
, id=200)
1314 menu
.Append(201, 'Print Preview...', 'Show the current plot on page')
1315 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrintPreview
, id=201)
1317 menu
.Append(202, 'Print...', 'Print the current plot')
1318 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrint
, id=202)
1320 menu
.Append(203, 'Save Plot...', 'Save current plot')
1321 self
.Bind(wx
.EVT_MENU
, self
.OnSaveFile
, id=203)
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')
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)
1338 menu
.Append(260, 'Draw6', 'Draw plots6')
1339 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw6
, id=260)
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)
1361 self
.mainmenu
.Append(menu
, '&Plot')
1364 menu
.Append(300, '&About', 'About this thing...')
1365 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=300)
1366 self
.mainmenu
.Append(menu
, '&Help')
1368 self
.SetMenuBar(self
.mainmenu
)
1370 # A status bar to tell people what's happening
1371 self
.CreateStatusBar(1)
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
)
1378 def OnMouseLeftDown(self
,event
):
1379 s
= "Left Mouse Down at Point: (%.4f, %.4f)" % self
.client
.GetXY(event
)
1380 self
.SetStatusText(s
)
1383 def OnFilePageSetup(self
, event
):
1384 self
.client
.PageSetup()
1386 def OnFilePrintPreview(self
, event
):
1387 self
.client
.PrintPreview()
1389 def OnFilePrint(self
, event
):
1390 self
.client
.Printout()
1392 def OnSaveFile(self
, event
):
1393 self
.client
.SaveFile()
1395 def OnFileExit(self
, event
):
1398 def OnPlotDraw1(self
, event
):
1399 self
.resetDefaults()
1400 self
.client
.Draw(_draw1Objects())
1402 def OnPlotDraw2(self
, event
):
1403 self
.resetDefaults()
1404 self
.client
.Draw(_draw2Objects())
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())
1415 def OnPlotDraw4(self
, event
):
1416 self
.resetDefaults()
1417 drawObj
= _draw4Objects()
1418 self
.client
.Draw(drawObj
)
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)
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))
1434 def OnPlotDraw6(self
, event
):
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))
1443 def OnPlotRedraw(self
,event
):
1444 self
.client
.Redraw()
1446 def OnPlotClear(self
,event
):
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))
1454 def OnEnableZoom(self
, event
):
1455 self
.client
.SetEnableZoom(event
.IsChecked())
1457 def OnEnableGrid(self
, event
):
1458 self
.client
.SetEnableGrid(event
.IsChecked())
1460 def OnEnableLegend(self
, event
):
1461 self
.client
.SetEnableLegend(event
.IsChecked())
1463 def OnScrUp(self
, event
):
1464 self
.client
.ScrollUp(1)
1466 def OnScrRt(self
,event
):
1467 self
.client
.ScrollRight(2)
1469 def OnReset(self
,event
):
1472 def OnHelpAbout(self
, event
):
1473 from wx
.lib
.dialogs
import ScrolledMessageDialog
1474 about
= ScrolledMessageDialog(self
, __doc__
, "About...")
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')
1488 class MyApp(wx
.App
):
1490 wx
.InitAllImageHandlers()
1491 frame
= TestFrame(None, -1, "PlotCanvas")
1493 self
.SetTopWindow(frame
)
1500 if __name__
== '__main__':