]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/plot.py
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
).astype(_Numeric
.Float64
)
125 self
._logscale
= (False, False)
126 self
.currentScale
= (1,1)
127 self
.currentShift
= (0,0)
128 self
.scaled
= self
.points
130 self
.attributes
.update(self
._attributes
)
131 for name
, value
in attr
.items():
132 if name
not in self
._attributes
.keys():
133 raise KeyError, "Style attribute incorrect. Should be one of %s" % self
._attributes
.keys()
134 self
.attributes
[name
] = value
136 def setLogScale(self
, logscale
):
137 self
._logscale
= logscale
139 def __getattr__(self
, name
):
141 if len(self
._points
)>0:
142 data
= _Numeric
.array(self
._points
,copy
=True)
143 if self
._logscale
[0]:
144 data
= self
.log10(data
, 0)
145 if self
._logscale
[1]:
146 data
= self
.log10(data
, 1)
151 raise AttributeError, name
153 def log10(self
, data
, ind
):
154 data
= _Numeric
.compress(data
[:,ind
]>0,data
,0)
155 data
[:,ind
] = _Numeric
.log10(data
[:,ind
])
158 def boundingBox(self
):
159 if len(self
.points
) == 0:
161 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
162 minXY
= _Numeric
.array([-1.0,-1.0])
163 maxXY
= _Numeric
.array([ 1.0, 1.0])
165 minXY
= _Numeric
.minimum
.reduce(self
.points
)
166 maxXY
= _Numeric
.maximum
.reduce(self
.points
)
169 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
170 if len(self
.points
) == 0:
173 if (scale
is not self
.currentScale
) or (shift
is not self
.currentShift
):
174 # update point scaling
175 self
.scaled
= scale
*self
.points
+shift
176 self
.currentScale
= scale
177 self
.currentShift
= shift
178 # else unchanged use the current scaling
181 return self
.attributes
['legend']
183 def getClosestPoint(self
, pntXY
, pointScaled
= True):
184 """Returns the index of closest point on the curve, pointXY, scaledXY, distance
186 if pointScaled == True based on screen coords
187 if pointScaled == False based on user coords
189 if pointScaled
== True:
192 pxy
= self
.currentScale
* _Numeric
.array(pntXY
)+ self
.currentShift
196 pxy
= _Numeric
.array(pntXY
)
197 #determine distance for each point
198 d
= _Numeric
.sqrt(_Numeric
.add
.reduce((p
-pxy
)**2,1)) #sqrt(dx^2+dy^2)
199 pntIndex
= _Numeric
.argmin(d
)
201 return [pntIndex
, self
.points
[pntIndex
], self
.scaled
[pntIndex
], dist
]
204 class PolyLine(PolyPoints
):
205 """Class to define line type and style
206 - All methods except __init__ are private.
209 _attributes
= {'colour': 'black',
214 def __init__(self
, points
, **attr
):
215 """Creates PolyLine object
216 points - sequence (array, tuple or list) of (x,y) points making up line
217 **attr - key word attributes
219 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
220 'width'= 1, - Pen width
221 'style'= wx.SOLID, - wx.Pen style
222 'legend'= '' - Line Legend to display
224 PolyPoints
.__init
__(self
, points
, attr
)
226 def draw(self
, dc
, printerScale
, coord
= None):
227 colour
= self
.attributes
['colour']
228 width
= self
.attributes
['width'] * printerScale
229 style
= self
.attributes
['style']
230 if not isinstance(colour
, wx
.Colour
):
231 colour
= wx
.NamedColour(colour
)
232 pen
= wx
.Pen(colour
, width
, style
)
233 pen
.SetCap(wx
.CAP_BUTT
)
236 dc
.DrawLines(self
.scaled
)
238 dc
.DrawLines(coord
) # draw legend line
240 def getSymExtent(self
, printerScale
):
241 """Width and Height of Marker"""
242 h
= self
.attributes
['width'] * printerScale
247 class PolyMarker(PolyPoints
):
248 """Class to define marker type and style
249 - All methods except __init__ are private.
252 _attributes
= {'colour': 'black',
256 'fillstyle': wx
.SOLID
,
260 def __init__(self
, points
, **attr
):
261 """Creates PolyMarker object
262 points - sequence (array, tuple or list) of (x,y) points
263 **attr - key word attributes
265 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
266 'width'= 1, - Pen width
267 'size'= 2, - Marker size
268 'fillcolour'= same as colour, - wx.Brush Colour any wx.NamedColour
269 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
270 'marker'= 'circle' - Marker shape
271 'legend'= '' - Marker Legend to display
283 PolyPoints
.__init
__(self
, points
, attr
)
285 def draw(self
, dc
, printerScale
, coord
= None):
286 colour
= self
.attributes
['colour']
287 width
= self
.attributes
['width'] * printerScale
288 size
= self
.attributes
['size'] * printerScale
289 fillcolour
= self
.attributes
['fillcolour']
290 fillstyle
= self
.attributes
['fillstyle']
291 marker
= self
.attributes
['marker']
293 if colour
and not isinstance(colour
, wx
.Colour
):
294 colour
= wx
.NamedColour(colour
)
295 if fillcolour
and not isinstance(fillcolour
, wx
.Colour
):
296 fillcolour
= wx
.NamedColour(fillcolour
)
298 dc
.SetPen(wx
.Pen(colour
, width
))
300 dc
.SetBrush(wx
.Brush(fillcolour
,fillstyle
))
302 dc
.SetBrush(wx
.Brush(colour
, fillstyle
))
304 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
306 self
._drawmarkers
(dc
, coord
, marker
, size
) # draw legend marker
308 def getSymExtent(self
, printerScale
):
309 """Width and Height of Marker"""
310 s
= 5*self
.attributes
['size'] * printerScale
313 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
314 f
= eval('self._' +marker
)
317 def _circle(self
, dc
, coords
, size
=1):
320 rect
= _Numeric
.zeros((len(coords
),4),_Numeric
.Float
)+[0.0,0.0,wh
,wh
]
321 rect
[:,0:2]= coords
-[fact
,fact
]
322 dc
.DrawEllipseList(rect
.astype(_Numeric
.Int32
))
324 def _dot(self
, dc
, coords
, size
=1):
325 dc
.DrawPointList(coords
)
327 def _square(self
, dc
, coords
, size
=1):
330 rect
= _Numeric
.zeros((len(coords
),4),_Numeric
.Float
)+[0.0,0.0,wh
,wh
]
331 rect
[:,0:2]= coords
-[fact
,fact
]
332 dc
.DrawRectangleList(rect
.astype(_Numeric
.Int32
))
334 def _triangle(self
, dc
, coords
, size
=1):
335 shape
= [(-2.5*size
,1.44*size
), (2.5*size
,1.44*size
), (0.0,-2.88*size
)]
336 poly
= _Numeric
.repeat(coords
,3)
337 poly
.shape
= (len(coords
),3,2)
339 dc
.DrawPolygonList(poly
.astype(_Numeric
.Int32
))
341 def _triangle_down(self
, dc
, coords
, size
=1):
342 shape
= [(-2.5*size
,-1.44*size
), (2.5*size
,-1.44*size
), (0.0,2.88*size
)]
343 poly
= _Numeric
.repeat(coords
,3)
344 poly
.shape
= (len(coords
),3,2)
346 dc
.DrawPolygonList(poly
.astype(_Numeric
.Int32
))
348 def _cross(self
, dc
, coords
, size
=1):
350 for f
in [[-fact
,-fact
,fact
,fact
],[-fact
,fact
,fact
,-fact
]]:
351 lines
= _Numeric
.concatenate((coords
,coords
),axis
=1)+f
352 dc
.DrawLineList(lines
.astype(_Numeric
.Int32
))
354 def _plus(self
, dc
, coords
, size
=1):
356 for f
in [[-fact
,0,fact
,0],[0,-fact
,0,fact
]]:
357 lines
= _Numeric
.concatenate((coords
,coords
),axis
=1)+f
358 dc
.DrawLineList(lines
.astype(_Numeric
.Int32
))
361 """Container to hold PolyXXX objects and graph labels
362 - All methods except __init__ are private.
365 def __init__(self
, objects
, title
='', xLabel
='', yLabel
= ''):
366 """Creates PlotGraphics object
367 objects - list of PolyXXX objects to make graph
368 title - title shown at top of graph
369 xLabel - label shown on x-axis
370 yLabel - label shown on y-axis
372 if type(objects
) not in [list,tuple]:
373 raise TypeError, "objects argument should be list or tuple"
374 self
.objects
= objects
379 def setLogScale(self
, logscale
):
380 if type(logscale
) != tuple:
381 raise TypeError, 'logscale must be a tuple of bools, e.g. (False, False)'
382 if len(self
.objects
) == 0:
384 for o
in self
.objects
:
385 o
.setLogScale(logscale
)
387 def boundingBox(self
):
388 p1
, p2
= self
.objects
[0].boundingBox()
389 for o
in self
.objects
[1:]:
390 p1o
, p2o
= o
.boundingBox()
391 p1
= _Numeric
.minimum(p1
, p1o
)
392 p2
= _Numeric
.maximum(p2
, p2o
)
395 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
396 for o
in self
.objects
:
397 o
.scaleAndShift(scale
, shift
)
399 def setPrinterScale(self
, scale
):
400 """Thickens up lines and markers only for printing"""
401 self
.printerScale
= scale
403 def setXLabel(self
, xLabel
= ''):
404 """Set the X axis label on the graph"""
407 def setYLabel(self
, yLabel
= ''):
408 """Set the Y axis label on the graph"""
411 def setTitle(self
, title
= ''):
412 """Set the title at the top of graph"""
416 """Get x axis label string"""
420 """Get y axis label string"""
423 def getTitle(self
, title
= ''):
424 """Get the title at the top of graph"""
428 for o
in self
.objects
:
429 #t=_time.clock() # profile info
430 o
.draw(dc
, self
.printerScale
)
432 #print o, "time=", dt
434 def getSymExtent(self
, printerScale
):
435 """Get max width and height of lines and markers symbols for legend"""
436 symExt
= self
.objects
[0].getSymExtent(printerScale
)
437 for o
in self
.objects
[1:]:
438 oSymExt
= o
.getSymExtent(printerScale
)
439 symExt
= _Numeric
.maximum(symExt
, oSymExt
)
442 def getLegendNames(self
):
443 """Returns list of legend names"""
444 lst
= [None]*len(self
)
445 for i
in range(len(self
)):
446 lst
[i
]= self
.objects
[i
].getLegend()
450 return len(self
.objects
)
452 def __getitem__(self
, item
):
453 return self
.objects
[item
]
456 #-------------------------------------------------------------------------------
457 # Main window that you will want to import into your application.
459 class PlotCanvas(wx
.Panel
):
461 Subclass of a wx.Panel which holds two scrollbars and the actual
462 plotting canvas (self.canvas). It allows for simple general plotting
463 of data with zoom, labels, and automatic axis scaling."""
465 def __init__(self
, parent
):
466 """Constructs a panel, which can be a child of a frame or
467 any other non-control window"""
469 wx
.Panel
.__init
__(self
, parent
)
471 sizer
= wx
.FlexGridSizer(2,2,0,0)
472 self
.canvas
= wx
.Window(self
, -1)
473 self
.sb_vert
= wx
.ScrollBar(self
, -1, style
=wx
.SB_VERTICAL
)
474 self
.sb_vert
.SetScrollbar(0,1000,1000,1000)
475 self
.sb_hor
= wx
.ScrollBar(self
, -1, style
=wx
.SB_HORIZONTAL
)
476 self
.sb_hor
.SetScrollbar(0,1000,1000,1000)
478 sizer
.Add(self
.canvas
, 1, wx
.EXPAND
)
479 sizer
.Add(self
.sb_vert
, 0, wx
.EXPAND
)
480 sizer
.Add(self
.sb_hor
, 0, wx
.EXPAND
)
483 sizer
.AddGrowableRow(0, 1)
484 sizer
.AddGrowableCol(0, 1)
486 self
.sb_vert
.Show(False)
487 self
.sb_hor
.Show(False)
494 self
.SetBackgroundColour("white")
496 # Create some mouse events for zooming
497 self
.canvas
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
498 self
.canvas
.Bind(wx
.EVT_LEFT_UP
, self
.OnMouseLeftUp
)
499 self
.canvas
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
500 self
.canvas
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnMouseDoubleClick
)
501 self
.canvas
.Bind(wx
.EVT_RIGHT_DOWN
, self
.OnMouseRightDown
)
504 self
.Bind(wx
.EVT_SCROLL_THUMBTRACK
, self
.OnScroll
)
505 self
.Bind(wx
.EVT_SCROLL_PAGEUP
, self
.OnScroll
)
506 self
.Bind(wx
.EVT_SCROLL_PAGEDOWN
, self
.OnScroll
)
507 self
.Bind(wx
.EVT_SCROLL_LINEUP
, self
.OnScroll
)
508 self
.Bind(wx
.EVT_SCROLL_LINEDOWN
, self
.OnScroll
)
510 # set curser as cross-hairs
511 self
.canvas
.SetCursor(wx
.CROSS_CURSOR
)
512 self
.HandCursor
= wx
.CursorFromImage(getHandImage())
513 self
.GrabHandCursor
= wx
.CursorFromImage(getGrabHandImage())
514 self
.MagCursor
= wx
.CursorFromImage(getMagPlusImage())
516 # Things for printing
517 self
.print_data
= wx
.PrintData()
518 self
.print_data
.SetPaperId(wx
.PAPER_LETTER
)
519 self
.print_data
.SetOrientation(wx
.LANDSCAPE
)
520 self
.pageSetupData
= wx
.PageSetupDialogData()
521 self
.pageSetupData
.SetMarginBottomRight((25,25))
522 self
.pageSetupData
.SetMarginTopLeft((25,25))
523 self
.pageSetupData
.SetPrintData(self
.print_data
)
524 self
.printerScale
= 1
527 # scrollbar variables
528 self
._sb
_ignore
= False
529 self
._adjustingSB
= False
530 self
._sb
_xfullrange
= 0
531 self
._sb
_yfullrange
= 0
535 self
._dragEnabled
= False
536 self
._screenCoordinates
= _Numeric
.array([0.0, 0.0])
538 self
._logscale
= (False, False)
541 self
._zoomInFactor
= 0.5
542 self
._zoomOutFactor
= 2
543 self
._zoomCorner
1= _Numeric
.array([0.0, 0.0]) # left mouse down corner
544 self
._zoomCorner
2= _Numeric
.array([0.0, 0.0]) # left mouse up corner
545 self
._zoomEnabled
= False
546 self
._hasDragged
= False
549 self
.last_draw
= None
554 self
._gridEnabled
= False
555 self
._legendEnabled
= False
559 self
._fontSizeAxis
= 10
560 self
._fontSizeTitle
= 15
561 self
._fontSizeLegend
= 7
564 self
._pointLabelEnabled
= False
565 self
.last_PointLabel
= None
566 self
._pointLabelFunc
= None
567 self
.canvas
.Bind(wx
.EVT_LEAVE_WINDOW
, self
.OnLeave
)
569 self
.canvas
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
570 self
.canvas
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
571 # OnSize called to make sure the buffer is initialized.
572 # This might result in OnSize getting called twice on some
573 # platforms at initialization, but little harm done.
574 self
.OnSize(None) # sets the initial size based on client size
576 self
._gridColour
= wx
.NamedColour('black')
578 def SetCursor(self
, cursor
):
579 self
.canvas
.SetCursor(cursor
)
581 def GetGridColour(self
):
582 return self
._gridColour
584 def SetGridColour(self
, colour
):
585 if isinstance(colour
, wx
.Colour
):
586 self
._gridColour
= colour
588 self
._gridColour
= wx
.NamedColour(colour
)
592 def SaveFile(self
, fileName
= ''):
593 """Saves the file to the type specified in the extension. If no file
594 name is specified a dialog box is provided. Returns True if sucessful,
597 .bmp Save a Windows bitmap file.
598 .xbm Save an X bitmap file.
599 .xpm Save an XPM bitmap file.
600 .png Save a Portable Network Graphics file.
601 .jpg Save a Joint Photographic Experts Group file.
603 if _string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
604 dlg1
= wx
.FileDialog(
606 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
607 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
608 wx
.SAVE|wx
.OVERWRITE_PROMPT
612 if dlg1
.ShowModal() == wx
.ID_OK
:
613 fileName
= dlg1
.GetPath()
614 # Check for proper exension
615 if _string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
616 dlg2
= wx
.MessageDialog(self
, 'File name extension\n'
618 'bmp, xbm, xpm, png, or jpg',
619 'File Name Error', wx
.OK | wx
.ICON_ERROR
)
625 break # now save file
626 else: # exit without saving
631 # File name has required extension
632 fType
= _string
.lower(fileName
[-3:])
634 tp
= wx
.BITMAP_TYPE_BMP
# Save a Windows bitmap file.
636 tp
= wx
.BITMAP_TYPE_XBM
# Save an X bitmap file.
638 tp
= wx
.BITMAP_TYPE_XPM
# Save an XPM bitmap file.
640 tp
= wx
.BITMAP_TYPE_JPEG
# Save a JPG file.
642 tp
= wx
.BITMAP_TYPE_PNG
# Save a PNG file.
644 res
= self
._Buffer
.SaveFile(fileName
, tp
)
648 """Brings up the page setup dialog"""
649 data
= self
.pageSetupData
650 data
.SetPrintData(self
.print_data
)
651 dlg
= wx
.PageSetupDialog(self
.parent
, data
)
653 if dlg
.ShowModal() == wx
.ID_OK
:
654 data
= dlg
.GetPageSetupData() # returns wx.PageSetupDialogData
655 # updates page parameters from dialog
656 self
.pageSetupData
.SetMarginBottomRight(data
.GetMarginBottomRight())
657 self
.pageSetupData
.SetMarginTopLeft(data
.GetMarginTopLeft())
658 self
.pageSetupData
.SetPrintData(data
.GetPrintData())
659 self
.print_data
=wx
.PrintData(data
.GetPrintData()) # updates print_data
663 def Printout(self
, paper
=None):
664 """Print current plot."""
666 self
.print_data
.SetPaperId(paper
)
667 pdd
= wx
.PrintDialogData(self
.print_data
)
668 printer
= wx
.Printer(pdd
)
669 out
= PlotPrintout(self
)
670 print_ok
= printer
.Print(self
.parent
, out
)
672 self
.print_data
= wx
.PrintData(printer
.GetPrintDialogData().GetPrintData())
675 def PrintPreview(self
):
676 """Print-preview current plot."""
677 printout
= PlotPrintout(self
)
678 printout2
= PlotPrintout(self
)
679 self
.preview
= wx
.PrintPreview(printout
, printout2
, self
.print_data
)
680 if not self
.preview
.Ok():
681 wx
.MessageDialog(self
, "Print Preview failed.\n" \
682 "Check that default printer is configured\n", \
683 "Print error", wx
.OK|wx
.CENTRE
).ShowModal()
684 self
.preview
.SetZoom(40)
685 # search up tree to find frame instance
687 while not isinstance(frameInst
, wx
.Frame
):
688 frameInst
= frameInst
.GetParent()
689 frame
= wx
.PreviewFrame(self
.preview
, frameInst
, "Preview")
691 frame
.SetPosition(self
.GetPosition())
692 frame
.SetSize((600,550))
693 frame
.Centre(wx
.BOTH
)
696 def setLogScale(self
, logscale
):
697 if type(logscale
) != tuple:
698 raise TypeError, 'logscale must be a tuple of bools, e.g. (False, False)'
699 if self
.last_draw
is not None:
700 graphics
, xAxis
, yAxis
= self
.last_draw
701 graphics
.setLogScale(logscale
)
702 self
.last_draw
= (graphics
, None, None)
705 self
._logscale
= logscale
707 def getLogScale(self
):
708 return self
._logscale
710 def SetFontSizeAxis(self
, point
= 10):
711 """Set the tick and axis label font size (default is 10 point)"""
712 self
._fontSizeAxis
= point
714 def GetFontSizeAxis(self
):
715 """Get current tick and axis label font size in points"""
716 return self
._fontSizeAxis
718 def SetFontSizeTitle(self
, point
= 15):
719 """Set Title font size (default is 15 point)"""
720 self
._fontSizeTitle
= point
722 def GetFontSizeTitle(self
):
723 """Get current Title font size in points"""
724 return self
._fontSizeTitle
726 def SetFontSizeLegend(self
, point
= 7):
727 """Set Legend font size (default is 7 point)"""
728 self
._fontSizeLegend
= point
730 def GetFontSizeLegend(self
):
731 """Get current Legend font size in points"""
732 return self
._fontSizeLegend
734 def SetShowScrollbars(self
, value
):
735 """Set True to show scrollbars"""
736 if value
not in [True,False]:
737 raise TypeError, "Value should be True or False"
738 if value
== self
.GetShowScrollbars():
740 self
.sb_vert
.Show(value
)
741 self
.sb_hor
.Show(value
)
742 wx
.CallAfter(self
.Layout
)
744 def GetShowScrollbars(self
):
745 """Set True to show scrollbars"""
746 return self
.sb_vert
.IsShown()
748 def SetEnableDrag(self
, value
):
749 """Set True to enable drag."""
750 if value
not in [True,False]:
751 raise TypeError, "Value should be True or False"
753 if self
.GetEnableZoom():
754 self
.SetEnableZoom(False)
755 self
.SetCursor(self
.HandCursor
)
757 self
.SetCursor(wx
.CROSS_CURSOR
)
758 self
._dragEnabled
= value
760 def GetEnableDrag(self
):
761 return self
._dragEnabled
763 def SetEnableZoom(self
, value
):
764 """Set True to enable zooming."""
765 if value
not in [True,False]:
766 raise TypeError, "Value should be True or False"
768 if self
.GetEnableDrag():
769 self
.SetEnableDrag(False)
770 self
.SetCursor(self
.MagCursor
)
772 self
.SetCursor(wx
.CROSS_CURSOR
)
773 self
._zoomEnabled
= value
775 def GetEnableZoom(self
):
776 """True if zooming enabled."""
777 return self
._zoomEnabled
779 def SetEnableGrid(self
, value
):
780 """Set True to enable grid."""
781 if value
not in [True,False,'Horizontal','Vertical']:
782 raise TypeError, "Value should be True, False, Horizontal or Vertical"
783 self
._gridEnabled
= value
786 def GetEnableGrid(self
):
787 """True if grid enabled."""
788 return self
._gridEnabled
790 def SetEnableLegend(self
, value
):
791 """Set True to enable legend."""
792 if value
not in [True,False]:
793 raise TypeError, "Value should be True or False"
794 self
._legendEnabled
= value
797 def GetEnableLegend(self
):
798 """True if Legend enabled."""
799 return self
._legendEnabled
801 def SetEnablePointLabel(self
, value
):
802 """Set True to enable pointLabel."""
803 if value
not in [True,False]:
804 raise TypeError, "Value should be True or False"
805 self
._pointLabelEnabled
= value
806 self
.Redraw() #will erase existing pointLabel if present
807 self
.last_PointLabel
= None
809 def GetEnablePointLabel(self
):
810 """True if pointLabel enabled."""
811 return self
._pointLabelEnabled
813 def SetPointLabelFunc(self
, func
):
814 """Sets the function with custom code for pointLabel drawing
815 ******** more info needed ***************
817 self
._pointLabelFunc
= func
819 def GetPointLabelFunc(self
):
820 """Returns pointLabel Drawing Function"""
821 return self
._pointLabelFunc
824 """Unzoom the plot."""
825 self
.last_PointLabel
= None #reset pointLabel
826 if self
.last_draw
is not None:
827 self
._Draw
(self
.last_draw
[0])
829 def ScrollRight(self
, units
):
830 """Move view right number of axis units."""
831 self
.last_PointLabel
= None #reset pointLabel
832 if self
.last_draw
is not None:
833 graphics
, xAxis
, yAxis
= self
.last_draw
834 xAxis
= (xAxis
[0]+units
, xAxis
[1]+units
)
835 self
._Draw
(graphics
,xAxis
,yAxis
)
837 def ScrollUp(self
, units
):
838 """Move view up number of axis units."""
839 self
.last_PointLabel
= None #reset pointLabel
840 if self
.last_draw
is not None:
841 graphics
, xAxis
, yAxis
= self
.last_draw
842 yAxis
= (yAxis
[0]+units
, yAxis
[1]+units
)
843 self
._Draw
(graphics
,xAxis
,yAxis
)
845 def GetXY(self
, event
):
846 """Wrapper around _getXY, which handles log scales"""
847 x
,y
= self
._getXY
(event
)
848 if self
.getLogScale()[0]:
849 x
= _Numeric
.power(10,x
)
850 if self
.getLogScale()[1]:
851 y
= _Numeric
.power(10,y
)
854 def _getXY(self
,event
):
855 """Takes a mouse event and returns the XY user axis values."""
856 x
,y
= self
.PositionScreenToUser(event
.GetPosition())
859 def PositionUserToScreen(self
, pntXY
):
860 """Converts User position to Screen Coordinates"""
861 userPos
= _Numeric
.array(pntXY
)
862 x
,y
= userPos
* self
._pointScale
+ self
._pointShift
865 def PositionScreenToUser(self
, pntXY
):
866 """Converts Screen position to User Coordinates"""
867 screenPos
= _Numeric
.array(pntXY
)
868 x
,y
= (screenPos
-self
._pointShift
)/self
._pointScale
871 def SetXSpec(self
, type= 'auto'):
872 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
874 'none' - shows no axis or tick mark values
875 'min' - shows min bounding box values
876 'auto' - rounds axis range to sensible values
880 def SetYSpec(self
, type= 'auto'):
881 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
883 'none' - shows no axis or tick mark values
884 'min' - shows min bounding box values
885 'auto' - rounds axis range to sensible values
890 """Returns current XSpec for axis"""
894 """Returns current YSpec for axis"""
897 def GetXMaxRange(self
):
898 xAxis
= self
._getXMaxRange
()
899 if self
.getLogScale()[0]:
900 xAxis
= _Numeric
.power(10,xAxis
)
903 def _getXMaxRange(self
):
904 """Returns (minX, maxX) x-axis range for displayed graph"""
905 graphics
= self
.last_draw
[0]
906 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
907 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
910 def GetYMaxRange(self
):
911 yAxis
= self
._getYMaxRange
()
912 if self
.getLogScale()[1]:
913 yAxis
= _Numeric
.power(10,yAxis
)
916 def _getYMaxRange(self
):
917 """Returns (minY, maxY) y-axis range for displayed graph"""
918 graphics
= self
.last_draw
[0]
919 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
920 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
923 def GetXCurrentRange(self
):
924 xAxis
= self
._getXCurrentRange
()
925 if self
.getLogScale()[0]:
926 xAxis
= _Numeric
.power(10,xAxis
)
929 def _getXCurrentRange(self
):
930 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
931 return self
.last_draw
[1]
933 def GetYCurrentRange(self
):
934 yAxis
= self
._getYCurrentRange
()
935 if self
.getLogScale()[1]:
936 yAxis
= _Numeric
.power(10,yAxis
)
939 def _getYCurrentRange(self
):
940 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
941 return self
.last_draw
[2]
943 def Draw(self
, graphics
, xAxis
= None, yAxis
= None, dc
= None):
944 """Wrapper around _Draw, which handles log axes"""
946 graphics
.setLogScale(self
.getLogScale())
948 # check Axis is either tuple or none
949 if type(xAxis
) not in [type(None),tuple]:
950 raise TypeError, "xAxis should be None or (minX,maxX)"+str(type(xAxis
))
951 if type(yAxis
) not in [type(None),tuple]:
952 raise TypeError, "yAxis should be None or (minY,maxY)"+str(type(xAxis
))
954 # check case for axis = (a,b) where a==b caused by improper zooms
956 if xAxis
[0] == xAxis
[1]:
958 if self
.getLogScale()[0]:
959 xAxis
= _Numeric
.log10(xAxis
)
961 if yAxis
[0] == yAxis
[1]:
963 if self
.getLogScale()[1]:
964 yAxis
= _Numeric
.log10(yAxis
)
965 self
._Draw
(graphics
, xAxis
, yAxis
, dc
)
967 def _Draw(self
, graphics
, xAxis
= None, yAxis
= None, dc
= None):
969 Draw objects in graphics with specified x and y axis.
970 graphics- instance of PlotGraphics with list of PolyXXX objects
971 xAxis - tuple with (min, max) axis range to view
972 yAxis - same as xAxis
973 dc - drawing context - doesn't have to be specified.
974 If it's not, the offscreen buffer is used
978 # sets new dc and clears it
979 dc
= wx
.BufferedDC(wx
.ClientDC(self
.canvas
), self
._Buffer
)
985 # set font size for every thing but title and legend
986 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
988 # sizes axis to axis type, create lower left and upper right corners of plot
989 if xAxis
== None or yAxis
== None:
990 # One or both axis not specified in Draw
991 p1
, p2
= graphics
.boundingBox() # min, max points of graphics
993 xAxis
= self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) # in user units
995 yAxis
= self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1])
996 # Adjust bounding box for axis spec
997 p1
[0],p1
[1] = xAxis
[0], yAxis
[0] # lower left corner user scale (xmin,ymin)
998 p2
[0],p2
[1] = xAxis
[1], yAxis
[1] # upper right corner user scale (xmax,ymax)
1000 # Both axis specified in Draw
1001 p1
= _Numeric
.array([xAxis
[0], yAxis
[0]]) # lower left corner user scale (xmin,ymin)
1002 p2
= _Numeric
.array([xAxis
[1], yAxis
[1]]) # upper right corner user scale (xmax,ymax)
1004 self
.last_draw
= (graphics
, _Numeric
.array(xAxis
), _Numeric
.array(yAxis
)) # saves most recient values
1006 # Get ticks and textExtents for axis if required
1007 if self
._xSpec
is not 'none':
1008 xticks
= self
._xticks
(xAxis
[0], xAxis
[1])
1009 xTextExtent
= dc
.GetTextExtent(xticks
[-1][1])# w h of x axis text last number on axis
1012 xTextExtent
= (0,0) # No text for ticks
1013 if self
._ySpec
is not 'none':
1014 yticks
= self
._yticks
(yAxis
[0], yAxis
[1])
1015 if self
.getLogScale()[1]:
1016 yTextExtent
= dc
.GetTextExtent('-2e-2')
1018 yTextExtentBottom
= dc
.GetTextExtent(yticks
[0][1])
1019 yTextExtentTop
= dc
.GetTextExtent(yticks
[-1][1])
1020 yTextExtent
= (max(yTextExtentBottom
[0],yTextExtentTop
[0]),
1021 max(yTextExtentBottom
[1],yTextExtentTop
[1]))
1024 yTextExtent
= (0,0) # No text for ticks
1026 # TextExtents for Title and Axis Labels
1027 titleWH
, xLabelWH
, yLabelWH
= self
._titleLablesWH
(dc
, graphics
)
1029 # TextExtents for Legend
1030 legendBoxWH
, legendSymExt
, legendTextExt
= self
._legendWH
(dc
, graphics
)
1032 # room around graph area
1033 rhsW
= max(xTextExtent
[0], legendBoxWH
[0]) # use larger of number width or legend width
1034 lhsW
= yTextExtent
[0]+ yLabelWH
[1]
1035 bottomH
= max(xTextExtent
[1], yTextExtent
[1]/2.)+ xLabelWH
[1]
1036 topH
= yTextExtent
[1]/2. + titleWH
[1]
1037 textSize_scale
= _Numeric
.array([rhsW
+lhsW
,bottomH
+topH
]) # make plot area smaller by text size
1038 textSize_shift
= _Numeric
.array([lhsW
, bottomH
]) # shift plot area by this amount
1040 # drawing title and labels text
1041 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
1042 titlePos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- titleWH
[0]/2.,
1043 self
.plotbox_origin
[1]- self
.plotbox_size
[1])
1044 dc
.DrawText(graphics
.getTitle(),titlePos
[0],titlePos
[1])
1045 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1046 xLabelPos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- xLabelWH
[0]/2.,
1047 self
.plotbox_origin
[1]- xLabelWH
[1])
1048 dc
.DrawText(graphics
.getXLabel(),xLabelPos
[0],xLabelPos
[1])
1049 yLabelPos
= (self
.plotbox_origin
[0],
1050 self
.plotbox_origin
[1]- bottomH
- (self
.plotbox_size
[1]-bottomH
-topH
)/2.+ yLabelWH
[0]/2.)
1051 if graphics
.getYLabel(): # bug fix for Linux
1052 dc
.DrawRotatedText(graphics
.getYLabel(),yLabelPos
[0],yLabelPos
[1],90)
1054 # drawing legend makers and text
1055 if self
._legendEnabled
:
1056 self
._drawLegend
(dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
)
1058 # allow for scaling and shifting plotted points
1059 scale
= (self
.plotbox_size
-textSize_scale
) / (p2
-p1
)* _Numeric
.array((1,-1))
1060 shift
= -p1
*scale
+ self
.plotbox_origin
+ textSize_shift
* _Numeric
.array((1,-1))
1061 self
._pointScale
= scale
# make available for mouse events
1062 self
._pointShift
= shift
1063 self
._drawAxes
(dc
, p1
, p2
, scale
, shift
, xticks
, yticks
)
1065 graphics
.scaleAndShift(scale
, shift
)
1066 graphics
.setPrinterScale(self
.printerScale
) # thicken up lines and markers if printing
1068 # set clipping area so drawing does not occur outside axis box
1069 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(p1
, p2
)
1070 dc
.SetClippingRegion(ptx
,pty
,rectWidth
,rectHeight
)
1071 # Draw the lines and markers
1072 #start = _time.clock()
1074 # print "entire graphics drawing took: %f second"%(_time.clock() - start)
1075 # remove the clipping region
1076 dc
.DestroyClippingRegion()
1079 self
._adjustScrollbars
()
1081 def Redraw(self
, dc
=None):
1082 """Redraw the existing plot."""
1083 if self
.last_draw
is not None:
1084 graphics
, xAxis
, yAxis
= self
.last_draw
1085 self
._Draw
(graphics
,xAxis
,yAxis
,dc
)
1088 """Erase the window."""
1089 self
.last_PointLabel
= None #reset pointLabel
1090 dc
= wx
.BufferedDC(wx
.ClientDC(self
.canvas
), self
._Buffer
)
1092 self
.last_draw
= None
1094 def Zoom(self
, Center
, Ratio
):
1095 """ Zoom on the plot
1096 Centers on the X,Y coords given in Center
1097 Zooms by the Ratio = (Xratio, Yratio) given
1099 self
.last_PointLabel
= None #reset maker
1101 if self
.last_draw
!= None:
1102 (graphics
, xAxis
, yAxis
) = self
.last_draw
1103 w
= (xAxis
[1] - xAxis
[0]) * Ratio
[0]
1104 h
= (yAxis
[1] - yAxis
[0]) * Ratio
[1]
1105 xAxis
= ( x
- w
/2, x
+ w
/2 )
1106 yAxis
= ( y
- h
/2, y
+ h
/2 )
1107 self
._Draw
(graphics
, xAxis
, yAxis
)
1109 def GetClosestPoints(self
, pntXY
, pointScaled
= True):
1110 """Returns list with
1111 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1112 list for each curve.
1113 Returns [] if no curves are being plotted.
1116 if pointScaled == True based on screen coords
1117 if pointScaled == False based on user coords
1119 if self
.last_draw
== None:
1122 graphics
, xAxis
, yAxis
= self
.last_draw
1124 for curveNum
,obj
in enumerate(graphics
):
1125 #check there are points in the curve
1126 if len(obj
.points
) == 0:
1127 continue #go to next obj
1128 #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1129 cn
= [curveNum
]+ [obj
.getLegend()]+ obj
.getClosestPoint( pntXY
, pointScaled
)
1133 def GetClosetPoint(self
, pntXY
, pointScaled
= True):
1134 """Returns list with
1135 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1136 list for only the closest curve.
1137 Returns [] if no curves are being plotted.
1140 if pointScaled == True based on screen coords
1141 if pointScaled == False based on user coords
1143 #closest points on screen based on screen scaling (pointScaled= True)
1144 #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
1145 closestPts
= self
.GetClosestPoints(pntXY
, pointScaled
)
1146 if closestPts
== []:
1147 return [] #no graph present
1148 #find one with least distance
1149 dists
= [c
[-1] for c
in closestPts
]
1150 mdist
= min(dists
) #Min dist
1151 i
= dists
.index(mdist
) #index for min dist
1152 return closestPts
[i
] #this is the closest point on closest curve
1154 def UpdatePointLabel(self
, mDataDict
):
1155 """Updates the pointLabel point on screen with data contained in
1158 mDataDict will be passed to your function set by
1159 SetPointLabelFunc. It can contain anything you
1160 want to display on the screen at the scaledXY point
1163 This function can be called from parent window with onClick,
1164 onMotion events etc.
1166 if self
.last_PointLabel
!= None:
1168 if _Numeric
.sometrue(mDataDict
["pointXY"] != self
.last_PointLabel
["pointXY"]):
1170 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1171 self
._drawPointLabel
(mDataDict
) #plot new
1173 #just plot new with no erase
1174 self
._drawPointLabel
(mDataDict
) #plot new
1175 #save for next erase
1176 self
.last_PointLabel
= mDataDict
1178 # event handlers **********************************
1179 def OnMotion(self
, event
):
1180 if self
._zoomEnabled
and event
.LeftIsDown():
1181 if self
._hasDragged
:
1182 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
1184 self
._hasDragged
= True
1185 self
._zoomCorner
2[0], self
._zoomCorner
2[1] = self
._getXY
(event
)
1186 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # add new
1187 elif self
._dragEnabled
and event
.LeftIsDown():
1188 coordinates
= event
.GetPosition()
1189 newpos
, oldpos
= map(_Numeric
.array
, map(self
.PositionScreenToUser
, [coordinates
, self
._screenCoordinates
]))
1190 dist
= newpos
-oldpos
1191 self
._screenCoordinates
= coordinates
1193 if self
.last_draw
is not None:
1194 graphics
, xAxis
, yAxis
= self
.last_draw
1197 self
._Draw
(graphics
,xAxis
,yAxis
)
1199 def OnMouseLeftDown(self
,event
):
1200 self
._zoomCorner
1[0], self
._zoomCorner
1[1]= self
._getXY
(event
)
1201 self
._screenCoordinates
= _Numeric
.array(event
.GetPosition())
1202 if self
._dragEnabled
:
1203 self
.SetCursor(self
.GrabHandCursor
)
1204 self
.canvas
.CaptureMouse()
1206 def OnMouseLeftUp(self
, event
):
1207 if self
._zoomEnabled
:
1208 if self
._hasDragged
== True:
1209 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
1210 self
._zoomCorner
2[0], self
._zoomCorner
2[1]= self
._getXY
(event
)
1211 self
._hasDragged
= False # reset flag
1212 minX
, minY
= _Numeric
.minimum( self
._zoomCorner
1, self
._zoomCorner
2)
1213 maxX
, maxY
= _Numeric
.maximum( self
._zoomCorner
1, self
._zoomCorner
2)
1214 self
.last_PointLabel
= None #reset pointLabel
1215 if self
.last_draw
!= None:
1216 self
._Draw
(self
.last_draw
[0], xAxis
= (minX
,maxX
), yAxis
= (minY
,maxY
), dc
= None)
1217 #else: # A box has not been drawn, zoom in on a point
1218 ## this interfered with the double click, so I've disables it.
1219 # X,Y = self._getXY(event)
1220 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
1221 if self
._dragEnabled
:
1222 self
.SetCursor(self
.HandCursor
)
1223 if self
.canvas
.HasCapture():
1224 self
.canvas
.ReleaseMouse()
1226 def OnMouseDoubleClick(self
,event
):
1227 if self
._zoomEnabled
:
1228 # Give a little time for the click to be totally finished
1229 # before (possibly) removing the scrollbars and trigering
1231 wx
.FutureCall(200,self
.Reset
)
1233 def OnMouseRightDown(self
,event
):
1234 if self
._zoomEnabled
:
1235 X
,Y
= self
._getXY
(event
)
1236 self
.Zoom( (X
,Y
), (self
._zoomOutFactor
, self
._zoomOutFactor
) )
1238 def OnPaint(self
, event
):
1239 # All that is needed here is to draw the buffer to screen
1240 if self
.last_PointLabel
!= None:
1241 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1242 self
.last_PointLabel
= None
1243 dc
= wx
.BufferedPaintDC(self
.canvas
, self
._Buffer
)
1245 def OnSize(self
,event
):
1246 # The Buffer init is done here, to make sure the buffer is always
1247 # the same size as the Window
1248 Size
= self
.canvas
.GetClientSize()
1249 Size
.width
= max(1, Size
.width
)
1250 Size
.height
= max(1, Size
.height
)
1252 # Make new offscreen bitmap: this bitmap will always have the
1253 # current drawing in it, so it can be used to save the image to
1254 # a file, or whatever.
1255 self
._Buffer
= wx
.EmptyBitmap(Size
.width
, Size
.height
)
1258 self
.last_PointLabel
= None #reset pointLabel
1260 if self
.last_draw
is None:
1263 graphics
, xSpec
, ySpec
= self
.last_draw
1264 self
._Draw
(graphics
,xSpec
,ySpec
)
1266 def OnLeave(self
, event
):
1267 """Used to erase pointLabel when mouse outside window"""
1268 if self
.last_PointLabel
!= None:
1269 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1270 self
.last_PointLabel
= None
1272 def OnScroll(self
, evt
):
1273 if not self
._adjustingSB
:
1274 self
._sb
_ignore
= True
1275 sbpos
= evt
.GetPosition()
1277 if evt
.GetOrientation() == wx
.VERTICAL
:
1278 fullrange
,pagesize
= self
.sb_vert
.GetRange(),self
.sb_vert
.GetPageSize()
1279 sbpos
= fullrange
-pagesize
-sbpos
1280 dist
= sbpos
*self
._sb
_yunit
-(self
._getYCurrentRange
()[0]-self
._sb
_yfullrange
[0])
1283 if evt
.GetOrientation() == wx
.HORIZONTAL
:
1284 dist
= sbpos
*self
._sb
_xunit
-(self
._getXCurrentRange
()[0]-self
._sb
_xfullrange
[0])
1285 self
.ScrollRight(dist
)
1287 # Private Methods **************************************************
1288 def _setSize(self
, width
=None, height
=None):
1289 """DC width and height."""
1291 (self
.width
,self
.height
) = self
.canvas
.GetClientSize()
1293 self
.width
, self
.height
= width
,height
1294 self
.plotbox_size
= 0.97*_Numeric
.array([self
.width
, self
.height
])
1295 xo
= 0.5*(self
.width
-self
.plotbox_size
[0])
1296 yo
= self
.height
-0.5*(self
.height
-self
.plotbox_size
[1])
1297 self
.plotbox_origin
= _Numeric
.array([xo
, yo
])
1299 def _setPrinterScale(self
, scale
):
1300 """Used to thicken lines and increase marker size for print out."""
1301 # line thickness on printer is very thin at 600 dot/in. Markers small
1302 self
.printerScale
= scale
1304 def _printDraw(self
, printDC
):
1305 """Used for printing."""
1306 if self
.last_draw
!= None:
1307 graphics
, xSpec
, ySpec
= self
.last_draw
1308 self
._Draw
(graphics
,xSpec
,ySpec
,printDC
)
1310 def _drawPointLabel(self
, mDataDict
):
1311 """Draws and erases pointLabels"""
1312 width
= self
._Buffer
.GetWidth()
1313 height
= self
._Buffer
.GetHeight()
1314 tmp_Buffer
= wx
.EmptyBitmap(width
,height
)
1316 dcs
.SelectObject(tmp_Buffer
)
1319 self
._pointLabelFunc
(dcs
,mDataDict
) #custom user pointLabel function
1322 dc
= wx
.ClientDC( self
.canvas
)
1323 #this will erase if called twice
1324 dc
.Blit(0, 0, width
, height
, dcs
, 0, 0, wx
.EQUIV
) #(NOT src) XOR dst
1327 def _drawLegend(self
,dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, legendTextExt
):
1328 """Draws legend symbols and text"""
1329 # top right hand corner of graph box is ref corner
1330 trhc
= self
.plotbox_origin
+ (self
.plotbox_size
-[rhsW
,topH
])*[1,-1]
1331 legendLHS
= .091* legendBoxWH
[0] # border space between legend sym and graph box
1332 lineHeight
= max(legendSymExt
[1], legendTextExt
[1]) * 1.1 #1.1 used as space between lines
1333 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
1334 for i
in range(len(graphics
)):
1337 if isinstance(o
,PolyMarker
):
1338 # draw marker with legend
1339 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0]/2., trhc
[1]+s
+lineHeight
/2.)
1340 o
.draw(dc
, self
.printerScale
, coord
= _Numeric
.array([pnt
]))
1341 elif isinstance(o
,PolyLine
):
1342 # draw line with legend
1343 pnt1
= (trhc
[0]+legendLHS
, trhc
[1]+s
+lineHeight
/2.)
1344 pnt2
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.)
1345 o
.draw(dc
, self
.printerScale
, coord
= _Numeric
.array([pnt1
,pnt2
]))
1347 raise TypeError, "object is neither PolyMarker or PolyLine instance"
1349 pnt
= (trhc
[0]+legendLHS
+legendSymExt
[0], trhc
[1]+s
+lineHeight
/2.-legendTextExt
[1]/2)
1350 dc
.DrawText(o
.getLegend(),pnt
[0],pnt
[1])
1351 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
)) # reset
1353 def _titleLablesWH(self
, dc
, graphics
):
1354 """Draws Title and labels and returns width and height for each"""
1355 # TextExtents for Title and Axis Labels
1356 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
1357 title
= graphics
.getTitle()
1358 titleWH
= dc
.GetTextExtent(title
)
1359 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1360 xLabel
, yLabel
= graphics
.getXLabel(),graphics
.getYLabel()
1361 xLabelWH
= dc
.GetTextExtent(xLabel
)
1362 yLabelWH
= dc
.GetTextExtent(yLabel
)
1363 return titleWH
, xLabelWH
, yLabelWH
1365 def _legendWH(self
, dc
, graphics
):
1366 """Returns the size in screen units for legend box"""
1367 if self
._legendEnabled
!= True:
1368 legendBoxWH
= symExt
= txtExt
= (0,0)
1370 # find max symbol size
1371 symExt
= graphics
.getSymExtent(self
.printerScale
)
1372 # find max legend text extent
1373 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
1374 txtList
= graphics
.getLegendNames()
1375 txtExt
= dc
.GetTextExtent(txtList
[0])
1376 for txt
in graphics
.getLegendNames()[1:]:
1377 txtExt
= _Numeric
.maximum(txtExt
,dc
.GetTextExtent(txt
))
1378 maxW
= symExt
[0]+txtExt
[0]
1379 maxH
= max(symExt
[1],txtExt
[1])
1380 # padding .1 for lhs of legend box and space between lines
1382 maxH
= maxH
* 1.1 * len(txtList
)
1383 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1384 legendBoxWH
= (maxW
,maxH
)
1385 return (legendBoxWH
, symExt
, txtExt
)
1387 def _drawRubberBand(self
, corner1
, corner2
):
1388 """Draws/erases rect box from corner1 to corner2"""
1389 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(corner1
, corner2
)
1391 dc
= wx
.ClientDC( self
.canvas
)
1393 dc
.SetPen(wx
.Pen(wx
.BLACK
))
1394 dc
.SetBrush(wx
.Brush( wx
.WHITE
, wx
.TRANSPARENT
) )
1395 dc
.SetLogicalFunction(wx
.INVERT
)
1396 dc
.DrawRectangle( ptx
,pty
, rectWidth
,rectHeight
)
1397 dc
.SetLogicalFunction(wx
.COPY
)
1400 def _getFont(self
,size
):
1401 """Take font size, adjusts if printing and returns wx.Font"""
1402 s
= size
*self
.printerScale
1404 # Linux speed up to get font from cache rather than X font server
1405 key
= (int(s
), of
.GetFamily (), of
.GetStyle (), of
.GetWeight ())
1406 font
= self
._fontCache
.get (key
, None)
1408 return font
# yeah! cache hit
1410 font
= wx
.Font(int(s
), of
.GetFamily(), of
.GetStyle(), of
.GetWeight())
1411 self
._fontCache
[key
] = font
1415 def _point2ClientCoord(self
, corner1
, corner2
):
1416 """Converts user point coords to client screen int coords x,y,width,height"""
1417 c1
= _Numeric
.array(corner1
)
1418 c2
= _Numeric
.array(corner2
)
1419 # convert to screen coords
1420 pt1
= c1
*self
._pointScale
+self
._pointShift
1421 pt2
= c2
*self
._pointScale
+self
._pointShift
1422 # make height and width positive
1423 pul
= _Numeric
.minimum(pt1
,pt2
) # Upper left corner
1424 plr
= _Numeric
.maximum(pt1
,pt2
) # Lower right corner
1425 rectWidth
, rectHeight
= plr
-pul
1427 return ptx
, pty
, rectWidth
, rectHeight
1429 def _axisInterval(self
, spec
, lower
, upper
):
1430 """Returns sensible axis range for given spec"""
1431 if spec
== 'none' or spec
== 'min':
1433 return lower
-0.5, upper
+0.5
1436 elif spec
== 'auto':
1439 return lower
-0.5, upper
+0.5
1440 log
= _Numeric
.log10(range)
1441 power
= _Numeric
.floor(log
)
1442 fraction
= log
-power
1443 if fraction
<= 0.05:
1446 lower
= lower
- lower
% grid
1449 upper
= upper
- mod
+ grid
1451 elif type(spec
) == type(()):
1458 raise ValueError, str(spec
) + ': illegal axis specification'
1460 def _drawAxes(self
, dc
, p1
, p2
, scale
, shift
, xticks
, yticks
):
1462 penWidth
= self
.printerScale
# increases thickness for printing only
1463 dc
.SetPen(wx
.Pen(self
._gridColour
, penWidth
))
1465 # set length of tick marks--long ones make grid
1466 if self
._gridEnabled
:
1467 x
,y
,width
,height
= self
._point
2ClientCoord
(p1
,p2
)
1468 if self
._gridEnabled
== 'Horizontal':
1469 yTickLength
= width
/2.0 +1
1470 xTickLength
= 3 * self
.printerScale
1471 elif self
._gridEnabled
== 'Vertical':
1472 yTickLength
= 3 * self
.printerScale
1473 xTickLength
= height
/2.0 +1
1475 yTickLength
= width
/2.0 +1
1476 xTickLength
= height
/2.0 +1
1478 yTickLength
= 3 * self
.printerScale
# lengthens lines for printing
1479 xTickLength
= 3 * self
.printerScale
1481 if self
._xSpec
is not 'none':
1482 lower
, upper
= p1
[0],p2
[0]
1484 for y
, d
in [(p1
[1], -xTickLength
), (p2
[1], xTickLength
)]: # miny, maxy and tick lengths
1485 a1
= scale
*_Numeric
.array([lower
, y
])+shift
1486 a2
= scale
*_Numeric
.array([upper
, y
])+shift
1487 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1]) # draws upper and lower axis line
1488 for x
, label
in xticks
:
1489 pt
= scale
*_Numeric
.array([x
, y
])+shift
1490 dc
.DrawLine(pt
[0],pt
[1],pt
[0],pt
[1] + d
) # draws tick mark d units
1492 dc
.DrawText(label
,pt
[0],pt
[1])
1493 text
= 0 # axis values not drawn on top side
1495 if self
._ySpec
is not 'none':
1496 lower
, upper
= p1
[1],p2
[1]
1498 h
= dc
.GetCharHeight()
1499 for x
, d
in [(p1
[0], -yTickLength
), (p2
[0], yTickLength
)]:
1500 a1
= scale
*_Numeric
.array([x
, lower
])+shift
1501 a2
= scale
*_Numeric
.array([x
, upper
])+shift
1502 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1])
1503 for y
, label
in yticks
:
1504 pt
= scale
*_Numeric
.array([x
, y
])+shift
1505 dc
.DrawLine(pt
[0],pt
[1],pt
[0]-d
,pt
[1])
1507 dc
.DrawText(label
,pt
[0]-dc
.GetTextExtent(label
)[0],
1509 text
= 0 # axis values not drawn on right side
1511 def _xticks(self
, *args
):
1512 if self
._logscale
[0]:
1513 return self
._logticks
(*args
)
1515 return self
._ticks
(*args
)
1517 def _yticks(self
, *args
):
1518 if self
._logscale
[1]:
1519 return self
._logticks
(*args
)
1521 return self
._ticks
(*args
)
1523 def _logticks(self
, lower
, upper
):
1524 #lower,upper = map(_Numeric.log10,[lower,upper])
1525 #print 'logticks',lower,upper
1527 mag
= _Numeric
.power(10,_Numeric
.floor(lower
))
1529 t
= _Numeric
.power(10,_Numeric
.ceil(lower
))
1530 base
= _Numeric
.power(10,_Numeric
.floor((upper
-lower
)/6))
1534 t
= _Numeric
.ceil(_Numeric
.power(10,lower
)/mag
)*mag
1536 return 10**int(_Numeric
.floor(_Numeric
.log10(t
)+1e-16))
1537 majortick
= int(_Numeric
.log10(mag
))
1538 while t
<= pow(10,upper
):
1539 if majortick
!= int(_Numeric
.floor(_Numeric
.log10(t
)+1e-16)):
1540 majortick
= int(_Numeric
.floor(_Numeric
.log10(t
)+1e-16))
1541 ticklabel
= '1e%d'%majortick
1544 minortick
= int(t
/pow(10,majortick
)+.5)
1545 ticklabel
= '%de%d'%(minortick
,majortick
)
1548 ticks
.append((_Numeric
.log10(t
), ticklabel
))
1554 def _ticks(self
, lower
, upper
):
1555 ideal
= (upper
-lower
)/7.
1556 log
= _Numeric
.log10(ideal
)
1557 power
= _Numeric
.floor(log
)
1558 fraction
= log
-power
1561 for f
, lf
in self
._multiples
:
1562 e
= _Numeric
.fabs(fraction
-lf
)
1566 grid
= factor
* 10.**power
1567 if power
> 4 or power
< -4:
1570 digits
= max(1, int(power
))
1571 format
= '%' + `digits`
+'.0f'
1573 digits
= -int(power
)
1574 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
1576 t
= -grid
*_Numeric
.floor(-lower
/grid
)
1578 ticks
.append( (t
, format
% (t
,)) )
1582 _multiples
= [(2., _Numeric
.log10(2.)), (5., _Numeric
.log10(5.))]
1585 def _adjustScrollbars(self
):
1587 self
._sb
_ignore
= False
1590 self
._adjustingSB
= True
1591 needScrollbars
= False
1593 # horizontal scrollbar
1594 r_current
= self
._getXCurrentRange
()
1595 r_max
= list(self
._getXMaxRange
())
1596 sbfullrange
= float(self
.sb_hor
.GetRange())
1598 r_max
[0] = min(r_max
[0],r_current
[0])
1599 r_max
[1] = max(r_max
[1],r_current
[1])
1601 self
._sb
_xfullrange
= r_max
1603 unit
= (r_max
[1]-r_max
[0])/float(self
.sb_hor
.GetRange())
1604 pos
= int((r_current
[0]-r_max
[0])/unit
)
1607 pagesize
= int((r_current
[1]-r_current
[0])/unit
)
1609 self
.sb_hor
.SetScrollbar(pos
, pagesize
, sbfullrange
, pagesize
)
1610 self
._sb
_xunit
= unit
1611 needScrollbars
= needScrollbars
or (pagesize
!= sbfullrange
)
1613 self
.sb_hor
.SetScrollbar(0, 1000, 1000, 1000)
1615 # vertical scrollbar
1616 r_current
= self
._getYCurrentRange
()
1617 r_max
= list(self
._getYMaxRange
())
1618 sbfullrange
= float(self
.sb_vert
.GetRange())
1620 r_max
[0] = min(r_max
[0],r_current
[0])
1621 r_max
[1] = max(r_max
[1],r_current
[1])
1623 self
._sb
_yfullrange
= r_max
1625 unit
= (r_max
[1]-r_max
[0])/sbfullrange
1626 pos
= int((r_current
[0]-r_max
[0])/unit
)
1629 pagesize
= int((r_current
[1]-r_current
[0])/unit
)
1630 pos
= (sbfullrange
-1-pos
-pagesize
)
1631 self
.sb_vert
.SetScrollbar(pos
, pagesize
, sbfullrange
, pagesize
)
1632 self
._sb
_yunit
= unit
1633 needScrollbars
= needScrollbars
or (pagesize
!= sbfullrange
)
1635 self
.sb_vert
.SetScrollbar(0, 1000, 1000, 1000)
1637 self
.SetShowScrollbars(needScrollbars
)
1638 self
._adjustingSB
= False
1640 #-------------------------------------------------------------------------------
1641 # Used to layout the printer page
1643 class PlotPrintout(wx
.Printout
):
1644 """Controls how the plot is made in printing and previewing"""
1645 # Do not change method names in this class,
1646 # we have to override wx.Printout methods here!
1647 def __init__(self
, graph
):
1648 """graph is instance of plotCanvas to be printed or previewed"""
1649 wx
.Printout
.__init
__(self
)
1652 def HasPage(self
, page
):
1658 def GetPageInfo(self
):
1659 return (1, 1, 1, 1) # disable page numbers
1661 def OnPrintPage(self
, page
):
1662 dc
= self
.GetDC() # allows using floats for certain functions
1663 ## print "PPI Printer",self.GetPPIPrinter()
1664 ## print "PPI Screen", self.GetPPIScreen()
1665 ## print "DC GetSize", dc.GetSize()
1666 ## print "GetPageSizePixels", self.GetPageSizePixels()
1667 # Note PPIScreen does not give the correct number
1668 # Calulate everything for printer and then scale for preview
1669 PPIPrinter
= self
.GetPPIPrinter() # printer dots/inch (w,h)
1670 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1671 dcSize
= dc
.GetSize() # DC size
1672 pageSize
= self
.GetPageSizePixels() # page size in terms of pixcels
1673 clientDcSize
= self
.graph
.GetClientSize()
1675 # find what the margins are (mm)
1676 margLeftSize
,margTopSize
= self
.graph
.pageSetupData
.GetMarginTopLeft()
1677 margRightSize
, margBottomSize
= self
.graph
.pageSetupData
.GetMarginBottomRight()
1679 # calculate offset and scale for dc
1680 pixLeft
= margLeftSize
*PPIPrinter
[0]/25.4 # mm*(dots/in)/(mm/in)
1681 pixRight
= margRightSize
*PPIPrinter
[0]/25.4
1682 pixTop
= margTopSize
*PPIPrinter
[1]/25.4
1683 pixBottom
= margBottomSize
*PPIPrinter
[1]/25.4
1685 plotAreaW
= pageSize
[0]-(pixLeft
+pixRight
)
1686 plotAreaH
= pageSize
[1]-(pixTop
+pixBottom
)
1688 # ratio offset and scale to screen size if preview
1689 if self
.IsPreview():
1690 ratioW
= float(dcSize
[0])/pageSize
[0]
1691 ratioH
= float(dcSize
[1])/pageSize
[1]
1697 # rescale plot to page or preview plot area
1698 self
.graph
._setSize
(plotAreaW
,plotAreaH
)
1700 # Set offset and scale
1701 dc
.SetDeviceOrigin(pixLeft
,pixTop
)
1703 # Thicken up pens and increase marker size for printing
1704 ratioW
= float(plotAreaW
)/clientDcSize
[0]
1705 ratioH
= float(plotAreaH
)/clientDcSize
[1]
1706 aveScale
= (ratioW
+ratioH
)/2
1707 self
.graph
._setPrinterScale
(aveScale
) # tickens up pens for printing
1709 self
.graph
._printDraw
(dc
)
1710 # rescale back to original
1711 self
.graph
._setSize
()
1712 self
.graph
._setPrinterScale
(1)
1713 self
.graph
.Redraw() #to get point label scale and shift correct
1718 #----------------------------------------------------------------------
1719 from wx
import ImageFromStream
, BitmapFromImage
1720 import cStringIO
, zlib
1723 def getMagPlusData():
1724 return zlib
.decompress(
1725 'x\xda\x01*\x01\xd5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1726 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1727 \x08\x08|\x08d\x88\x00\x00\x00\xe1IDATx\x9c\xb5U\xd1\x0e\xc4 \x08\xa3n\xff\
1728 \xff\xc5\xdb\xb8\xa7\xee<\x04\x86gFb\xb2\x88\xb6\x14\x90\x01m\x937m\x8f\x1c\
1729 \xd7yh\xe4k\xdb\x8e*\x01<\x05\x04\x07F\x1cU\x9d"\x19\x14\\\xe7\xa1\x1e\xf07"\
1730 \x90H+$?\x04\x16\x9c\xd1z\x04\x00J$m\x06\xdc\xee\x03Hku\x13\xd8C\x16\x84+"O\
1731 \x1b\xa2\x07\xca"\xb7\xc6sY\xbdD\x926\xf5.\xce\x06!\xd2)x\xcb^\'\x08S\xe4\
1732 \xe5x&5\xb4[A\xb5h\xb4j=\x9a\xc8\xf8\xecm\xd4\\\x9e\xdf\xbb?\x10\xf0P\x06\
1733 \x12\xed?=\xb6a\xd8=\xcd\xa2\xc8T\xd5U2t\x11\x95d\xa3"\x9aQ\x9e\x12\xb7M\x19\
1734 I\x9f\xff\x1e\xd8\xa63#q\xff\x07U\x8b\xd2\xd9\xa7k\xe9\xa1U\x94,\xbf\xe4\x88\
1735 \xe4\xf6\xaf\x12x$}\x8a\xc2Q\xf1\'\x89\xf2\x9b\xfbKE\xae\xd8\x07+\xd2\xa7c\
1736 \xdf\x0e\xc3D\x00\x00\x00\x00IEND\xaeB`\x82\xe2ovy' )
1738 def getMagPlusBitmap():
1739 return BitmapFromImage(getMagPlusImage())
1741 def getMagPlusImage():
1742 stream
= cStringIO
.StringIO(getMagPlusData())
1743 return ImageFromStream(stream
)
1745 #----------------------------------------------------------------------
1746 def getGrabHandData():
1747 return zlib
.decompress(
1748 'x\xda\x01Z\x01\xa5\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1749 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1750 \x08\x08|\x08d\x88\x00\x00\x01\x11IDATx\x9c\xb5U\xd1\x12\x830\x08Kh\xff\xff\
1751 \x8b7\xb3\x97\xd1C\xa4Zw\x93;\x1fJ1\t\x98VJ\x92\xb5N<\x14\x04 I\x00\x80H\xb4\
1752 \xbd_\x8a9_{\\\x89\xf2z\x02\x18/J\x82\xb5\xce\xed\xfd\x12\xc9\x91\x03\x00_\
1753 \xc7\xda\x8al\x00{\xfdW\xfex\xf2zeO\x92h\xed\x80\x05@\xa45D\xc5\xb3\x98u\x12\
1754 \xf7\xab.\xa9\xd0k\x1eK\x95\xbb\x1a]&0\x92\xf0\'\xc6]gI\xda\tsr\xab\x8aI\x1e\
1755 \\\xe3\xa4\x0e\xb4*`7"\x07\x8f\xaa"x\x05\xe0\xdfo6B\xf3\x17\xe3\x98r\xf1\xaf\
1756 \x07\xd1Z\'%\x95\x0erW\xac\x8c\xe3\xe0\xfd\xd8AN\xae\xb8\xa3R\x9as>\x11\x8bl\
1757 yD\xab\x1f\xf3\xec\x1cY\x06\x89$\xbf\x80\xfb\x14\\dw\x90x\x12\xa3+\xeeD\x16%\
1758 I\xe3\x1c\xb8\xc7c\'\xd5Y8S\x9f\xc3Zg\xcf\x89\xe8\xaao\'\xbbk{U\xfd\xc0\xacX\
1759 \xab\xbb\xe8\xae\xfa)AEr\x15g\x86(\t\xfe\x19\xa4\xb5\xe9f\xfem\xde\xdd\xbf$\
1760 \xf8G<>\xa2\xc7\t>\tE\xfc\x8a\xf6\x8dqc\x00\x00\x00\x00IEND\xaeB`\x82\xdb\
1763 def getGrabHandBitmap():
1764 return BitmapFromImage(getGrabHandImage())
1766 def getGrabHandImage():
1767 stream
= cStringIO
.StringIO(getGrabHandData())
1768 return ImageFromStream(stream
)
1770 #----------------------------------------------------------------------
1772 return zlib
.decompress(
1773 'x\xda\x01Y\x01\xa6\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\
1774 \x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0w=\xf8\x00\x00\x00\x04sBIT\x08\x08\
1775 \x08\x08|\x08d\x88\x00\x00\x01\x10IDATx\x9c\xad\x96\xe1\x02\xc2 \x08\x849\
1776 \xf5\xfd\x9fx\xdb\xf5\'\x8c!\xa8\xab\xee\x975\xe5\x83\x0b\\@\xa9\xb2\xab\xeb\
1777 <\xa8\xebR\x1bv\xce\xb4\'\xc1\x81OL\x92\xdc\x81\x0c\x00\x1b\x88\xa4\x94\xda\
1778 \xe0\x83\x8b\x88\x00\x10\x92\xcb\x8a\xca,K\x1fT\xa1\x1e\x04\xe0f_\n\x88\x02\
1779 \xf1:\xc3\x83>\x81\x0c\x92\x02v\xe5+\xba\xce\x83\xb7f\xb8\xd1\x9c\x8fz8\xb2*\
1780 \x93\xb7l\xa8\xe0\x9b\xa06\xb8]_\xe7\xc1\x01\x10U\xe1m\x98\xc9\xefm"ck\xea\
1781 \x1a\x80\xa0Th\xb9\xfd\x877{V*Qk\xda,\xb4\x8b\xf4;[\xa1\xcf6\xaa4\x9cd\x85X\
1782 \xb0\r\\j\x83\x9dd\x92\xc3 \xf6\xbd\xab\x0c2\x05\xc0p\x9a\xa7]\xf4\x14\x18]3\
1783 7\x80}h?\xff\xa2\xa2\xe5e\x90\xact\xaf\xe8B\x14y[4\x83|\x13\xdc\x9e\xeb\x16e\
1784 \x90\xa7\xf2I\rw\x91\x87d\xd7p\x96\xbd\xd70\x07\xda\xe3v\x9a\xf5\xc5\xb2\xb2\
1785 +\xb24\xbc\xaew\xedZe\x9f\x02"\xc8J\xdb\x83\xf6oa\xf5\xb7\xa5\xbf8\x12\xffW\
1786 \xcf_\xbd;\xe4\x8c\x03\x10\xdb^\x00\x00\x00\x00IEND\xaeB`\x82\xd1>\x97B' )
1788 def getHandBitmap():
1789 return BitmapFromImage(getHandImage())
1792 stream
= cStringIO
.StringIO(getHandData())
1793 return ImageFromStream(stream
)
1797 #---------------------------------------------------------------------------
1798 # if running standalone...
1800 # ...a sample implementation using the above
1803 def _draw1Objects():
1804 # 100 points sin function, plotted as green circles
1805 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(200)/200.
1806 data1
.shape
= (100, 2)
1807 data1
[:,1] = _Numeric
.sin(data1
[:,0])
1808 markers1
= PolyMarker(data1
, legend
='Green Markers', colour
='green', marker
='circle',size
=1)
1810 # 50 points cos function, plotted as red line
1811 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(100)/100.
1812 data1
.shape
= (50,2)
1813 data1
[:,1] = _Numeric
.cos(data1
[:,0])
1814 lines
= PolyLine(data1
, legend
= 'Red Line', colour
='red')
1816 # A few more points...
1818 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1819 (3.*pi
/4., -1)], legend
='Cross Legend', colour
='blue',
1822 return PlotGraphics([markers1
, lines
, markers2
],"Graph Title", "X Axis", "Y Axis")
1824 def _draw2Objects():
1825 # 100 points sin function, plotted as green dots
1826 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(200)/200.
1827 data1
.shape
= (100, 2)
1828 data1
[:,1] = _Numeric
.sin(data1
[:,0])
1829 line1
= PolyLine(data1
, legend
='Green Line', colour
='green', width
=6, style
=wx
.DOT
)
1831 # 50 points cos function, plotted as red dot-dash
1832 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(100)/100.
1833 data1
.shape
= (50,2)
1834 data1
[:,1] = _Numeric
.cos(data1
[:,0])
1835 line2
= PolyLine(data1
, legend
='Red Line', colour
='red', width
=3, style
= wx
.DOT_DASH
)
1837 # A few more points...
1839 markers1
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1840 (3.*pi
/4., -1)], legend
='Cross Hatch Square', colour
='blue', width
= 3, size
= 6,
1841 fillcolour
= 'red', fillstyle
= wx
.CROSSDIAG_HATCH
,
1844 return PlotGraphics([markers1
, line1
, line2
], "Big Markers with Different Line Styles")
1846 def _draw3Objects():
1847 markerList
= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1848 'cross', 'plus', 'circle']
1850 for i
in range(len(markerList
)):
1851 m
.append(PolyMarker([(2*i
+.5,i
+.5)], legend
=markerList
[i
], colour
='blue',
1852 marker
=markerList
[i
]))
1853 return PlotGraphics(m
, "Selection of Markers", "Minimal Axis", "No Axis")
1855 def _draw4Objects():
1857 data1
= _Numeric
.arange(5e5
,1e6
,10)
1858 data1
.shape
= (25000, 2)
1859 line1
= PolyLine(data1
, legend
='Wide Line', colour
='green', width
=5)
1861 # A few more points...
1862 markers2
= PolyMarker(data1
, legend
='Square', colour
='blue',
1864 return PlotGraphics([line1
, markers2
], "25,000 Points", "Value X", "")
1866 def _draw5Objects():
1867 # Empty graph with axis defined but no points/lines
1869 line1
= PolyLine(points
, legend
='Wide Line', colour
='green', width
=5)
1870 return PlotGraphics([line1
], "Empty Plot With Just Axes", "Value X", "Value Y")
1872 def _draw6Objects():
1874 points1
=[(1,0), (1,10)]
1875 line1
= PolyLine(points1
, colour
='green', legend
='Feb.', width
=10)
1876 points1g
=[(2,0), (2,4)]
1877 line1g
= PolyLine(points1g
, colour
='red', legend
='Mar.', width
=10)
1878 points1b
=[(3,0), (3,6)]
1879 line1b
= PolyLine(points1b
, colour
='blue', legend
='Apr.', width
=10)
1881 points2
=[(4,0), (4,12)]
1882 line2
= PolyLine(points2
, colour
='Yellow', legend
='May', width
=10)
1883 points2g
=[(5,0), (5,8)]
1884 line2g
= PolyLine(points2g
, colour
='orange', legend
='June', width
=10)
1885 points2b
=[(6,0), (6,4)]
1886 line2b
= PolyLine(points2b
, colour
='brown', legend
='July', width
=10)
1888 return PlotGraphics([line1
, line1g
, line1b
, line2
, line2g
, line2b
],
1889 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1890 def _draw7Objects():
1891 # Empty graph with axis defined but no points/lines
1892 x
= _Numeric
.arange(1,1000,1)
1895 points1
= _Numeric
.transpose([x
,y1
])
1896 points2
= _Numeric
.transpose([x
,y2
])
1897 line1
= PolyLine(points1
, legend
='quadratic', colour
='blue', width
=1)
1898 line2
= PolyLine(points2
, legend
='cubic', colour
='red', width
=1)
1899 return PlotGraphics([line1
,line2
], "double log plot", "Value X", "Value Y")
1902 class TestFrame(wx
.Frame
):
1903 def __init__(self
, parent
, id, title
):
1904 wx
.Frame
.__init
__(self
, parent
, id, title
,
1905 wx
.DefaultPosition
, (600, 400))
1907 # Now Create the menu bar and items
1908 self
.mainmenu
= wx
.MenuBar()
1911 menu
.Append(200, 'Page Setup...', 'Setup the printer page')
1912 self
.Bind(wx
.EVT_MENU
, self
.OnFilePageSetup
, id=200)
1914 menu
.Append(201, 'Print Preview...', 'Show the current plot on page')
1915 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrintPreview
, id=201)
1917 menu
.Append(202, 'Print...', 'Print the current plot')
1918 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrint
, id=202)
1920 menu
.Append(203, 'Save Plot...', 'Save current plot')
1921 self
.Bind(wx
.EVT_MENU
, self
.OnSaveFile
, id=203)
1923 menu
.Append(205, 'E&xit', 'Enough of this already!')
1924 self
.Bind(wx
.EVT_MENU
, self
.OnFileExit
, id=205)
1925 self
.mainmenu
.Append(menu
, '&File')
1928 menu
.Append(206, 'Draw1', 'Draw plots1')
1929 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw1
, id=206)
1930 menu
.Append(207, 'Draw2', 'Draw plots2')
1931 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw2
, id=207)
1932 menu
.Append(208, 'Draw3', 'Draw plots3')
1933 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw3
, id=208)
1934 menu
.Append(209, 'Draw4', 'Draw plots4')
1935 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw4
, id=209)
1936 menu
.Append(210, 'Draw5', 'Draw plots5')
1937 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw5
, id=210)
1938 menu
.Append(260, 'Draw6', 'Draw plots6')
1939 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw6
, id=260)
1940 menu
.Append(261, 'Draw7', 'Draw plots7')
1941 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw7
, id=261)
1943 menu
.Append(211, '&Redraw', 'Redraw plots')
1944 self
.Bind(wx
.EVT_MENU
,self
.OnPlotRedraw
, id=211)
1945 menu
.Append(212, '&Clear', 'Clear canvas')
1946 self
.Bind(wx
.EVT_MENU
,self
.OnPlotClear
, id=212)
1947 menu
.Append(213, '&Scale', 'Scale canvas')
1948 self
.Bind(wx
.EVT_MENU
,self
.OnPlotScale
, id=213)
1949 menu
.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind
=wx
.ITEM_CHECK
)
1950 self
.Bind(wx
.EVT_MENU
,self
.OnEnableZoom
, id=214)
1951 menu
.Append(215, 'Enable &Grid', 'Turn on Grid', kind
=wx
.ITEM_CHECK
)
1952 self
.Bind(wx
.EVT_MENU
,self
.OnEnableGrid
, id=215)
1953 menu
.Append(217, 'Enable &Drag', 'Activates dragging mode', kind
=wx
.ITEM_CHECK
)
1954 self
.Bind(wx
.EVT_MENU
,self
.OnEnableDrag
, id=217)
1955 menu
.Append(220, 'Enable &Legend', 'Turn on Legend', kind
=wx
.ITEM_CHECK
)
1956 self
.Bind(wx
.EVT_MENU
,self
.OnEnableLegend
, id=220)
1957 menu
.Append(222, 'Enable &Point Label', 'Show Closest Point', kind
=wx
.ITEM_CHECK
)
1958 self
.Bind(wx
.EVT_MENU
,self
.OnEnablePointLabel
, id=222)
1960 menu
.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1961 self
.Bind(wx
.EVT_MENU
,self
.OnScrUp
, id=225)
1962 menu
.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1963 self
.Bind(wx
.EVT_MENU
,self
.OnScrRt
, id=230)
1964 menu
.Append(235, '&Plot Reset', 'Reset to original plot')
1965 self
.Bind(wx
.EVT_MENU
,self
.OnReset
, id=235)
1967 self
.mainmenu
.Append(menu
, '&Plot')
1970 menu
.Append(300, '&About', 'About this thing...')
1971 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=300)
1972 self
.mainmenu
.Append(menu
, '&Help')
1974 self
.SetMenuBar(self
.mainmenu
)
1976 # A status bar to tell people what's happening
1977 self
.CreateStatusBar(1)
1979 self
.client
= PlotCanvas(self
)
1980 #define the function for drawing pointLabels
1981 self
.client
.SetPointLabelFunc(self
.DrawPointLabel
)
1982 # Create mouse event for showing cursor coords in status bar
1983 self
.client
.canvas
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
1984 # Show closest point when enabled
1985 self
.client
.canvas
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
1989 def DrawPointLabel(self
, dc
, mDataDict
):
1990 """This is the fuction that defines how the pointLabels are plotted
1991 dc - DC that will be passed
1992 mDataDict - Dictionary of data that you want to use for the pointLabel
1994 As an example I have decided I want a box at the curve point
1995 with some text information about the curve plotted below.
1996 Any wxDC method can be used.
1999 dc
.SetPen(wx
.Pen(wx
.BLACK
))
2000 dc
.SetBrush(wx
.Brush( wx
.BLACK
, wx
.SOLID
) )
2002 sx
, sy
= mDataDict
["scaledXY"] #scaled x,y of closest point
2003 dc
.DrawRectangle( sx
-5,sy
-5, 10, 10) #10by10 square centered on point
2004 px
,py
= mDataDict
["pointXY"]
2005 cNum
= mDataDict
["curveNum"]
2006 pntIn
= mDataDict
["pIndex"]
2007 legend
= mDataDict
["legend"]
2008 #make a string to display
2009 s
= "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum
, legend
, px
, py
, pntIn
)
2010 dc
.DrawText(s
, sx
, sy
+1)
2013 def OnMouseLeftDown(self
,event
):
2014 s
= "Left Mouse Down at Point: (%.4f, %.4f)" % self
.client
._getXY
(event
)
2015 self
.SetStatusText(s
)
2016 event
.Skip() #allows plotCanvas OnMouseLeftDown to be called
2018 def OnMotion(self
, event
):
2019 #show closest point (when enbled)
2020 if self
.client
.GetEnablePointLabel() == True:
2021 #make up dict with info for the pointLabel
2022 #I've decided to mark the closest point on the closest curve
2023 dlst
= self
.client
.GetClosetPoint( self
.client
._getXY
(event
), pointScaled
= True)
2024 if dlst
!= []: #returns [] if none
2025 curveNum
, legend
, pIndex
, pointXY
, scaledXY
, distance
= dlst
2026 #make up dictionary to pass to my user function (see DrawPointLabel)
2027 mDataDict
= {"curveNum":curveNum
, "legend":legend
, "pIndex":pIndex
,\
2028 "pointXY":pointXY
, "scaledXY":scaledXY
}
2029 #pass dict to update the pointLabel
2030 self
.client
.UpdatePointLabel(mDataDict
)
2031 event
.Skip() #go to next handler
2033 def OnFilePageSetup(self
, event
):
2034 self
.client
.PageSetup()
2036 def OnFilePrintPreview(self
, event
):
2037 self
.client
.PrintPreview()
2039 def OnFilePrint(self
, event
):
2040 self
.client
.Printout()
2042 def OnSaveFile(self
, event
):
2043 self
.client
.SaveFile()
2045 def OnFileExit(self
, event
):
2048 def OnPlotDraw1(self
, event
):
2049 self
.resetDefaults()
2050 self
.client
.Draw(_draw1Objects())
2052 def OnPlotDraw2(self
, event
):
2053 self
.resetDefaults()
2054 self
.client
.Draw(_draw2Objects())
2056 def OnPlotDraw3(self
, event
):
2057 self
.resetDefaults()
2058 self
.client
.SetFont(wx
.Font(10,wx
.SCRIPT
,wx
.NORMAL
,wx
.NORMAL
))
2059 self
.client
.SetFontSizeAxis(20)
2060 self
.client
.SetFontSizeLegend(12)
2061 self
.client
.SetXSpec('min')
2062 self
.client
.SetYSpec('none')
2063 self
.client
.Draw(_draw3Objects())
2065 def OnPlotDraw4(self
, event
):
2066 self
.resetDefaults()
2067 drawObj
= _draw4Objects()
2068 self
.client
.Draw(drawObj
)
2070 ## start = _time.clock()
2071 ## for x in range(10):
2072 ## self.client.Draw(drawObj)
2073 ## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start)
2076 def OnPlotDraw5(self
, event
):
2077 # Empty plot with just axes
2078 self
.resetDefaults()
2079 drawObj
= _draw5Objects()
2080 # make the axis X= (0,5), Y=(0,10)
2081 # (default with None is X= (-1,1), Y= (-1,1))
2082 self
.client
.Draw(drawObj
, xAxis
= (0,5), yAxis
= (0,10))
2084 def OnPlotDraw6(self
, event
):
2086 self
.resetDefaults()
2087 #self.client.SetEnableLegend(True) #turn on Legend
2088 #self.client.SetEnableGrid(True) #turn on Grid
2089 self
.client
.SetXSpec('none') #turns off x-axis scale
2090 self
.client
.SetYSpec('auto')
2091 self
.client
.Draw(_draw6Objects(), xAxis
= (0,7))
2093 def OnPlotDraw7(self
, event
):
2095 self
.resetDefaults()
2096 self
.client
.setLogScale((True,True))
2097 self
.client
.Draw(_draw7Objects())
2099 def OnPlotRedraw(self
,event
):
2100 self
.client
.Redraw()
2102 def OnPlotClear(self
,event
):
2105 def OnPlotScale(self
, event
):
2106 if self
.client
.last_draw
!= None:
2107 graphics
, xAxis
, yAxis
= self
.client
.last_draw
2108 self
.client
.Draw(graphics
,(1,3.05),(0,1))
2110 def OnEnableZoom(self
, event
):
2111 self
.client
.SetEnableZoom(event
.IsChecked())
2112 self
.mainmenu
.Check(217, not event
.IsChecked())
2114 def OnEnableGrid(self
, event
):
2115 self
.client
.SetEnableGrid(event
.IsChecked())
2117 def OnEnableDrag(self
, event
):
2118 self
.client
.SetEnableDrag(event
.IsChecked())
2119 self
.mainmenu
.Check(214, not event
.IsChecked())
2121 def OnEnableLegend(self
, event
):
2122 self
.client
.SetEnableLegend(event
.IsChecked())
2124 def OnEnablePointLabel(self
, event
):
2125 self
.client
.SetEnablePointLabel(event
.IsChecked())
2127 def OnScrUp(self
, event
):
2128 self
.client
.ScrollUp(1)
2130 def OnScrRt(self
,event
):
2131 self
.client
.ScrollRight(2)
2133 def OnReset(self
,event
):
2136 def OnHelpAbout(self
, event
):
2137 from wx
.lib
.dialogs
import ScrolledMessageDialog
2138 about
= ScrolledMessageDialog(self
, __doc__
, "About...")
2141 def resetDefaults(self
):
2142 """Just to reset the fonts back to the PlotCanvas defaults"""
2143 self
.client
.SetFont(wx
.Font(10,wx
.SWISS
,wx
.NORMAL
,wx
.NORMAL
))
2144 self
.client
.SetFontSizeAxis(10)
2145 self
.client
.SetFontSizeLegend(7)
2146 self
.client
.setLogScale((False,False))
2147 self
.client
.SetXSpec('auto')
2148 self
.client
.SetYSpec('auto')
2154 class MyApp(wx
.App
):
2156 wx
.InitAllImageHandlers()
2157 frame
= TestFrame(None, -1, "PlotCanvas")
2159 self
.SetTopWindow(frame
)
2166 if __name__
== '__main__':