]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/plot.py
1 #-----------------------------------------------------------------------------
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
25 This is a simple light weight plotting module that can be used with
26 Boa or easily integrated into your own wxPython application. The
27 emphasis is on small size and fast plotting for large data sets. It
28 has a reasonable number of features to do line and scatter graphs
29 easily. It is not as sophisticated or as powerful as SciPy Plt or
30 Chaco. Both of these are great packages but consume huge amounts of
31 computer resources for simple plots. They can be found at
34 This file contains two parts; first the re-usable library stuff, then,
35 after a "if __name__=='__main__'" test, a simple frame and a few default
36 plots for examples and testing.
39 Written by K.Hinsen, R. Srinivasan;
40 Ported to wxPython Harm van der Heijden, feb 1999
42 Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
44 -Zooming using mouse "rubber band"
47 -Printing, preview, and page set up (margins)
48 -Axis and title labels
49 -Cursor xy axis values
50 -Doc strings and lots of comments
51 -Optimizations for large number of points
54 Did a lot of work here to speed markers up. Only a factor of 4
55 improvement though. Lines are much faster than markers, especially
56 filled markers. Stay away from circles and triangles unless you
57 only have a few thousand points.
59 Times for 25,000 points
66 triangle, triangle_down - 0.90
68 Thanks to Chris Barker for getting this version working on Linux.
70 Zooming controls with mouse (when enabled):
71 Left mouse drag - Zoom box.
72 Left mouse double click - reset zoom.
73 Right mouse click - zoom out centred on click location.
85 import numarray
as Numeric
#if numarray is used it is renamed Numeric
88 This module requires the Numeric or numarray module,
89 which could not be imported. It probably is not installed
90 (it's not part of the standard Python distribution). See the
91 Python site (http://www.python.org) for information on
92 downloading source or binaries."""
93 raise ImportError, "Numeric or numarray not found. \n" + msg
101 """Base Class for lines and markers
102 - All methods are private.
105 def __init__(self
, points
, attr
):
106 self
.points
= Numeric
.array(points
)
107 self
.currentScale
= (1,1)
108 self
.currentShift
= (0,0)
109 self
.scaled
= self
.points
111 self
.attributes
.update(self
._attributes
)
112 for name
, value
in attr
.items():
113 if name
not in self
._attributes
.keys():
114 raise KeyError, "Style attribute incorrect. Should be one of %s" % self
._attributes
.keys()
115 self
.attributes
[name
] = value
117 def boundingBox(self
):
118 if len(self
.points
) == 0:
120 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
121 minXY
= Numeric
.array([-1,-1])
122 maxXY
= Numeric
.array([ 1, 1])
124 minXY
= Numeric
.minimum
.reduce(self
.points
)
125 maxXY
= Numeric
.maximum
.reduce(self
.points
)
128 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
129 if len(self
.points
) == 0:
132 if (scale
is not self
.currentScale
) or (shift
is not self
.currentShift
):
133 # update point scaling
134 self
.scaled
= scale
*self
.points
+shift
135 self
.currentScale
= scale
136 self
.currentShift
= shift
137 # else unchanged use the current scaling
140 return self
.attributes
['legend']
143 class PolyLine(PolyPoints
):
144 """Class to define line type and style
145 - All methods except __init__ are private.
148 _attributes
= {'colour': 'black',
153 def __init__(self
, points
, **attr
):
154 """Creates PolyLine object
155 points - sequence (array, tuple or list) of (x,y) points making up line
156 **attr - key word attributes
158 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
159 'width'= 1, - Pen width
160 'style'= wx.SOLID, - wx.Pen style
161 'legend'= '' - Line Legend to display
163 PolyPoints
.__init
__(self
, points
, attr
)
165 def draw(self
, dc
, printerScale
, coord
= None):
166 colour
= self
.attributes
['colour']
167 width
= self
.attributes
['width'] * printerScale
168 style
= self
.attributes
['style']
169 dc
.SetPen(wx
.Pen(wx
.NamedColour(colour
), int(width
), style
))
171 dc
.DrawLines(self
.scaled
)
173 dc
.DrawLines(coord
) # draw legend line
175 def getSymExtent(self
, printerScale
):
176 """Width and Height of Marker"""
177 h
= self
.attributes
['width'] * printerScale
182 class PolyMarker(PolyPoints
):
183 """Class to define marker type and style
184 - All methods except __init__ are private.
187 _attributes
= {'colour': 'black',
191 'fillstyle': wx
.SOLID
,
195 def __init__(self
, points
, **attr
):
196 """Creates PolyMarker object
197 points - sequence (array, tuple or list) of (x,y) points
198 **attr - key word attributes
200 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
201 'width'= 1, - Pen width
202 'size'= 2, - Marker size
203 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
204 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
205 'marker'= 'circle' - Marker shape
206 'legend'= '' - Marker Legend to display
218 PolyPoints
.__init
__(self
, points
, attr
)
220 def draw(self
, dc
, printerScale
, coord
= None):
221 colour
= self
.attributes
['colour']
222 width
= self
.attributes
['width'] * printerScale
223 size
= self
.attributes
['size'] * printerScale
224 fillcolour
= self
.attributes
['fillcolour']
225 fillstyle
= self
.attributes
['fillstyle']
226 marker
= self
.attributes
['marker']
228 dc
.SetPen(wx
.Pen(wx
.NamedColour(colour
),int(width
)))
230 dc
.SetBrush(wx
.Brush(wx
.NamedColour(fillcolour
),fillstyle
))
232 dc
.SetBrush(wx
.Brush(wx
.NamedColour(colour
), fillstyle
))
234 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
236 self
._drawmarkers
(dc
, coord
, marker
, size
) # draw legend marker
238 def getSymExtent(self
, printerScale
):
239 """Width and Height of Marker"""
240 s
= 5*self
.attributes
['size'] * printerScale
243 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
244 f
= eval('self._' +marker
)
247 def _circle(self
, dc
, coords
, size
=1):
250 rect
= Numeric
.zeros((len(coords
),4),Numeric
.Float
)+[0.0,0.0,wh
,wh
]
251 rect
[:,0:2]= coords
-[fact
,fact
]
252 dc
.DrawEllipseList(rect
.astype(Numeric
.Int32
))
254 def _dot(self
, dc
, coords
, size
=1):
255 dc
.DrawPointList(coords
)
257 def _square(self
, dc
, coords
, size
=1):
260 rect
= Numeric
.zeros((len(coords
),4),Numeric
.Float
)+[0.0,0.0,wh
,wh
]
261 rect
[:,0:2]= coords
-[fact
,fact
]
262 dc
.DrawRectangleList(rect
.astype(Numeric
.Int32
))
264 def _triangle(self
, dc
, coords
, size
=1):
265 shape
= [(-2.5*size
,1.44*size
), (2.5*size
,1.44*size
), (0.0,-2.88*size
)]
266 poly
= Numeric
.repeat(coords
,3)
267 poly
.shape
= (len(coords
),3,2)
269 dc
.DrawPolygonList(poly
.astype(Numeric
.Int32
))
271 def _triangle_down(self
, dc
, coords
, size
=1):
272 shape
= [(-2.5*size
,-1.44*size
), (2.5*size
,-1.44*size
), (0.0,2.88*size
)]
273 poly
= Numeric
.repeat(coords
,3)
274 poly
.shape
= (len(coords
),3,2)
276 dc
.DrawPolygonList(poly
.astype(Numeric
.Int32
))
278 def _cross(self
, dc
, coords
, size
=1):
280 for f
in [[-fact
,-fact
,fact
,fact
],[-fact
,fact
,fact
,-fact
]]:
281 lines
= Numeric
.concatenate((coords
,coords
),axis
=1)+f
282 dc
.DrawLineList(lines
.astype(Numeric
.Int32
))
284 def _plus(self
, dc
, coords
, size
=1):
286 for f
in [[-fact
,0,fact
,0],[0,-fact
,0,fact
]]:
287 lines
= Numeric
.concatenate((coords
,coords
),axis
=1)+f
288 dc
.DrawLineList(lines
.astype(Numeric
.Int32
))
291 """Container to hold PolyXXX objects and graph labels
292 - All methods except __init__ are private.
295 def __init__(self
, objects
, title
='', xLabel
='', yLabel
= ''):
296 """Creates PlotGraphics object
297 objects - list of PolyXXX objects to make graph
298 title - title shown at top of graph
299 xLabel - label shown on x-axis
300 yLabel - label shown on y-axis
302 if type(objects
) not in [list,tuple]:
303 raise TypeError, "objects argument should be list or tuple"
304 self
.objects
= objects
309 def boundingBox(self
):
310 p1
, p2
= self
.objects
[0].boundingBox()
311 for o
in self
.objects
[1:]:
312 p1o
, p2o
= o
.boundingBox()
313 p1
= Numeric
.minimum(p1
, p1o
)
314 p2
= Numeric
.maximum(p2
, p2o
)
317 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
318 for o
in self
.objects
:
319 o
.scaleAndShift(scale
, shift
)
321 def setPrinterScale(self
, scale
):
322 """Thickens up lines and markers only for printing"""
323 self
.printerScale
= scale
325 def setXLabel(self
, xLabel
= ''):
326 """Set the X axis label on the graph"""
329 def setYLabel(self
, yLabel
= ''):
330 """Set the Y axis label on the graph"""
333 def setTitle(self
, title
= ''):
334 """Set the title at the top of graph"""
338 """Get x axis label string"""
342 """Get y axis label string"""
345 def getTitle(self
, title
= ''):
346 """Get the title at the top of graph"""
350 for o
in self
.objects
:
351 #t=time.clock() # profile info
352 o
.draw(dc
, self
.printerScale
)
354 #print o, "time=", dt
356 def getSymExtent(self
, printerScale
):
357 """Get max width and height of lines and markers symbols for legend"""
358 symExt
= self
.objects
[0].getSymExtent(printerScale
)
359 for o
in self
.objects
[1:]:
360 oSymExt
= o
.getSymExtent(printerScale
)
361 symExt
= Numeric
.maximum(symExt
, oSymExt
)
364 def getLegendNames(self
):
365 """Returns list of legend names"""
366 lst
= [None]*len(self
)
367 for i
in range(len(self
)):
368 lst
[i
]= self
.objects
[i
].getLegend()
372 return len(self
.objects
)
374 def __getitem__(self
, item
):
375 return self
.objects
[item
]
378 #-------------------------------------------------------------------------------
379 # Main window that you will want to import into your application.
381 class PlotCanvas(wx
.Window
):
382 """Subclass of a wx.Window to allow simple general plotting
383 of data with zoom, labels, and automatic axis scaling."""
385 def __init__(self
, parent
, id = -1, pos
=wx
.DefaultPosition
,
386 size
=wx
.DefaultSize
, style
= wx
.DEFAULT_FRAME_STYLE
, name
= ""):
387 """Constucts a window, which can be a child of a frame, dialog or
388 any other non-control window"""
390 wx
.Window
.__init
__(self
, parent
, id, pos
, size
, style
, name
)
393 self
.SetBackgroundColour("white")
395 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
396 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
398 # Create some mouse events for zooming
399 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
400 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnMouseLeftUp
)
401 self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
402 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnMouseDoubleClick
)
403 self
.Bind(wx
.EVT_RIGHT_DOWN
, self
.OnMouseRightDown
)
405 # set curser as cross-hairs
406 self
.SetCursor(wx
.CROSS_CURSOR
)
408 # Things for printing
409 self
.print_data
= wx
.PrintData()
410 self
.print_data
.SetPaperId(wx
.PAPER_LETTER
)
411 self
.print_data
.SetOrientation(wx
.LANDSCAPE
)
412 self
.pageSetupData
= wx
.PageSetupDialogData()
413 self
.pageSetupData
.SetMarginBottomRight((25,25))
414 self
.pageSetupData
.SetMarginTopLeft((25,25))
415 self
.pageSetupData
.SetPrintData(self
.print_data
)
416 self
.printerScale
= 1
420 self
._zoomInFactor
= 0.5
421 self
._zoomOutFactor
= 2
422 self
._zoomCorner
1= Numeric
.array([0.0, 0.0]) # left mouse down corner
423 self
._zoomCorner
2= Numeric
.array([0.0, 0.0]) # left mouse up corner
424 self
._zoomEnabled
= False
425 self
._hasDragged
= False
428 self
.last_draw
= None
433 self
._gridEnabled
= False
434 self
._legendEnabled
= False
438 self
._fontSizeAxis
= 10
439 self
._fontSizeTitle
= 15
440 self
._fontSizeLegend
= 7
442 # OnSize called to make sure the buffer is initialized.
443 # This might result in OnSize getting called twice on some
444 # platforms at initialization, but little harm done.
445 self
.OnSize(None) # sets the initial size based on client size
449 def SaveFile(self
, fileName
= ''):
450 """Saves the file to the type specified in the extension. If no file
451 name is specified a dialog box is provided. Returns True if sucessful,
454 .bmp Save a Windows bitmap file.
455 .xbm Save an X bitmap file.
456 .xpm Save an XPM bitmap file.
457 .png Save a Portable Network Graphics file.
458 .jpg Save a Joint Photographic Experts Group file.
460 if string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
461 dlg1
= wx
.FileDialog(
463 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
464 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
465 wx
.SAVE|wx
.OVERWRITE_PROMPT
469 if dlg1
.ShowModal() == wx
.ID_OK
:
470 fileName
= dlg1
.GetPath()
471 # Check for proper exension
472 if string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
473 dlg2
= wx
.MessageDialog(self
, 'File name extension\n'
475 'bmp, xbm, xpm, png, or jpg',
476 'File Name Error', wx
.OK | wx
.ICON_ERROR
)
482 break # now save file
483 else: # exit without saving
488 # File name has required extension
489 fType
= string
.lower(fileName
[-3:])
491 tp
= wx
.BITMAP_TYPE_BMP
# Save a Windows bitmap file.
493 tp
= wx
.BITMAP_TYPE_XBM
# Save an X bitmap file.
495 tp
= wx
.BITMAP_TYPE_XPM
# Save an XPM bitmap file.
497 tp
= wx
.BITMAP_TYPE_JPEG
# Save a JPG file.
499 tp
= wx
.BITMAP_TYPE_PNG
# Save a PNG file.
501 res
= self
._Buffer
.SaveFile(fileName
, tp
)
505 """Brings up the page setup dialog"""
506 data
= self
.pageSetupData
507 data
.SetPrintData(self
.print_data
)
508 dlg
= wx
.PageSetupDialog(self
.parent
, data
)
510 if dlg
.ShowModal() == wx
.ID_OK
:
511 data
= dlg
.GetPageSetupData() # returns wx.PageSetupDialogData
512 # updates page parameters from dialog
513 self
.pageSetupData
.SetMarginBottomRight(data
.GetMarginBottomRight())
514 self
.pageSetupData
.SetMarginTopLeft(data
.GetMarginTopLeft())
515 self
.pageSetupData
.SetPrintData(data
.GetPrintData())
516 self
.print_data
=data
.GetPrintData() # updates print_data
520 def Printout(self
, paper
=None):
521 """Print current plot."""
523 self
.print_data
.SetPaperId(paper
)
524 pdd
= wx
.PrintDialogData()
525 pdd
.SetPrintData(self
.print_data
)
526 printer
= wx
.Printer(pdd
)
527 out
= PlotPrintout(self
)
528 print_ok
= printer
.Print(self
.parent
, out
)
530 self
.print_data
= printer
.GetPrintDialogData().GetPrintData()
533 def PrintPreview(self
):
534 """Print-preview current plot."""
535 printout
= PlotPrintout(self
)
536 printout2
= PlotPrintout(self
)
537 self
.preview
= wx
.PrintPreview(printout
, printout2
, self
.print_data
)
538 if not self
.preview
.Ok():
539 wx
.MessageDialog(self
, "Print Preview failed.\n" \
540 "Check that default printer is configured\n", \
541 "Print error", wx
.OK|wx
.CENTRE
).ShowModal()
542 self
.preview
.SetZoom(30)
543 # search up tree to find frame instance
545 while not isinstance(frameInst
, wx
.Frame
):
546 frameInst
= frameInst
.GetParent()
547 frame
= wx
.PreviewFrame(self
.preview
, frameInst
, "Preview")
549 frame
.SetPosition(self
.GetPosition())
550 frame
.SetSize((500,400))
551 frame
.Centre(wx
.BOTH
)
554 def SetFontSizeAxis(self
, point
= 10):
555 """Set the tick and axis label font size (default is 10 point)"""
556 self
._fontSizeAxis
= point
558 def GetFontSizeAxis(self
):
559 """Get current tick and axis label font size in points"""
560 return self
._fontSizeAxis
562 def SetFontSizeTitle(self
, point
= 15):
563 """Set Title font size (default is 15 point)"""
564 self
._fontSizeTitle
= point
566 def GetFontSizeTitle(self
):
567 """Get current Title font size in points"""
568 return self
._fontSizeTitle
570 def SetFontSizeLegend(self
, point
= 7):
571 """Set Legend font size (default is 7 point)"""
572 self
._fontSizeLegend
= point
574 def GetFontSizeLegend(self
):
575 """Get current Legend font size in points"""
576 return self
._fontSizeLegend
578 def SetEnableZoom(self
, value
):
579 """Set True to enable zooming."""
580 if value
not in [True,False]:
581 raise TypeError, "Value should be True or False"
582 self
._zoomEnabled
= value
584 def GetEnableZoom(self
):
585 """True if zooming enabled."""
586 return self
._zoomEnabled
588 def SetEnableGrid(self
, value
):
589 """Set True to enable grid."""
590 if value
not in [True,False]:
591 raise TypeError, "Value should be True or False"
592 self
._gridEnabled
= value
595 def GetEnableGrid(self
):
596 """True if grid enabled."""
597 return self
._gridEnabled
599 def SetEnableLegend(self
, value
):
600 """Set True to enable legend."""
601 if value
not in [True,False]:
602 raise TypeError, "Value should be True or False"
603 self
._legendEnabled
= value
606 def GetEnableLegend(self
):
607 """True if Legend enabled."""
608 return self
._legendEnabled
611 """Unzoom the plot."""
612 if self
.last_draw
is not None:
613 self
.Draw(self
.last_draw
[0])
615 def ScrollRight(self
, units
):
616 """Move view right number of axis units."""
617 if self
.last_draw
is not None:
618 graphics
, xAxis
, yAxis
= self
.last_draw
619 xAxis
= (xAxis
[0]+units
, xAxis
[1]+units
)
620 self
.Draw(graphics
,xAxis
,yAxis
)
622 def ScrollUp(self
, units
):
623 """Move view up number of axis units."""
624 if self
.last_draw
is not None:
625 graphics
, xAxis
, yAxis
= self
.last_draw
626 yAxis
= (yAxis
[0]+units
, yAxis
[1]+units
)
627 self
.Draw(graphics
,xAxis
,yAxis
)
629 def GetXY(self
,event
):
630 """Takes a mouse event and returns the XY user axis values."""
631 screenPos
= Numeric
.array( event
.GetPosition())
632 x
,y
= (screenPos
-self
._pointShift
)/self
._pointScale
635 def SetXSpec(self
, type= 'auto'):
636 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
638 'none' - shows no axis or tick mark values
639 'min' - shows min bounding box values
640 'auto' - rounds axis range to sensible values
644 def SetYSpec(self
, type= 'auto'):
645 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
647 'none' - shows no axis or tick mark values
648 'min' - shows min bounding box values
649 'auto' - rounds axis range to sensible values
654 """Returns current XSpec for axis"""
658 """Returns current YSpec for axis"""
661 def GetXMaxRange(self
):
662 """Returns (minX, maxX) x-axis range for displayed graph"""
663 graphics
= self
.last_draw
[0]
664 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
665 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
668 def GetYMaxRange(self
):
669 """Returns (minY, maxY) y-axis range for displayed graph"""
670 graphics
= self
.last_draw
[0]
671 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
672 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
675 def GetXCurrentRange(self
):
676 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
677 return self
.last_draw
[1]
679 def GetYCurrentRange(self
):
680 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
681 return self
.last_draw
[2]
683 def Draw(self
, graphics
, xAxis
= None, yAxis
= None, dc
= None):
684 """Draw objects in graphics with specified x and y axis.
685 graphics- instance of PlotGraphics with list of PolyXXX objects
686 xAxis - tuple with (min, max) axis range to view
687 yAxis - same as xAxis
688 dc - drawing context - doesn't have to be specified.
689 If it's not, the offscreen buffer is used
691 # check Axis is either tuple or none
692 if type(xAxis
) not in [type(None),tuple]:
693 raise TypeError, "xAxis should be None or (minX,maxX)"
694 if type(yAxis
) not in [type(None),tuple]:
695 raise TypeError, "yAxis should be None or (minY,maxY)"
697 # check case for axis = (a,b) where a==b caused by improper zooms
699 if xAxis
[0] == xAxis
[1]:
702 if yAxis
[0] == yAxis
[1]:
706 # allows using floats for certain functions
707 dc
= FloatDCWrapper(wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
))
713 # set font size for every thing but title and legend
714 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
716 # sizes axis to axis type, create lower left and upper right corners of plot
717 if xAxis
== None or yAxis
== None:
718 # One or both axis not specified in Draw
719 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
721 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
723 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
724 # Adjust bounding box for axis spec
725 p1
[0],p1
[1] = xAxis
[0], yAxis
[0] # lower left corner user scale (xmin,ymin)
726 p2
[0],p2
[1] = xAxis
[1], yAxis
[1] # upper right corner user scale (xmax,ymax)
728 # Both axis specified in Draw
729 p1
= Numeric
.array([xAxis
[0], yAxis
[0]]) # lower left corner user scale (xmin,ymin)
730 p2
= Numeric
.array([xAxis
[1], yAxis
[1]]) # upper right corner user scale (xmax,ymax)
732 self
.last_draw
= (graphics
, xAxis
, yAxis
) # saves most recient values
734 # Get ticks and textExtents for axis if required
735 if self
._xSpec
is not 'none':
736 xticks
= self
._ticks
(xAxis
[0], xAxis
[1])
737 xTextExtent
= dc
.GetTextExtent(xticks
[-1][1])# w h of x axis text last number on axis
740 xTextExtent
= (0,0) # No text for ticks
741 if self
._ySpec
is not 'none':
742 yticks
= self
._ticks
(yAxis
[0], yAxis
[1])
743 yTextExtentBottom
= dc
.GetTextExtent(yticks
[0][1])
744 yTextExtentTop
= dc
.GetTextExtent(yticks
[-1][1])
745 yTextExtent
= (max(yTextExtentBottom
[0],yTextExtentTop
[0]),
746 max(yTextExtentBottom
[1],yTextExtentTop
[1]))
749 yTextExtent
= (0,0) # No text for ticks
751 # TextExtents for Title and Axis Labels
752 titleWH
, xLabelWH
, yLabelWH
= self
._titleLablesWH
(dc
, graphics
)
754 # TextExtents for Legend
755 legendBoxWH
, legendSymExt
, legendTextExt
= self
._legendWH
(dc
, graphics
)
757 # room around graph area
758 rhsW
= max(xTextExtent
[0], legendBoxWH
[0]) # use larger of number width or legend width
759 lhsW
= yTextExtent
[0]+ yLabelWH
[1]
760 bottomH
= max(xTextExtent
[1], yTextExtent
[1]/2.)+ xLabelWH
[1]
761 topH
= yTextExtent
[1]/2. + titleWH
[1]
762 textSize_scale
= Numeric
.array([rhsW
+lhsW
,bottomH
+topH
]) # make plot area smaller by text size
763 textSize_shift
= Numeric
.array([lhsW
, bottomH
]) # shift plot area by this amount
765 # drawing title and labels text
766 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
767 titlePos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- titleWH
[0]/2.,
768 self
.plotbox_origin
[1]- self
.plotbox_size
[1])
769 dc
.DrawText(graphics
.getTitle(),titlePos
[0],titlePos
[1])
770 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
771 xLabelPos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- xLabelWH
[0]/2.,
772 self
.plotbox_origin
[1]- xLabelWH
[1])
773 dc
.DrawText(graphics
.getXLabel(),xLabelPos
[0],xLabelPos
[1])
774 yLabelPos
= (self
.plotbox_origin
[0],
775 self
.plotbox_origin
[1]- bottomH
- (self
.plotbox_size
[1]-bottomH
-topH
)/2.+ yLabelWH
[0]/2.)
776 if graphics
.getYLabel(): # bug fix for Linux
777 dc
.DrawRotatedText(graphics
.getYLabel(),yLabelPos
[0],yLabelPos
[1],90)
779 # drawing legend makers and text
780 if self
._legendEnabled
:
781 self
._drawLegend
(dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
)
783 # allow for scaling and shifting plotted points
784 scale
= (self
.plotbox_size
-textSize_scale
) / (p2
-p1
)* Numeric
.array((1,-1))
785 shift
= -p1
*scale
+ self
.plotbox_origin
+ textSize_shift
* Numeric
.array((1,-1))
786 self
._pointScale
= scale
# make available for mouse events
787 self
._pointShift
= shift
788 self
._drawAxes
(dc
, p1
, p2
, scale
, shift
, xticks
, yticks
)
790 graphics
.scaleAndShift(scale
, shift
)
791 graphics
.setPrinterScale(self
.printerScale
) # thicken up lines and markers if printing
793 # set clipping area so drawing does not occur outside axis box
794 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(p1
, p2
)
795 dc
.SetClippingRegion(ptx
,pty
,rectWidth
,rectHeight
)
796 # Draw the lines and markers
797 #start = time.clock()
799 # print "entire graphics drawing took: %f second"%(time.clock() - start)
800 # remove the clipping region
801 dc
.DestroyClippingRegion()
804 def Redraw(self
, dc
= None):
805 """Redraw the existing plot."""
806 if self
.last_draw
is not None:
807 graphics
, xAxis
, yAxis
= self
.last_draw
808 self
.Draw(graphics
,xAxis
,yAxis
,dc
)
811 """Erase the window."""
812 dc
= wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
)
814 self
.last_draw
= None
816 def Zoom(self
, Center
, Ratio
):
818 Centers on the X,Y coords given in Center
819 Zooms by the Ratio = (Xratio, Yratio) given
822 if self
.last_draw
!= None:
823 (graphics
, xAxis
, yAxis
) = self
.last_draw
824 w
= (xAxis
[1] - xAxis
[0]) * Ratio
[0]
825 h
= (yAxis
[1] - yAxis
[0]) * Ratio
[1]
826 xAxis
= ( x
- w
/2, x
+ w
/2 )
827 yAxis
= ( y
- h
/2, y
+ h
/2 )
828 self
.Draw(graphics
, xAxis
, yAxis
)
831 # event handlers **********************************
832 def OnMotion(self
, event
):
833 if self
._zoomEnabled
and event
.LeftIsDown():
835 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
837 self
._hasDragged
= True
838 self
._zoomCorner
2[0], self
._zoomCorner
2[1] = self
.GetXY(event
)
839 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # add new
841 def OnMouseLeftDown(self
,event
):
842 self
._zoomCorner
1[0], self
._zoomCorner
1[1]= self
.GetXY(event
)
844 def OnMouseLeftUp(self
, event
):
845 if self
._zoomEnabled
:
846 if self
._hasDragged
== True:
847 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
848 self
._zoomCorner
2[0], self
._zoomCorner
2[1]= self
.GetXY(event
)
849 self
._hasDragged
= False # reset flag
850 minX
, minY
= Numeric
.minimum( self
._zoomCorner
1, self
._zoomCorner
2)
851 maxX
, maxY
= Numeric
.maximum( self
._zoomCorner
1, self
._zoomCorner
2)
852 if self
.last_draw
!= None:
853 self
.Draw(self
.last_draw
[0], xAxis
= (minX
,maxX
), yAxis
= (minY
,maxY
), dc
= None)
854 #else: # A box has not been drawn, zoom in on a point
855 ## this interfered with the double click, so I've disables it.
856 # X,Y = self.GetXY(event)
857 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
859 def OnMouseDoubleClick(self
,event
):
860 if self
._zoomEnabled
:
863 def OnMouseRightDown(self
,event
):
864 if self
._zoomEnabled
:
865 X
,Y
= self
.GetXY(event
)
866 self
.Zoom( (X
,Y
), (self
._zoomOutFactor
, self
._zoomOutFactor
) )
868 def OnPaint(self
, event
):
869 # All that is needed here is to draw the buffer to screen
870 dc
= wx
.BufferedPaintDC(self
, self
._Buffer
)
872 def OnSize(self
,event
):
873 # The Buffer init is done here, to make sure the buffer is always
874 # the same size as the Window
875 Size
= self
.GetClientSize()
877 # Make new offscreen bitmap: this bitmap will always have the
878 # current drawing in it, so it can be used to save the image to
879 # a file, or whatever.
880 self
._Buffer
= wx
.EmptyBitmap(Size
[0],Size
[1])
882 if self
.last_draw
is None:
885 graphics
, xSpec
, ySpec
= self
.last_draw
886 self
.Draw(graphics
,xSpec
,ySpec
)
889 # Private Methods **************************************************
890 def _setSize(self
, width
=None, height
=None):
891 """DC width and height."""
893 (self
.width
,self
.height
) = self
.GetClientSize()
895 self
.width
, self
.height
= width
,height
896 self
.plotbox_size
= 0.97*Numeric
.array([self
.width
, self
.height
])
897 xo
= 0.5*(self
.width
-self
.plotbox_size
[0])
898 yo
= self
.height
-0.5*(self
.height
-self
.plotbox_size
[1])
899 self
.plotbox_origin
= Numeric
.array([xo
, yo
])
901 def _setPrinterScale(self
, scale
):
902 """Used to thicken lines and increase marker size for print out."""
903 # line thickness on printer is very thin at 600 dot/in. Markers small
904 self
.printerScale
= scale
906 def _printDraw(self
, printDC
):
907 """Used for printing."""
908 if self
.last_draw
!= None:
909 graphics
, xSpec
, ySpec
= self
.last_draw
910 self
.Draw(graphics
,xSpec
,ySpec
,printDC
)
912 def _drawLegend(self
,dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
):
913 """Draws legend symbols and text"""
914 # top right hand corner of graph box is ref corner
915 trhc
= self
.plotbox_origin
+ (self
.plotbox_size
-[rhsW
,topH
])*[1,-1]
916 legendLHS
= .091* legendBoxWH
[0] # border space between legend sym and graph box
917 lineHeight
= max(legendSymExt
[1], legendTextExt
[1]) * 1.1 #1.1 used as space between lines
918 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
919 for i
in range(len(graphics
)):
922 if isinstance(o
,PolyMarker
):
923 # draw marker with legend
924 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0]/2., trhc
[1]+s
+lineHeight
/2.)
925 o
.draw(dc
, self
.printerScale
, coord
= Numeric
.array([pnt
]))
926 elif isinstance(o
,PolyLine
):
927 # draw line with legend
928 pnt1
= (trhc
[0]+legendLHS
, trhc
[1]+s
+lineHeight
/2.)
929 pnt2
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.)
930 o
.draw(dc
, self
.printerScale
, coord
= Numeric
.array([pnt1
,pnt2
]))
932 raise TypeError, "object is neither PolyMarker or PolyLine instance"
934 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.-legendTextExt
[1]/2)
935 dc
.DrawText(o
.getLegend(),pnt
[0],pnt
[1])
936 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
)) # reset
938 def _titleLablesWH(self
, dc
, graphics
):
939 """Draws Title and labels and returns width and height for each"""
940 # TextExtents for Title and Axis Labels
941 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
942 title
= graphics
.getTitle()
943 titleWH
= dc
.GetTextExtent(title
)
944 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
945 xLabel
, yLabel
= graphics
.getXLabel(),graphics
.getYLabel()
946 xLabelWH
= dc
.GetTextExtent(xLabel
)
947 yLabelWH
= dc
.GetTextExtent(yLabel
)
948 return titleWH
, xLabelWH
, yLabelWH
950 def _legendWH(self
, dc
, graphics
):
951 """Returns the size in screen units for legend box"""
952 if self
._legendEnabled
!= True:
953 legendBoxWH
= symExt
= txtExt
= (0,0)
955 # find max symbol size
956 symExt
= graphics
.getSymExtent(self
.printerScale
)
957 # find max legend text extent
958 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
959 txtList
= graphics
.getLegendNames()
960 txtExt
= dc
.GetTextExtent(txtList
[0])
961 for txt
in graphics
.getLegendNames()[1:]:
962 txtExt
= Numeric
.maximum(txtExt
,dc
.GetTextExtent(txt
))
963 maxW
= symExt
[0]+txtExt
[0]
964 maxH
= max(symExt
[1],txtExt
[1])
965 # padding .1 for lhs of legend box and space between lines
967 maxH
= maxH
* 1.1 * len(txtList
)
968 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
969 legendBoxWH
= (maxW
,maxH
)
970 return (legendBoxWH
, symExt
, txtExt
)
972 def _drawRubberBand(self
, corner1
, corner2
):
973 """Draws/erases rect box from corner1 to corner2"""
974 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(corner1
, corner2
)
976 dc
= wx
.ClientDC( self
)
978 dc
.SetPen(wx
.Pen(wx
.BLACK
))
979 dc
.SetBrush(wx
.Brush( wx
.WHITE
, wx
.TRANSPARENT
) )
980 dc
.SetLogicalFunction(wx
.INVERT
)
981 dc
.DrawRectangle( (ptx
,pty
), (rectWidth
,rectHeight
))
982 dc
.SetLogicalFunction(wx
.COPY
)
985 def _getFont(self
,size
):
986 """Take font size, adjusts if printing and returns wx.Font"""
987 s
= size
*self
.printerScale
989 # Linux speed up to get font from cache rather than X font server
990 key
= (int(s
), of
.GetFamily (), of
.GetStyle (), of
.GetWeight ())
991 font
= self
._fontCache
.get (key
, None)
993 return font
# yeah! cache hit
995 font
= wx
.Font(int(s
), of
.GetFamily(), of
.GetStyle(), of
.GetWeight())
996 self
._fontCache
[key
] = font
1000 def _point2ClientCoord(self
, corner1
, corner2
):
1001 """Converts user point coords to client screen int coords x,y,width,height"""
1002 c1
= Numeric
.array(corner1
)
1003 c2
= Numeric
.array(corner2
)
1004 # convert to screen coords
1005 pt1
= c1
*self
._pointScale
+self
._pointShift
1006 pt2
= c2
*self
._pointScale
+self
._pointShift
1007 # make height and width positive
1008 pul
= Numeric
.minimum(pt1
,pt2
) # Upper left corner
1009 plr
= Numeric
.maximum(pt1
,pt2
) # Lower right corner
1010 rectWidth
, rectHeight
= plr
-pul
1012 return int(ptx
),int(pty
),int(rectWidth
),int(rectHeight
) # return ints
1014 def _axisInterval(self
, spec
, lower
, upper
):
1015 """Returns sensible axis range for given spec"""
1016 if spec
== 'none' or spec
== 'min':
1018 return lower
-0.5, upper
+0.5
1021 elif spec
== 'auto':
1024 return lower
-0.5, upper
+0.5
1025 log
= Numeric
.log10(range)
1026 power
= Numeric
.floor(log
)
1027 fraction
= log
-power
1028 if fraction
<= 0.05:
1031 lower
= lower
- lower
% grid
1034 upper
= upper
- mod
+ grid
1036 elif type(spec
) == type(()):
1043 raise ValueError, str(spec
) + ': illegal axis specification'
1045 def _drawAxes(self
, dc
, p1
, p2
, scale
, shift
, xticks
, yticks
):
1047 penWidth
= self
.printerScale
# increases thickness for printing only
1048 dc
.SetPen(wx
.Pen(wx
.NamedColour('BLACK'),int(penWidth
)))
1050 # set length of tick marks--long ones make grid
1051 if self
._gridEnabled
:
1052 x
,y
,width
,height
= self
._point
2ClientCoord
(p1
,p2
)
1053 yTickLength
= width
/2.0 +1
1054 xTickLength
= height
/2.0 +1
1056 yTickLength
= 3 * self
.printerScale
# lengthens lines for printing
1057 xTickLength
= 3 * self
.printerScale
1059 if self
._xSpec
is not 'none':
1060 lower
, upper
= p1
[0],p2
[0]
1062 for y
, d
in [(p1
[1], -xTickLength
), (p2
[1], xTickLength
)]: # miny, maxy and tick lengths
1063 a1
= scale
*Numeric
.array([lower
, y
])+shift
1064 a2
= scale
*Numeric
.array([upper
, y
])+shift
1065 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1]) # draws upper and lower axis line
1066 for x
, label
in xticks
:
1067 pt
= scale
*Numeric
.array([x
, y
])+shift
1068 dc
.DrawLine(pt
[0],pt
[1],pt
[0],pt
[1] + d
) # draws tick mark d units
1070 dc
.DrawText(label
,pt
[0],pt
[1])
1071 text
= 0 # axis values not drawn on top side
1073 if self
._ySpec
is not 'none':
1074 lower
, upper
= p1
[1],p2
[1]
1076 h
= dc
.GetCharHeight()
1077 for x
, d
in [(p1
[0], -yTickLength
), (p2
[0], yTickLength
)]:
1078 a1
= scale
*Numeric
.array([x
, lower
])+shift
1079 a2
= scale
*Numeric
.array([x
, upper
])+shift
1080 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1])
1081 for y
, label
in yticks
:
1082 pt
= scale
*Numeric
.array([x
, y
])+shift
1083 dc
.DrawLine(pt
[0],pt
[1],pt
[0]-d
,pt
[1])
1085 dc
.DrawText(label
,pt
[0]-dc
.GetTextExtent(label
)[0],
1087 text
= 0 # axis values not drawn on right side
1089 def _ticks(self
, lower
, upper
):
1090 ideal
= (upper
-lower
)/7.
1091 log
= Numeric
.log10(ideal
)
1092 power
= Numeric
.floor(log
)
1093 fraction
= log
-power
1096 for f
, lf
in self
._multiples
:
1097 e
= Numeric
.fabs(fraction
-lf
)
1101 grid
= factor
* 10.**power
1102 if power
> 4 or power
< -4:
1105 digits
= max(1, int(power
))
1106 format
= '%' + `digits`
+'.0f'
1108 digits
= -int(power
)
1109 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
1111 t
= -grid
*Numeric
.floor(-lower
/grid
)
1113 ticks
.append( (t
, format
% (t
,)) )
1117 _multiples
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))]
1120 #-------------------------------------------------------------------------------
1121 # Used to layout the printer page
1123 class PlotPrintout(wx
.Printout
):
1124 """Controls how the plot is made in printing and previewing"""
1125 # Do not change method names in this class,
1126 # we have to override wx.Printout methods here!
1127 def __init__(self
, graph
):
1128 """graph is instance of plotCanvas to be printed or previewed"""
1129 wx
.Printout
.__init
__(self
)
1132 def HasPage(self
, page
):
1138 def GetPageInfo(self
):
1139 return (0, 1, 1, 1) # disable page numbers
1141 def OnPrintPage(self
, page
):
1142 dc
= FloatDCWrapper(self
.GetDC()) # allows using floats for certain functions
1143 ## print "PPI Printer",self.GetPPIPrinter()
1144 ## print "PPI Screen", self.GetPPIScreen()
1145 ## print "DC GetSize", dc.GetSize()
1146 ## print "GetPageSizePixels", self.GetPageSizePixels()
1147 # Note PPIScreen does not give the correct number
1148 # Calulate everything for printer and then scale for preview
1149 PPIPrinter
= self
.GetPPIPrinter() # printer dots/inch (w,h)
1150 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1151 dcSize
= dc
.GetSize() # DC size
1152 pageSize
= self
.GetPageSizePixels() # page size in terms of pixcels
1153 clientDcSize
= self
.graph
.GetClientSize()
1155 # find what the margins are (mm)
1156 margLeftSize
,margTopSize
= self
.graph
.pageSetupData
.GetMarginTopLeft()
1157 margRightSize
, margBottomSize
= self
.graph
.pageSetupData
.GetMarginBottomRight()
1159 # calculate offset and scale for dc
1160 pixLeft
= margLeftSize
*PPIPrinter
[0]/25.4 # mm*(dots/in)/(mm/in)
1161 pixRight
= margRightSize
*PPIPrinter
[0]/25.4
1162 pixTop
= margTopSize
*PPIPrinter
[1]/25.4
1163 pixBottom
= margBottomSize
*PPIPrinter
[1]/25.4
1165 plotAreaW
= pageSize
[0]-(pixLeft
+pixRight
)
1166 plotAreaH
= pageSize
[1]-(pixTop
+pixBottom
)
1168 # ratio offset and scale to screen size if preview
1169 if self
.IsPreview():
1170 ratioW
= float(dcSize
[0])/pageSize
[0]
1171 ratioH
= float(dcSize
[1])/pageSize
[1]
1177 # rescale plot to page or preview plot area
1178 self
.graph
._setSize
(plotAreaW
,plotAreaH
)
1180 # Set offset and scale
1181 dc
.SetDeviceOrigin(pixLeft
,pixTop
)
1183 # Thicken up pens and increase marker size for printing
1184 ratioW
= float(plotAreaW
)/clientDcSize
[0]
1185 ratioH
= float(plotAreaH
)/clientDcSize
[1]
1186 aveScale
= (ratioW
+ratioH
)/2
1187 self
.graph
._setPrinterScale
(aveScale
) # tickens up pens for printing
1189 self
.graph
._printDraw
(dc
)
1190 # rescale back to original
1191 self
.graph
._setSize
()
1192 self
.graph
._setPrinterScale
(1)
1196 # Hack to allow plotting real numbers for the methods listed.
1197 # All others passed directly to DC.
1198 # For Drawing it is used as
1199 # dc = FloatDCWrapper(wx.BufferedDC(wx.ClientDC(self), self._Buffer))
1200 # For printing is is used as
1201 # dc = FloatDCWrapper(self.GetDC())
1202 class FloatDCWrapper
:
1203 def __init__(self
, aDC
):
1206 def DrawLine(self
, x1
,y1
,x2
,y2
):
1207 self
.theDC
.DrawLine((int(x1
),int(y1
)),(int(x2
),int(y2
)))
1209 def DrawText(self
, txt
, x
, y
):
1210 self
.theDC
.DrawText(txt
, (int(x
), int(y
)))
1212 def DrawRotatedText(self
, txt
, x
, y
, angle
):
1213 self
.theDC
.DrawRotatedText(txt
, (int(x
), int(y
)), angle
)
1215 def SetClippingRegion(self
, x
, y
, width
, height
):
1216 self
.theDC
.SetClippingRegion((int(x
), int(y
)), (int(width
), int(height
)))
1218 def SetDeviceOrigin(self
, x
, y
):
1219 self
.theDC
.SetDeviceOrigin(int(x
), int(y
))
1221 def __getattr__(self
, name
):
1222 return getattr(self
.theDC
, name
)
1227 #---------------------------------------------------------------------------
1228 # if running standalone...
1230 # ...a sample implementation using the above
1233 def _draw1Objects():
1234 # 100 points sin function, plotted as green circles
1235 data1
= 2.*Numeric
.pi
*Numeric
.arange(200)/200.
1236 data1
.shape
= (100, 2)
1237 data1
[:,1] = Numeric
.sin(data1
[:,0])
1238 markers1
= PolyMarker(data1
, legend
='Green Markers', colour
='green', marker
='circle',size
=1)
1240 # 50 points cos function, plotted as red line
1241 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
1242 data1
.shape
= (50,2)
1243 data1
[:,1] = Numeric
.cos(data1
[:,0])
1244 lines
= PolyLine(data1
, legend
= 'Red Line', colour
='red')
1246 # A few more points...
1248 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1249 (3.*pi
/4., -1)], legend
='Cross Legend', colour
='blue',
1252 return PlotGraphics([markers1
, lines
, markers2
],"Graph Title", "X Axis", "Y Axis")
1254 def _draw2Objects():
1255 # 100 points sin function, plotted as green dots
1256 data1
= 2.*Numeric
.pi
*Numeric
.arange(200)/200.
1257 data1
.shape
= (100, 2)
1258 data1
[:,1] = Numeric
.sin(data1
[:,0])
1259 line1
= PolyLine(data1
, legend
='Green Line', colour
='green', width
=6, style
=wx
.DOT
)
1261 # 50 points cos function, plotted as red dot-dash
1262 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
1263 data1
.shape
= (50,2)
1264 data1
[:,1] = Numeric
.cos(data1
[:,0])
1265 line2
= PolyLine(data1
, legend
='Red Line', colour
='red', width
=3, style
= wx
.DOT_DASH
)
1267 # A few more points...
1269 markers1
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1270 (3.*pi
/4., -1)], legend
='Cross Hatch Square', colour
='blue', width
= 3, size
= 6,
1271 fillcolour
= 'red', fillstyle
= wx
.CROSSDIAG_HATCH
,
1274 return PlotGraphics([markers1
, line1
, line2
], "Big Markers with Different Line Styles")
1276 def _draw3Objects():
1277 markerList
= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1278 'cross', 'plus', 'circle']
1280 for i
in range(len(markerList
)):
1281 m
.append(PolyMarker([(2*i
+.5,i
+.5)], legend
=markerList
[i
], colour
='blue',
1282 marker
=markerList
[i
]))
1283 return PlotGraphics(m
, "Selection of Markers", "Minimal Axis", "No Axis")
1285 def _draw4Objects():
1287 data1
= Numeric
.arange(5e5
,1e6
,10)
1288 data1
.shape
= (25000, 2)
1289 line1
= PolyLine(data1
, legend
='Wide Line', colour
='green', width
=5)
1291 # A few more points...
1292 markers2
= PolyMarker(data1
, legend
='Square', colour
='blue',
1294 return PlotGraphics([line1
, markers2
], "25,000 Points", "Value X", "")
1296 def _draw5Objects():
1297 # Empty graph with axis defined but no points/lines
1299 line1
= PolyLine(points
, legend
='Wide Line', colour
='green', width
=5)
1300 return PlotGraphics([line1
], "Empty Plot With Just Axes", "Value X", "Value Y")
1303 class TestFrame(wx
.Frame
):
1304 def __init__(self
, parent
, id, title
):
1305 wx
.Frame
.__init
__(self
, parent
, id, title
,
1306 wx
.DefaultPosition
, (600, 400))
1308 # Now Create the menu bar and items
1309 self
.mainmenu
= wx
.MenuBar()
1312 menu
.Append(200, 'Page Setup...', 'Setup the printer page')
1313 self
.Bind(wx
.EVT_MENU
, self
.OnFilePageSetup
, id=200)
1315 menu
.Append(201, 'Print Preview...', 'Show the current plot on page')
1316 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrintPreview
, id=201)
1318 menu
.Append(202, 'Print...', 'Print the current plot')
1319 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrint
, id=202)
1321 menu
.Append(203, 'Save Plot...', 'Save current plot')
1322 self
.Bind(wx
.EVT_MENU
, self
.OnSaveFile
, id=203)
1324 menu
.Append(205, 'E&xit', 'Enough of this already!')
1325 self
.Bind(wx
.EVT_MENU
, self
.OnFileExit
, id=205)
1326 self
.mainmenu
.Append(menu
, '&File')
1329 menu
.Append(206, 'Draw1', 'Draw plots1')
1330 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw1
, id=206)
1331 menu
.Append(207, 'Draw2', 'Draw plots2')
1332 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw2
, id=207)
1333 menu
.Append(208, 'Draw3', 'Draw plots3')
1334 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw3
, id=208)
1335 menu
.Append(209, 'Draw4', 'Draw plots4')
1336 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw4
, id=209)
1337 menu
.Append(210, 'Draw5', 'Draw plots5')
1338 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw5
, id=210)
1340 menu
.Append(211, '&Redraw', 'Redraw plots')
1341 self
.Bind(wx
.EVT_MENU
,self
.OnPlotRedraw
, id=211)
1342 menu
.Append(212, '&Clear', 'Clear canvas')
1343 self
.Bind(wx
.EVT_MENU
,self
.OnPlotClear
, id=212)
1344 menu
.Append(213, '&Scale', 'Scale canvas')
1345 self
.Bind(wx
.EVT_MENU
,self
.OnPlotScale
, id=213)
1346 menu
.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind
=wx
.ITEM_CHECK
)
1347 self
.Bind(wx
.EVT_MENU
,self
.OnEnableZoom
, id=214)
1348 menu
.Append(215, 'Enable &Grid', 'Turn on Grid', kind
=wx
.ITEM_CHECK
)
1349 self
.Bind(wx
.EVT_MENU
,self
.OnEnableGrid
, id=215)
1350 menu
.Append(220, 'Enable &Legend', 'Turn on Legend', kind
=wx
.ITEM_CHECK
)
1351 self
.Bind(wx
.EVT_MENU
,self
.OnEnableLegend
, id=220)
1352 menu
.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1353 self
.Bind(wx
.EVT_MENU
,self
.OnScrUp
, id=225)
1354 menu
.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1355 self
.Bind(wx
.EVT_MENU
,self
.OnScrRt
, id=230)
1356 menu
.Append(235, '&Plot Reset', 'Reset to original plot')
1357 self
.Bind(wx
.EVT_MENU
,self
.OnReset
, id=235)
1359 self
.mainmenu
.Append(menu
, '&Plot')
1362 menu
.Append(300, '&About', 'About this thing...')
1363 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=300)
1364 self
.mainmenu
.Append(menu
, '&Help')
1366 self
.SetMenuBar(self
.mainmenu
)
1368 # A status bar to tell people what's happening
1369 self
.CreateStatusBar(1)
1371 self
.client
= PlotCanvas(self
)
1372 # Create mouse event for showing cursor coords in status bar
1373 self
.client
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
1376 def OnMouseLeftDown(self
,event
):
1377 s
= "Left Mouse Down at Point: (%.4f, %.4f)" % self
.client
.GetXY(event
)
1378 self
.SetStatusText(s
)
1381 def OnFilePageSetup(self
, event
):
1382 self
.client
.PageSetup()
1384 def OnFilePrintPreview(self
, event
):
1385 self
.client
.PrintPreview()
1387 def OnFilePrint(self
, event
):
1388 self
.client
.Printout()
1390 def OnSaveFile(self
, event
):
1391 self
.client
.SaveFile()
1393 def OnFileExit(self
, event
):
1396 def OnPlotDraw1(self
, event
):
1397 self
.resetDefaults()
1398 self
.client
.Draw(_draw1Objects())
1400 def OnPlotDraw2(self
, event
):
1401 self
.resetDefaults()
1402 self
.client
.Draw(_draw2Objects())
1404 def OnPlotDraw3(self
, event
):
1405 self
.resetDefaults()
1406 self
.client
.SetFont(wx
.Font(10,wx
.SCRIPT
,wx
.NORMAL
,wx
.NORMAL
))
1407 self
.client
.SetFontSizeAxis(20)
1408 self
.client
.SetFontSizeLegend(12)
1409 self
.client
.SetXSpec('min')
1410 self
.client
.SetYSpec('none')
1411 self
.client
.Draw(_draw3Objects())
1413 def OnPlotDraw4(self
, event
):
1414 self
.resetDefaults()
1415 drawObj
= _draw4Objects()
1416 self
.client
.Draw(drawObj
)
1418 ## start = time.clock()
1419 ## for x in range(10):
1420 ## self.client.Draw(drawObj)
1421 ## print "10 plots of Draw4 took: %f sec."%(time.clock() - start)
1424 def OnPlotDraw5(self
, event
):
1425 # Empty plot with just axes
1426 self
.resetDefaults()
1427 drawObj
= _draw5Objects()
1428 # make the axis X= (0,5), Y=(0,10)
1429 # (default with None is X= (-1,1), Y= (-1,1))
1430 self
.client
.Draw(drawObj
, xAxis
= (0,5), yAxis
= (0,10))
1432 def OnPlotRedraw(self
,event
):
1433 self
.client
.Redraw()
1435 def OnPlotClear(self
,event
):
1438 def OnPlotScale(self
, event
):
1439 if self
.client
.last_draw
!= None:
1440 graphics
, xAxis
, yAxis
= self
.client
.last_draw
1441 self
.client
.Draw(graphics
,(1,3.05),(0,1))
1443 def OnEnableZoom(self
, event
):
1444 self
.client
.SetEnableZoom(event
.IsChecked())
1446 def OnEnableGrid(self
, event
):
1447 self
.client
.SetEnableGrid(event
.IsChecked())
1449 def OnEnableLegend(self
, event
):
1450 self
.client
.SetEnableLegend(event
.IsChecked())
1452 def OnScrUp(self
, event
):
1453 self
.client
.ScrollUp(1)
1455 def OnScrRt(self
,event
):
1456 self
.client
.ScrollRight(2)
1458 def OnReset(self
,event
):
1461 def OnHelpAbout(self
, event
):
1462 from wx
.lib
.dialogs
import ScrolledMessageDialog
1463 about
= ScrolledMessageDialog(self
, __doc__
, "About...")
1466 def resetDefaults(self
):
1467 """Just to reset the fonts back to the PlotCanvas defaults"""
1468 self
.client
.SetFont(wx
.Font(10,wx
.SWISS
,wx
.NORMAL
,wx
.NORMAL
))
1469 self
.client
.SetFontSizeAxis(10)
1470 self
.client
.SetFontSizeLegend(7)
1471 self
.client
.SetXSpec('auto')
1472 self
.client
.SetYSpec('auto')
1477 class MyApp(wx
.App
):
1479 wx
.InitAllImageHandlers()
1480 frame
= TestFrame(None, -1, "PlotCanvas")
1482 self
.SetTopWindow(frame
)
1489 if __name__
== '__main__':