]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/plot.py
2a5be751207b3d98a4d306000d5593fe0abe30da
1 #-----------------------------------------------------------------------------
3 # Purpose: Line, Bar and Scatter Graphs
5 # Author: Gordon Williams
10 # Licence: Use as you wish.
11 #-----------------------------------------------------------------------------
12 # 12/15/2003 - Jeff Grimmett (grimmtooth@softhome.net)
14 # o 2.5 compatability update.
15 # o Renamed to plot.py in the wx.lib directory.
16 # o Reworked test frame to work with wx demo framework. This saves a bit
17 # of tedious cut and paste, and the test app is excellent.
19 # 12/18/2003 - Jeff Grimmett (grimmtooth@softhome.net)
21 # o wxScrolledMessageDialog -> ScrolledMessageDialog
23 # Oct 6, 2004 Gordon Williams (g_will@cyberus.ca)
24 # - Added bar graph demo
25 # - Modified line end shape from round to square.
26 # - Removed FloatDCWrapper for conversion to ints and ints in arguments
28 # Oct 15, 2004 Gordon Williams (g_will@cyberus.ca)
29 # - Imported modules given leading underscore to name.
30 # - Added Cursor Line Tracking and User Point Labels.
31 # - Demo for Cursor Line Tracking and Point Labels.
32 # - Size of plot preview frame adjusted to show page better.
33 # - Added helper functions PositionUserToScreen and PositionScreenToUser in PlotCanvas.
34 # - Added functions GetClosestPoints (all curves) and GetClosestPoint (only closest curve)
35 # can be in either user coords or screen coords.
40 This is a simple light weight plotting module that can be used with
41 Boa or easily integrated into your own wxPython application. The
42 emphasis is on small size and fast plotting for large data sets. It
43 has a reasonable number of features to do line and scatter graphs
44 easily as well as simple bar graphs. It is not as sophisticated or
45 as powerful as SciPy Plt or Chaco. Both of these are great packages
46 but consume huge amounts of computer resources for simple plots.
47 They can be found at http://scipy.com
49 This file contains two parts; first the re-usable library stuff, then,
50 after a "if __name__=='__main__'" test, a simple frame and a few default
51 plots for examples and testing.
54 Written by K.Hinsen, R. Srinivasan;
55 Ported to wxPython Harm van der Heijden, feb 1999
57 Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
59 -Zooming using mouse "rubber band"
62 -Printing, preview, and page set up (margins)
63 -Axis and title labels
64 -Cursor xy axis values
65 -Doc strings and lots of comments
66 -Optimizations for large number of points
69 Did a lot of work here to speed markers up. Only a factor of 4
70 improvement though. Lines are much faster than markers, especially
71 filled markers. Stay away from circles and triangles unless you
72 only have a few thousand points.
74 Times for 25,000 points
81 triangle, triangle_down - 0.90
83 Thanks to Chris Barker for getting this version working on Linux.
85 Zooming controls with mouse (when enabled):
86 Left mouse drag - Zoom box.
87 Left mouse double click - reset zoom.
88 Right mouse click - zoom out centred on click location.
91 import string
as _string
95 # Needs Numeric or numarray or NumPy
97 import numpy
as _Numeric
100 import numarray
as _Numeric
#if numarray is used it is renamed Numeric
103 import Numeric
as _Numeric
106 This module requires the Numeric/numarray or NumPy module,
107 which could not be imported. It probably is not installed
108 (it's not part of the standard Python distribution). See the
109 Numeric Python site (http://numpy.scipy.org) for information on
110 downloading source or binaries."""
111 raise ImportError, "Numeric,numarray or NumPy not found. \n" + msg
116 # Plotting classes...
119 """Base Class for lines and markers
120 - All methods are private.
123 def __init__(self
, points
, attr
):
124 self
.points
= _Numeric
.array(points
)
125 self
.currentScale
= (1,1)
126 self
.currentShift
= (0,0)
127 self
.scaled
= self
.points
129 self
.attributes
.update(self
._attributes
)
130 for name
, value
in attr
.items():
131 if name
not in self
._attributes
.keys():
132 raise KeyError, "Style attribute incorrect. Should be one of %s" % self
._attributes
.keys()
133 self
.attributes
[name
] = value
135 def boundingBox(self
):
136 if len(self
.points
) == 0:
138 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
139 minXY
= _Numeric
.array([-1,-1])
140 maxXY
= _Numeric
.array([ 1, 1])
142 minXY
= _Numeric
.minimum
.reduce(self
.points
)
143 maxXY
= _Numeric
.maximum
.reduce(self
.points
)
146 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
147 if len(self
.points
) == 0:
150 if (scale
is not self
.currentScale
) or (shift
is not self
.currentShift
):
151 # update point scaling
152 self
.scaled
= scale
*self
.points
+shift
153 self
.currentScale
= scale
154 self
.currentShift
= shift
155 # else unchanged use the current scaling
158 return self
.attributes
['legend']
160 def getClosestPoint(self
, pntXY
, pointScaled
= True):
161 """Returns the index of closest point on the curve, pointXY, scaledXY, distance
163 if pointScaled == True based on screen coords
164 if pointScaled == False based on user coords
166 if pointScaled
== True:
169 pxy
= self
.currentScale
* _Numeric
.array(pntXY
)+ self
.currentShift
173 pxy
= _Numeric
.array(pntXY
)
174 #determine distance for each point
175 d
= _Numeric
.sqrt(_Numeric
.add
.reduce((p
-pxy
)**2,1)) #sqrt(dx^2+dy^2)
176 pntIndex
= _Numeric
.argmin(d
)
178 return [pntIndex
, self
.points
[pntIndex
], self
.scaled
[pntIndex
], dist
]
181 class PolyLine(PolyPoints
):
182 """Class to define line type and style
183 - All methods except __init__ are private.
186 _attributes
= {'colour': 'black',
191 def __init__(self
, points
, **attr
):
192 """Creates PolyLine object
193 points - sequence (array, tuple or list) of (x,y) points making up line
194 **attr - key word attributes
196 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
197 'width'= 1, - Pen width
198 'style'= wx.SOLID, - wx.Pen style
199 'legend'= '' - Line Legend to display
201 PolyPoints
.__init
__(self
, points
, attr
)
203 def draw(self
, dc
, printerScale
, coord
= None):
204 colour
= self
.attributes
['colour']
205 width
= self
.attributes
['width'] * printerScale
206 style
= self
.attributes
['style']
207 if not isinstance(colour
, wx
.Colour
):
208 colour
= wx
.NamedColour(colour
)
209 pen
= wx
.Pen(colour
, width
, style
)
210 pen
.SetCap(wx
.CAP_BUTT
)
213 dc
.DrawLines(self
.scaled
)
215 dc
.DrawLines(coord
) # draw legend line
217 def getSymExtent(self
, printerScale
):
218 """Width and Height of Marker"""
219 h
= self
.attributes
['width'] * printerScale
224 class PolyMarker(PolyPoints
):
225 """Class to define marker type and style
226 - All methods except __init__ are private.
229 _attributes
= {'colour': 'black',
233 'fillstyle': wx
.SOLID
,
237 def __init__(self
, points
, **attr
):
238 """Creates PolyMarker object
239 points - sequence (array, tuple or list) of (x,y) points
240 **attr - key word attributes
242 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
243 'width'= 1, - Pen width
244 'size'= 2, - Marker size
245 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
246 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
247 'marker'= 'circle' - Marker shape
248 'legend'= '' - Marker Legend to display
260 PolyPoints
.__init
__(self
, points
, attr
)
262 def draw(self
, dc
, printerScale
, coord
= None):
263 colour
= self
.attributes
['colour']
264 width
= self
.attributes
['width'] * printerScale
265 size
= self
.attributes
['size'] * printerScale
266 fillcolour
= self
.attributes
['fillcolour']
267 fillstyle
= self
.attributes
['fillstyle']
268 marker
= self
.attributes
['marker']
270 if colour
and not isinstance(colour
, wx
.Colour
):
271 colour
= wx
.NamedColour(colour
)
272 if fillcolour
and not isinstance(fillcolour
, wx
.Colour
):
273 fillcolour
= wx
.NamedColour(fillcolour
)
275 dc
.SetPen(wx
.Pen(colour
, width
))
277 dc
.SetBrush(wx
.Brush(fillcolour
,fillstyle
))
279 dc
.SetBrush(wx
.Brush(colour
, fillstyle
))
281 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
283 self
._drawmarkers
(dc
, coord
, marker
, size
) # draw legend marker
285 def getSymExtent(self
, printerScale
):
286 """Width and Height of Marker"""
287 s
= 5*self
.attributes
['size'] * printerScale
290 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
291 f
= eval('self._' +marker
)
294 def _circle(self
, dc
, coords
, size
=1):
297 rect
= _Numeric
.zeros((len(coords
),4),_Numeric
.Float
)+[0.0,0.0,wh
,wh
]
298 rect
[:,0:2]= coords
-[fact
,fact
]
299 dc
.DrawEllipseList(rect
.astype(_Numeric
.Int32
))
301 def _dot(self
, dc
, coords
, size
=1):
302 dc
.DrawPointList(coords
)
304 def _square(self
, dc
, coords
, size
=1):
307 rect
= _Numeric
.zeros((len(coords
),4),_Numeric
.Float
)+[0.0,0.0,wh
,wh
]
308 rect
[:,0:2]= coords
-[fact
,fact
]
309 dc
.DrawRectangleList(rect
.astype(_Numeric
.Int32
))
311 def _triangle(self
, dc
, coords
, size
=1):
312 shape
= [(-2.5*size
,1.44*size
), (2.5*size
,1.44*size
), (0.0,-2.88*size
)]
313 poly
= _Numeric
.repeat(coords
,3)
314 poly
.shape
= (len(coords
),3,2)
316 dc
.DrawPolygonList(poly
.astype(_Numeric
.Int32
))
318 def _triangle_down(self
, dc
, coords
, size
=1):
319 shape
= [(-2.5*size
,-1.44*size
), (2.5*size
,-1.44*size
), (0.0,2.88*size
)]
320 poly
= _Numeric
.repeat(coords
,3)
321 poly
.shape
= (len(coords
),3,2)
323 dc
.DrawPolygonList(poly
.astype(_Numeric
.Int32
))
325 def _cross(self
, dc
, coords
, size
=1):
327 for f
in [[-fact
,-fact
,fact
,fact
],[-fact
,fact
,fact
,-fact
]]:
328 lines
= _Numeric
.concatenate((coords
,coords
),axis
=1)+f
329 dc
.DrawLineList(lines
.astype(_Numeric
.Int32
))
331 def _plus(self
, dc
, coords
, size
=1):
333 for f
in [[-fact
,0,fact
,0],[0,-fact
,0,fact
]]:
334 lines
= _Numeric
.concatenate((coords
,coords
),axis
=1)+f
335 dc
.DrawLineList(lines
.astype(_Numeric
.Int32
))
338 """Container to hold PolyXXX objects and graph labels
339 - All methods except __init__ are private.
342 def __init__(self
, objects
, title
='', xLabel
='', yLabel
= ''):
343 """Creates PlotGraphics object
344 objects - list of PolyXXX objects to make graph
345 title - title shown at top of graph
346 xLabel - label shown on x-axis
347 yLabel - label shown on y-axis
349 if type(objects
) not in [list,tuple]:
350 raise TypeError, "objects argument should be list or tuple"
351 self
.objects
= objects
356 def boundingBox(self
):
357 p1
, p2
= self
.objects
[0].boundingBox()
358 for o
in self
.objects
[1:]:
359 p1o
, p2o
= o
.boundingBox()
360 p1
= _Numeric
.minimum(p1
, p1o
)
361 p2
= _Numeric
.maximum(p2
, p2o
)
364 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
365 for o
in self
.objects
:
366 o
.scaleAndShift(scale
, shift
)
368 def setPrinterScale(self
, scale
):
369 """Thickens up lines and markers only for printing"""
370 self
.printerScale
= scale
372 def setXLabel(self
, xLabel
= ''):
373 """Set the X axis label on the graph"""
376 def setYLabel(self
, yLabel
= ''):
377 """Set the Y axis label on the graph"""
380 def setTitle(self
, title
= ''):
381 """Set the title at the top of graph"""
385 """Get x axis label string"""
389 """Get y axis label string"""
392 def getTitle(self
, title
= ''):
393 """Get the title at the top of graph"""
397 for o
in self
.objects
:
398 #t=_time.clock() # profile info
399 o
.draw(dc
, self
.printerScale
)
401 #print o, "time=", dt
403 def getSymExtent(self
, printerScale
):
404 """Get max width and height of lines and markers symbols for legend"""
405 symExt
= self
.objects
[0].getSymExtent(printerScale
)
406 for o
in self
.objects
[1:]:
407 oSymExt
= o
.getSymExtent(printerScale
)
408 symExt
= _Numeric
.maximum(symExt
, oSymExt
)
411 def getLegendNames(self
):
412 """Returns list of legend names"""
413 lst
= [None]*len(self
)
414 for i
in range(len(self
)):
415 lst
[i
]= self
.objects
[i
].getLegend()
419 return len(self
.objects
)
421 def __getitem__(self
, item
):
422 return self
.objects
[item
]
425 #-------------------------------------------------------------------------------
426 # Main window that you will want to import into your application.
428 class PlotCanvas(wx
.Panel
):
430 Subclass of a wx.Panel which holds two scrollbars and the actual
431 plotting canvas (self.canvas). It allows for simple general plotting
432 of data with zoom, labels, and automatic axis scaling."""
434 def __init__(self
, parent
):
435 """Constructs a panel, which can be a child of a frame or
436 any other non-control window"""
438 wx
.Panel
.__init
__(self
, parent
)
440 sizer
= wx
.FlexGridSizer(2,2,0,0)
441 self
.canvas
= wx
.Window(self
, -1)
442 #self.canvas.SetMinSize((10,10))
443 self
.sb_vert
= wx
.ScrollBar(self
, -1, style
=wx
.SB_VERTICAL
)
444 self
.sb_vert
.SetScrollbar(0,1000,1000,1000)
445 self
.sb_hor
= wx
.ScrollBar(self
, -1, style
=wx
.SB_HORIZONTAL
)
446 self
.sb_hor
.SetScrollbar(0,1000,1000,1000)
448 sizer
.Add(self
.canvas
, 1, wx
.EXPAND
)
449 sizer
.Add(self
.sb_vert
, 0, wx
.EXPAND
)
450 sizer
.Add(self
.sb_hor
, 0, wx
.EXPAND
)
452 #corner = wx.Window(self)
453 #corner.SetMinSize((0,0))
454 #sizer.Add(corner, 0, wx.EXPAND)
456 sizer
.AddGrowableRow(0, 1)
457 sizer
.AddGrowableCol(0, 1)
458 self
.sb_vert
.Show(False)
459 self
.sb_hor
.Show(False)
463 self
.SetBackgroundColour("white")
465 # Create some mouse events for zooming
466 self
.canvas
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
467 self
.canvas
.Bind(wx
.EVT_LEFT_UP
, self
.OnMouseLeftUp
)
468 self
.canvas
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
469 self
.canvas
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnMouseDoubleClick
)
470 self
.canvas
.Bind(wx
.EVT_RIGHT_DOWN
, self
.OnMouseRightDown
)
473 self
.Bind(wx
.EVT_SCROLL_THUMBTRACK
, self
.OnScroll
)
474 self
.Bind(wx
.EVT_SCROLL_PAGEUP
, self
.OnScroll
)
475 self
.Bind(wx
.EVT_SCROLL_PAGEDOWN
, self
.OnScroll
)
476 self
.Bind(wx
.EVT_SCROLL_LINEUP
, self
.OnScroll
)
477 self
.Bind(wx
.EVT_SCROLL_LINEDOWN
, self
.OnScroll
)
479 # set curser as cross-hairs
480 self
.canvas
.SetCursor(wx
.CROSS_CURSOR
)
481 self
.HandCursor
= wx
.CursorFromImage(getHandImage())
482 self
.GrabHandCursor
= wx
.CursorFromImage(getGrabHandImage())
483 self
.MagCursor
= wx
.CursorFromImage(getMagPlusImage())
485 # Things for printing
486 self
.print_data
= wx
.PrintData()
487 self
.print_data
.SetPaperId(wx
.PAPER_LETTER
)
488 self
.print_data
.SetOrientation(wx
.LANDSCAPE
)
489 self
.pageSetupData
= wx
.PageSetupDialogData()
490 self
.pageSetupData
.SetMarginBottomRight((25,25))
491 self
.pageSetupData
.SetMarginTopLeft((25,25))
492 self
.pageSetupData
.SetPrintData(self
.print_data
)
493 self
.printerScale
= 1
496 # scrollbar variables
497 self
._sb
_ignore
= False
498 self
._adjustingSB
= False
499 self
._sb
_xfullrange
= 0
500 self
._sb
_yfullrange
= 0
504 self
._dragEnabled
= False
505 self
._screenCoordinates
= _Numeric
.array([0.0, 0.0])
508 self
._zoomInFactor
= 0.5
509 self
._zoomOutFactor
= 2
510 self
._zoomCorner
1= _Numeric
.array([0.0, 0.0]) # left mouse down corner
511 self
._zoomCorner
2= _Numeric
.array([0.0, 0.0]) # left mouse up corner
512 self
._zoomEnabled
= False
513 self
._hasDragged
= False
516 self
.last_draw
= None
521 self
._gridEnabled
= False
522 self
._legendEnabled
= False
526 self
._fontSizeAxis
= 10
527 self
._fontSizeTitle
= 15
528 self
._fontSizeLegend
= 7
531 self
._pointLabelEnabled
= False
532 self
.last_PointLabel
= None
533 self
._pointLabelFunc
= None
534 self
.canvas
.Bind(wx
.EVT_LEAVE_WINDOW
, self
.OnLeave
)
536 self
.canvas
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
537 self
.canvas
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
538 # OnSize called to make sure the buffer is initialized.
539 # This might result in OnSize getting called twice on some
540 # platforms at initialization, but little harm done.
541 self
.OnSize(None) # sets the initial size based on client size
543 self
._gridColour
= wx
.NamedColour('black')
545 def SetCursor(self
, cursor
):
546 self
.canvas
.SetCursor(cursor
)
548 def GetGridColour(self
):
549 return self
._gridColour
551 def SetGridColour(self
, colour
):
552 if isinstance(colour
, wx
.Colour
):
553 self
._gridColour
= colour
555 self
._gridColour
= wx
.NamedColour(colour
)
559 def SaveFile(self
, fileName
= ''):
560 """Saves the file to the type specified in the extension. If no file
561 name is specified a dialog box is provided. Returns True if sucessful,
564 .bmp Save a Windows bitmap file.
565 .xbm Save an X bitmap file.
566 .xpm Save an XPM bitmap file.
567 .png Save a Portable Network Graphics file.
568 .jpg Save a Joint Photographic Experts Group file.
570 if _string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
571 dlg1
= wx
.FileDialog(
573 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
574 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
575 wx
.SAVE|wx
.OVERWRITE_PROMPT
579 if dlg1
.ShowModal() == wx
.ID_OK
:
580 fileName
= dlg1
.GetPath()
581 # Check for proper exension
582 if _string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
583 dlg2
= wx
.MessageDialog(self
, 'File name extension\n'
585 'bmp, xbm, xpm, png, or jpg',
586 'File Name Error', wx
.OK | wx
.ICON_ERROR
)
592 break # now save file
593 else: # exit without saving
598 # File name has required extension
599 fType
= _string
.lower(fileName
[-3:])
601 tp
= wx
.BITMAP_TYPE_BMP
# Save a Windows bitmap file.
603 tp
= wx
.BITMAP_TYPE_XBM
# Save an X bitmap file.
605 tp
= wx
.BITMAP_TYPE_XPM
# Save an XPM bitmap file.
607 tp
= wx
.BITMAP_TYPE_JPEG
# Save a JPG file.
609 tp
= wx
.BITMAP_TYPE_PNG
# Save a PNG file.
611 res
= self
._Buffer
.SaveFile(fileName
, tp
)
615 """Brings up the page setup dialog"""
616 data
= self
.pageSetupData
617 data
.SetPrintData(self
.print_data
)
618 dlg
= wx
.PageSetupDialog(self
.parent
, data
)
620 if dlg
.ShowModal() == wx
.ID_OK
:
621 data
= dlg
.GetPageSetupData() # returns wx.PageSetupDialogData
622 # updates page parameters from dialog
623 self
.pageSetupData
.SetMarginBottomRight(data
.GetMarginBottomRight())
624 self
.pageSetupData
.SetMarginTopLeft(data
.GetMarginTopLeft())
625 self
.pageSetupData
.SetPrintData(data
.GetPrintData())
626 self
.print_data
=wx
.PrintData(data
.GetPrintData()) # updates print_data
630 def Printout(self
, paper
=None):
631 """Print current plot."""
633 self
.print_data
.SetPaperId(paper
)
634 pdd
= wx
.PrintDialogData(self
.print_data
)
635 printer
= wx
.Printer(pdd
)
636 out
= PlotPrintout(self
)
637 print_ok
= printer
.Print(self
.parent
, out
)
639 self
.print_data
= wx
.PrintData(printer
.GetPrintDialogData().GetPrintData())
642 def PrintPreview(self
):
643 """Print-preview current plot."""
644 printout
= PlotPrintout(self
)
645 printout2
= PlotPrintout(self
)
646 self
.preview
= wx
.PrintPreview(printout
, printout2
, self
.print_data
)
647 if not self
.preview
.Ok():
648 wx
.MessageDialog(self
, "Print Preview failed.\n" \
649 "Check that default printer is configured\n", \
650 "Print error", wx
.OK|wx
.CENTRE
).ShowModal()
651 self
.preview
.SetZoom(40)
652 # search up tree to find frame instance
654 while not isinstance(frameInst
, wx
.Frame
):
655 frameInst
= frameInst
.GetParent()
656 frame
= wx
.PreviewFrame(self
.preview
, frameInst
, "Preview")
658 frame
.SetPosition(self
.GetPosition())
659 frame
.SetSize((600,550))
660 frame
.Centre(wx
.BOTH
)
663 def SetFontSizeAxis(self
, point
= 10):
664 """Set the tick and axis label font size (default is 10 point)"""
665 self
._fontSizeAxis
= point
667 def GetFontSizeAxis(self
):
668 """Get current tick and axis label font size in points"""
669 return self
._fontSizeAxis
671 def SetFontSizeTitle(self
, point
= 15):
672 """Set Title font size (default is 15 point)"""
673 self
._fontSizeTitle
= point
675 def GetFontSizeTitle(self
):
676 """Get current Title font size in points"""
677 return self
._fontSizeTitle
679 def SetFontSizeLegend(self
, point
= 7):
680 """Set Legend font size (default is 7 point)"""
681 self
._fontSizeLegend
= point
683 def GetFontSizeLegend(self
):
684 """Get current Legend font size in points"""
685 return self
._fontSizeLegend
687 def SetShowScrollbars(self
, value
):
688 """Set True to show scrollbars"""
689 if value
not in [True,False]:
690 raise TypeError, "Value should be True or False"
691 if value
== self
.GetShowScrollbars():
693 self
.sb_vert
.Show(value
)
694 self
.sb_hor
.Show(value
)
695 wx
.CallAfter(self
.Layout
)
697 def GetShowScrollbars(self
):
698 """Set True to show scrollbars"""
699 return self
.sb_vert
.IsShown()
701 def SetEnableDrag(self
, value
):
702 """Set True to enable drag."""
703 if value
not in [True,False]:
704 raise TypeError, "Value should be True or False"
706 if self
.GetEnableZoom():
707 self
.SetEnableZoom(False)
708 self
.SetCursor(self
.HandCursor
)
710 self
.SetCursor(wx
.CROSS_CURSOR
)
711 self
._dragEnabled
= value
713 def GetEnableDrag(self
):
714 return self
._dragEnabled
716 def SetEnableZoom(self
, value
):
717 """Set True to enable zooming."""
718 if value
not in [True,False]:
719 raise TypeError, "Value should be True or False"
721 if self
.GetEnableDrag():
722 self
.SetEnableDrag(False)
723 self
.SetCursor(self
.MagCursor
)
725 self
.SetCursor(wx
.CROSS_CURSOR
)
726 self
._zoomEnabled
= value
728 def GetEnableZoom(self
):
729 """True if zooming enabled."""
730 return self
._zoomEnabled
732 def SetEnableGrid(self
, value
):
733 """Set True to enable grid."""
734 if value
not in [True,False,'Horizontal','Vertical']:
735 raise TypeError, "Value should be True, False, Horizontal or Vertical"
736 self
._gridEnabled
= value
739 def GetEnableGrid(self
):
740 """True if grid enabled."""
741 return self
._gridEnabled
743 def SetEnableLegend(self
, value
):
744 """Set True to enable legend."""
745 if value
not in [True,False]:
746 raise TypeError, "Value should be True or False"
747 self
._legendEnabled
= value
750 def GetEnableLegend(self
):
751 """True if Legend enabled."""
752 return self
._legendEnabled
754 def SetEnablePointLabel(self
, value
):
755 """Set True to enable pointLabel."""
756 if value
not in [True,False]:
757 raise TypeError, "Value should be True or False"
758 self
._pointLabelEnabled
= value
759 self
.Redraw() #will erase existing pointLabel if present
760 self
.last_PointLabel
= None
762 def GetEnablePointLabel(self
):
763 """True if pointLabel enabled."""
764 return self
._pointLabelEnabled
766 def SetPointLabelFunc(self
, func
):
767 """Sets the function with custom code for pointLabel drawing
768 ******** more info needed ***************
770 self
._pointLabelFunc
= func
772 def GetPointLabelFunc(self
):
773 """Returns pointLabel Drawing Function"""
774 return self
._pointLabelFunc
777 """Unzoom the plot."""
778 self
.last_PointLabel
= None #reset pointLabel
779 if self
.last_draw
is not None:
780 self
.Draw(self
.last_draw
[0])
782 def ScrollRight(self
, units
):
783 """Move view right number of axis units."""
784 self
.last_PointLabel
= None #reset pointLabel
785 if self
.last_draw
is not None:
786 graphics
, xAxis
, yAxis
= self
.last_draw
787 xAxis
= (xAxis
[0]+units
, xAxis
[1]+units
)
788 self
.Draw(graphics
,xAxis
,yAxis
)
790 def ScrollUp(self
, units
):
791 """Move view up number of axis units."""
792 self
.last_PointLabel
= None #reset pointLabel
793 if self
.last_draw
is not None:
794 graphics
, xAxis
, yAxis
= self
.last_draw
795 yAxis
= (yAxis
[0]+units
, yAxis
[1]+units
)
796 self
.Draw(graphics
,xAxis
,yAxis
)
799 def GetXY(self
,event
):
800 """Takes a mouse event and returns the XY user axis values."""
801 x
,y
= self
.PositionScreenToUser(event
.GetPosition())
804 def PositionUserToScreen(self
, pntXY
):
805 """Converts User position to Screen Coordinates"""
806 userPos
= _Numeric
.array(pntXY
)
807 x
,y
= userPos
* self
._pointScale
+ self
._pointShift
810 def PositionScreenToUser(self
, pntXY
):
811 """Converts Screen position to User Coordinates"""
812 screenPos
= _Numeric
.array(pntXY
)
813 x
,y
= (screenPos
-self
._pointShift
)/self
._pointScale
816 def SetXSpec(self
, type= 'auto'):
817 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
819 'none' - shows no axis or tick mark values
820 'min' - shows min bounding box values
821 'auto' - rounds axis range to sensible values
825 def SetYSpec(self
, type= 'auto'):
826 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
828 'none' - shows no axis or tick mark values
829 'min' - shows min bounding box values
830 'auto' - rounds axis range to sensible values
835 """Returns current XSpec for axis"""
839 """Returns current YSpec for axis"""
842 def GetXMaxRange(self
):
843 """Returns (minX, maxX) x-axis range for displayed graph"""
844 graphics
= self
.last_draw
[0]
845 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
846 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
849 def GetYMaxRange(self
):
850 """Returns (minY, maxY) y-axis range for displayed graph"""
851 graphics
= self
.last_draw
[0]
852 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
853 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
856 def GetXCurrentRange(self
):
857 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
858 return self
.last_draw
[1]
860 def GetYCurrentRange(self
):
861 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
862 return self
.last_draw
[2]
864 def Draw(self
, graphics
, xAxis
= None, yAxis
= None, dc
= None):
865 """Draw objects in graphics with specified x and y axis.
866 graphics- instance of PlotGraphics with list of PolyXXX objects
867 xAxis - tuple with (min, max) axis range to view
868 yAxis - same as xAxis
869 dc - drawing context - doesn't have to be specified.
870 If it's not, the offscreen buffer is used
872 # check Axis is either tuple or none
873 if type(xAxis
) not in [type(None),tuple]:
874 raise TypeError, "xAxis should be None or (minX,maxX)"
875 if type(yAxis
) not in [type(None),tuple]:
876 raise TypeError, "yAxis should be None or (minY,maxY)"
878 # check case for axis = (a,b) where a==b caused by improper zooms
880 if xAxis
[0] == xAxis
[1]:
883 if yAxis
[0] == yAxis
[1]:
887 # sets new dc and clears it
888 dc
= wx
.BufferedDC(wx
.ClientDC(self
.canvas
), self
._Buffer
)
894 # set font size for every thing but title and legend
895 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
897 # sizes axis to axis type, create lower left and upper right corners of plot
898 if xAxis
== None or yAxis
== None:
899 # One or both axis not specified in Draw
900 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
902 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
904 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
905 # Adjust bounding box for axis spec
906 p1
[0],p1
[1] = xAxis
[0], yAxis
[0] # lower left corner user scale (xmin,ymin)
907 p2
[0],p2
[1] = xAxis
[1], yAxis
[1] # upper right corner user scale (xmax,ymax)
909 # Both axis specified in Draw
910 p1
= _Numeric
.array([xAxis
[0], yAxis
[0]]) # lower left corner user scale (xmin,ymin)
911 p2
= _Numeric
.array([xAxis
[1], yAxis
[1]]) # upper right corner user scale (xmax,ymax)
913 self
.last_draw
= (graphics
, xAxis
, yAxis
) # saves most recient values
915 # Get ticks and textExtents for axis if required
916 if self
._xSpec
is not 'none':
917 xticks
= self
._ticks
(xAxis
[0], xAxis
[1])
918 xTextExtent
= dc
.GetTextExtent(xticks
[-1][1])# w h of x axis text last number on axis
921 xTextExtent
= (0,0) # No text for ticks
922 if self
._ySpec
is not 'none':
923 yticks
= self
._ticks
(yAxis
[0], yAxis
[1])
924 yTextExtentBottom
= dc
.GetTextExtent(yticks
[0][1])
925 yTextExtentTop
= dc
.GetTextExtent(yticks
[-1][1])
926 yTextExtent
= (max(yTextExtentBottom
[0],yTextExtentTop
[0]),
927 max(yTextExtentBottom
[1],yTextExtentTop
[1]))
930 yTextExtent
= (0,0) # No text for ticks
932 # TextExtents for Title and Axis Labels
933 titleWH
, xLabelWH
, yLabelWH
= self
._titleLablesWH
(dc
, graphics
)
935 # TextExtents for Legend
936 legendBoxWH
, legendSymExt
, legendTextExt
= self
._legendWH
(dc
, graphics
)
938 # room around graph area
939 rhsW
= max(xTextExtent
[0], legendBoxWH
[0]) # use larger of number width or legend width
940 lhsW
= yTextExtent
[0]+ yLabelWH
[1]
941 bottomH
= max(xTextExtent
[1], yTextExtent
[1]/2.)+ xLabelWH
[1]
942 topH
= yTextExtent
[1]/2. + titleWH
[1]
943 textSize_scale
= _Numeric
.array([rhsW
+lhsW
,bottomH
+topH
]) # make plot area smaller by text size
944 textSize_shift
= _Numeric
.array([lhsW
, bottomH
]) # shift plot area by this amount
946 # drawing title and labels text
947 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
948 titlePos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- titleWH
[0]/2.,
949 self
.plotbox_origin
[1]- self
.plotbox_size
[1])
950 dc
.DrawText(graphics
.getTitle(),titlePos
[0],titlePos
[1])
951 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
952 xLabelPos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- xLabelWH
[0]/2.,
953 self
.plotbox_origin
[1]- xLabelWH
[1])
954 dc
.DrawText(graphics
.getXLabel(),xLabelPos
[0],xLabelPos
[1])
955 yLabelPos
= (self
.plotbox_origin
[0],
956 self
.plotbox_origin
[1]- bottomH
- (self
.plotbox_size
[1]-bottomH
-topH
)/2.+ yLabelWH
[0]/2.)
957 if graphics
.getYLabel(): # bug fix for Linux
958 dc
.DrawRotatedText(graphics
.getYLabel(),yLabelPos
[0],yLabelPos
[1],90)
960 # drawing legend makers and text
961 if self
._legendEnabled
:
962 self
._drawLegend
(dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
)
964 # allow for scaling and shifting plotted points
965 scale
= (self
.plotbox_size
-textSize_scale
) / (p2
-p1
)* _Numeric
.array((1,-1))
966 shift
= -p1
*scale
+ self
.plotbox_origin
+ textSize_shift
* _Numeric
.array((1,-1))
967 self
._pointScale
= scale
# make available for mouse events
968 self
._pointShift
= shift
969 self
._drawAxes
(dc
, p1
, p2
, scale
, shift
, xticks
, yticks
)
971 graphics
.scaleAndShift(scale
, shift
)
972 graphics
.setPrinterScale(self
.printerScale
) # thicken up lines and markers if printing
974 # set clipping area so drawing does not occur outside axis box
975 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(p1
, p2
)
976 dc
.SetClippingRegion(ptx
,pty
,rectWidth
,rectHeight
)
977 # Draw the lines and markers
978 #start = _time.clock()
980 # print "entire graphics drawing took: %f second"%(_time.clock() - start)
981 # remove the clipping region
982 dc
.DestroyClippingRegion()
984 self
._adjustScrollbars
()
986 def Redraw(self
, dc
= None):
987 """Redraw the existing plot."""
988 if self
.last_draw
is not None:
989 graphics
, xAxis
, yAxis
= self
.last_draw
990 self
.Draw(graphics
,xAxis
,yAxis
,dc
)
993 """Erase the window."""
994 self
.last_PointLabel
= None #reset pointLabel
995 dc
= wx
.BufferedDC(wx
.ClientDC(self
.canvas
), self
._Buffer
)
997 self
.last_draw
= None
999 def Zoom(self
, Center
, Ratio
):
1000 """ Zoom on the plot
1001 Centers on the X,Y coords given in Center
1002 Zooms by the Ratio = (Xratio, Yratio) given
1004 self
.last_PointLabel
= None #reset maker
1006 if self
.last_draw
!= None:
1007 (graphics
, xAxis
, yAxis
) = self
.last_draw
1008 w
= (xAxis
[1] - xAxis
[0]) * Ratio
[0]
1009 h
= (yAxis
[1] - yAxis
[0]) * Ratio
[1]
1010 xAxis
= ( x
- w
/2, x
+ w
/2 )
1011 yAxis
= ( y
- h
/2, y
+ h
/2 )
1012 self
.Draw(graphics
, xAxis
, yAxis
)
1014 def GetClosestPoints(self
, pntXY
, pointScaled
= True):
1015 """Returns list with
1016 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1017 list for each curve.
1018 Returns [] if no curves are being plotted.
1021 if pointScaled == True based on screen coords
1022 if pointScaled == False based on user coords
1024 if self
.last_draw
== None:
1027 graphics
, xAxis
, yAxis
= self
.last_draw
1029 for curveNum
,obj
in enumerate(graphics
):
1030 #check there are points in the curve
1031 if len(obj
.points
) == 0:
1032 continue #go to next obj
1033 #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1034 cn
= [curveNum
]+ [obj
.getLegend()]+ obj
.getClosestPoint( pntXY
, pointScaled
)
1038 def GetClosetPoint(self
, pntXY
, pointScaled
= True):
1039 """Returns list with
1040 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1041 list for only the closest curve.
1042 Returns [] if no curves are being plotted.
1045 if pointScaled == True based on screen coords
1046 if pointScaled == False based on user coords
1048 #closest points on screen based on screen scaling (pointScaled= True)
1049 #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
1050 closestPts
= self
.GetClosestPoints(pntXY
, pointScaled
)
1051 if closestPts
== []:
1052 return [] #no graph present
1053 #find one with least distance
1054 dists
= [c
[-1] for c
in closestPts
]
1055 mdist
= min(dists
) #Min dist
1056 i
= dists
.index(mdist
) #index for min dist
1057 return closestPts
[i
] #this is the closest point on closest curve
1059 def UpdatePointLabel(self
, mDataDict
):
1060 """Updates the pointLabel point on screen with data contained in
1063 mDataDict will be passed to your function set by
1064 SetPointLabelFunc. It can contain anything you
1065 want to display on the screen at the scaledXY point
1068 This function can be called from parent window with onClick,
1069 onMotion events etc.
1071 if self
.last_PointLabel
!= None:
1073 if mDataDict
["pointXY"] != self
.last_PointLabel
["pointXY"]:
1075 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1076 self
._drawPointLabel
(mDataDict
) #plot new
1078 #just plot new with no erase
1079 self
._drawPointLabel
(mDataDict
) #plot new
1080 #save for next erase
1081 self
.last_PointLabel
= mDataDict
1083 # event handlers **********************************
1084 def OnMotion(self
, event
):
1085 if self
._zoomEnabled
and event
.LeftIsDown():
1086 if self
._hasDragged
:
1087 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
1089 self
._hasDragged
= True
1090 self
._zoomCorner
2[0], self
._zoomCorner
2[1] = self
.GetXY(event
)
1091 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # add new
1092 elif self
._dragEnabled
and event
.LeftIsDown():
1093 coordinates
= event
.GetPosition()
1094 newpos
, oldpos
= map(_Numeric
.array
, map(self
.PositionScreenToUser
, [coordinates
, self
._screenCoordinates
]))
1095 dist
= newpos
-oldpos
1096 self
._screenCoordinates
= coordinates
1098 self
.ScrollUp(-dist
[1])
1099 self
.ScrollRight(-dist
[0])
1101 def OnMouseLeftDown(self
,event
):
1102 self
._zoomCorner
1[0], self
._zoomCorner
1[1]= self
.GetXY(event
)
1103 self
._screenCoordinates
= _Numeric
.array(event
.GetPosition())
1104 if self
._dragEnabled
:
1105 self
.SetCursor(self
.GrabHandCursor
)
1106 self
.canvas
.CaptureMouse()
1108 def OnMouseLeftUp(self
, event
):
1109 if self
._zoomEnabled
:
1110 if self
._hasDragged
== True:
1111 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
1112 self
._zoomCorner
2[0], self
._zoomCorner
2[1]= self
.GetXY(event
)
1113 self
._hasDragged
= False # reset flag
1114 minX
, minY
= _Numeric
.minimum( self
._zoomCorner
1, self
._zoomCorner
2)
1115 maxX
, maxY
= _Numeric
.maximum( self
._zoomCorner
1, self
._zoomCorner
2)
1116 self
.last_PointLabel
= None #reset pointLabel
1117 if self
.last_draw
!= None:
1118 self
.Draw(self
.last_draw
[0], xAxis
= (minX
,maxX
), yAxis
= (minY
,maxY
), dc
= None)
1119 #else: # A box has not been drawn, zoom in on a point
1120 ## this interfered with the double click, so I've disables it.
1121 # X,Y = self.GetXY(event)
1122 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
1123 if self
._dragEnabled
:
1124 self
.SetCursor(self
.HandCursor
)
1125 if self
.canvas
.HasCapture():
1126 self
.canvas
.ReleaseMouse()
1128 def OnMouseDoubleClick(self
,event
):
1129 if self
._zoomEnabled
:
1130 # Give a little time for the click to be totally finished
1131 # before (possibly) removing the scrollbars and trigering
1133 wx
.FutureCall(200,self
.Reset
)
1135 def OnMouseRightDown(self
,event
):
1136 if self
._zoomEnabled
:
1137 X
,Y
= self
.GetXY(event
)
1138 self
.Zoom( (X
,Y
), (self
._zoomOutFactor
, self
._zoomOutFactor
) )
1140 def OnPaint(self
, event
):
1141 # All that is needed here is to draw the buffer to screen
1142 if self
.last_PointLabel
!= None:
1143 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1144 self
.last_PointLabel
= None
1145 dc
= wx
.BufferedPaintDC(self
.canvas
, self
._Buffer
)
1147 def OnSize(self
,event
):
1148 # The Buffer init is done here, to make sure the buffer is always
1149 # the same size as the Window
1150 Size
= self
.canvas
.GetClientSize()
1151 if Size
.width
<= 0 or Size
.height
<= 0:
1154 # Make new offscreen bitmap: this bitmap will always have the
1155 # current drawing in it, so it can be used to save the image to
1156 # a file, or whatever.
1157 self
._Buffer
= wx
.EmptyBitmap(Size
[0],Size
[1])
1160 self
.last_PointLabel
= None #reset pointLabel
1162 if self
.last_draw
is None:
1165 graphics
, xSpec
, ySpec
= self
.last_draw
1166 self
.Draw(graphics
,xSpec
,ySpec
)
1168 def OnLeave(self
, event
):
1169 """Used to erase pointLabel when mouse outside window"""
1170 if self
.last_PointLabel
!= None:
1171 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1172 self
.last_PointLabel
= None
1174 def OnScroll(self
, evt
):
1175 if not self
._adjustingSB
:
1176 self
._sb
_ignore
= True
1177 sbpos
= evt
.GetPosition()
1179 if evt
.GetOrientation() == wx
.VERTICAL
:
1180 fullrange
,pagesize
= self
.sb_vert
.GetRange(),self
.sb_vert
.GetPageSize()
1181 sbpos
= fullrange
-pagesize
-sbpos
1182 dist
= sbpos
*self
._sb
_yunit
-(self
.GetYCurrentRange()[0]-self
._sb
_yfullrange
[0])
1185 if evt
.GetOrientation() == wx
.HORIZONTAL
:
1186 dist
= sbpos
*self
._sb
_xunit
-(self
.GetXCurrentRange()[0]-self
._sb
_xfullrange
[0])
1187 self
.ScrollRight(dist
)
1189 # Private Methods **************************************************
1190 def _setSize(self
, width
=None, height
=None):
1191 """DC width and height."""
1193 (self
.width
,self
.height
) = self
.canvas
.GetClientSize()
1195 self
.width
, self
.height
= width
,height
1196 self
.plotbox_size
= 0.97*_Numeric
.array([self
.width
, self
.height
])
1197 xo
= 0.5*(self
.width
-self
.plotbox_size
[0])
1198 yo
= self
.height
-0.5*(self
.height
-self
.plotbox_size
[1])
1199 self
.plotbox_origin
= _Numeric
.array([xo
, yo
])
1201 def _setPrinterScale(self
, scale
):
1202 """Used to thicken lines and increase marker size for print out."""
1203 # line thickness on printer is very thin at 600 dot/in. Markers small
1204 self
.printerScale
= scale
1206 def _printDraw(self
, printDC
):
1207 """Used for printing."""
1208 if self
.last_draw
!= None:
1209 graphics
, xSpec
, ySpec
= self
.last_draw
1210 self
.Draw(graphics
,xSpec
,ySpec
,printDC
)
1212 def _drawPointLabel(self
, mDataDict
):
1213 """Draws and erases pointLabels"""
1214 width
= self
._Buffer
.GetWidth()
1215 height
= self
._Buffer
.GetHeight()
1216 tmp_Buffer
= wx
.EmptyBitmap(width
,height
)
1218 dcs
.SelectObject(tmp_Buffer
)
1221 self
._pointLabelFunc
(dcs
,mDataDict
) #custom user pointLabel function
1224 dc
= wx
.ClientDC( self
.canvas
)
1225 #this will erase if called twice
1226 dc
.Blit(0, 0, width
, height
, dcs
, 0, 0, wx
.EQUIV
) #(NOT src) XOR dst
1229 def _drawLegend(self
,dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
):
1230 """Draws legend symbols and text"""
1231 # top right hand corner of graph box is ref corner
1232 trhc
= self
.plotbox_origin
+ (self
.plotbox_size
-[rhsW
,topH
])*[1,-1]
1233 legendLHS
= .091* legendBoxWH
[0] # border space between legend sym and graph box
1234 lineHeight
= max(legendSymExt
[1], legendTextExt
[1]) * 1.1 #1.1 used as space between lines
1235 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
1236 for i
in range(len(graphics
)):
1239 if isinstance(o
,PolyMarker
):
1240 # draw marker with legend
1241 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0]/2., trhc
[1]+s
+lineHeight
/2.)
1242 o
.draw(dc
, self
.printerScale
, coord
= _Numeric
.array([pnt
]))
1243 elif isinstance(o
,PolyLine
):
1244 # draw line with legend
1245 pnt1
= (trhc
[0]+legendLHS
, trhc
[1]+s
+lineHeight
/2.)
1246 pnt2
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.)
1247 o
.draw(dc
, self
.printerScale
, coord
= _Numeric
.array([pnt1
,pnt2
]))
1249 raise TypeError, "object is neither PolyMarker or PolyLine instance"
1251 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.-legendTextExt
[1]/2)
1252 dc
.DrawText(o
.getLegend(),pnt
[0],pnt
[1])
1253 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
)) # reset
1255 def _titleLablesWH(self
, dc
, graphics
):
1256 """Draws Title and labels and returns width and height for each"""
1257 # TextExtents for Title and Axis Labels
1258 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
1259 title
= graphics
.getTitle()
1260 titleWH
= dc
.GetTextExtent(title
)
1261 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1262 xLabel
, yLabel
= graphics
.getXLabel(),graphics
.getYLabel()
1263 xLabelWH
= dc
.GetTextExtent(xLabel
)
1264 yLabelWH
= dc
.GetTextExtent(yLabel
)
1265 return titleWH
, xLabelWH
, yLabelWH
1267 def _legendWH(self
, dc
, graphics
):
1268 """Returns the size in screen units for legend box"""
1269 if self
._legendEnabled
!= True:
1270 legendBoxWH
= symExt
= txtExt
= (0,0)
1272 # find max symbol size
1273 symExt
= graphics
.getSymExtent(self
.printerScale
)
1274 # find max legend text extent
1275 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
1276 txtList
= graphics
.getLegendNames()
1277 txtExt
= dc
.GetTextExtent(txtList
[0])
1278 for txt
in graphics
.getLegendNames()[1:]:
1279 txtExt
= _Numeric
.maximum(txtExt
,dc
.GetTextExtent(txt
))
1280 maxW
= symExt
[0]+txtExt
[0]
1281 maxH
= max(symExt
[1],txtExt
[1])
1282 # padding .1 for lhs of legend box and space between lines
1284 maxH
= maxH
* 1.1 * len(txtList
)
1285 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1286 legendBoxWH
= (maxW
,maxH
)
1287 return (legendBoxWH
, symExt
, txtExt
)
1289 def _drawRubberBand(self
, corner1
, corner2
):
1290 """Draws/erases rect box from corner1 to corner2"""
1291 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(corner1
, corner2
)
1293 dc
= wx
.ClientDC( self
.canvas
)
1295 dc
.SetPen(wx
.Pen(wx
.BLACK
))
1296 dc
.SetBrush(wx
.Brush( wx
.WHITE
, wx
.TRANSPARENT
) )
1297 dc
.SetLogicalFunction(wx
.INVERT
)
1298 dc
.DrawRectangle( ptx
,pty
, rectWidth
,rectHeight
)
1299 dc
.SetLogicalFunction(wx
.COPY
)
1302 def _getFont(self
,size
):
1303 """Take font size, adjusts if printing and returns wx.Font"""
1304 s
= size
*self
.printerScale
1306 # Linux speed up to get font from cache rather than X font server
1307 key
= (int(s
), of
.GetFamily (), of
.GetStyle (), of
.GetWeight ())
1308 font
= self
._fontCache
.get (key
, None)
1310 return font
# yeah! cache hit
1312 font
= wx
.Font(int(s
), of
.GetFamily(), of
.GetStyle(), of
.GetWeight())
1313 self
._fontCache
[key
] = font
1317 def _point2ClientCoord(self
, corner1
, corner2
):
1318 """Converts user point coords to client screen int coords x,y,width,height"""
1319 c1
= _Numeric
.array(corner1
)
1320 c2
= _Numeric
.array(corner2
)
1321 # convert to screen coords
1322 pt1
= c1
*self
._pointScale
+self
._pointShift
1323 pt2
= c2
*self
._pointScale
+self
._pointShift
1324 # make height and width positive
1325 pul
= _Numeric
.minimum(pt1
,pt2
) # Upper left corner
1326 plr
= _Numeric
.maximum(pt1
,pt2
) # Lower right corner
1327 rectWidth
, rectHeight
= plr
-pul
1329 return ptx
, pty
, rectWidth
, rectHeight
1331 def _axisInterval(self
, spec
, lower
, upper
):
1332 """Returns sensible axis range for given spec"""
1333 if spec
== 'none' or spec
== 'min':
1335 return lower
-0.5, upper
+0.5
1338 elif spec
== 'auto':
1341 return lower
-0.5, upper
+0.5
1342 log
= _Numeric
.log10(range)
1343 power
= _Numeric
.floor(log
)
1344 fraction
= log
-power
1345 if fraction
<= 0.05:
1348 lower
= lower
- lower
% grid
1351 upper
= upper
- mod
+ grid
1353 elif type(spec
) == type(()):
1360 raise ValueError, str(spec
) + ': illegal axis specification'
1362 def _drawAxes(self
, dc
, p1
, p2
, scale
, shift
, xticks
, yticks
):
1364 penWidth
= self
.printerScale
# increases thickness for printing only
1365 dc
.SetPen(wx
.Pen(self
._gridColour
, penWidth
))
1367 # set length of tick marks--long ones make grid
1368 if self
._gridEnabled
:
1369 x
,y
,width
,height
= self
._point
2ClientCoord
(p1
,p2
)
1370 if self
._gridEnabled
== 'Horizontal':
1371 yTickLength
= width
/2.0 +1
1372 xTickLength
= 3 * self
.printerScale
1373 elif self
._gridEnabled
== 'Vertical':
1374 yTickLength
= 3 * self
.printerScale
1375 xTickLength
= height
/2.0 +1
1377 yTickLength
= width
/2.0 +1
1378 xTickLength
= height
/2.0 +1
1380 yTickLength
= 3 * self
.printerScale
# lengthens lines for printing
1381 xTickLength
= 3 * self
.printerScale
1383 if self
._xSpec
is not 'none':
1384 lower
, upper
= p1
[0],p2
[0]
1386 for y
, d
in [(p1
[1], -xTickLength
), (p2
[1], xTickLength
)]: # miny, maxy and tick lengths
1387 a1
= scale
*_Numeric
.array([lower
, y
])+shift
1388 a2
= scale
*_Numeric
.array([upper
, y
])+shift
1389 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1]) # draws upper and lower axis line
1390 for x
, label
in xticks
:
1391 pt
= scale
*_Numeric
.array([x
, y
])+shift
1392 dc
.DrawLine(pt
[0],pt
[1],pt
[0],pt
[1] + d
) # draws tick mark d units
1394 dc
.DrawText(label
,pt
[0],pt
[1])
1395 text
= 0 # axis values not drawn on top side
1397 if self
._ySpec
is not 'none':
1398 lower
, upper
= p1
[1],p2
[1]
1400 h
= dc
.GetCharHeight()
1401 for x
, d
in [(p1
[0], -yTickLength
), (p2
[0], yTickLength
)]:
1402 a1
= scale
*_Numeric
.array([x
, lower
])+shift
1403 a2
= scale
*_Numeric
.array([x
, upper
])+shift
1404 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1])
1405 for y
, label
in yticks
:
1406 pt
= scale
*_Numeric
.array([x
, y
])+shift
1407 dc
.DrawLine(pt
[0],pt
[1],pt
[0]-d
,pt
[1])
1409 dc
.DrawText(label
,pt
[0]-dc
.GetTextExtent(label
)[0],
1411 text
= 0 # axis values not drawn on right side
1413 def _ticks(self
, lower
, upper
):
1414 ideal
= (upper
-lower
)/7.
1415 log
= _Numeric
.log10(ideal
)
1416 power
= _Numeric
.floor(log
)
1417 fraction
= log
-power
1420 for f
, lf
in self
._multiples
:
1421 e
= _Numeric
.fabs(fraction
-lf
)
1425 grid
= factor
* 10.**power
1426 if power
> 4 or power
< -4:
1429 digits
= max(1, int(power
))
1430 format
= '%' + `digits`
+'.0f'
1432 digits
= -int(power
)
1433 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
1435 t
= -grid
*_Numeric
.floor(-lower
/grid
)
1437 ticks
.append( (t
, format
% (t
,)) )
1441 _multiples
= [(2., _Numeric
.log10(2.)), (5., _Numeric
.log10(5.))]
1444 def _adjustScrollbars(self
):
1446 self
._sb
_ignore
= False
1449 self
._adjustingSB
= True
1450 needScrollbars
= False
1452 # horizontal scrollbar
1453 r_current
= self
.GetXCurrentRange()
1454 r_max
= list(self
.GetXMaxRange())
1455 sbfullrange
= float(self
.sb_hor
.GetRange())
1457 r_max
[0] = min(r_max
[0],r_current
[0])
1458 r_max
[1] = max(r_max
[1],r_current
[1])
1460 self
._sb
_xfullrange
= r_max
1462 unit
= (r_max
[1]-r_max
[0])/float(self
.sb_hor
.GetRange())
1463 pos
= int((r_current
[0]-r_max
[0])/unit
)
1466 pagesize
= int((r_current
[1]-r_current
[0])/unit
)
1468 self
.sb_hor
.SetScrollbar(pos
, pagesize
, sbfullrange
, pagesize
)
1469 self
._sb
_xunit
= unit
1470 needScrollbars
= needScrollbars
or (pagesize
!= sbfullrange
)
1472 self
.sb_hor
.SetScrollbar(0, 1000, 1000, 1000)
1474 # vertical scrollbar
1475 r_current
= self
.GetYCurrentRange()
1476 r_max
= list(self
.GetYMaxRange())
1477 sbfullrange
= float(self
.sb_vert
.GetRange())
1479 r_max
[0] = min(r_max
[0],r_current
[0])
1480 r_max
[1] = max(r_max
[1],r_current
[1])
1482 self
._sb
_yfullrange
= r_max
1484 unit
= (r_max
[1]-r_max
[0])/sbfullrange
1485 pos
= int((r_current
[0]-r_max
[0])/unit
)
1488 pagesize
= int((r_current
[1]-r_current
[0])/unit
)
1489 pos
= (sbfullrange
-1-pos
-pagesize
)
1490 self
.sb_vert
.SetScrollbar(pos
, pagesize
, sbfullrange
, pagesize
)
1491 self
._sb
_yunit
= unit
1492 needScrollbars
= needScrollbars
or (pagesize
!= sbfullrange
)
1494 self
.sb_vert
.SetScrollbar(0, 1000, 1000, 1000)
1496 self
.SetShowScrollbars(needScrollbars
)
1497 self
._adjustingSB
= False
1499 #-------------------------------------------------------------------------------
1500 # Used to layout the printer page
1502 class PlotPrintout(wx
.Printout
):
1503 """Controls how the plot is made in printing and previewing"""
1504 # Do not change method names in this class,
1505 # we have to override wx.Printout methods here!
1506 def __init__(self
, graph
):
1507 """graph is instance of plotCanvas to be printed or previewed"""
1508 wx
.Printout
.__init
__(self
)
1511 def HasPage(self
, page
):
1517 def GetPageInfo(self
):
1518 return (1, 1, 1, 1) # disable page numbers
1520 def OnPrintPage(self
, page
):
1521 dc
= self
.GetDC() # allows using floats for certain functions
1522 ## print "PPI Printer",self.GetPPIPrinter()
1523 ## print "PPI Screen", self.GetPPIScreen()
1524 ## print "DC GetSize", dc.GetSize()
1525 ## print "GetPageSizePixels", self.GetPageSizePixels()
1526 # Note PPIScreen does not give the correct number
1527 # Calulate everything for printer and then scale for preview
1528 PPIPrinter
= self
.GetPPIPrinter() # printer dots/inch (w,h)
1529 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1530 dcSize
= dc
.GetSize() # DC size
1531 pageSize
= self
.GetPageSizePixels() # page size in terms of pixcels
1532 clientDcSize
= self
.graph
.GetClientSize()
1534 # find what the margins are (mm)
1535 margLeftSize
,margTopSize
= self
.graph
.pageSetupData
.GetMarginTopLeft()
1536 margRightSize
, margBottomSize
= self
.graph
.pageSetupData
.GetMarginBottomRight()
1538 # calculate offset and scale for dc
1539 pixLeft
= margLeftSize
*PPIPrinter
[0]/25.4 # mm*(dots/in)/(mm/in)
1540 pixRight
= margRightSize
*PPIPrinter
[0]/25.4
1541 pixTop
= margTopSize
*PPIPrinter
[1]/25.4
1542 pixBottom
= margBottomSize
*PPIPrinter
[1]/25.4
1544 plotAreaW
= pageSize
[0]-(pixLeft
+pixRight
)
1545 plotAreaH
= pageSize
[1]-(pixTop
+pixBottom
)
1547 # ratio offset and scale to screen size if preview
1548 if self
.IsPreview():
1549 ratioW
= float(dcSize
[0])/pageSize
[0]
1550 ratioH
= float(dcSize
[1])/pageSize
[1]
1556 # rescale plot to page or preview plot area
1557 self
.graph
._setSize
(plotAreaW
,plotAreaH
)
1559 # Set offset and scale
1560 dc
.SetDeviceOrigin(pixLeft
,pixTop
)
1562 # Thicken up pens and increase marker size for printing
1563 ratioW
= float(plotAreaW
)/clientDcSize
[0]
1564 ratioH
= float(plotAreaH
)/clientDcSize
[1]
1565 aveScale
= (ratioW
+ratioH
)/2
1566 self
.graph
._setPrinterScale
(aveScale
) # tickens up pens for printing
1568 self
.graph
._printDraw
(dc
)
1569 # rescale back to original
1570 self
.graph
._setSize
()
1571 self
.graph
._setPrinterScale
(1)
1572 self
.graph
.Redraw() #to get point label scale and shift correct
1577 #----------------------------------------------------------------------
1578 from wx
import ImageFromStream
, BitmapFromImage
1579 import cStringIO
, zlib
1582 def getMagPlusData():
1583 return zlib
.decompress(
1584 'x\xda\x01*\x01\xd5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1585 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1586 \x08\x08|\x08d\x88\x00\x00\x00\xe1IDATx\x9c\xb5U\xd1\x0e\xc4 \x08\xa3n\xff\
1587 \xff\xc5\xdb\xb8\xa7\xee<\x04\x86gFb\xb2\x88\xb6\x14\x90\x01m\x937m\x8f\x1c\
1588 \xd7yh\xe4k\xdb\x8e*\x01<\x05\x04\x07F\x1cU\x9d"\x19\x14\\\xe7\xa1\x1e\xf07"\
1589 \x90H+$?\x04\x16\x9c\xd1z\x04\x00J$m\x06\xdc\xee\x03Hku\x13\xd8C\x16\x84+"O\
1590 \x1b\xa2\x07\xca"\xb7\xc6sY\xbdD\x926\xf5.\xce\x06!\xd2)x\xcb^\'\x08S\xe4\
1591 \xe5x&5\xb4[A\xb5h\xb4j=\x9a\xc8\xf8\xecm\xd4\\\x9e\xdf\xbb?\x10\xf0P\x06\
1592 \x12\xed?=\xb6a\xd8=\xcd\xa2\xc8T\xd5U2t\x11\x95d\xa3"\x9aQ\x9e\x12\xb7M\x19\
1593 I\x9f\xff\x1e\xd8\xa63#q\xff\x07U\x8b\xd2\xd9\xa7k\xe9\xa1U\x94,\xbf\xe4\x88\
1594 \xe4\xf6\xaf\x12x$}\x8a\xc2Q\xf1\'\x89\xf2\x9b\xfbKE\xae\xd8\x07+\xd2\xa7c\
1595 \xdf\x0e\xc3D\x00\x00\x00\x00IEND\xaeB`\x82\xe2ovy' )
1597 def getMagPlusBitmap():
1598 return BitmapFromImage(getMagPlusImage())
1600 def getMagPlusImage():
1601 stream
= cStringIO
.StringIO(getMagPlusData())
1602 return ImageFromStream(stream
)
1604 #----------------------------------------------------------------------
1605 def getGrabHandData():
1606 return zlib
.decompress(
1607 'x\xda\x01Z\x01\xa5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1608 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1609 \x08\x08|\x08d\x88\x00\x00\x01\x11IDATx\x9c\xb5U\xd1\x12\x830\x08Kh\xff\xff\
1610 \x8b7\xb3\x97\xd1C\xa4Zw\x93;\x1fJ1\t\x98VJ\x92\xb5N<\x14\x04 I\x00\x80H\xb4\
1611 \xbd_\x8a9_{\\\x89\xf2z\x02\x18/J\x82\xb5\xce\xed\xfd\x12\xc9\x91\x03\x00_\
1612 \xc7\xda\x8al\x00{\xfdW\xfex\xf2zeO\x92h\xed\x80\x05@\xa45D\xc5\xb3\x98u\x12\
1613 \xf7\xab.\xa9\xd0k\x1eK\x95\xbb\x1a]&0\x92\xf0\'\xc6]gI\xda\tsr\xab\x8aI\x1e\
1614 \\\xe3\xa4\x0e\xb4*`7"\x07\x8f\xaa"x\x05\xe0\xdfo6B\xf3\x17\xe3\x98r\xf1\xaf\
1615 \x07\xd1Z\'%\x95\x0erW\xac\x8c\xe3\xe0\xfd\xd8AN\xae\xb8\xa3R\x9as>\x11\x8bl\
1616 yD\xab\x1f\xf3\xec\x1cY\x06\x89$\xbf\x80\xfb\x14\\dw\x90x\x12\xa3+\xeeD\x16%\
1617 I\xe3\x1c\xb8\xc7c\'\xd5Y8S\x9f\xc3Zg\xcf\x89\xe8\xaao\'\xbbk{U\xfd\xc0\xacX\
1618 \xab\xbb\xe8\xae\xfa)AEr\x15g\x86(\t\xfe\x19\xa4\xb5\xe9f\xfem\xde\xdd\xbf$\
1619 \xf8G<>\xa2\xc7\t>\tE\xfc\x8a\xf6\x8dqc\x00\x00\x00\x00IEND\xaeB`\x82\xdb\
1622 def getGrabHandBitmap():
1623 return BitmapFromImage(getGrabHandImage())
1625 def getGrabHandImage():
1626 stream
= cStringIO
.StringIO(getGrabHandData())
1627 return ImageFromStream(stream
)
1629 #----------------------------------------------------------------------
1631 return zlib
.decompress(
1632 'x\xda\x01Y\x01\xa6\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1633 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1634 \x08\x08|\x08d\x88\x00\x00\x01\x10IDATx\x9c\xad\x96\xe1\x02\xc2 \x08\x849\
1635 \xf5\xfd\x9fx\xdb\xf5\'\x8c!\xa8\xab\xee\x975\xe5\x83\x0b\\@\xa9\xb2\xab\xeb\
1636 <\xa8\xebR\x1bv\xce\xb4\'\xc1\x81OL\x92\xdc\x81\x0c\x00\x1b\x88\xa4\x94\xda\
1637 \xe0\x83\x8b\x88\x00\x10\x92\xcb\x8a\xca,K\x1fT\xa1\x1e\x04\xe0f_\n\x88\x02\
1638 \xf1:\xc3\x83>\x81\x0c\x92\x02v\xe5+\xba\xce\x83\xb7f\xb8\xd1\x9c\x8fz8\xb2*\
1639 \x93\xb7l\xa8\xe0\x9b\xa06\xb8]_\xe7\xc1\x01\x10U\xe1m\x98\xc9\xefm"ck\xea\
1640 \x1a\x80\xa0Th\xb9\xfd\x877{V*Qk\xda,\xb4\x8b\xf4;[\xa1\xcf6\xaa4\x9cd\x85X\
1641 \xb0\r\\j\x83\x9dd\x92\xc3 \xf6\xbd\xab\x0c2\x05\xc0p\x9a\xa7]\xf4\x14\x18]3\
1642 7\x80}h?\xff\xa2\xa2\xe5e\x90\xact\xaf\xe8B\x14y[4\x83|\x13\xdc\x9e\xeb\x16e\
1643 \x90\xa7\xf2I\rw\x91\x87d\xd7p\x96\xbd\xd70\x07\xda\xe3v\x9a\xf5\xc5\xb2\xb2\
1644 +\xb24\xbc\xaew\xedZe\x9f\x02"\xc8J\xdb\x83\xf6oa\xf5\xb7\xa5\xbf8\x12\xffW\
1645 \xcf_\xbd;\xe4\x8c\x03\x10\xdb^\x00\x00\x00\x00IEND\xaeB`\x82\xd1>\x97B' )
1647 def getHandBitmap():
1648 return BitmapFromImage(getHandImage())
1651 stream
= cStringIO
.StringIO(getHandData())
1652 return ImageFromStream(stream
)
1656 #---------------------------------------------------------------------------
1657 # if running standalone...
1659 # ...a sample implementation using the above
1662 def _draw1Objects():
1663 # 100 points sin function, plotted as green circles
1664 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(200)/200.
1665 data1
.shape
= (100, 2)
1666 data1
[:,1] = _Numeric
.sin(data1
[:,0])
1667 markers1
= PolyMarker(data1
, legend
='Green Markers', colour
='green', marker
='circle',size
=1)
1669 # 50 points cos function, plotted as red line
1670 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(100)/100.
1671 data1
.shape
= (50,2)
1672 data1
[:,1] = _Numeric
.cos(data1
[:,0])
1673 lines
= PolyLine(data1
, legend
= 'Red Line', colour
='red')
1675 # A few more points...
1677 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1678 (3.*pi
/4., -1)], legend
='Cross Legend', colour
='blue',
1681 return PlotGraphics([markers1
, lines
, markers2
],"Graph Title", "X Axis", "Y Axis")
1683 def _draw2Objects():
1684 # 100 points sin function, plotted as green dots
1685 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(200)/200.
1686 data1
.shape
= (100, 2)
1687 data1
[:,1] = _Numeric
.sin(data1
[:,0])
1688 line1
= PolyLine(data1
, legend
='Green Line', colour
='green', width
=6, style
=wx
.DOT
)
1690 # 50 points cos function, plotted as red dot-dash
1691 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(100)/100.
1692 data1
.shape
= (50,2)
1693 data1
[:,1] = _Numeric
.cos(data1
[:,0])
1694 line2
= PolyLine(data1
, legend
='Red Line', colour
='red', width
=3, style
= wx
.DOT_DASH
)
1696 # A few more points...
1698 markers1
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1699 (3.*pi
/4., -1)], legend
='Cross Hatch Square', colour
='blue', width
= 3, size
= 6,
1700 fillcolour
= 'red', fillstyle
= wx
.CROSSDIAG_HATCH
,
1703 return PlotGraphics([markers1
, line1
, line2
], "Big Markers with Different Line Styles")
1705 def _draw3Objects():
1706 markerList
= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1707 'cross', 'plus', 'circle']
1709 for i
in range(len(markerList
)):
1710 m
.append(PolyMarker([(2*i
+.5,i
+.5)], legend
=markerList
[i
], colour
='blue',
1711 marker
=markerList
[i
]))
1712 return PlotGraphics(m
, "Selection of Markers", "Minimal Axis", "No Axis")
1714 def _draw4Objects():
1716 data1
= _Numeric
.arange(5e5
,1e6
,10)
1717 data1
.shape
= (25000, 2)
1718 line1
= PolyLine(data1
, legend
='Wide Line', colour
='green', width
=5)
1720 # A few more points...
1721 markers2
= PolyMarker(data1
, legend
='Square', colour
='blue',
1723 return PlotGraphics([line1
, markers2
], "25,000 Points", "Value X", "")
1725 def _draw5Objects():
1726 # Empty graph with axis defined but no points/lines
1728 line1
= PolyLine(points
, legend
='Wide Line', colour
='green', width
=5)
1729 return PlotGraphics([line1
], "Empty Plot With Just Axes", "Value X", "Value Y")
1731 def _draw6Objects():
1733 points1
=[(1,0), (1,10)]
1734 line1
= PolyLine(points1
, colour
='green', legend
='Feb.', width
=10)
1735 points1g
=[(2,0), (2,4)]
1736 line1g
= PolyLine(points1g
, colour
='red', legend
='Mar.', width
=10)
1737 points1b
=[(3,0), (3,6)]
1738 line1b
= PolyLine(points1b
, colour
='blue', legend
='Apr.', width
=10)
1740 points2
=[(4,0), (4,12)]
1741 line2
= PolyLine(points2
, colour
='Yellow', legend
='May', width
=10)
1742 points2g
=[(5,0), (5,8)]
1743 line2g
= PolyLine(points2g
, colour
='orange', legend
='June', width
=10)
1744 points2b
=[(6,0), (6,4)]
1745 line2b
= PolyLine(points2b
, colour
='brown', legend
='July', width
=10)
1747 return PlotGraphics([line1
, line1g
, line1b
, line2
, line2g
, line2b
],
1748 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1751 class TestFrame(wx
.Frame
):
1752 def __init__(self
, parent
, id, title
):
1753 wx
.Frame
.__init
__(self
, parent
, id, title
,
1754 wx
.DefaultPosition
, (600, 400))
1756 # Now Create the menu bar and items
1757 self
.mainmenu
= wx
.MenuBar()
1760 menu
.Append(200, 'Page Setup...', 'Setup the printer page')
1761 self
.Bind(wx
.EVT_MENU
, self
.OnFilePageSetup
, id=200)
1763 menu
.Append(201, 'Print Preview...', 'Show the current plot on page')
1764 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrintPreview
, id=201)
1766 menu
.Append(202, 'Print...', 'Print the current plot')
1767 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrint
, id=202)
1769 menu
.Append(203, 'Save Plot...', 'Save current plot')
1770 self
.Bind(wx
.EVT_MENU
, self
.OnSaveFile
, id=203)
1772 menu
.Append(205, 'E&xit', 'Enough of this already!')
1773 self
.Bind(wx
.EVT_MENU
, self
.OnFileExit
, id=205)
1774 self
.mainmenu
.Append(menu
, '&File')
1777 menu
.Append(206, 'Draw1', 'Draw plots1')
1778 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw1
, id=206)
1779 menu
.Append(207, 'Draw2', 'Draw plots2')
1780 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw2
, id=207)
1781 menu
.Append(208, 'Draw3', 'Draw plots3')
1782 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw3
, id=208)
1783 menu
.Append(209, 'Draw4', 'Draw plots4')
1784 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw4
, id=209)
1785 menu
.Append(210, 'Draw5', 'Draw plots5')
1786 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw5
, id=210)
1787 menu
.Append(260, 'Draw6', 'Draw plots6')
1788 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw6
, id=260)
1791 menu
.Append(211, '&Redraw', 'Redraw plots')
1792 self
.Bind(wx
.EVT_MENU
,self
.OnPlotRedraw
, id=211)
1793 menu
.Append(212, '&Clear', 'Clear canvas')
1794 self
.Bind(wx
.EVT_MENU
,self
.OnPlotClear
, id=212)
1795 menu
.Append(213, '&Scale', 'Scale canvas')
1796 self
.Bind(wx
.EVT_MENU
,self
.OnPlotScale
, id=213)
1797 menu
.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind
=wx
.ITEM_CHECK
)
1798 self
.Bind(wx
.EVT_MENU
,self
.OnEnableZoom
, id=214)
1799 menu
.Append(215, 'Enable &Grid', 'Turn on Grid', kind
=wx
.ITEM_CHECK
)
1800 self
.Bind(wx
.EVT_MENU
,self
.OnEnableGrid
, id=215)
1801 menu
.Append(217, 'Enable &Drag', 'Activates dragging mode', kind
=wx
.ITEM_CHECK
)
1802 self
.Bind(wx
.EVT_MENU
,self
.OnEnableDrag
, id=217)
1803 menu
.Append(220, 'Enable &Legend', 'Turn on Legend', kind
=wx
.ITEM_CHECK
)
1804 self
.Bind(wx
.EVT_MENU
,self
.OnEnableLegend
, id=220)
1805 menu
.Append(222, 'Enable &Point Label', 'Show Closest Point', kind
=wx
.ITEM_CHECK
)
1806 self
.Bind(wx
.EVT_MENU
,self
.OnEnablePointLabel
, id=222)
1808 menu
.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1809 self
.Bind(wx
.EVT_MENU
,self
.OnScrUp
, id=225)
1810 menu
.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1811 self
.Bind(wx
.EVT_MENU
,self
.OnScrRt
, id=230)
1812 menu
.Append(235, '&Plot Reset', 'Reset to original plot')
1813 self
.Bind(wx
.EVT_MENU
,self
.OnReset
, id=235)
1815 self
.mainmenu
.Append(menu
, '&Plot')
1818 menu
.Append(300, '&About', 'About this thing...')
1819 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=300)
1820 self
.mainmenu
.Append(menu
, '&Help')
1822 self
.SetMenuBar(self
.mainmenu
)
1824 # A status bar to tell people what's happening
1825 self
.CreateStatusBar(1)
1827 self
.client
= PlotCanvas(self
)
1828 #define the function for drawing pointLabels
1829 self
.client
.SetPointLabelFunc(self
.DrawPointLabel
)
1830 # Create mouse event for showing cursor coords in status bar
1831 self
.client
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
1832 # Show closest point when enabled
1833 self
.client
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
1838 def DrawPointLabel(self
, dc
, mDataDict
):
1839 """This is the fuction that defines how the pointLabels are plotted
1840 dc - DC that will be passed
1841 mDataDict - Dictionary of data that you want to use for the pointLabel
1843 As an example I have decided I want a box at the curve point
1844 with some text information about the curve plotted below.
1845 Any wxDC method can be used.
1848 dc
.SetPen(wx
.Pen(wx
.BLACK
))
1849 dc
.SetBrush(wx
.Brush( wx
.BLACK
, wx
.SOLID
) )
1851 sx
, sy
= mDataDict
["scaledXY"] #scaled x,y of closest point
1852 dc
.DrawRectangle( sx
-5,sy
-5, 10, 10) #10by10 square centered on point
1853 px
,py
= mDataDict
["pointXY"]
1854 cNum
= mDataDict
["curveNum"]
1855 pntIn
= mDataDict
["pIndex"]
1856 legend
= mDataDict
["legend"]
1857 #make a string to display
1858 s
= "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum
, legend
, px
, py
, pntIn
)
1859 dc
.DrawText(s
, sx
, sy
+1)
1862 def OnMouseLeftDown(self
,event
):
1863 s
= "Left Mouse Down at Point: (%.4f, %.4f)" % self
.client
.GetXY(event
)
1864 self
.SetStatusText(s
)
1865 event
.Skip() #allows plotCanvas OnMouseLeftDown to be called
1867 def OnMotion(self
, event
):
1868 #show closest point (when enbled)
1869 if self
.client
.GetEnablePointLabel() == True:
1870 #make up dict with info for the pointLabel
1871 #I've decided to mark the closest point on the closest curve
1872 dlst
= self
.client
.GetClosetPoint( self
.client
.GetXY(event
), pointScaled
= True)
1873 if dlst
!= []: #returns [] if none
1874 curveNum
, legend
, pIndex
, pointXY
, scaledXY
, distance
= dlst
1875 #make up dictionary to pass to my user function (see DrawPointLabel)
1876 mDataDict
= {"curveNum":curveNum
, "legend":legend
, "pIndex":pIndex
,\
1877 "pointXY":pointXY
, "scaledXY":scaledXY
}
1878 #pass dict to update the pointLabel
1879 self
.client
.UpdatePointLabel(mDataDict
)
1880 event
.Skip() #go to next handler
1882 def OnFilePageSetup(self
, event
):
1883 self
.client
.PageSetup()
1885 def OnFilePrintPreview(self
, event
):
1886 self
.client
.PrintPreview()
1888 def OnFilePrint(self
, event
):
1889 self
.client
.Printout()
1891 def OnSaveFile(self
, event
):
1892 self
.client
.SaveFile()
1894 def OnFileExit(self
, event
):
1897 def OnPlotDraw1(self
, event
):
1898 self
.resetDefaults()
1899 self
.client
.Draw(_draw1Objects())
1901 def OnPlotDraw2(self
, event
):
1902 self
.resetDefaults()
1903 self
.client
.Draw(_draw2Objects())
1905 def OnPlotDraw3(self
, event
):
1906 self
.resetDefaults()
1907 self
.client
.SetFont(wx
.Font(10,wx
.SCRIPT
,wx
.NORMAL
,wx
.NORMAL
))
1908 self
.client
.SetFontSizeAxis(20)
1909 self
.client
.SetFontSizeLegend(12)
1910 self
.client
.SetXSpec('min')
1911 self
.client
.SetYSpec('none')
1912 self
.client
.Draw(_draw3Objects())
1914 def OnPlotDraw4(self
, event
):
1915 self
.resetDefaults()
1916 drawObj
= _draw4Objects()
1917 self
.client
.Draw(drawObj
)
1919 ## start = _time.clock()
1920 ## for x in range(10):
1921 ## self.client.Draw(drawObj)
1922 ## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start)
1925 def OnPlotDraw5(self
, event
):
1926 # Empty plot with just axes
1927 self
.resetDefaults()
1928 drawObj
= _draw5Objects()
1929 # make the axis X= (0,5), Y=(0,10)
1930 # (default with None is X= (-1,1), Y= (-1,1))
1931 self
.client
.Draw(drawObj
, xAxis
= (0,5), yAxis
= (0,10))
1933 def OnPlotDraw6(self
, event
):
1935 self
.resetDefaults()
1936 #self.client.SetEnableLegend(True) #turn on Legend
1937 #self.client.SetEnableGrid(True) #turn on Grid
1938 self
.client
.SetXSpec('none') #turns off x-axis scale
1939 self
.client
.SetYSpec('auto')
1940 self
.client
.Draw(_draw6Objects(), xAxis
= (0,7))
1942 def OnPlotRedraw(self
,event
):
1943 self
.client
.Redraw()
1945 def OnPlotClear(self
,event
):
1948 def OnPlotScale(self
, event
):
1949 if self
.client
.last_draw
!= None:
1950 graphics
, xAxis
, yAxis
= self
.client
.last_draw
1951 self
.client
.Draw(graphics
,(1,3.05),(0,1))
1953 def OnEnableZoom(self
, event
):
1954 self
.client
.SetEnableZoom(event
.IsChecked())
1955 self
.mainmenu
.Check(217, not event
.IsChecked())
1957 def OnEnableGrid(self
, event
):
1958 self
.client
.SetEnableGrid(event
.IsChecked())
1960 def OnEnableDrag(self
, event
):
1961 self
.client
.SetEnableDrag(event
.IsChecked())
1962 self
.mainmenu
.Check(214, not event
.IsChecked())
1964 def OnEnableLegend(self
, event
):
1965 self
.client
.SetEnableLegend(event
.IsChecked())
1967 def OnEnablePointLabel(self
, event
):
1968 self
.client
.SetEnablePointLabel(event
.IsChecked())
1970 def OnScrUp(self
, event
):
1971 self
.client
.ScrollUp(1)
1973 def OnScrRt(self
,event
):
1974 self
.client
.ScrollRight(2)
1976 def OnReset(self
,event
):
1979 def OnHelpAbout(self
, event
):
1980 from wx
.lib
.dialogs
import ScrolledMessageDialog
1981 about
= ScrolledMessageDialog(self
, __doc__
, "About...")
1984 def resetDefaults(self
):
1985 """Just to reset the fonts back to the PlotCanvas defaults"""
1986 self
.client
.SetFont(wx
.Font(10,wx
.SWISS
,wx
.NORMAL
,wx
.NORMAL
))
1987 self
.client
.SetFontSizeAxis(10)
1988 self
.client
.SetFontSizeLegend(7)
1989 self
.client
.SetXSpec('auto')
1990 self
.client
.SetYSpec('auto')
1995 class MyApp(wx
.App
):
1997 wx
.InitAllImageHandlers()
1998 frame
= TestFrame(None, -1, "PlotCanvas")
2000 self
.SetTopWindow(frame
)
2007 if __name__
== '__main__':