]>
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.
21 This is a simple light weight plotting module that can be used with
22 Boa or easily integrated into your own wxPython application. The
23 emphasis is on small size and fast plotting for large data sets. It
24 has a reasonable number of features to do line and scatter graphs
25 easily. It is not as sophisticated or as powerful as SciPy Plt or
26 Chaco. Both of these are great packages but consume huge amounts of
27 computer resources for simple plots. They can be found at
30 This file contains two parts; first the re-usable library stuff, then,
31 after a "if __name__=='__main__'" test, a simple frame and a few default
32 plots for examples and testing.
35 Written by K.Hinsen, R. Srinivasan;
36 Ported to wxPython Harm van der Heijden, feb 1999
38 Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
40 -Zooming using mouse "rubber band"
43 -Printing, preview, and page set up (margins)
44 -Axis and title labels
45 -Cursor xy axis values
46 -Doc strings and lots of comments
47 -Optimizations for large number of points
50 Did a lot of work here to speed markers up. Only a factor of 4
51 improvement though. Lines are much faster than markers, especially
52 filled markers. Stay away from circles and triangles unless you
53 only have a few thousand points.
55 Times for 25,000 points
62 triangle, triangle_down - 0.90
64 Thanks to Chris Barker for getting this version working on Linux.
66 Zooming controls with mouse (when enabled):
67 Left mouse drag - Zoom box.
68 Left mouse double click - reset zoom.
69 Right mouse click - zoom out centred on click location.
81 import numarray
as Numeric
#if numarray is used it is renamed Numeric
84 This module requires the Numeric or numarray module,
85 which could not be imported. It probably is not installed
86 (it's not part of the standard Python distribution). See the
87 Python site (http://www.python.org) for information on
88 downloading source or binaries."""
89 raise ImportError, "Numeric or numarray not found. \n" + msg
97 """Base Class for lines and markers
98 - All methods are private.
101 def __init__(self
, points
, attr
):
102 self
.points
= Numeric
.array(points
)
103 self
.currentScale
= (1,1)
104 self
.currentShift
= (0,0)
105 self
.scaled
= self
.points
107 self
.attributes
.update(self
._attributes
)
108 for name
, value
in attr
.items():
109 if name
not in self
._attributes
.keys():
110 raise KeyError, "Style attribute incorrect. Should be one of %s" % self
._attributes
.keys()
111 self
.attributes
[name
] = value
113 def boundingBox(self
):
114 if len(self
.points
) == 0:
116 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
117 minXY
= Numeric
.array([-1,-1])
118 maxXY
= Numeric
.array([ 1, 1])
120 minXY
= Numeric
.minimum
.reduce(self
.points
)
121 maxXY
= Numeric
.maximum
.reduce(self
.points
)
124 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
125 if len(self
.points
) == 0:
128 if (scale
is not self
.currentScale
) or (shift
is not self
.currentShift
):
129 # update point scaling
130 self
.scaled
= scale
*self
.points
+shift
131 self
.currentScale
= scale
132 self
.currentShift
= shift
133 # else unchanged use the current scaling
136 return self
.attributes
['legend']
139 class PolyLine(PolyPoints
):
140 """Class to define line type and style
141 - All methods except __init__ are private.
144 _attributes
= {'colour': 'black',
149 def __init__(self
, points
, **attr
):
150 """Creates PolyLine object
151 points - sequence (array, tuple or list) of (x,y) points making up line
152 **attr - key word attributes
154 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
155 'width'= 1, - Pen width
156 'style'= wx.SOLID, - wx.Pen style
157 'legend'= '' - Line Legend to display
159 PolyPoints
.__init
__(self
, points
, attr
)
161 def draw(self
, dc
, printerScale
, coord
= None):
162 colour
= self
.attributes
['colour']
163 width
= self
.attributes
['width'] * printerScale
164 style
= self
.attributes
['style']
165 dc
.SetPen(wx
.Pen(wx
.NamedColour(colour
), int(width
), style
))
167 dc
.DrawLines(self
.scaled
)
169 dc
.DrawLines(coord
) # draw legend line
171 def getSymExtent(self
, printerScale
):
172 """Width and Height of Marker"""
173 h
= self
.attributes
['width'] * printerScale
178 class PolyMarker(PolyPoints
):
179 """Class to define marker type and style
180 - All methods except __init__ are private.
183 _attributes
= {'colour': 'black',
187 'fillstyle': wx
.SOLID
,
191 def __init__(self
, points
, **attr
):
192 """Creates PolyMarker object
193 points - sequence (array, tuple or list) of (x,y) points
194 **attr - key word attributes
196 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
197 'width'= 1, - Pen width
198 'size'= 2, - Marker size
199 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
200 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
201 'marker'= 'circle' - Marker shape
202 'legend'= '' - Marker Legend to display
214 PolyPoints
.__init
__(self
, points
, attr
)
216 def draw(self
, dc
, printerScale
, coord
= None):
217 colour
= self
.attributes
['colour']
218 width
= self
.attributes
['width'] * printerScale
219 size
= self
.attributes
['size'] * printerScale
220 fillcolour
= self
.attributes
['fillcolour']
221 fillstyle
= self
.attributes
['fillstyle']
222 marker
= self
.attributes
['marker']
224 dc
.SetPen(wx
.Pen(wx
.NamedColour(colour
),int(width
)))
226 dc
.SetBrush(wx
.Brush(wx
.NamedColour(fillcolour
),fillstyle
))
228 dc
.SetBrush(wx
.Brush(wx
.NamedColour(colour
), fillstyle
))
230 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
232 self
._drawmarkers
(dc
, coord
, marker
, size
) # draw legend marker
234 def getSymExtent(self
, printerScale
):
235 """Width and Height of Marker"""
236 s
= 5*self
.attributes
['size'] * printerScale
239 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
240 f
= eval('self._' +marker
)
243 def _circle(self
, dc
, coords
, size
=1):
246 rect
= Numeric
.zeros((len(coords
),4),Numeric
.Float
)+[0.0,0.0,wh
,wh
]
247 rect
[:,0:2]= coords
-[fact
,fact
]
248 dc
.DrawEllipseList(rect
.astype(Numeric
.Int32
))
250 def _dot(self
, dc
, coords
, size
=1):
251 dc
.DrawPointList(coords
)
253 def _square(self
, dc
, coords
, size
=1):
256 rect
= Numeric
.zeros((len(coords
),4),Numeric
.Float
)+[0.0,0.0,wh
,wh
]
257 rect
[:,0:2]= coords
-[fact
,fact
]
258 dc
.DrawRectangleList(rect
.astype(Numeric
.Int32
))
260 def _triangle(self
, dc
, coords
, size
=1):
261 shape
= [(-2.5*size
,1.44*size
), (2.5*size
,1.44*size
), (0.0,-2.88*size
)]
262 poly
= Numeric
.repeat(coords
,3)
263 poly
.shape
= (len(coords
),3,2)
265 dc
.DrawPolygonList(poly
.astype(Numeric
.Int32
))
267 def _triangle_down(self
, dc
, coords
, size
=1):
268 shape
= [(-2.5*size
,-1.44*size
), (2.5*size
,-1.44*size
), (0.0,2.88*size
)]
269 poly
= Numeric
.repeat(coords
,3)
270 poly
.shape
= (len(coords
),3,2)
272 dc
.DrawPolygonList(poly
.astype(Numeric
.Int32
))
274 def _cross(self
, dc
, coords
, size
=1):
276 for f
in [[-fact
,-fact
,fact
,fact
],[-fact
,fact
,fact
,-fact
]]:
277 lines
= Numeric
.concatenate((coords
,coords
),axis
=1)+f
278 dc
.DrawLineList(lines
.astype(Numeric
.Int32
))
280 def _plus(self
, dc
, coords
, size
=1):
282 for f
in [[-fact
,0,fact
,0],[0,-fact
,0,fact
]]:
283 lines
= Numeric
.concatenate((coords
,coords
),axis
=1)+f
284 dc
.DrawLineList(lines
.astype(Numeric
.Int32
))
287 """Container to hold PolyXXX objects and graph labels
288 - All methods except __init__ are private.
291 def __init__(self
, objects
, title
='', xLabel
='', yLabel
= ''):
292 """Creates PlotGraphics object
293 objects - list of PolyXXX objects to make graph
294 title - title shown at top of graph
295 xLabel - label shown on x-axis
296 yLabel - label shown on y-axis
298 if type(objects
) not in [list,tuple]:
299 raise TypeError, "objects argument should be list or tuple"
300 self
.objects
= objects
305 def boundingBox(self
):
306 p1
, p2
= self
.objects
[0].boundingBox()
307 for o
in self
.objects
[1:]:
308 p1o
, p2o
= o
.boundingBox()
309 p1
= Numeric
.minimum(p1
, p1o
)
310 p2
= Numeric
.maximum(p2
, p2o
)
313 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
314 for o
in self
.objects
:
315 o
.scaleAndShift(scale
, shift
)
317 def setPrinterScale(self
, scale
):
318 """Thickens up lines and markers only for printing"""
319 self
.printerScale
= scale
321 def setXLabel(self
, xLabel
= ''):
322 """Set the X axis label on the graph"""
325 def setYLabel(self
, yLabel
= ''):
326 """Set the Y axis label on the graph"""
329 def setTitle(self
, title
= ''):
330 """Set the title at the top of graph"""
334 """Get x axis label string"""
338 """Get y axis label string"""
341 def getTitle(self
, title
= ''):
342 """Get the title at the top of graph"""
346 for o
in self
.objects
:
347 #t=time.clock() # profile info
348 o
.draw(dc
, self
.printerScale
)
350 #print o, "time=", dt
352 def getSymExtent(self
, printerScale
):
353 """Get max width and height of lines and markers symbols for legend"""
354 symExt
= self
.objects
[0].getSymExtent(printerScale
)
355 for o
in self
.objects
[1:]:
356 oSymExt
= o
.getSymExtent(printerScale
)
357 symExt
= Numeric
.maximum(symExt
, oSymExt
)
360 def getLegendNames(self
):
361 """Returns list of legend names"""
362 lst
= [None]*len(self
)
363 for i
in range(len(self
)):
364 lst
[i
]= self
.objects
[i
].getLegend()
368 return len(self
.objects
)
370 def __getitem__(self
, item
):
371 return self
.objects
[item
]
374 #-------------------------------------------------------------------------------
375 # Main window that you will want to import into your application.
377 class PlotCanvas(wx
.Window
):
378 """Subclass of a wx.Window to allow simple general plotting
379 of data with zoom, labels, and automatic axis scaling."""
381 def __init__(self
, parent
, id = -1, pos
=wx
.DefaultPosition
,
382 size
=wx
.DefaultSize
, style
= wx
.DEFAULT_FRAME_STYLE
, name
= ""):
383 """Constucts a window, which can be a child of a frame, dialog or
384 any other non-control window"""
386 wx
.Window
.__init
__(self
, parent
, id, pos
, size
, style
, name
)
389 self
.SetBackgroundColour("white")
391 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
392 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
394 # Create some mouse events for zooming
395 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
396 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnMouseLeftUp
)
397 self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
398 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnMouseDoubleClick
)
399 self
.Bind(wx
.EVT_RIGHT_DOWN
, self
.OnMouseRightDown
)
401 # set curser as cross-hairs
402 self
.SetCursor(wx
.CROSS_CURSOR
)
404 # Things for printing
405 self
.print_data
= wx
.PrintData()
406 self
.print_data
.SetPaperId(wx
.PAPER_LETTER
)
407 self
.print_data
.SetOrientation(wx
.LANDSCAPE
)
408 self
.pageSetupData
= wx
.PageSetupDialogData()
409 self
.pageSetupData
.SetMarginBottomRight((25,25))
410 self
.pageSetupData
.SetMarginTopLeft((25,25))
411 self
.pageSetupData
.SetPrintData(self
.print_data
)
412 self
.printerScale
= 1
416 self
._zoomInFactor
= 0.5
417 self
._zoomOutFactor
= 2
418 self
._zoomCorner
1= Numeric
.array([0.0, 0.0]) # left mouse down corner
419 self
._zoomCorner
2= Numeric
.array([0.0, 0.0]) # left mouse up corner
420 self
._zoomEnabled
= False
421 self
._hasDragged
= False
424 self
.last_draw
= None
429 self
._gridEnabled
= False
430 self
._legendEnabled
= False
434 self
._fontSizeAxis
= 10
435 self
._fontSizeTitle
= 15
436 self
._fontSizeLegend
= 7
438 # OnSize called to make sure the buffer is initialized.
439 # This might result in OnSize getting called twice on some
440 # platforms at initialization, but little harm done.
441 self
.OnSize(None) # sets the initial size based on client size
445 def SaveFile(self
, fileName
= ''):
446 """Saves the file to the type specified in the extension. If no file
447 name is specified a dialog box is provided. Returns True if sucessful,
450 .bmp Save a Windows bitmap file.
451 .xbm Save an X bitmap file.
452 .xpm Save an XPM bitmap file.
453 .png Save a Portable Network Graphics file.
454 .jpg Save a Joint Photographic Experts Group file.
456 if string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
457 dlg1
= wx
.FileDialog(
459 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
460 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
461 wx
.SAVE|wx
.OVERWRITE_PROMPT
465 if dlg1
.ShowModal() == wx
.ID_OK
:
466 fileName
= dlg1
.GetPath()
467 # Check for proper exension
468 if string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
469 dlg2
= wx
.MessageDialog(self
, 'File name extension\n'
471 'bmp, xbm, xpm, png, or jpg',
472 'File Name Error', wx
.OK | wx
.ICON_ERROR
)
478 break # now save file
479 else: # exit without saving
484 # File name has required extension
485 fType
= string
.lower(fileName
[-3:])
487 tp
= wx
.BITMAP_TYPE_BMP
# Save a Windows bitmap file.
489 tp
= wx
.BITMAP_TYPE_XBM
# Save an X bitmap file.
491 tp
= wx
.BITMAP_TYPE_XPM
# Save an XPM bitmap file.
493 tp
= wx
.BITMAP_TYPE_JPEG
# Save a JPG file.
495 tp
= wx
.BITMAP_TYPE_PNG
# Save a PNG file.
497 res
= self
._Buffer
.SaveFile(fileName
, tp
)
501 """Brings up the page setup dialog"""
502 data
= self
.pageSetupData
503 data
.SetPrintData(self
.print_data
)
504 dlg
= wx
.PageSetupDialog(self
.parent
, data
)
506 if dlg
.ShowModal() == wx
.ID_OK
:
507 data
= dlg
.GetPageSetupData() # returns wx.PageSetupDialogData
508 # updates page parameters from dialog
509 self
.pageSetupData
.SetMarginBottomRight(data
.GetMarginBottomRight())
510 self
.pageSetupData
.SetMarginTopLeft(data
.GetMarginTopLeft())
511 self
.pageSetupData
.SetPrintData(data
.GetPrintData())
512 self
.print_data
=data
.GetPrintData() # updates print_data
516 def Printout(self
, paper
=None):
517 """Print current plot."""
519 self
.print_data
.SetPaperId(paper
)
520 pdd
= wx
.PrintDialogData()
521 pdd
.SetPrintData(self
.print_data
)
522 printer
= wx
.Printer(pdd
)
523 out
= PlotPrintout(self
)
524 print_ok
= printer
.Print(self
.parent
, out
)
526 self
.print_data
= printer
.GetPrintDialogData().GetPrintData()
529 def PrintPreview(self
):
530 """Print-preview current plot."""
531 printout
= PlotPrintout(self
)
532 printout2
= PlotPrintout(self
)
533 self
.preview
= wx
.PrintPreview(printout
, printout2
, self
.print_data
)
534 if not self
.preview
.Ok():
535 wx
.MessageDialog(self
, "Print Preview failed.\n" \
536 "Check that default printer is configured\n", \
537 "Print error", wx
.OK|wx
.CENTRE
).ShowModal()
538 self
.preview
.SetZoom(30)
539 # search up tree to find frame instance
541 while not isinstance(frameInst
, wx
.Frame
):
542 frameInst
= frameInst
.GetParent()
543 frame
= wx
.PreviewFrame(self
.preview
, frameInst
, "Preview")
545 frame
.SetPosition(self
.GetPosition())
546 frame
.SetSize((500,400))
547 frame
.Centre(wx
.BOTH
)
550 def SetFontSizeAxis(self
, point
= 10):
551 """Set the tick and axis label font size (default is 10 point)"""
552 self
._fontSizeAxis
= point
554 def GetFontSizeAxis(self
):
555 """Get current tick and axis label font size in points"""
556 return self
._fontSizeAxis
558 def SetFontSizeTitle(self
, point
= 15):
559 """Set Title font size (default is 15 point)"""
560 self
._fontSizeTitle
= point
562 def GetFontSizeTitle(self
):
563 """Get current Title font size in points"""
564 return self
._fontSizeTitle
566 def SetFontSizeLegend(self
, point
= 7):
567 """Set Legend font size (default is 7 point)"""
568 self
._fontSizeLegend
= point
570 def GetFontSizeLegend(self
):
571 """Get current Legend font size in points"""
572 return self
._fontSizeLegend
574 def SetEnableZoom(self
, value
):
575 """Set True to enable zooming."""
576 if value
not in [True,False]:
577 raise TypeError, "Value should be True or False"
578 self
._zoomEnabled
= value
580 def GetEnableZoom(self
):
581 """True if zooming enabled."""
582 return self
._zoomEnabled
584 def SetEnableGrid(self
, value
):
585 """Set True to enable grid."""
586 if value
not in [True,False]:
587 raise TypeError, "Value should be True or False"
588 self
._gridEnabled
= value
591 def GetEnableGrid(self
):
592 """True if grid enabled."""
593 return self
._gridEnabled
595 def SetEnableLegend(self
, value
):
596 """Set True to enable legend."""
597 if value
not in [True,False]:
598 raise TypeError, "Value should be True or False"
599 self
._legendEnabled
= value
602 def GetEnableLegend(self
):
603 """True if Legend enabled."""
604 return self
._legendEnabled
607 """Unzoom the plot."""
608 if self
.last_draw
is not None:
609 self
.Draw(self
.last_draw
[0])
611 def ScrollRight(self
, units
):
612 """Move view right number of axis units."""
613 if self
.last_draw
is not None:
614 graphics
, xAxis
, yAxis
= self
.last_draw
615 xAxis
= (xAxis
[0]+units
, xAxis
[1]+units
)
616 self
.Draw(graphics
,xAxis
,yAxis
)
618 def ScrollUp(self
, units
):
619 """Move view up number of axis units."""
620 if self
.last_draw
is not None:
621 graphics
, xAxis
, yAxis
= self
.last_draw
622 yAxis
= (yAxis
[0]+units
, yAxis
[1]+units
)
623 self
.Draw(graphics
,xAxis
,yAxis
)
625 def GetXY(self
,event
):
626 """Takes a mouse event and returns the XY user axis values."""
627 screenPos
= Numeric
.array( event
.GetPosition())
628 x
,y
= (screenPos
-self
._pointShift
)/self
._pointScale
631 def SetXSpec(self
, type= 'auto'):
632 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
634 'none' - shows no axis or tick mark values
635 'min' - shows min bounding box values
636 'auto' - rounds axis range to sensible values
640 def SetYSpec(self
, type= 'auto'):
641 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
643 'none' - shows no axis or tick mark values
644 'min' - shows min bounding box values
645 'auto' - rounds axis range to sensible values
650 """Returns current XSpec for axis"""
654 """Returns current YSpec for axis"""
657 def GetXMaxRange(self
):
658 """Returns (minX, maxX) x-axis range for displayed graph"""
659 graphics
= self
.last_draw
[0]
660 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
661 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
664 def GetYMaxRange(self
):
665 """Returns (minY, maxY) y-axis range for displayed graph"""
666 graphics
= self
.last_draw
[0]
667 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
668 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
671 def GetXCurrentRange(self
):
672 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
673 return self
.last_draw
[1]
675 def GetYCurrentRange(self
):
676 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
677 return self
.last_draw
[2]
679 def Draw(self
, graphics
, xAxis
= None, yAxis
= None, dc
= None):
680 """Draw objects in graphics with specified x and y axis.
681 graphics- instance of PlotGraphics with list of PolyXXX objects
682 xAxis - tuple with (min, max) axis range to view
683 yAxis - same as xAxis
684 dc - drawing context - doesn't have to be specified.
685 If it's not, the offscreen buffer is used
687 # check Axis is either tuple or none
688 if type(xAxis
) not in [type(None),tuple]:
689 raise TypeError, "xAxis should be None or (minX,maxX)"
690 if type(yAxis
) not in [type(None),tuple]:
691 raise TypeError, "yAxis should be None or (minY,maxY)"
693 # check case for axis = (a,b) where a==b caused by improper zooms
695 if xAxis
[0] == xAxis
[1]:
698 if yAxis
[0] == yAxis
[1]:
702 # allows using floats for certain functions
703 dc
= FloatDCWrapper(wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
))
709 # set font size for every thing but title and legend
710 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
712 # sizes axis to axis type, create lower left and upper right corners of plot
713 if xAxis
== None or yAxis
== None:
714 # One or both axis not specified in Draw
715 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
717 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
719 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
720 # Adjust bounding box for axis spec
721 p1
[0],p1
[1] = xAxis
[0], yAxis
[0] # lower left corner user scale (xmin,ymin)
722 p2
[0],p2
[1] = xAxis
[1], yAxis
[1] # upper right corner user scale (xmax,ymax)
724 # Both axis specified in Draw
725 p1
= Numeric
.array([xAxis
[0], yAxis
[0]]) # lower left corner user scale (xmin,ymin)
726 p2
= Numeric
.array([xAxis
[1], yAxis
[1]]) # upper right corner user scale (xmax,ymax)
728 self
.last_draw
= (graphics
, xAxis
, yAxis
) # saves most recient values
730 # Get ticks and textExtents for axis if required
731 if self
._xSpec
is not 'none':
732 xticks
= self
._ticks
(xAxis
[0], xAxis
[1])
733 xTextExtent
= dc
.GetTextExtent(xticks
[-1][1])# w h of x axis text last number on axis
736 xTextExtent
= (0,0) # No text for ticks
737 if self
._ySpec
is not 'none':
738 yticks
= self
._ticks
(yAxis
[0], yAxis
[1])
739 yTextExtentBottom
= dc
.GetTextExtent(yticks
[0][1])
740 yTextExtentTop
= dc
.GetTextExtent(yticks
[-1][1])
741 yTextExtent
= (max(yTextExtentBottom
[0],yTextExtentTop
[0]),
742 max(yTextExtentBottom
[1],yTextExtentTop
[1]))
745 yTextExtent
= (0,0) # No text for ticks
747 # TextExtents for Title and Axis Labels
748 titleWH
, xLabelWH
, yLabelWH
= self
._titleLablesWH
(dc
, graphics
)
750 # TextExtents for Legend
751 legendBoxWH
, legendSymExt
, legendTextExt
= self
._legendWH
(dc
, graphics
)
753 # room around graph area
754 rhsW
= max(xTextExtent
[0], legendBoxWH
[0]) # use larger of number width or legend width
755 lhsW
= yTextExtent
[0]+ yLabelWH
[1]
756 bottomH
= max(xTextExtent
[1], yTextExtent
[1]/2.)+ xLabelWH
[1]
757 topH
= yTextExtent
[1]/2. + titleWH
[1]
758 textSize_scale
= Numeric
.array([rhsW
+lhsW
,bottomH
+topH
]) # make plot area smaller by text size
759 textSize_shift
= Numeric
.array([lhsW
, bottomH
]) # shift plot area by this amount
761 # drawing title and labels text
762 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
763 titlePos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- titleWH
[0]/2.,
764 self
.plotbox_origin
[1]- self
.plotbox_size
[1])
765 dc
.DrawText(graphics
.getTitle(),titlePos
[0],titlePos
[1])
766 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
767 xLabelPos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- xLabelWH
[0]/2.,
768 self
.plotbox_origin
[1]- xLabelWH
[1])
769 dc
.DrawText(graphics
.getXLabel(),xLabelPos
[0],xLabelPos
[1])
770 yLabelPos
= (self
.plotbox_origin
[0],
771 self
.plotbox_origin
[1]- bottomH
- (self
.plotbox_size
[1]-bottomH
-topH
)/2.+ yLabelWH
[0]/2.)
772 if graphics
.getYLabel(): # bug fix for Linux
773 dc
.DrawRotatedText(graphics
.getYLabel(),yLabelPos
[0],yLabelPos
[1],90)
775 # drawing legend makers and text
776 if self
._legendEnabled
:
777 self
._drawLegend
(dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
)
779 # allow for scaling and shifting plotted points
780 scale
= (self
.plotbox_size
-textSize_scale
) / (p2
-p1
)* Numeric
.array((1,-1))
781 shift
= -p1
*scale
+ self
.plotbox_origin
+ textSize_shift
* Numeric
.array((1,-1))
782 self
._pointScale
= scale
# make available for mouse events
783 self
._pointShift
= shift
784 self
._drawAxes
(dc
, p1
, p2
, scale
, shift
, xticks
, yticks
)
786 graphics
.scaleAndShift(scale
, shift
)
787 graphics
.setPrinterScale(self
.printerScale
) # thicken up lines and markers if printing
789 # set clipping area so drawing does not occur outside axis box
790 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(p1
, p2
)
791 dc
.SetClippingRegion(ptx
,pty
,rectWidth
,rectHeight
)
792 # Draw the lines and markers
793 #start = time.clock()
795 # print "entire graphics drawing took: %f second"%(time.clock() - start)
796 # remove the clipping region
797 dc
.DestroyClippingRegion()
800 def Redraw(self
, dc
= None):
801 """Redraw the existing plot."""
802 if self
.last_draw
is not None:
803 graphics
, xAxis
, yAxis
= self
.last_draw
804 self
.Draw(graphics
,xAxis
,yAxis
,dc
)
807 """Erase the window."""
808 dc
= wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
)
810 self
.last_draw
= None
812 def Zoom(self
, Center
, Ratio
):
814 Centers on the X,Y coords given in Center
815 Zooms by the Ratio = (Xratio, Yratio) given
818 if self
.last_draw
!= None:
819 (graphics
, xAxis
, yAxis
) = self
.last_draw
820 w
= (xAxis
[1] - xAxis
[0]) * Ratio
[0]
821 h
= (yAxis
[1] - yAxis
[0]) * Ratio
[1]
822 xAxis
= ( x
- w
/2, x
+ w
/2 )
823 yAxis
= ( y
- h
/2, y
+ h
/2 )
824 self
.Draw(graphics
, xAxis
, yAxis
)
827 # event handlers **********************************
828 def OnMotion(self
, event
):
829 if self
._zoomEnabled
and event
.LeftIsDown():
831 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
833 self
._hasDragged
= True
834 self
._zoomCorner
2[0], self
._zoomCorner
2[1] = self
.GetXY(event
)
835 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # add new
837 def OnMouseLeftDown(self
,event
):
838 self
._zoomCorner
1[0], self
._zoomCorner
1[1]= self
.GetXY(event
)
840 def OnMouseLeftUp(self
, event
):
841 if self
._zoomEnabled
:
842 if self
._hasDragged
== True:
843 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
844 self
._zoomCorner
2[0], self
._zoomCorner
2[1]= self
.GetXY(event
)
845 self
._hasDragged
= False # reset flag
846 minX
, minY
= Numeric
.minimum( self
._zoomCorner
1, self
._zoomCorner
2)
847 maxX
, maxY
= Numeric
.maximum( self
._zoomCorner
1, self
._zoomCorner
2)
848 if self
.last_draw
!= None:
849 self
.Draw(self
.last_draw
[0], xAxis
= (minX
,maxX
), yAxis
= (minY
,maxY
), dc
= None)
850 #else: # A box has not been drawn, zoom in on a point
851 ## this interfered with the double click, so I've disables it.
852 # X,Y = self.GetXY(event)
853 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
855 def OnMouseDoubleClick(self
,event
):
856 if self
._zoomEnabled
:
859 def OnMouseRightDown(self
,event
):
860 if self
._zoomEnabled
:
861 X
,Y
= self
.GetXY(event
)
862 self
.Zoom( (X
,Y
), (self
._zoomOutFactor
, self
._zoomOutFactor
) )
864 def OnPaint(self
, event
):
865 # All that is needed here is to draw the buffer to screen
866 dc
= wx
.BufferedPaintDC(self
, self
._Buffer
)
868 def OnSize(self
,event
):
869 # The Buffer init is done here, to make sure the buffer is always
870 # the same size as the Window
871 Size
= self
.GetClientSize()
873 # Make new offscreen bitmap: this bitmap will always have the
874 # current drawing in it, so it can be used to save the image to
875 # a file, or whatever.
876 self
._Buffer
= wx
.EmptyBitmap(Size
[0],Size
[1])
878 if self
.last_draw
is None:
881 graphics
, xSpec
, ySpec
= self
.last_draw
882 self
.Draw(graphics
,xSpec
,ySpec
)
885 # Private Methods **************************************************
886 def _setSize(self
, width
=None, height
=None):
887 """DC width and height."""
889 (self
.width
,self
.height
) = self
.GetClientSize()
891 self
.width
, self
.height
= width
,height
892 self
.plotbox_size
= 0.97*Numeric
.array([self
.width
, self
.height
])
893 xo
= 0.5*(self
.width
-self
.plotbox_size
[0])
894 yo
= self
.height
-0.5*(self
.height
-self
.plotbox_size
[1])
895 self
.plotbox_origin
= Numeric
.array([xo
, yo
])
897 def _setPrinterScale(self
, scale
):
898 """Used to thicken lines and increase marker size for print out."""
899 # line thickness on printer is very thin at 600 dot/in. Markers small
900 self
.printerScale
= scale
902 def _printDraw(self
, printDC
):
903 """Used for printing."""
904 if self
.last_draw
!= None:
905 graphics
, xSpec
, ySpec
= self
.last_draw
906 self
.Draw(graphics
,xSpec
,ySpec
,printDC
)
908 def _drawLegend(self
,dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
):
909 """Draws legend symbols and text"""
910 # top right hand corner of graph box is ref corner
911 trhc
= self
.plotbox_origin
+ (self
.plotbox_size
-[rhsW
,topH
])*[1,-1]
912 legendLHS
= .091* legendBoxWH
[0] # border space between legend sym and graph box
913 lineHeight
= max(legendSymExt
[1], legendTextExt
[1]) * 1.1 #1.1 used as space between lines
914 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
915 for i
in range(len(graphics
)):
918 if isinstance(o
,PolyMarker
):
919 # draw marker with legend
920 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0]/2., trhc
[1]+s
+lineHeight
/2.)
921 o
.draw(dc
, self
.printerScale
, coord
= Numeric
.array([pnt
]))
922 elif isinstance(o
,PolyLine
):
923 # draw line with legend
924 pnt1
= (trhc
[0]+legendLHS
, trhc
[1]+s
+lineHeight
/2.)
925 pnt2
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.)
926 o
.draw(dc
, self
.printerScale
, coord
= Numeric
.array([pnt1
,pnt2
]))
928 raise TypeError, "object is neither PolyMarker or PolyLine instance"
930 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.-legendTextExt
[1]/2)
931 dc
.DrawText(o
.getLegend(),pnt
[0],pnt
[1])
932 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
)) # reset
934 def _titleLablesWH(self
, dc
, graphics
):
935 """Draws Title and labels and returns width and height for each"""
936 # TextExtents for Title and Axis Labels
937 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
938 title
= graphics
.getTitle()
939 titleWH
= dc
.GetTextExtent(title
)
940 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
941 xLabel
, yLabel
= graphics
.getXLabel(),graphics
.getYLabel()
942 xLabelWH
= dc
.GetTextExtent(xLabel
)
943 yLabelWH
= dc
.GetTextExtent(yLabel
)
944 return titleWH
, xLabelWH
, yLabelWH
946 def _legendWH(self
, dc
, graphics
):
947 """Returns the size in screen units for legend box"""
948 if self
._legendEnabled
!= True:
949 legendBoxWH
= symExt
= txtExt
= (0,0)
951 # find max symbol size
952 symExt
= graphics
.getSymExtent(self
.printerScale
)
953 # find max legend text extent
954 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
955 txtList
= graphics
.getLegendNames()
956 txtExt
= dc
.GetTextExtent(txtList
[0])
957 for txt
in graphics
.getLegendNames()[1:]:
958 txtExt
= Numeric
.maximum(txtExt
,dc
.GetTextExtent(txt
))
959 maxW
= symExt
[0]+txtExt
[0]
960 maxH
= max(symExt
[1],txtExt
[1])
961 # padding .1 for lhs of legend box and space between lines
963 maxH
= maxH
* 1.1 * len(txtList
)
964 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
965 legendBoxWH
= (maxW
,maxH
)
966 return (legendBoxWH
, symExt
, txtExt
)
968 def _drawRubberBand(self
, corner1
, corner2
):
969 """Draws/erases rect box from corner1 to corner2"""
970 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(corner1
, corner2
)
972 dc
= wx
.ClientDC( self
)
974 dc
.SetPen(wx
.Pen(wx
.BLACK
))
975 dc
.SetBrush(wx
.Brush( wx
.WHITE
, wx
.TRANSPARENT
) )
976 dc
.SetLogicalFunction(wx
.INVERT
)
977 dc
.DrawRectangle( (ptx
,pty
), (rectWidth
,rectHeight
))
978 dc
.SetLogicalFunction(wx
.COPY
)
981 def _getFont(self
,size
):
982 """Take font size, adjusts if printing and returns wx.Font"""
983 s
= size
*self
.printerScale
985 # Linux speed up to get font from cache rather than X font server
986 key
= (int(s
), of
.GetFamily (), of
.GetStyle (), of
.GetWeight ())
987 font
= self
._fontCache
.get (key
, None)
989 return font
# yeah! cache hit
991 font
= wx
.Font(int(s
), of
.GetFamily(), of
.GetStyle(), of
.GetWeight())
992 self
._fontCache
[key
] = font
996 def _point2ClientCoord(self
, corner1
, corner2
):
997 """Converts user point coords to client screen int coords x,y,width,height"""
998 c1
= Numeric
.array(corner1
)
999 c2
= Numeric
.array(corner2
)
1000 # convert to screen coords
1001 pt1
= c1
*self
._pointScale
+self
._pointShift
1002 pt2
= c2
*self
._pointScale
+self
._pointShift
1003 # make height and width positive
1004 pul
= Numeric
.minimum(pt1
,pt2
) # Upper left corner
1005 plr
= Numeric
.maximum(pt1
,pt2
) # Lower right corner
1006 rectWidth
, rectHeight
= plr
-pul
1008 return int(ptx
),int(pty
),int(rectWidth
),int(rectHeight
) # return ints
1010 def _axisInterval(self
, spec
, lower
, upper
):
1011 """Returns sensible axis range for given spec"""
1012 if spec
== 'none' or spec
== 'min':
1014 return lower
-0.5, upper
+0.5
1017 elif spec
== 'auto':
1020 return lower
-0.5, upper
+0.5
1021 log
= Numeric
.log10(range)
1022 power
= Numeric
.floor(log
)
1023 fraction
= log
-power
1024 if fraction
<= 0.05:
1027 lower
= lower
- lower
% grid
1030 upper
= upper
- mod
+ grid
1032 elif type(spec
) == type(()):
1039 raise ValueError, str(spec
) + ': illegal axis specification'
1041 def _drawAxes(self
, dc
, p1
, p2
, scale
, shift
, xticks
, yticks
):
1043 penWidth
= self
.printerScale
# increases thickness for printing only
1044 dc
.SetPen(wx
.Pen(wx
.NamedColour('BLACK'),int(penWidth
)))
1046 # set length of tick marks--long ones make grid
1047 if self
._gridEnabled
:
1048 x
,y
,width
,height
= self
._point
2ClientCoord
(p1
,p2
)
1049 yTickLength
= width
/2.0 +1
1050 xTickLength
= height
/2.0 +1
1052 yTickLength
= 3 * self
.printerScale
# lengthens lines for printing
1053 xTickLength
= 3 * self
.printerScale
1055 if self
._xSpec
is not 'none':
1056 lower
, upper
= p1
[0],p2
[0]
1058 for y
, d
in [(p1
[1], -xTickLength
), (p2
[1], xTickLength
)]: # miny, maxy and tick lengths
1059 a1
= scale
*Numeric
.array([lower
, y
])+shift
1060 a2
= scale
*Numeric
.array([upper
, y
])+shift
1061 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1]) # draws upper and lower axis line
1062 for x
, label
in xticks
:
1063 pt
= scale
*Numeric
.array([x
, y
])+shift
1064 dc
.DrawLine(pt
[0],pt
[1],pt
[0],pt
[1] + d
) # draws tick mark d units
1066 dc
.DrawText(label
,pt
[0],pt
[1])
1067 text
= 0 # axis values not drawn on top side
1069 if self
._ySpec
is not 'none':
1070 lower
, upper
= p1
[1],p2
[1]
1072 h
= dc
.GetCharHeight()
1073 for x
, d
in [(p1
[0], -yTickLength
), (p2
[0], yTickLength
)]:
1074 a1
= scale
*Numeric
.array([x
, lower
])+shift
1075 a2
= scale
*Numeric
.array([x
, upper
])+shift
1076 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1])
1077 for y
, label
in yticks
:
1078 pt
= scale
*Numeric
.array([x
, y
])+shift
1079 dc
.DrawLine(pt
[0],pt
[1],pt
[0]-d
,pt
[1])
1081 dc
.DrawText(label
,pt
[0]-dc
.GetTextExtent(label
)[0],
1083 text
= 0 # axis values not drawn on right side
1085 def _ticks(self
, lower
, upper
):
1086 ideal
= (upper
-lower
)/7.
1087 log
= Numeric
.log10(ideal
)
1088 power
= Numeric
.floor(log
)
1089 fraction
= log
-power
1092 for f
, lf
in self
._multiples
:
1093 e
= Numeric
.fabs(fraction
-lf
)
1097 grid
= factor
* 10.**power
1098 if power
> 4 or power
< -4:
1101 digits
= max(1, int(power
))
1102 format
= '%' + `digits`
+'.0f'
1104 digits
= -int(power
)
1105 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
1107 t
= -grid
*Numeric
.floor(-lower
/grid
)
1109 ticks
.append( (t
, format
% (t
,)) )
1113 _multiples
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))]
1116 #-------------------------------------------------------------------------------
1117 # Used to layout the printer page
1119 class PlotPrintout(wx
.Printout
):
1120 """Controls how the plot is made in printing and previewing"""
1121 # Do not change method names in this class,
1122 # we have to override wx.Printout methods here!
1123 def __init__(self
, graph
):
1124 """graph is instance of plotCanvas to be printed or previewed"""
1125 wx
.Printout
.__init
__(self
)
1128 def HasPage(self
, page
):
1134 def GetPageInfo(self
):
1135 return (0, 1, 1, 1) # disable page numbers
1137 def OnPrintPage(self
, page
):
1138 dc
= FloatDCWrapper(self
.GetDC()) # allows using floats for certain functions
1139 ## print "PPI Printer",self.GetPPIPrinter()
1140 ## print "PPI Screen", self.GetPPIScreen()
1141 ## print "DC GetSize", dc.GetSize()
1142 ## print "GetPageSizePixels", self.GetPageSizePixels()
1143 # Note PPIScreen does not give the correct number
1144 # Calulate everything for printer and then scale for preview
1145 PPIPrinter
= self
.GetPPIPrinter() # printer dots/inch (w,h)
1146 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1147 dcSize
= dc
.GetSize() # DC size
1148 pageSize
= self
.GetPageSizePixels() # page size in terms of pixcels
1149 clientDcSize
= self
.graph
.GetClientSize()
1151 # find what the margins are (mm)
1152 margLeftSize
,margTopSize
= self
.graph
.pageSetupData
.GetMarginTopLeft()
1153 margRightSize
, margBottomSize
= self
.graph
.pageSetupData
.GetMarginBottomRight()
1155 # calculate offset and scale for dc
1156 pixLeft
= margLeftSize
*PPIPrinter
[0]/25.4 # mm*(dots/in)/(mm/in)
1157 pixRight
= margRightSize
*PPIPrinter
[0]/25.4
1158 pixTop
= margTopSize
*PPIPrinter
[1]/25.4
1159 pixBottom
= margBottomSize
*PPIPrinter
[1]/25.4
1161 plotAreaW
= pageSize
[0]-(pixLeft
+pixRight
)
1162 plotAreaH
= pageSize
[1]-(pixTop
+pixBottom
)
1164 # ratio offset and scale to screen size if preview
1165 if self
.IsPreview():
1166 ratioW
= float(dcSize
[0])/pageSize
[0]
1167 ratioH
= float(dcSize
[1])/pageSize
[1]
1173 # rescale plot to page or preview plot area
1174 self
.graph
._setSize
(plotAreaW
,plotAreaH
)
1176 # Set offset and scale
1177 dc
.SetDeviceOrigin(pixLeft
,pixTop
)
1179 # Thicken up pens and increase marker size for printing
1180 ratioW
= float(plotAreaW
)/clientDcSize
[0]
1181 ratioH
= float(plotAreaH
)/clientDcSize
[1]
1182 aveScale
= (ratioW
+ratioH
)/2
1183 self
.graph
._setPrinterScale
(aveScale
) # tickens up pens for printing
1185 self
.graph
._printDraw
(dc
)
1186 # rescale back to original
1187 self
.graph
._setSize
()
1188 self
.graph
._setPrinterScale
(1)
1192 # Hack to allow plotting real numbers for the methods listed.
1193 # All others passed directly to DC.
1194 # For Drawing it is used as
1195 # dc = FloatDCWrapper(wx.BufferedDC(wx.ClientDC(self), self._Buffer))
1196 # For printing is is used as
1197 # dc = FloatDCWrapper(self.GetDC())
1198 class FloatDCWrapper
:
1199 def __init__(self
, aDC
):
1202 def DrawLine(self
, x1
,y1
,x2
,y2
):
1203 self
.theDC
.DrawLine((int(x1
),int(y1
)),(int(x2
),int(y2
)))
1205 def DrawText(self
, txt
, x
, y
):
1206 self
.theDC
.DrawText(txt
, (int(x
), int(y
)))
1208 def DrawRotatedText(self
, txt
, x
, y
, angle
):
1209 self
.theDC
.DrawRotatedText(txt
, (int(x
), int(y
)), angle
)
1211 def SetClippingRegion(self
, x
, y
, width
, height
):
1212 self
.theDC
.SetClippingRegion((int(x
), int(y
)), (int(width
), int(height
)))
1214 def SetDeviceOrigin(self
, x
, y
):
1215 self
.theDC
.SetDeviceOrigin(int(x
), int(y
))
1217 def __getattr__(self
, name
):
1218 return getattr(self
.theDC
, name
)
1223 #---------------------------------------------------------------------------
1224 # if running standalone...
1226 # ...a sample implementation using the above
1229 def _draw1Objects():
1230 # 100 points sin function, plotted as green circles
1231 data1
= 2.*Numeric
.pi
*Numeric
.arange(200)/200.
1232 data1
.shape
= (100, 2)
1233 data1
[:,1] = Numeric
.sin(data1
[:,0])
1234 markers1
= PolyMarker(data1
, legend
='Green Markers', colour
='green', marker
='circle',size
=1)
1236 # 50 points cos function, plotted as red line
1237 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
1238 data1
.shape
= (50,2)
1239 data1
[:,1] = Numeric
.cos(data1
[:,0])
1240 lines
= PolyLine(data1
, legend
= 'Red Line', colour
='red')
1242 # A few more points...
1244 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1245 (3.*pi
/4., -1)], legend
='Cross Legend', colour
='blue',
1248 return PlotGraphics([markers1
, lines
, markers2
],"Graph Title", "X Axis", "Y Axis")
1250 def _draw2Objects():
1251 # 100 points sin function, plotted as green dots
1252 data1
= 2.*Numeric
.pi
*Numeric
.arange(200)/200.
1253 data1
.shape
= (100, 2)
1254 data1
[:,1] = Numeric
.sin(data1
[:,0])
1255 line1
= PolyLine(data1
, legend
='Green Line', colour
='green', width
=6, style
=wx
.DOT
)
1257 # 50 points cos function, plotted as red dot-dash
1258 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
1259 data1
.shape
= (50,2)
1260 data1
[:,1] = Numeric
.cos(data1
[:,0])
1261 line2
= PolyLine(data1
, legend
='Red Line', colour
='red', width
=3, style
= wx
.DOT_DASH
)
1263 # A few more points...
1265 markers1
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1266 (3.*pi
/4., -1)], legend
='Cross Hatch Square', colour
='blue', width
= 3, size
= 6,
1267 fillcolour
= 'red', fillstyle
= wx
.CROSSDIAG_HATCH
,
1270 return PlotGraphics([markers1
, line1
, line2
], "Big Markers with Different Line Styles")
1272 def _draw3Objects():
1273 markerList
= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1274 'cross', 'plus', 'circle']
1276 for i
in range(len(markerList
)):
1277 m
.append(PolyMarker([(2*i
+.5,i
+.5)], legend
=markerList
[i
], colour
='blue',
1278 marker
=markerList
[i
]))
1279 return PlotGraphics(m
, "Selection of Markers", "Minimal Axis", "No Axis")
1281 def _draw4Objects():
1283 data1
= Numeric
.arange(5e5
,1e6
,10)
1284 data1
.shape
= (25000, 2)
1285 line1
= PolyLine(data1
, legend
='Wide Line', colour
='green', width
=5)
1287 # A few more points...
1288 markers2
= PolyMarker(data1
, legend
='Square', colour
='blue',
1290 return PlotGraphics([line1
, markers2
], "25,000 Points", "Value X", "")
1292 def _draw5Objects():
1293 # Empty graph with axis defined but no points/lines
1295 line1
= PolyLine(points
, legend
='Wide Line', colour
='green', width
=5)
1296 return PlotGraphics([line1
], "Empty Plot With Just Axes", "Value X", "Value Y")
1299 class TestFrame(wx
.Frame
):
1300 def __init__(self
, parent
, id, title
):
1301 wx
.Frame
.__init
__(self
, parent
, id, title
,
1302 wx
.DefaultPosition
, (600, 400))
1304 # Now Create the menu bar and items
1305 self
.mainmenu
= wx
.MenuBar()
1308 menu
.Append(200, 'Page Setup...', 'Setup the printer page')
1309 self
.Bind(wx
.EVT_MENU
, self
.OnFilePageSetup
, id=200)
1311 menu
.Append(201, 'Print Preview...', 'Show the current plot on page')
1312 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrintPreview
, id=201)
1314 menu
.Append(202, 'Print...', 'Print the current plot')
1315 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrint
, id=202)
1317 menu
.Append(203, 'Save Plot...', 'Save current plot')
1318 self
.Bind(wx
.EVT_MENU
, self
.OnSaveFile
, id=203)
1320 menu
.Append(205, 'E&xit', 'Enough of this already!')
1321 self
.Bind(wx
.EVT_MENU
, self
.OnFileExit
, id=205)
1322 self
.mainmenu
.Append(menu
, '&File')
1325 menu
.Append(206, 'Draw1', 'Draw plots1')
1326 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw1
, id=206)
1327 menu
.Append(207, 'Draw2', 'Draw plots2')
1328 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw2
, id=207)
1329 menu
.Append(208, 'Draw3', 'Draw plots3')
1330 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw3
, id=208)
1331 menu
.Append(209, 'Draw4', 'Draw plots4')
1332 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw4
, id=209)
1333 menu
.Append(210, 'Draw5', 'Draw plots5')
1334 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw5
, id=210)
1336 menu
.Append(211, '&Redraw', 'Redraw plots')
1337 self
.Bind(wx
.EVT_MENU
,self
.OnPlotRedraw
, id=211)
1338 menu
.Append(212, '&Clear', 'Clear canvas')
1339 self
.Bind(wx
.EVT_MENU
,self
.OnPlotClear
, id=212)
1340 menu
.Append(213, '&Scale', 'Scale canvas')
1341 self
.Bind(wx
.EVT_MENU
,self
.OnPlotScale
, id=213)
1342 menu
.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind
=wx
.ITEM_CHECK
)
1343 self
.Bind(wx
.EVT_MENU
,self
.OnEnableZoom
, id=214)
1344 menu
.Append(215, 'Enable &Grid', 'Turn on Grid', kind
=wx
.ITEM_CHECK
)
1345 self
.Bind(wx
.EVT_MENU
,self
.OnEnableGrid
, id=215)
1346 menu
.Append(220, 'Enable &Legend', 'Turn on Legend', kind
=wx
.ITEM_CHECK
)
1347 self
.Bind(wx
.EVT_MENU
,self
.OnEnableLegend
, id=220)
1348 menu
.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1349 self
.Bind(wx
.EVT_MENU
,self
.OnScrUp
, id=225)
1350 menu
.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1351 self
.Bind(wx
.EVT_MENU
,self
.OnScrRt
, id=230)
1352 menu
.Append(235, '&Plot Reset', 'Reset to original plot')
1353 self
.Bind(wx
.EVT_MENU
,self
.OnReset
, id=235)
1355 self
.mainmenu
.Append(menu
, '&Plot')
1358 menu
.Append(300, '&About', 'About this thing...')
1359 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=300)
1360 self
.mainmenu
.Append(menu
, '&Help')
1362 self
.SetMenuBar(self
.mainmenu
)
1364 # A status bar to tell people what's happening
1365 self
.CreateStatusBar(1)
1367 self
.client
= PlotCanvas(self
)
1368 # Create mouse event for showing cursor coords in status bar
1369 self
.client
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
1372 def OnMouseLeftDown(self
,event
):
1373 s
= "Left Mouse Down at Point: (%.4f, %.4f)" % self
.client
.GetXY(event
)
1374 self
.SetStatusText(s
)
1377 def OnFilePageSetup(self
, event
):
1378 self
.client
.PageSetup()
1380 def OnFilePrintPreview(self
, event
):
1381 self
.client
.PrintPreview()
1383 def OnFilePrint(self
, event
):
1384 self
.client
.Printout()
1386 def OnSaveFile(self
, event
):
1387 self
.client
.SaveFile()
1389 def OnFileExit(self
, event
):
1392 def OnPlotDraw1(self
, event
):
1393 self
.resetDefaults()
1394 self
.client
.Draw(_draw1Objects())
1396 def OnPlotDraw2(self
, event
):
1397 self
.resetDefaults()
1398 self
.client
.Draw(_draw2Objects())
1400 def OnPlotDraw3(self
, event
):
1401 self
.resetDefaults()
1402 self
.client
.SetFont(wx
.Font(10,wx
.SCRIPT
,wx
.NORMAL
,wx
.NORMAL
))
1403 self
.client
.SetFontSizeAxis(20)
1404 self
.client
.SetFontSizeLegend(12)
1405 self
.client
.SetXSpec('min')
1406 self
.client
.SetYSpec('none')
1407 self
.client
.Draw(_draw3Objects())
1409 def OnPlotDraw4(self
, event
):
1410 self
.resetDefaults()
1411 drawObj
= _draw4Objects()
1412 self
.client
.Draw(drawObj
)
1414 ## start = time.clock()
1415 ## for x in range(10):
1416 ## self.client.Draw(drawObj)
1417 ## print "10 plots of Draw4 took: %f sec."%(time.clock() - start)
1420 def OnPlotDraw5(self
, event
):
1421 # Empty plot with just axes
1422 self
.resetDefaults()
1423 drawObj
= _draw5Objects()
1424 # make the axis X= (0,5), Y=(0,10)
1425 # (default with None is X= (-1,1), Y= (-1,1))
1426 self
.client
.Draw(drawObj
, xAxis
= (0,5), yAxis
= (0,10))
1428 def OnPlotRedraw(self
,event
):
1429 self
.client
.Redraw()
1431 def OnPlotClear(self
,event
):
1434 def OnPlotScale(self
, event
):
1435 if self
.client
.last_draw
!= None:
1436 graphics
, xAxis
, yAxis
= self
.client
.last_draw
1437 self
.client
.Draw(graphics
,(1,3.05),(0,1))
1439 def OnEnableZoom(self
, event
):
1440 self
.client
.SetEnableZoom(event
.IsChecked())
1442 def OnEnableGrid(self
, event
):
1443 self
.client
.SetEnableGrid(event
.IsChecked())
1445 def OnEnableLegend(self
, event
):
1446 self
.client
.SetEnableLegend(event
.IsChecked())
1448 def OnScrUp(self
, event
):
1449 self
.client
.ScrollUp(1)
1451 def OnScrRt(self
,event
):
1452 self
.client
.ScrollRight(2)
1454 def OnReset(self
,event
):
1457 def OnHelpAbout(self
, event
):
1458 from wx
.lib
.dialogs
import wxScrolledMessageDialog
1459 about
= wxScrolledMessageDialog(self
, __doc__
, "About...")
1462 def resetDefaults(self
):
1463 """Just to reset the fonts back to the PlotCanvas defaults"""
1464 self
.client
.SetFont(wx
.Font(10,wx
.SWISS
,wx
.NORMAL
,wx
.NORMAL
))
1465 self
.client
.SetFontSizeAxis(10)
1466 self
.client
.SetFontSizeLegend(7)
1467 self
.client
.SetXSpec('auto')
1468 self
.client
.SetYSpec('auto')
1473 class MyApp(wx
.App
):
1475 wx
.InitAllImageHandlers()
1476 frame
= TestFrame(None, -1, "PlotCanvas")
1478 self
.SetTopWindow(frame
)
1485 if __name__
== '__main__':