]>
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.
37 # Nov 2004 Oliver Schoenborn (oliver.schoenborn@utoronto.ca) with
38 # valuable input from and testing by Gary)
39 # - Factored out "draw command" so extensions easier to implement: clean
40 # separation b/w user's "draw" (Draw) and internal "draw" (_draw)
41 # - Added ability to define your own ticks at PlotCanvas.Draw
42 # - Added better bar charts: PolyBar class and getBarTicksGen()
43 # - Put legend writing for a Poly* into Poly* class where it belongs
44 # - If no legend specified or exists, no gap created
48 This is a simple light weight plotting module that can be used with
49 Boa or easily integrated into your own wxPython application. The
50 emphasis is on small size and fast plotting for large data sets. It
51 has a reasonable number of features to do line and scatter graphs
52 easily as well as simple bar graphs. It is not as sophisticated or
53 as powerful as SciPy Plt or Chaco. Both of these are great packages
54 but consume huge amounts of computer resources for simple plots.
55 They can be found at http://scipy.com
57 This file contains two parts; first the re-usable library stuff, then,
58 after a "if __name__=='__main__'" test, a simple frame and a few default
59 plots for examples and testing.
62 Written by K.Hinsen, R. Srinivasan;
63 Ported to wxPython Harm van der Heijden, feb 1999
65 Major Additions Gordon Williams Feb. 2003 (g_will@cyberus.ca)
67 -Zooming using mouse "rubber band"
70 -Printing, preview, and page set up (margins)
71 -Axis and title labels
72 -Cursor xy axis values
73 -Doc strings and lots of comments
74 -Optimizations for large number of points
77 Did a lot of work here to speed markers up. Only a factor of 4
78 improvement though. Lines are much faster than markers, especially
79 filled markers. Stay away from circles and triangles unless you
80 only have a few thousand points.
82 Times for 25,000 points
89 triangle, triangle_down - 0.90
91 Thanks to Chris Barker for getting this version working on Linux.
93 Zooming controls with mouse (when enabled):
94 Left mouse drag - Zoom box.
95 Left mouse double click - reset zoom.
96 Right mouse click - zoom out centred on click location.
99 import string
as _string
103 # Needs Numeric or numarray
105 import Numeric
as _Numeric
108 import numarray
as _Numeric
#if numarray is used it is renamed Numeric
111 This module requires the Numeric or numarray module,
112 which could not be imported. It probably is not installed
113 (it's not part of the standard Python distribution). See the
114 Python site (http://www.python.org) for information on
115 downloading source or binaries."""
116 raise ImportError, "Numeric or numarray not found. \n" + msg
121 # Plotting classes...
124 """Base Class for lines and markers
125 - All methods are private.
128 def __init__(self
, points
, attr
):
129 self
.points
= _Numeric
.array(points
)
130 self
.currentScale
= (1,1)
131 self
.currentShift
= (0,0)
132 self
.scaled
= self
.points
134 self
.attributes
.update(self
._attributes
)
135 for name
, value
in attr
.items():
136 if name
not in self
._attributes
.keys():
137 raise KeyError, "Style attribute incorrect. Should be one of %s" % self
._attributes
.keys()
138 self
.attributes
[name
] = value
140 def boundingBox(self
):
141 if len(self
.points
) == 0:
143 # defaults to (-1,-1) and (1,1) but axis can be set in Draw
144 minXY
= _Numeric
.array([-1,-1])
145 maxXY
= _Numeric
.array([ 1, 1])
147 minXY
= _Numeric
.minimum
.reduce(self
.points
)
148 maxXY
= _Numeric
.maximum
.reduce(self
.points
)
151 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
152 if len(self
.points
) == 0:
155 if (scale
is not self
.currentScale
) or (shift
is not self
.currentShift
):
156 # update point scaling
157 self
.scaled
= scale
*self
.points
+shift
158 self
.currentScale
= scale
159 self
.currentShift
= shift
160 # else unchanged use the current scaling
162 def drawLegend(self
, dc
, printerScale
, symWidth
, hgap
, textHeight
, x0
, y0
):
163 legend
= self
.getLegend()
165 self
._drawLegendSym
(dc
, printerScale
, symWidth
, x0
, y0
)
166 dc
.DrawText(legend
, x0
+symWidth
+hgap
, y0
-textHeight
/2)
171 def _drawLegendSym(self
, dc
, printerScale
, symWidth
, x0
, y0
):
173 pnt
= (x0
+symWidth
/2., y0
)
174 self
.draw(dc
, printerScale
, coord
= _Numeric
.array([pnt
]))
177 return self
.attributes
['legend']
179 def getClosestPoint(self
, pntXY
, pointScaled
= True):
180 """Returns the index of closest point on the curve, pointXY, scaledXY, distance
182 if pointScaled == True based on screen coords
183 if pointScaled == False based on user coords
185 if pointScaled
== True:
188 pxy
= self
.currentScale
* _Numeric
.array(pntXY
)+ self
.currentShift
192 pxy
= _Numeric
.array(pntXY
)
193 #determine distance for each point
194 d
= _Numeric
.sqrt(_Numeric
.add
.reduce((p
-pxy
)**2,1)) #sqrt(dx^2+dy^2)
195 pntIndex
= _Numeric
.argmin(d
)
197 return [pntIndex
, self
.points
[pntIndex
], self
.scaled
[pntIndex
], dist
]
200 class PolyLine(PolyPoints
):
201 """Class to define line type and style
202 - All methods except __init__ are private.
205 _attributes
= {'colour': 'black',
210 def __init__(self
, points
, **attr
):
211 """Creates PolyLine object
212 points - sequence (array, tuple or list) of (x,y) points making up line
213 **attr - key word attributes
215 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
216 'width'= 1, - Pen width
217 'style'= wx.SOLID, - wx.Pen style
218 'legend'= '' - Line Legend to display
220 PolyPoints
.__init
__(self
, points
, attr
)
222 def draw(self
, dc
, printerScale
, coord
= None):
223 colour
= self
.attributes
['colour']
224 width
= self
.attributes
['width'] * printerScale
225 style
= self
.attributes
['style']
226 pen
= wx
.Pen(colour
, width
, style
)
227 pen
.SetCap(wx
.CAP_BUTT
)
230 dc
.DrawLines(self
.scaled
)
232 dc
.DrawLines(coord
) # draw legend line
234 def _drawLegendSym(self
, dc
, printerScale
, symWidth
, x0
, y0
):
236 pnt2
= (x0
+symWidth
/1.5, y0
)
237 self
.draw(dc
, printerScale
, coord
= _Numeric
.array([pnt1
,pnt2
]))
239 def getSymExtent(self
, printerScale
):
240 """Width and Height of Marker"""
241 h
= self
.attributes
['width'] * printerScale
246 class PolyMarker(PolyPoints
):
247 """Class to define marker type and style
248 - All methods except __init__ are private.
251 _attributes
= {'colour': 'black',
255 'fillstyle': wx
.SOLID
,
259 def __init__(self
, points
, **attr
):
260 """Creates PolyMarker object
261 points - sequence (array, tuple or list) of (x,y) points
262 **attr - key word attributes
264 'colour'= 'black', - wx.Pen Colour any wx.NamedColour
265 'width'= 1, - Pen width
266 'size'= 2, - Marker size
267 'fillcolour'= same as colour, - wx.Brush Colour
268 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
269 'marker'= 'circle' - Marker shape
270 'legend'= '' - Marker Legend to display
282 PolyPoints
.__init
__(self
, points
, attr
)
284 def draw(self
, dc
, printerScale
, coord
= None):
285 colour
= self
.attributes
['colour']
286 width
= self
.attributes
['width'] * printerScale
287 size
= self
.attributes
['size'] * printerScale
288 fillcolour
= self
.attributes
['fillcolour']
289 fillstyle
= self
.attributes
['fillstyle']
290 marker
= self
.attributes
['marker']
292 dc
.SetPen(wx
.Pen(colour
, width
))
294 dc
.SetBrush(wx
.Brush(fillcolour
,fillstyle
))
296 dc
.SetBrush(wx
.Brush(colour
, fillstyle
))
298 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
300 self
._drawmarkers
(dc
, coord
, marker
, size
) # draw legend marker
302 def getSymExtent(self
, printerScale
):
303 """Width and Height of Marker"""
304 s
= 5*self
.attributes
['size'] * printerScale
307 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
308 f
= eval('self._' +marker
)
311 def _circle(self
, dc
, coords
, size
=1):
314 rect
= _Numeric
.zeros((len(coords
),4),_Numeric
.Float
)+[0.0,0.0,wh
,wh
]
315 rect
[:,0:2]= coords
-[fact
,fact
]
316 dc
.DrawEllipseList(rect
.astype(_Numeric
.Int32
))
318 def _dot(self
, dc
, coords
, size
=1):
319 dc
.DrawPointList(coords
)
321 def _square(self
, dc
, coords
, size
=1):
324 rect
= _Numeric
.zeros((len(coords
),4),_Numeric
.Float
)+[0.0,0.0,wh
,wh
]
325 rect
[:,0:2]= coords
-[fact
,fact
]
326 dc
.DrawRectangleList(rect
.astype(_Numeric
.Int32
))
328 def _triangle(self
, dc
, coords
, size
=1):
329 shape
= [(-2.5*size
,1.44*size
), (2.5*size
,1.44*size
), (0.0,-2.88*size
)]
330 poly
= _Numeric
.repeat(coords
,3)
331 poly
.shape
= (len(coords
),3,2)
333 dc
.DrawPolygonList(poly
.astype(_Numeric
.Int32
))
335 def _triangle_down(self
, dc
, coords
, size
=1):
336 shape
= [(-2.5*size
,-1.44*size
), (2.5*size
,-1.44*size
), (0.0,2.88*size
)]
337 poly
= _Numeric
.repeat(coords
,3)
338 poly
.shape
= (len(coords
),3,2)
340 dc
.DrawPolygonList(poly
.astype(_Numeric
.Int32
))
342 def _cross(self
, dc
, coords
, size
=1):
344 for f
in [[-fact
,-fact
,fact
,fact
],[-fact
,fact
,fact
,-fact
]]:
345 lines
= _Numeric
.concatenate((coords
,coords
),axis
=1)+f
346 dc
.DrawLineList(lines
.astype(_Numeric
.Int32
))
348 def _plus(self
, dc
, coords
, size
=1):
350 for f
in [[-fact
,0,fact
,0],[0,-fact
,0,fact
]]:
351 lines
= _Numeric
.concatenate((coords
,coords
),axis
=1)+f
352 dc
.DrawLineList(lines
.astype(_Numeric
.Int32
))
354 class PolyBar(PolyPoints
):
355 """Class to define one or more bars for bar chart. All bars in a
356 PolyBar share same style etc.
357 - All methods except __init__ are private.
360 _attributes
= {'colour': 'black',
365 'fillstyle': wx
.SOLID
}
367 def __init__(self
, points
, **attr
):
368 """Creates several bars.
369 points - sequence (array, tuple or list) of (x,y) points
370 indicating *top left* corner of bar. Can also be
371 just one (x,y) tuple if labels attribute is just
372 a string (not a list)
373 **attr - key word attributes
375 'colour'= wx.BLACK, - wx.Pen Colour
376 'width'= 1, - Pen width
377 'size'= 2, - Bar size
378 'fillcolour'= same as colour, - wx.Brush Colour
379 'fillstyle'= wx.SOLID, - wx.Brush fill style (use wx.TRANSPARENT for no fill)
380 'legend'= '' - string used for PolyBar legend
381 'labels'= '' - string if only one point, or list of labels, one per point
382 Note that if no legend is given the label is used.
384 barPoints
= [(0,0)] # needed so height of bar can be determined
386 # add labels and points to above data members:
388 labels
= attr
['labels']
392 if labels
is None: # figure out if we have 1 point or many
394 points
[0][0] # ok: we have a seq of points
395 barPoints
.extend(points
)
396 except: # failed, so have just one point:
397 barPoints
.append(points
)
398 elif isinstance(labels
, list) or isinstance(labels
, tuple):
399 # labels is a sequence so points must be too, with same length:
400 self
.labels
.extend(labels
)
401 barPoints
.extend(points
)
402 if len(labels
) != len(points
):
403 msg
= "%d bar labels missing" % (len(points
)-len(labels
))
404 raise ValueError, msg
405 else: # label given, but only one, so must be only one point:
406 barPoints
.append(points
)
407 self
.labels
.append(labels
)
409 PolyPoints
.__init
__(self
, barPoints
, attr
)
411 def draw(self
, dc
, printerScale
, coord
= None):
412 colour
= self
.attributes
['colour']
413 width
= self
.attributes
['width'] * printerScale
414 size
= self
.attributes
['size'] * printerScale
415 fillcolour
= self
.attributes
['fillcolour']
416 fillstyle
= self
.attributes
['fillstyle']
418 dc
.SetPen(wx
.Pen(colour
,int(width
)))
420 dc
.SetBrush(wx
.Brush(fillcolour
,fillstyle
))
422 dc
.SetBrush(wx
.Brush(colour
, fillstyle
))
424 self
._drawbar
(dc
, self
.scaled
, size
)
426 self
._drawLegendMarker
(dc
, coord
, size
) #draw legend marker
428 def _drawLegendMarker(self
, dc
, coord
, size
):
431 rect
= _Numeric
.zeros((len(coord
),4),_Numeric
.Float
)+[0.0,0.0,wh
,wh
]
432 rect
[:,0:2]= coord
-[fact
,fact
]
433 dc
.DrawRectangleList(rect
.astype(_Numeric
.Int32
))
435 def offset(self
, heights
, howMany
= 1, shift
=0.25):
436 """Return a list of points, where x's are those of this bar,
437 shifted by shift*howMany (in caller's units, not pixel units), and
438 heights are given in heights."""
439 points
= [(point
[0][0]+shift
*howMany
,point
[1]) for point
440 in zip(self
.points
[1:], heights
)]
443 def getSymExtent(self
, printerScale
):
444 """Width and Height of bar symbol"""
445 s
= 5*self
.attributes
['size'] * printerScale
449 """This returns a comma-separated list of bar labels (one string)"""
450 try: legendItem
= self
.attributes
['legend']
452 legendItem
= ", ".join(self
.labels
)
455 def _drawbar(self
, dc
, coords
, size
=1):
459 for coord
in coords
[1:]:
460 x
= coord
[0] # - width/2 not as good
462 height
= int(y0
) - int(y
)
463 bars
.append((int(x
),int(y
),int(width
),int(height
)))
464 #print x,y,width, height
465 dc
.DrawRectangleList(bars
)
468 """Get the list of (tick pos, label) pairs for this PolyBar.
469 It is unlikely you need this, but it is used by
472 # remember to skip first point:
473 for point
, label
in zip(self
.points
[1:], self
.labels
):
474 ticks
.append((point
[0], label
))
477 def getBarTicksGen(*polyBars
):
478 """Get a function that can be given as xTicks argument when
479 calling PolyCanvas.Draw() when plotting one or more PolyBar.
480 The returned function allows the bar chart to have the bars
481 labelled at the x axis. """
483 for polyBar
in polyBars
:
484 ticks
.extend(polyBar
.getTicks())
486 def tickGenerator(lower
,upper
,gg
,ff
):
489 if lower
<= tick
[0] < upper
:
490 tickPairs
.append(tick
)
497 """Container to hold PolyXXX objects and graph labels
498 - All methods except __init__ are private.
501 def __init__(self
, objects
, title
='', xLabel
='', yLabel
= ''):
502 """Creates PlotGraphics object
503 objects - list of PolyXXX objects to make graph
504 title - title shown at top of graph
505 xLabel - label shown on x-axis
506 yLabel - label shown on y-axis
508 if type(objects
) not in [list,tuple]:
509 raise TypeError, "objects argument should be list or tuple"
510 self
.objects
= objects
515 def boundingBox(self
):
516 p1
, p2
= self
.objects
[0].boundingBox()
517 for o
in self
.objects
[1:]:
518 p1o
, p2o
= o
.boundingBox()
519 p1
= _Numeric
.minimum(p1
, p1o
)
520 p2
= _Numeric
.maximum(p2
, p2o
)
523 def scaleAndShift(self
, scale
=(1,1), shift
=(0,0)):
524 for o
in self
.objects
:
525 o
.scaleAndShift(scale
, shift
)
527 def setPrinterScale(self
, scale
):
528 """Thickens up lines and markers only for printing"""
529 self
.printerScale
= scale
531 def setXLabel(self
, xLabel
= ''):
532 """Set the X axis label on the graph"""
535 def setYLabel(self
, yLabel
= ''):
536 """Set the Y axis label on the graph"""
539 def setTitle(self
, title
= ''):
540 """Set the title at the top of graph"""
544 """Get x axis label string"""
548 """Get y axis label string"""
551 def getTitle(self
, title
= ''):
552 """Get the title at the top of graph"""
556 for o
in self
.objects
:
557 #t=_time.clock() # profile info
558 o
.draw(dc
, self
.printerScale
)
560 #print o, "time=", dt
562 def getSymExtent(self
, printerScale
):
563 """Get max width and height of lines and markers symbols for legend"""
564 symExt
= self
.objects
[0].getSymExtent(printerScale
)
565 for o
in self
.objects
[1:]:
566 oSymExt
= o
.getSymExtent(printerScale
)
567 symExt
= _Numeric
.maximum(symExt
, oSymExt
)
570 def getLegendNames(self
):
571 """Returns list of legend names"""
572 lst
= [None]*len(self
)
573 for i
in range(len(self
)):
574 lst
[i
]= self
.objects
[i
].getLegend()
578 return len(self
.objects
)
580 def __getitem__(self
, item
):
581 return self
.objects
[item
]
584 #-------------------------------------------------------------------------------
585 # Main window that you will want to import into your application.
587 class PlotCanvas(wx
.Window
):
588 """Subclass of a wx.Window to allow simple general plotting
589 of data with zoom, labels, and automatic axis scaling."""
591 def __init__(self
, parent
, id = -1, pos
=wx
.DefaultPosition
,
592 size
=wx
.DefaultSize
, style
= wx
.DEFAULT_FRAME_STYLE
, name
= ""):
593 """Constucts a window, which can be a child of a frame, dialog or
594 any other non-control window"""
596 wx
.Window
.__init
__(self
, parent
, id, pos
, size
, style
, name
)
599 self
.SetBackgroundColour("white")
601 # Create some mouse events for zooming
602 self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
603 self
.Bind(wx
.EVT_LEFT_UP
, self
.OnMouseLeftUp
)
604 self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
605 self
.Bind(wx
.EVT_LEFT_DCLICK
, self
.OnMouseDoubleClick
)
606 self
.Bind(wx
.EVT_RIGHT_DOWN
, self
.OnMouseRightDown
)
608 # set curser as cross-hairs
609 self
.SetCursor(wx
.CROSS_CURSOR
)
611 # Things for printing
612 self
.print_data
= wx
.PrintData()
613 self
.print_data
.SetPaperId(wx
.PAPER_LETTER
)
614 self
.print_data
.SetOrientation(wx
.LANDSCAPE
)
615 self
.pageSetupData
= wx
.PageSetupDialogData()
616 self
.pageSetupData
.SetMarginBottomRight((25,25))
617 self
.pageSetupData
.SetMarginTopLeft((25,25))
618 self
.pageSetupData
.SetPrintData(self
.print_data
)
619 self
.printerScale
= 1
623 self
._zoomInFactor
= 0.5
624 self
._zoomOutFactor
= 2
625 self
._zoomCorner
1= _Numeric
.array([0.0, 0.0]) # left mouse down corner
626 self
._zoomCorner
2= _Numeric
.array([0.0, 0.0]) # left mouse up corner
627 self
._zoomEnabled
= False
628 self
._hasDragged
= False
636 self
._gridEnabled
= False
637 self
._legendEnabled
= False
641 self
._fontSizeAxis
= 10
642 self
._fontSizeTitle
= 15
643 self
._fontSizeLegend
= 7
646 self
._pointLabelEnabled
= False
647 self
.last_PointLabel
= None
648 self
._pointLabelFunc
= None
649 self
.Bind(wx
.EVT_LEAVE_WINDOW
, self
.OnLeave
)
651 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
652 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
653 # OnSize called to make sure the buffer is initialized.
654 # This might result in OnSize getting called twice on some
655 # platforms at initialization, but little harm done.
656 if wx
.Platform
!= "__WXMAC__":
657 self
.OnSize(None) # sets the initial size based on client size
661 def SaveFile(self
, fileName
= ''):
662 """Saves the file to the type specified in the extension. If no file
663 name is specified a dialog box is provided. Returns True if sucessful,
666 .bmp Save a Windows bitmap file.
667 .xbm Save an X bitmap file.
668 .xpm Save an XPM bitmap file.
669 .png Save a Portable Network Graphics file.
670 .jpg Save a Joint Photographic Experts Group file.
672 if _string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
673 dlg1
= wx
.FileDialog(
675 "Choose a file with extension bmp, gif, xbm, xpm, png, or jpg", ".", "",
676 "BMP files (*.bmp)|*.bmp|XBM files (*.xbm)|*.xbm|XPM file (*.xpm)|*.xpm|PNG files (*.png)|*.png|JPG files (*.jpg)|*.jpg",
677 wx
.SAVE|wx
.OVERWRITE_PROMPT
681 if dlg1
.ShowModal() == wx
.ID_OK
:
682 fileName
= dlg1
.GetPath()
683 # Check for proper exension
684 if _string
.lower(fileName
[-3:]) not in ['bmp','xbm','xpm','png','jpg']:
685 dlg2
= wx
.MessageDialog(self
, 'File name extension\n'
687 'bmp, xbm, xpm, png, or jpg',
688 'File Name Error', wx
.OK | wx
.ICON_ERROR
)
694 break # now save file
695 else: # exit without saving
700 # File name has required extension
701 fType
= _string
.lower(fileName
[-3:])
703 tp
= wx
.BITMAP_TYPE_BMP
# Save a Windows bitmap file.
705 tp
= wx
.BITMAP_TYPE_XBM
# Save an X bitmap file.
707 tp
= wx
.BITMAP_TYPE_XPM
# Save an XPM bitmap file.
709 tp
= wx
.BITMAP_TYPE_JPEG
# Save a JPG file.
711 tp
= wx
.BITMAP_TYPE_PNG
# Save a PNG file.
713 res
= self
._Buffer
.SaveFile(fileName
, tp
)
717 """Brings up the page setup dialog"""
718 data
= self
.pageSetupData
719 data
.SetPrintData(self
.print_data
)
720 dlg
= wx
.PageSetupDialog(self
.parent
, data
)
722 if dlg
.ShowModal() == wx
.ID_OK
:
723 data
= dlg
.GetPageSetupData() # returns wx.PageSetupDialogData
724 # updates page parameters from dialog
725 self
.pageSetupData
.SetMarginBottomRight(data
.GetMarginBottomRight())
726 self
.pageSetupData
.SetMarginTopLeft(data
.GetMarginTopLeft())
727 self
.pageSetupData
.SetPrintData(data
.GetPrintData())
728 self
.print_data
=data
.GetPrintData() # updates print_data
732 def Printout(self
, paper
=None):
733 """Print current plot."""
735 self
.print_data
.SetPaperId(paper
)
736 pdd
= wx
.PrintDialogData()
737 pdd
.SetPrintData(self
.print_data
)
738 printer
= wx
.Printer(pdd
)
739 out
= PlotPrintout(self
)
740 print_ok
= printer
.Print(self
.parent
, out
)
742 self
.print_data
= printer
.GetPrintDialogData().GetPrintData()
745 def PrintPreview(self
):
746 """Print-preview current plot."""
747 printout
= PlotPrintout(self
)
748 printout2
= PlotPrintout(self
)
749 self
.preview
= wx
.PrintPreview(printout
, printout2
, self
.print_data
)
750 if not self
.preview
.Ok():
751 wx
.MessageDialog(self
, "Print Preview failed.\n" \
752 "Check that default printer is configured\n", \
753 "Print error", wx
.OK|wx
.CENTRE
).ShowModal()
754 self
.preview
.SetZoom(40)
755 # search up tree to find frame instance
757 while not isinstance(frameInst
, wx
.Frame
):
758 frameInst
= frameInst
.GetParent()
759 frame
= wx
.PreviewFrame(self
.preview
, frameInst
, "Preview")
761 frame
.SetPosition(self
.GetPosition())
762 frame
.SetSize((600,550))
763 frame
.Centre(wx
.BOTH
)
766 def SetFontSizeAxis(self
, point
= 10):
767 """Set the tick and axis label font size (default is 10 point)"""
768 self
._fontSizeAxis
= point
770 def GetFontSizeAxis(self
):
771 """Get current tick and axis label font size in points"""
772 return self
._fontSizeAxis
774 def SetFontSizeTitle(self
, point
= 15):
775 """Set Title font size (default is 15 point)"""
776 self
._fontSizeTitle
= point
778 def GetFontSizeTitle(self
):
779 """Get current Title font size in points"""
780 return self
._fontSizeTitle
782 def SetFontSizeLegend(self
, point
= 7):
783 """Set Legend font size (default is 7 point)"""
784 self
._fontSizeLegend
= point
786 def GetFontSizeLegend(self
):
787 """Get current Legend font size in points"""
788 return self
._fontSizeLegend
790 def SetEnableZoom(self
, value
):
791 """Set True to enable zooming."""
792 if value
not in [True,False]:
793 raise TypeError, "Value should be True or False"
794 self
._zoomEnabled
= value
796 def GetEnableZoom(self
):
797 """True if zooming enabled."""
798 return self
._zoomEnabled
800 def SetEnableGrid(self
, value
):
801 """Set True to enable grid."""
802 if value
not in [True,False]:
803 raise TypeError, "Value should be True or False"
804 self
._gridEnabled
= value
807 def GetEnableGrid(self
):
808 """True if grid enabled."""
809 return self
._gridEnabled
811 def SetEnableLegend(self
, value
):
812 """Set True to enable legend."""
813 if value
not in [True,False]:
814 raise TypeError, "Value should be True or False"
815 self
._legendEnabled
= value
818 def GetEnableLegend(self
):
819 """True if Legend enabled."""
820 return self
._legendEnabled
822 def SetEnablePointLabel(self
, value
):
823 """Set True to enable pointLabel."""
824 if value
not in [True,False]:
825 raise TypeError, "Value should be True or False"
826 self
._pointLabelEnabled
= value
827 self
.Redraw() #will erase existing pointLabel if present
828 self
.last_PointLabel
= None
830 def GetEnablePointLabel(self
):
831 """True if pointLabel enabled."""
832 return self
._pointLabelEnabled
834 def SetPointLabelFunc(self
, func
):
835 """Sets the function with custom code for pointLabel drawing
836 ******** more info needed ***************
838 self
._pointLabelFunc
= func
840 def GetPointLabelFunc(self
):
841 """Returns pointLabel Drawing Function"""
842 return self
._pointLabelFunc
845 """Unzoom the plot."""
846 self
.last_PointLabel
= None #reset pointLabel
848 self
._drawCmd
.resetAxes(self
._xSpec
, self
._ySpec
)
851 def ScrollRight(self
, units
):
852 """Move view right number of axis units."""
853 self
.last_PointLabel
= None #reset pointLabel
855 self
._drawCmd
.scrollAxisX(units
, self
._xSpec
)
858 def ScrollUp(self
, units
):
859 """Move view up number of axis units."""
860 self
.last_PointLabel
= None #reset pointLabel
862 self
._drawCmd
.scrollAxisY(units
, self
._ySpec
)
865 def GetXY(self
,event
):
866 """Takes a mouse event and returns the XY user axis values."""
867 x
,y
= self
.PositionScreenToUser(event
.GetPosition())
870 def PositionUserToScreen(self
, pntXY
):
871 """Converts User position to Screen Coordinates"""
872 userPos
= _Numeric
.array(pntXY
)
873 x
,y
= userPos
* self
._pointScale
+ self
._pointShift
876 def PositionScreenToUser(self
, pntXY
):
877 """Converts Screen position to User Coordinates"""
878 screenPos
= _Numeric
.array(pntXY
)
879 x
,y
= (screenPos
-self
._pointShift
)/self
._pointScale
882 def SetXSpec(self
, type= 'auto'):
883 """xSpec- defines x axis type. Can be 'none', 'min' or 'auto'
885 'none' - shows no axis or tick mark values
886 'min' - shows min bounding box values
887 'auto' - rounds axis range to sensible values
891 def SetYSpec(self
, type= 'auto'):
892 """ySpec- defines x axis type. Can be 'none', 'min' or 'auto'
894 'none' - shows no axis or tick mark values
895 'min' - shows min bounding box values
896 'auto' - rounds axis range to sensible values
901 """Returns current XSpec for axis"""
905 """Returns current YSpec for axis"""
908 def GetXMaxRange(self
):
909 """Returns (minX, maxX) x-axis range for displayed graph"""
910 return self
._drawCmd
.getXMaxRange()
912 def GetYMaxRange(self
):
913 """Returns (minY, maxY) y-axis range for displayed graph"""
914 return self
._drawCmd
.getYMaxRange()
916 def GetXCurrentRange(self
):
917 """Returns (minX, maxX) x-axis for currently displayed portion of graph"""
918 return self
._drawCmd
.xAxis
920 def GetYCurrentRange(self
):
921 """Returns (minY, maxY) y-axis for currently displayed portion of graph"""
922 return self
._drawCmd
.yAxis
925 """Return true if Draw() has been called once, false otherwise."""
926 return self
._drawCmd
is not None
928 def Draw(self
, graphics
, xAxis
= None, yAxis
= None, dc
= None,
929 xTicks
= None, yTicks
= None):
930 """Draw objects in graphics with specified x and y axis.
931 graphics- instance of PlotGraphics with list of PolyXXX objects
932 xAxis - tuple with (min, max) axis range to view
933 yAxis - same as xAxis
934 dc - drawing context - doesn't have to be specified.
935 If it's not, the offscreen buffer is used.
936 xTicks and yTicks - can be either
937 - a list of floats where the ticks should be, only visible
939 - a function that generates a list of (tick, label) pairs;
940 The function must be of form func(lower, upper, grid,
941 format), and the pairs must contain only ticks between lower and
942 upper. Function can use grid and format that are computed by
943 PlotCanvas, if desired, where grid is the spacing between ticks,
944 and format is the format string for how tick labels
945 will appear. See tickGeneratorDefault and
946 tickGeneratorFromList for examples of such functions.
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)"
951 if type(yAxis
) not in [type(None),tuple]:
952 raise TypeError, "yAxis should be None or (minY,maxY)"
954 self
._drawCmd
= DrawCmd(graphics
, xAxis
, yAxis
,
955 self
._xSpec
, self
._ySpec
,
959 def _draw(self
, dc
=None):
960 """Implement the draw command, with dc if given, using any new
961 settings (legend toggled, axis specs, zoom, etc). """
962 assert self
._drawCmd
is not None
964 # check case for axis = (a,b) where a==b caused by improper zooms
965 if self
._drawCmd
.isEmpty():
969 # sets new dc and clears it
970 dc
= wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
)
976 # set font size for every thing but title and legend
977 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
979 # Get ticks and textExtents for axis if required
980 if self
._xSpec
is not 'none':
981 xticks
= self
._drawCmd
.getTicksX()
982 xTextExtent
= dc
.GetTextExtent(xticks
[-1][1])# w h of x axis text last number on axis
985 xTextExtent
= (0,0) # No text for ticks
986 if self
._ySpec
is not 'none':
987 yticks
= self
._drawCmd
.getTicksY()
988 yTextExtentBottom
= dc
.GetTextExtent(yticks
[0][1])
989 yTextExtentTop
= dc
.GetTextExtent(yticks
[-1][1])
990 yTextExtent
= (max(yTextExtentBottom
[0],yTextExtentTop
[0]),
991 max(yTextExtentBottom
[1],yTextExtentTop
[1]))
994 yTextExtent
= (0,0) # No text for ticks
996 # TextExtents for Title and Axis Labels
997 graphics
= self
._drawCmd
.graphics
998 titleWH
, xLabelWH
, yLabelWH
= self
._titleLablesWH
(dc
, graphics
)
1000 # TextExtents for Legend
1001 legendBoxWH
, legendSymExt
, legendHGap
, legendTextExt \
1002 = self
._legendWH
(dc
, graphics
)
1004 # room around graph area
1005 rhsW
= max(xTextExtent
[0], legendBoxWH
[0]) # use larger of number width or legend width
1006 lhsW
= yTextExtent
[0]+ yLabelWH
[1]
1007 bottomH
= max(xTextExtent
[1], yTextExtent
[1]/2.)+ xLabelWH
[1]
1008 topH
= yTextExtent
[1]/2. + titleWH
[1]
1009 textSize_scale
= _Numeric
.array([rhsW
+lhsW
,bottomH
+topH
]) # make plot area smaller by text size
1010 textSize_shift
= _Numeric
.array([lhsW
, bottomH
]) # shift plot area by this amount
1012 # drawing title and labels text
1013 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
1014 titlePos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- titleWH
[0]/2.,
1015 self
.plotbox_origin
[1]- self
.plotbox_size
[1])
1016 dc
.DrawText(graphics
.getTitle(),titlePos
[0],titlePos
[1])
1017 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1018 xLabelPos
= (self
.plotbox_origin
[0]+ lhsW
+ (self
.plotbox_size
[0]-lhsW
-rhsW
)/2.- xLabelWH
[0]/2.,
1019 self
.plotbox_origin
[1]- xLabelWH
[1])
1020 dc
.DrawText(graphics
.getXLabel(),xLabelPos
[0],xLabelPos
[1])
1021 yLabelPos
= (self
.plotbox_origin
[0],
1022 self
.plotbox_origin
[1]- bottomH
- (self
.plotbox_size
[1]-bottomH
-topH
)/2.+ yLabelWH
[0]/2.)
1023 if graphics
.getYLabel(): # bug fix for Linux
1024 dc
.DrawRotatedText(graphics
.getYLabel(),yLabelPos
[0],yLabelPos
[1],90)
1026 # drawing legend makers and text
1027 if self
._legendEnabled
:
1028 self
._drawLegend
(dc
,graphics
,rhsW
,topH
,legendBoxWH
,
1029 legendSymExt
, legendHGap
, legendTextExt
)
1031 #sizes axis to axis type, create lower left and upper right corners of plot
1032 p1
, p2
= self
._drawCmd
.getBoundingBox()
1034 # allow for scaling and shifting plotted points
1035 scale
= (self
.plotbox_size
-textSize_scale
) / (p2
-p1
)* _Numeric
.array((1,-1))
1036 shift
= -p1
*scale
+ self
.plotbox_origin
+ textSize_shift
* _Numeric
.array((1,-1))
1037 self
._pointScale
= scale
# make available for mouse events
1038 self
._pointShift
= shift
1039 self
._drawAxes
(dc
, p1
, p2
, scale
, shift
, xticks
, yticks
)
1041 graphics
.scaleAndShift(scale
, shift
)
1042 graphics
.setPrinterScale(self
.printerScale
) # thicken up lines and markers if printing
1044 # set clipping area so drawing does not occur outside axis box
1045 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(p1
, p2
)
1046 dc
.SetClippingRegion(ptx
,pty
,rectWidth
,rectHeight
)
1047 # Draw the lines and markers
1048 #start = _time.clock()
1050 # print "entire graphics drawing took: %f second"%(_time.clock() - start)
1051 # remove the clipping region
1052 dc
.DestroyClippingRegion()
1055 def Redraw(self
, dc
= None):
1056 """Redraw the existing plot."""
1057 if self
.BeenDrawn():
1061 """Erase the window."""
1062 self
.last_PointLabel
= None #reset pointLabel
1063 dc
= wx
.BufferedDC(wx
.ClientDC(self
), self
._Buffer
)
1065 self
._drawCmd
= None
1067 def Zoom(self
, Center
, Ratio
):
1068 """ Zoom on the plot
1069 Centers on the X,Y coords given in Center
1070 Zooms by the Ratio = (Xratio, Yratio) given
1072 self
.last_PointLabel
= None #reset maker
1073 if self
.BeenDrawn():
1074 self
._drawCmd
.zoom(Center
, Ratio
)
1077 def ChangeAxes(self
, xAxis
, yAxis
):
1078 """Change axes specified at last Draw() command. If
1079 Draw() never called yet, RuntimeError raised. """
1080 if not self
.BeenDrawn():
1081 raise RuntimeError, "Must call Draw() at least once"
1082 self
._drawCmd
.changeAxisX(xAxis
, self
._xSpec
)
1083 self
._drawCmd
.changeAxisY(yAxis
, self
._ySpec
)
1086 def GetClosestPoints(self
, pntXY
, pointScaled
= True):
1087 """Returns list with
1088 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1089 list for each curve.
1090 Returns [] if no curves are being plotted.
1093 if pointScaled == True based on screen coords
1094 if pointScaled == False based on user coords
1096 if self
.BeenDrawn():
1097 return self
._drawCmd
.getClosestPoints(pntXY
, pointScaled
)
1102 def GetClosetPoint(self
, pntXY
, pointScaled
= True):
1103 """Returns list with
1104 [curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1105 list for only the closest curve.
1106 Returns [] if no curves are being plotted.
1109 if pointScaled == True based on screen coords
1110 if pointScaled == False based on user coords
1112 #closest points on screen based on screen scaling (pointScaled= True)
1113 #list [curveNumber, index, pointXY, scaledXY, distance] for each curve
1114 closestPts
= self
.GetClosestPoints(pntXY
, pointScaled
)
1115 if closestPts
== []:
1116 return [] #no graph present
1117 #find one with least distance
1118 dists
= [c
[-1] for c
in closestPts
]
1119 mdist
= min(dists
) #Min dist
1120 i
= dists
.index(mdist
) #index for min dist
1121 return closestPts
[i
] #this is the closest point on closest curve
1123 def UpdatePointLabel(self
, mDataDict
):
1124 """Updates the pointLabel point on screen with data contained in
1127 mDataDict will be passed to your function set by
1128 SetPointLabelFunc. It can contain anything you
1129 want to display on the screen at the scaledXY point
1132 This function can be called from parent window with onClick,
1133 onMotion events etc.
1135 if self
.last_PointLabel
!= None:
1137 if mDataDict
["pointXY"] != self
.last_PointLabel
["pointXY"]:
1139 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1140 self
._drawPointLabel
(mDataDict
) #plot new
1142 #just plot new with no erase
1143 self
._drawPointLabel
(mDataDict
) #plot new
1144 #save for next erase
1145 self
.last_PointLabel
= mDataDict
1147 # event handlers **********************************
1148 def OnMotion(self
, event
):
1149 if not self
.BeenDrawn(): return
1150 if self
._zoomEnabled
and event
.LeftIsDown():
1151 if self
._hasDragged
:
1152 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
1154 self
._hasDragged
= True
1155 self
._zoomCorner
2[0], self
._zoomCorner
2[1] = self
.GetXY(event
)
1156 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # add new
1158 def OnMouseLeftDown(self
,event
):
1159 if self
.BeenDrawn():
1160 self
._zoomCorner
1[0], self
._zoomCorner
1[1]= self
.GetXY(event
)
1162 def OnMouseLeftUp(self
, event
):
1163 if not self
.BeenDrawn(): return
1164 if self
._zoomEnabled
:
1165 if self
._hasDragged
== True:
1166 self
._drawRubberBand
(self
._zoomCorner
1, self
._zoomCorner
2) # remove old
1167 self
._zoomCorner
2[0], self
._zoomCorner
2[1]= self
.GetXY(event
)
1168 self
._hasDragged
= False # reset flag
1169 minX
, minY
= _Numeric
.minimum( self
._zoomCorner
1, self
._zoomCorner
2)
1170 maxX
, maxY
= _Numeric
.maximum( self
._zoomCorner
1, self
._zoomCorner
2)
1171 self
.last_PointLabel
= None #reset pointLabel
1172 self
._drawCmd
.changeAxisX((minX
,maxX
))
1173 self
._drawCmd
.changeAxisY((minY
,maxY
))
1175 #else: # A box has not been drawn, zoom in on a point
1176 ## this interfered with the double click, so I've disables it.
1177 # X,Y = self.GetXY(event)
1178 # self.Zoom( (X,Y), (self._zoomInFactor,self._zoomInFactor) )
1180 def OnMouseDoubleClick(self
,event
):
1181 if self
._zoomEnabled
:
1184 def OnMouseRightDown(self
,event
):
1185 if self
._zoomEnabled
:
1186 X
,Y
= self
.GetXY(event
)
1187 self
.Zoom( (X
,Y
), (self
._zoomOutFactor
, self
._zoomOutFactor
) )
1189 def OnPaint(self
, event
):
1190 # All that is needed here is to draw the buffer to screen
1191 if self
.last_PointLabel
!= None:
1192 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1193 self
.last_PointLabel
= None
1194 dc
= wx
.BufferedPaintDC(self
, self
._Buffer
)
1196 def OnSize(self
,event
):
1197 # The Buffer init is done here, to make sure the buffer is always
1198 # the same size as the Window
1199 Size
= self
.GetClientSize()
1201 # Make new offscreen bitmap: this bitmap will always have the
1202 # current drawing in it, so it can be used to save the image to
1203 # a file, or whatever.
1204 self
._Buffer
= wx
.EmptyBitmap(Size
[0],Size
[1])
1207 self
.last_PointLabel
= None #reset pointLabel
1209 if self
.BeenDrawn():
1214 def OnLeave(self
, event
):
1215 """Used to erase pointLabel when mouse outside window"""
1216 if self
.last_PointLabel
!= None:
1217 self
._drawPointLabel
(self
.last_PointLabel
) #erase old
1218 self
.last_PointLabel
= None
1221 # Private Methods **************************************************
1222 def _setSize(self
, width
=None, height
=None):
1223 """DC width and height."""
1225 (self
.width
,self
.height
) = self
.GetClientSize()
1227 self
.width
, self
.height
= width
,height
1228 self
.plotbox_size
= 0.97*_Numeric
.array([self
.width
, self
.height
])
1229 xo
= 0.5*(self
.width
-self
.plotbox_size
[0])
1230 yo
= self
.height
-0.5*(self
.height
-self
.plotbox_size
[1])
1231 self
.plotbox_origin
= _Numeric
.array([xo
, yo
])
1233 def _setPrinterScale(self
, scale
):
1234 """Used to thicken lines and increase marker size for print out."""
1235 # line thickness on printer is very thin at 600 dot/in. Markers small
1236 self
.printerScale
= scale
1238 def _printDraw(self
, printDC
):
1239 """Used for printing."""
1240 if self
.BeenDrawn():
1243 def _drawPointLabel(self
, mDataDict
):
1244 """Draws and erases pointLabels"""
1245 width
= self
._Buffer
.GetWidth()
1246 height
= self
._Buffer
.GetHeight()
1247 tmp_Buffer
= wx
.EmptyBitmap(width
,height
)
1249 dcs
.SelectObject(tmp_Buffer
)
1252 self
._pointLabelFunc
(dcs
,mDataDict
) #custom user pointLabel function
1255 dc
= wx
.ClientDC( self
)
1256 #this will erase if called twice
1257 dc
.Blit(0, 0, width
, height
, dcs
, 0, 0, wx
.EQUIV
) #(NOT src) XOR dst
1260 def _drawLegend(self
,dc
,graphics
,rhsW
,topH
,legendBoxWH
, legendSymExt
, hgap
, legendTextExt
):
1261 """Draws legend symbols and text"""
1262 # top right hand corner of graph box is ref corner
1263 trhc
= self
.plotbox_origin
+ (self
.plotbox_size
-[rhsW
,topH
])*[1,-1]
1264 legendLHS
= .091* legendBoxWH
[0] # border space between legend sym and graph box
1265 lineHeight
= max(legendSymExt
[1], legendTextExt
[1]) * 1.1 #1.1 used as space between lines
1266 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
1269 s
= legendIdx
* lineHeight
1270 drew
= gr
.drawLegend(dc
, self
.printerScale
,
1271 legendSymExt
[0], hgap
, legendTextExt
[1],
1272 trhc
[0]+legendLHS
, trhc
[1]+s
+lineHeight
/2.)
1275 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
)) # reset
1277 def _titleLablesWH(self
, dc
, graphics
):
1278 """Draws Title and labels and returns width and height for each"""
1279 # TextExtents for Title and Axis Labels
1280 dc
.SetFont(self
._getFont
(self
._fontSizeTitle
))
1281 title
= graphics
.getTitle()
1282 titleWH
= dc
.GetTextExtent(title
)
1283 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1284 xLabel
, yLabel
= graphics
.getXLabel(),graphics
.getYLabel()
1285 xLabelWH
= dc
.GetTextExtent(xLabel
)
1286 yLabelWH
= dc
.GetTextExtent(yLabel
)
1287 return titleWH
, xLabelWH
, yLabelWH
1289 def _legendWH(self
, dc
, graphics
):
1290 """Returns the size in screen units for legend box"""
1291 if self
._legendEnabled
!= True:
1292 legendBoxWH
= symExt
= txtExt
= (0,0)
1295 #find max symbol size and gap b/w symbol and text
1296 symExt
= graphics
.getSymExtent(self
.printerScale
)
1297 hgap
= symExt
[0]*0.1
1298 # find max legend text extent
1299 dc
.SetFont(self
._getFont
(self
._fontSizeLegend
))
1300 txtList
= graphics
.getLegendNames()
1301 txtExt
= dc
.GetTextExtent(txtList
[0])
1302 for txt
in graphics
.getLegendNames()[1:]:
1303 txtExt
= _Numeric
.maximum(txtExt
,dc
.GetTextExtent(txt
))
1304 maxW
= symExt
[0]+hgap
+txtExt
[0]
1305 maxH
= max(symExt
[1],txtExt
[1])
1306 # padding .1 for lhs of legend box and space between lines
1308 maxH
= maxH
* 1.1 * len(txtList
)
1309 dc
.SetFont(self
._getFont
(self
._fontSizeAxis
))
1310 legendBoxWH
= (maxW
,maxH
)
1311 return (legendBoxWH
, symExt
, hgap
, txtExt
)
1313 def _drawRubberBand(self
, corner1
, corner2
):
1314 """Draws/erases rect box from corner1 to corner2"""
1315 ptx
,pty
,rectWidth
,rectHeight
= self
._point
2ClientCoord
(corner1
, corner2
)
1317 dc
= wx
.ClientDC( self
)
1319 dc
.SetPen(wx
.Pen(wx
.BLACK
))
1320 dc
.SetBrush(wx
.Brush( wx
.WHITE
, wx
.TRANSPARENT
) )
1321 dc
.SetLogicalFunction(wx
.INVERT
)
1322 dc
.DrawRectangle( ptx
,pty
, rectWidth
,rectHeight
)
1323 dc
.SetLogicalFunction(wx
.COPY
)
1326 def _getFont(self
,size
):
1327 """Take font size, adjusts if printing and returns wx.Font"""
1328 s
= size
*self
.printerScale
1330 # Linux speed up to get font from cache rather than X font server
1331 key
= (int(s
), of
.GetFamily (), of
.GetStyle (), of
.GetWeight ())
1332 font
= self
._fontCache
.get (key
, None)
1334 return font
# yeah! cache hit
1336 font
= wx
.Font(int(s
), of
.GetFamily(), of
.GetStyle(), of
.GetWeight())
1337 self
._fontCache
[key
] = font
1341 def _point2ClientCoord(self
, corner1
, corner2
):
1342 """Converts user point coords to client screen int coords x,y,width,height"""
1343 c1
= _Numeric
.array(corner1
)
1344 c2
= _Numeric
.array(corner2
)
1345 # convert to screen coords
1346 pt1
= c1
*self
._pointScale
+self
._pointShift
1347 pt2
= c2
*self
._pointScale
+self
._pointShift
1348 # make height and width positive
1349 pul
= _Numeric
.minimum(pt1
,pt2
) # Upper left corner
1350 plr
= _Numeric
.maximum(pt1
,pt2
) # Lower right corner
1351 rectWidth
, rectHeight
= plr
-pul
1353 return ptx
, pty
, rectWidth
, rectHeight
1355 def _axisInterval(self
, spec
, lower
, upper
):
1356 """Returns sensible axis range for given spec"""
1357 if spec
== 'none' or spec
== 'min':
1359 return lower
-0.5, upper
+0.5
1362 elif spec
== 'auto':
1365 return lower
-0.5, upper
+0.5
1366 log
= _Numeric
.log10(range)
1367 power
= _Numeric
.floor(log
)
1368 fraction
= log
-power
1369 if fraction
<= 0.05:
1372 lower
= lower
- lower
% grid
1375 upper
= upper
- mod
+ grid
1377 elif type(spec
) == type(()):
1384 raise ValueError, str(spec
) + ': illegal axis specification'
1386 def _drawAxes(self
, dc
, p1
, p2
, scale
, shift
, xticks
, yticks
):
1388 penWidth
= self
.printerScale
# increases thickness for printing only
1389 dc
.SetPen(wx
.Pen(wx
.BLACK
, penWidth
))
1391 # set length of tick marks--long ones make grid
1392 if self
._gridEnabled
:
1393 x
,y
,width
,height
= self
._point
2ClientCoord
(p1
,p2
)
1394 yTickLength
= width
/2.0 +1
1395 xTickLength
= height
/2.0 +1
1397 yTickLength
= 3 * self
.printerScale
# lengthens lines for printing
1398 xTickLength
= 3 * self
.printerScale
1400 if self
._xSpec
is not 'none':
1401 lower
, upper
= p1
[0],p2
[0]
1403 for y
, d
in [(p1
[1], -xTickLength
), (p2
[1], xTickLength
)]: # miny, maxy and tick lengths
1404 a1
= scale
*_Numeric
.array([lower
, y
])+shift
1405 a2
= scale
*_Numeric
.array([upper
, y
])+shift
1406 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1]) # draws upper and lower axis line
1407 for x
, label
in xticks
:
1408 pt
= scale
*_Numeric
.array([x
, y
])+shift
1409 dc
.DrawLine(pt
[0],pt
[1],pt
[0],pt
[1] + d
) # draws tick mark d units
1411 dc
.DrawText(label
,pt
[0],pt
[1])
1412 text
= 0 # axis values not drawn on top side
1414 if self
._ySpec
is not 'none':
1415 lower
, upper
= p1
[1],p2
[1]
1417 h
= dc
.GetCharHeight()
1418 for x
, d
in [(p1
[0], -yTickLength
), (p2
[0], yTickLength
)]:
1419 a1
= scale
*_Numeric
.array([x
, lower
])+shift
1420 a2
= scale
*_Numeric
.array([x
, upper
])+shift
1421 dc
.DrawLine(a1
[0],a1
[1],a2
[0],a2
[1])
1422 for y
, label
in yticks
:
1423 pt
= scale
*_Numeric
.array([x
, y
])+shift
1424 dc
.DrawLine(pt
[0],pt
[1],pt
[0]-d
,pt
[1])
1426 dc
.DrawText(label
,pt
[0]-dc
.GetTextExtent(label
)[0],
1428 text
= 0 # axis values not drawn on right side
1432 """Represent a "draw" command, i.e. the one given in call to
1433 PlotCanvas.Draw(). The axis specs are not saved to preserve
1434 backward compatibility: they could be specified before the
1435 first Draw() command."""
1436 def __init__(self
, graphics
, xAxis
, yAxis
, xSpec
, ySpec
, xTicks
, yTicks
):
1437 """Same args as PlotCanvas.Draw(), plus axis specs."""
1438 self
.graphics
= graphics
1439 self
.xTickGen
= xTicks
1440 self
.yTickGen
= yTicks
1441 self
.xAxisInit
, self
.yAxisInit
= xAxis
, yAxis
1445 self
.changeAxisX(xAxis
, xSpec
)
1446 self
.changeAxisY(yAxis
, ySpec
)
1447 assert self
.xAxis
is not None
1448 assert self
.yAxis
is not None
1451 """Return true if either axis has 0 size."""
1452 if self
.xAxis
[0] == self
.xAxis
[1]:
1454 if self
.yAxis
[0] == self
.yAxis
[1]:
1459 def resetAxes(self
, xSpec
, ySpec
):
1460 """Reset the axes to what they were initially, using axes specs given."""
1461 self
.changeAxisX(self
.xAxisInit
, xSpec
)
1462 self
.changeAxisY(self
.yAxisInit
, ySpec
)
1464 def getBoundingBox(self
):
1465 """Returns p1, p2 (two pairs)"""
1466 p1
= _Numeric
.array((self
.xAxis
[0], self
.yAxis
[0]))
1467 p2
= _Numeric
.array((self
.xAxis
[1], self
.yAxis
[1]))
1470 def getClosestPoints(self
, pntXY
, pointScaled
=True):
1472 for curveNum
,obj
in enumerate(self
.graphics
):
1473 #check there are points in the curve
1474 if len(obj
.points
) == 0:
1475 continue #go to next obj
1476 #[curveNumber, legend, index of closest point, pointXY, scaledXY, distance]
1477 cn
= [curveNum
, obj
.getLegend()]+\
1478 obj
.getClosestPoint( pntXY
, pointScaled
)
1482 def scrollAxisX(self
, units
, xSpec
):
1483 """Scroll y axis by units, using ySpec for axis spec."""
1484 self
.changeAxisX((self
.xAxis
[0]+units
, self
.xAxis
[1]+units
), xSpec
)
1486 def scrollAxisY(self
, units
, ySpec
):
1487 """Scroll y axis by units, using ySpec for axis spec."""
1488 self
.changeAxisY((self
.yAxis
[0]+units
, self
.yAxis
[1]+units
), ySpec
)
1490 def changeAxisX(self
, xAxis
=None, xSpec
=None):
1491 """Change the x axis to new given, or if None use ySpec to get it. """
1492 assert type(xAxis
) in [type(None),tuple]
1494 p1
, p2
= self
.graphics
.boundingBox() #min, max points of graphics
1495 self
.xAxis
= self
._axisInterval
(xSpec
, p1
[0], p2
[0]) #in user units
1499 def changeAxisY(self
, yAxis
=None, ySpec
=None):
1500 """Change the y axis to new given, or if None use ySpec to get it. """
1501 assert type(yAxis
) in [type(None),tuple]
1503 p1
, p2
= self
.graphics
.boundingBox() #min, max points of graphics
1504 self
.yAxis
= self
._axisInterval
(ySpec
, p1
[1], p2
[1])
1508 def zoom(self
, center
, ratio
):
1509 """Zoom to center and ratio."""
1511 w
= (self
.xAxis
[1] - self
.xAxis
[0]) * Ratio
[0]
1512 h
= (self
.yAxis
[1] - self
.yAxis
[0]) * Ratio
[1]
1513 self
.xAxis
= ( x
- w
/2, x
+ w
/2 )
1514 self
.yAxis
= ( y
- h
/2, y
+ h
/2 )
1516 def getXMaxRange(self
):
1517 p1
, p2
= self
.graphics
.boundingBox() #min, max points of graphics
1518 return self
._axisInterval
(self
._xSpec
, p1
[0], p2
[0]) #in user units
1520 def getYMaxRange(self
):
1521 p1
, p2
= self
.graphics
.boundingBox() #min, max points of graphics
1522 return self
._axisInterval
(self
._ySpec
, p1
[1], p2
[1]) #in user units
1524 def getTicksX(self
):
1525 """Get the ticks along y axis. Actually pairs of (t, str)."""
1526 xticks
= self
._ticks
(self
.xAxis
[0], self
.xAxis
[1], self
.xTickGen
)
1527 if xticks
== []: # try without the generator
1528 xticks
= self
._ticks
(self
.xAxis
[0], self
.xAxis
[1])
1531 def getTicksY(self
):
1532 """Get the ticks along y axis. Actually pairs of (t, str)."""
1533 yticks
= self
._ticks
(self
.yAxis
[0], self
.yAxis
[1], self
.yTickGen
)
1534 if yticks
== []: # try without the generator
1535 yticks
= self
._ticks
(self
.yAxis
[0], self
.yAxis
[1])
1538 def _axisInterval(self
, spec
, lower
, upper
):
1539 """Returns sensible axis range for given spec."""
1540 if spec
== 'none' or spec
== 'min':
1542 return lower
-0.5, upper
+0.5
1545 elif spec
== 'auto':
1548 return lower
-0.5, upper
+0.5
1549 log
= _Numeric
.log10(range)
1550 power
= _Numeric
.floor(log
)
1551 fraction
= log
-power
1552 if fraction
<= 0.05:
1555 lower
= lower
- lower
% grid
1558 upper
= upper
- mod
+ grid
1560 elif type(spec
) == type(()):
1567 raise ValueError, str(spec
) + ': illegal axis specification'
1569 def _ticks(self
, lower
, upper
, generator
=None):
1570 """Get ticks between lower and upper, using generator if given. """
1571 ideal
= (upper
-lower
)/7.
1572 log
= _Numeric
.log10(ideal
)
1573 power
= _Numeric
.floor(log
)
1574 fraction
= log
-power
1577 for f
, lf
in self
._multiples
:
1578 e
= _Numeric
.fabs(fraction
-lf
)
1582 grid
= factor
* 10.**power
1583 if power
> 4 or power
< -4:
1586 digits
= max(1, int(power
))
1587 format
= '%' + `digits`
+'.0f'
1589 digits
= -int(power
)
1590 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
1592 if generator
is None:
1593 return tickGeneratorDefault(lower
, upper
, grid
, format
)
1594 elif isinstance(generator
, list):
1595 return tickGeneratorFromList(generator
, lower
, upper
, format
)
1597 return generator(lower
, upper
, grid
, format
)
1599 _multiples
= [(2., _Numeric
.log10(2.)), (5., _Numeric
.log10(5.))]
1602 def tickGeneratorDefault(lower
, upper
, grid
, format
):
1603 """Default tick generator used by PlotCanvas.Draw() if not specified."""
1605 t
= -grid
*_Numeric
.floor(-lower
/grid
)
1607 ticks
.append( (t
, format
% (t
,)) )
1611 def tickGeneratorFromList(ticks
, lower
, upper
, format
):
1612 """Tick generator used by PlotCanvas.Draw() if
1613 a list is given for xTicks or yTicks. """
1616 if lower
<= tick
<= upper
:
1617 tickPairs
.append((tick
, format
% tick
))
1620 #-------------------------------------------------------------------------------
1621 # Used to layout the printer page
1623 class PlotPrintout(wx
.Printout
):
1624 """Controls how the plot is made in printing and previewing"""
1625 # Do not change method names in this class,
1626 # we have to override wx.Printout methods here!
1627 def __init__(self
, graph
):
1628 """graph is instance of plotCanvas to be printed or previewed"""
1629 wx
.Printout
.__init
__(self
)
1632 def HasPage(self
, page
):
1638 def GetPageInfo(self
):
1639 return (1, 1, 1, 1) # disable page numbers
1641 def OnPrintPage(self
, page
):
1642 dc
= self
.GetDC() # allows using floats for certain functions
1643 ## print "PPI Printer",self.GetPPIPrinter()
1644 ## print "PPI Screen", self.GetPPIScreen()
1645 ## print "DC GetSize", dc.GetSize()
1646 ## print "GetPageSizePixels", self.GetPageSizePixels()
1647 # Note PPIScreen does not give the correct number
1648 # Calulate everything for printer and then scale for preview
1649 PPIPrinter
= self
.GetPPIPrinter() # printer dots/inch (w,h)
1650 #PPIScreen= self.GetPPIScreen() # screen dots/inch (w,h)
1651 dcSize
= dc
.GetSize() # DC size
1652 pageSize
= self
.GetPageSizePixels() # page size in terms of pixcels
1653 clientDcSize
= self
.graph
.GetClientSize()
1655 # find what the margins are (mm)
1656 margLeftSize
,margTopSize
= self
.graph
.pageSetupData
.GetMarginTopLeft()
1657 margRightSize
, margBottomSize
= self
.graph
.pageSetupData
.GetMarginBottomRight()
1659 # calculate offset and scale for dc
1660 pixLeft
= margLeftSize
*PPIPrinter
[0]/25.4 # mm*(dots/in)/(mm/in)
1661 pixRight
= margRightSize
*PPIPrinter
[0]/25.4
1662 pixTop
= margTopSize
*PPIPrinter
[1]/25.4
1663 pixBottom
= margBottomSize
*PPIPrinter
[1]/25.4
1665 plotAreaW
= pageSize
[0]-(pixLeft
+pixRight
)
1666 plotAreaH
= pageSize
[1]-(pixTop
+pixBottom
)
1668 # ratio offset and scale to screen size if preview
1669 if self
.IsPreview():
1670 ratioW
= float(dcSize
[0])/pageSize
[0]
1671 ratioH
= float(dcSize
[1])/pageSize
[1]
1677 # rescale plot to page or preview plot area
1678 self
.graph
._setSize
(plotAreaW
,plotAreaH
)
1680 # Set offset and scale
1681 dc
.SetDeviceOrigin(pixLeft
,pixTop
)
1683 # Thicken up pens and increase marker size for printing
1684 ratioW
= float(plotAreaW
)/clientDcSize
[0]
1685 ratioH
= float(plotAreaH
)/clientDcSize
[1]
1686 aveScale
= (ratioW
+ratioH
)/2
1687 self
.graph
._setPrinterScale
(aveScale
) # tickens up pens for printing
1689 self
.graph
._printDraw
(dc
)
1690 # rescale back to original
1691 self
.graph
._setSize
()
1692 self
.graph
._setPrinterScale
(1)
1693 self
.graph
.Redraw() #to get point label scale and shift correct
1700 #---------------------------------------------------------------------------
1701 # if running standalone...
1703 # ...a sample implementation using the above
1706 def _draw1Objects():
1707 # 100 points sin function, plotted as green circles
1708 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(200)/200.
1709 data1
.shape
= (100, 2)
1710 data1
[:,1] = _Numeric
.sin(data1
[:,0])
1711 markers1
= PolyMarker(data1
, legend
='Green Markers', colour
='green', marker
='circle',size
=1)
1713 # 50 points cos function, plotted as red line
1714 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(100)/100.
1715 data1
.shape
= (50,2)
1716 data1
[:,1] = _Numeric
.cos(data1
[:,0])
1717 lines
= PolyLine(data1
, legend
= 'Red Line', colour
='red')
1719 # A few more points...
1721 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1722 (3.*pi
/4., -1)], legend
='Cross Legend', colour
='blue',
1725 return PlotGraphics([markers1
, lines
, markers2
],"Graph Title", "X Axis", "Y Axis")
1727 def _draw2Objects():
1728 # 100 points sin function, plotted as green dots
1729 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(200)/200.
1730 data1
.shape
= (100, 2)
1731 data1
[:,1] = _Numeric
.sin(data1
[:,0])
1732 line1
= PolyLine(data1
, legend
='Green Line', colour
='green', width
=6, style
=wx
.DOT
)
1734 # 50 points cos function, plotted as red dot-dash
1735 data1
= 2.*_Numeric
.pi
*_Numeric
.arange(100)/100.
1736 data1
.shape
= (50,2)
1737 data1
[:,1] = _Numeric
.cos(data1
[:,0])
1738 line2
= PolyLine(data1
, legend
='Red Line', colour
='red', width
=3, style
= wx
.DOT_DASH
)
1740 # A few more points...
1742 markers1
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
1743 (3.*pi
/4., -1)], legend
='Cross Hatch Square', colour
='blue', width
= 3, size
= 6,
1744 fillcolour
= 'red', fillstyle
= wx
.CROSSDIAG_HATCH
,
1747 return PlotGraphics([markers1
, line1
, line2
], "Big Markers with Different Line Styles")
1749 def _draw3Objects():
1750 markerList
= ['circle', 'dot', 'square', 'triangle', 'triangle_down',
1751 'cross', 'plus', 'circle']
1753 for i
in range(len(markerList
)):
1754 m
.append(PolyMarker([(2*i
+.5,i
+.5)], legend
=markerList
[i
], colour
='blue',
1755 marker
=markerList
[i
]))
1756 return PlotGraphics(m
, "Selection of Markers", "Minimal Axis", "No Axis")
1758 def _draw4Objects():
1760 data1
= _Numeric
.arange(5e5
,1e6
,10)
1761 data1
.shape
= (25000, 2)
1762 line1
= PolyLine(data1
, legend
='Wide Line', colour
='green', width
=5)
1764 # A few more points...
1765 markers2
= PolyMarker(data1
, legend
='Square', colour
='blue',
1767 return PlotGraphics([line1
, markers2
], "25,000 Points", "Value X", "")
1769 def _draw5Objects():
1770 # Empty graph with axis defined but no points/lines
1772 line1
= PolyLine(points
, legend
='Wide Line', colour
='green', width
=5)
1773 return PlotGraphics([line1
], "Empty Plot With Just Axes", "Value X", "Value Y")
1775 def _draw6Objects():
1777 points1
=[(1,0), (1,10)]
1778 line1
= PolyLine(points1
, colour
='green', legend
='Feb.', width
=10)
1779 points1g
=[(2,0), (2,4)]
1780 line1g
= PolyLine(points1g
, colour
='red', legend
='Mar.', width
=10)
1781 points1b
=[(3,0), (3,6)]
1782 line1b
= PolyLine(points1b
, colour
='blue', legend
='Apr.', width
=10)
1784 points2
=[(4,0), (4,12)]
1785 line2
= PolyLine(points2
, colour
='Yellow', legend
='May', width
=10)
1786 points2g
=[(5,0), (5,8)]
1787 line2g
= PolyLine(points2g
, colour
='orange', legend
='June', width
=10)
1788 points2b
=[(6,0), (6,4)]
1789 line2b
= PolyLine(points2b
, colour
='brown', legend
='July', width
=10)
1791 return PlotGraphics([line1
, line1g
, line1b
, line2
, line2g
, line2b
],
1792 "Bar Graph - (Turn on Grid, Legend)", "Months", "Number of Students")
1794 def _draw7Objects():
1795 # four bars of various styles colors and sizes
1796 bar1
= PolyBar((0,1), colour
='green', size
=4, labels
='Green bar')
1797 bar2
= PolyBar((2,1), colour
='red', size
=7, labels
='Red bar',
1798 fillstyle
=wx
.CROSSDIAG_HATCH
)
1799 bar3
= PolyBar([(1,3),(3,4)], colour
='blue',
1800 labels
=['Blue bar 1', 'Blue bar 2'],
1801 fillstyle
=wx
.TRANSPARENT
)
1802 bars
= [bar1
, bar2
, bar3
]
1803 return PlotGraphics(bars
,
1804 "Graph Title", "Bar names", "Bar height"), getBarTicksGen(*bars
)
1806 def _draw8Objects():
1807 # four bars in two groups, overlayed to a line plot
1809 bar1
= PolyBar([(x1
,1.),(x1
+2,2.)], colour
='green', size
=4,
1810 legend
="1998", fillstyle
=wx
.CROSSDIAG_HATCH
)
1811 bar2
= PolyBar(bar1
.offset([1.2,2.5]), colour
='red', size
=7,
1812 legend
="2000", labels
=['cars','trucks'])
1813 line1
= PolyLine([(x1
,1.5), (x2
,1.2), (x1
+2,1), (x2
+2,2)], colour
='blue')
1814 return PlotGraphics([bar1
, bar2
, line1
],
1815 "Graph Title", "Bar names", "Bar height"), getBarTicksGen(bar1
, bar2
)
1817 class TestFrame(wx
.Frame
):
1818 def __init__(self
, parent
, id, title
):
1819 wx
.Frame
.__init
__(self
, parent
, id, title
,
1820 wx
.DefaultPosition
, (600, 400))
1822 # Now Create the menu bar and items
1823 self
.mainmenu
= wx
.MenuBar()
1826 menu
.Append(200, 'Page Setup...', 'Setup the printer page')
1827 self
.Bind(wx
.EVT_MENU
, self
.OnFilePageSetup
, id=200)
1829 menu
.Append(201, 'Print Preview...', 'Show the current plot on page')
1830 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrintPreview
, id=201)
1832 menu
.Append(202, 'Print...', 'Print the current plot')
1833 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrint
, id=202)
1835 menu
.Append(203, 'Save Plot...', 'Save current plot')
1836 self
.Bind(wx
.EVT_MENU
, self
.OnSaveFile
, id=203)
1838 menu
.Append(205, 'E&xit', 'Enough of this already!')
1839 self
.Bind(wx
.EVT_MENU
, self
.OnFileExit
, id=205)
1840 self
.mainmenu
.Append(menu
, '&File')
1843 menu
.Append(206, 'Draw1', 'Draw plots1')
1844 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw1
, id=206)
1845 menu
.Append(207, 'Draw2', 'Draw plots2')
1846 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw2
, id=207)
1847 menu
.Append(208, 'Draw3', 'Draw plots3')
1848 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw3
, id=208)
1849 menu
.Append(209, 'Draw4', 'Draw plots4')
1850 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw4
, id=209)
1851 menu
.Append(210, 'Draw5', 'Draw plots5')
1852 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw5
, id=210)
1853 menu
.Append(260, 'Draw6', 'Draw plots6')
1854 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw6
, id=260)
1855 menu
.Append(261, 'Draw7', 'Draw plots7')
1856 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw7
, id=261)
1857 menu
.Append(262, 'Draw8', 'Draw plots8')
1858 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw8
, id=262)
1861 menu
.Append(211, '&Redraw', 'Redraw plots')
1862 self
.Bind(wx
.EVT_MENU
,self
.OnPlotRedraw
, id=211)
1863 menu
.Append(212, '&Clear', 'Clear canvas')
1864 self
.Bind(wx
.EVT_MENU
,self
.OnPlotClear
, id=212)
1865 menu
.Append(213, '&Scale', 'Scale canvas')
1866 self
.Bind(wx
.EVT_MENU
,self
.OnPlotScale
, id=213)
1867 menu
.Append(214, 'Enable &Zoom', 'Enable Mouse Zoom', kind
=wx
.ITEM_CHECK
)
1868 self
.Bind(wx
.EVT_MENU
,self
.OnEnableZoom
, id=214)
1869 menu
.Append(215, 'Enable &Grid', 'Turn on Grid', kind
=wx
.ITEM_CHECK
)
1870 self
.Bind(wx
.EVT_MENU
,self
.OnEnableGrid
, id=215)
1871 menu
.Append(220, 'Enable &Legend', 'Turn on Legend', kind
=wx
.ITEM_CHECK
)
1872 self
.Bind(wx
.EVT_MENU
,self
.OnEnableLegend
, id=220)
1873 menu
.Append(222, 'Enable &Point Label', 'Show Closest Point', kind
=wx
.ITEM_CHECK
)
1874 self
.Bind(wx
.EVT_MENU
,self
.OnEnablePointLabel
, id=222)
1876 menu
.Append(225, 'Scroll Up 1', 'Move View Up 1 Unit')
1877 self
.Bind(wx
.EVT_MENU
,self
.OnScrUp
, id=225)
1878 menu
.Append(230, 'Scroll Rt 2', 'Move View Right 2 Units')
1879 self
.Bind(wx
.EVT_MENU
,self
.OnScrRt
, id=230)
1880 menu
.Append(235, '&Plot Reset', 'Reset to original plot')
1881 self
.Bind(wx
.EVT_MENU
,self
.OnReset
, id=235)
1883 self
.mainmenu
.Append(menu
, '&Plot')
1886 menu
.Append(300, '&About', 'About this thing...')
1887 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=300)
1888 self
.mainmenu
.Append(menu
, '&Help')
1890 self
.SetMenuBar(self
.mainmenu
)
1892 # A status bar to tell people what's happening
1893 self
.CreateStatusBar(1)
1895 self
.client
= PlotCanvas(self
)
1896 #define the function for drawing pointLabels
1897 self
.client
.SetPointLabelFunc(self
.DrawPointLabel
)
1898 # Create mouse event for showing cursor coords in status bar
1899 self
.client
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnMouseLeftDown
)
1900 # Show closest point when enabled
1901 self
.client
.Bind(wx
.EVT_MOTION
, self
.OnMotion
)
1905 def DrawPointLabel(self
, dc
, mDataDict
):
1906 """This is the fuction that defines how the pointLabels are plotted
1907 dc - DC that will be passed
1908 mDataDict - Dictionary of data that you want to use for the pointLabel
1910 As an example I have decided I want a box at the curve point
1911 with some text information about the curve plotted below.
1912 Any wxDC method can be used.
1915 dc
.SetPen(wx
.Pen(wx
.BLACK
))
1916 dc
.SetBrush(wx
.Brush( wx
.BLACK
, wx
.SOLID
) )
1918 sx
, sy
= mDataDict
["scaledXY"] #scaled x,y of closest point
1919 dc
.DrawRectangle( sx
-5,sy
-5, 10, 10) #10by10 square centered on point
1920 px
,py
= mDataDict
["pointXY"]
1921 cNum
= mDataDict
["curveNum"]
1922 pntIn
= mDataDict
["pIndex"]
1923 legend
= mDataDict
["legend"]
1924 #make a string to display
1925 s
= "Crv# %i, '%s', Pt. (%.2f,%.2f), PtInd %i" %(cNum
, legend
, px
, py
, pntIn
)
1926 dc
.DrawText(s
, sx
, sy
+1)
1929 def OnMouseLeftDown(self
,event
):
1930 s
= "Left Mouse Down at Point: (%.4f, %.4f)" % self
.client
.GetXY(event
)
1931 self
.SetStatusText(s
)
1932 event
.Skip() #allows plotCanvas OnMouseLeftDown to be called
1934 def OnMotion(self
, event
):
1935 #show closest point (when enbled)
1936 if self
.client
.GetEnablePointLabel() == True:
1937 #make up dict with info for the pointLabel
1938 #I've decided to mark the closest point on the closest curve
1939 dlst
= self
.client
.GetClosetPoint( self
.client
.GetXY(event
), pointScaled
= True)
1940 if dlst
!= []: #returns [] if none
1941 curveNum
, legend
, pIndex
, pointXY
, scaledXY
, distance
= dlst
1942 #make up dictionary to pass to my user function (see DrawPointLabel)
1943 mDataDict
= {"curveNum":curveNum
, "legend":legend
, "pIndex":pIndex
,\
1944 "pointXY":pointXY
, "scaledXY":scaledXY
}
1945 #pass dict to update the pointLabel
1946 self
.client
.UpdatePointLabel(mDataDict
)
1947 event
.Skip() #go to next handler
1949 def OnFilePageSetup(self
, event
):
1950 self
.client
.PageSetup()
1952 def OnFilePrintPreview(self
, event
):
1953 self
.client
.PrintPreview()
1955 def OnFilePrint(self
, event
):
1956 self
.client
.Printout()
1958 def OnSaveFile(self
, event
):
1959 self
.client
.SaveFile()
1961 def OnFileExit(self
, event
):
1964 def OnPlotDraw1(self
, event
):
1965 self
.resetDefaults()
1966 self
.client
.Draw(_draw1Objects())
1968 def OnPlotDraw2(self
, event
):
1969 self
.resetDefaults()
1970 self
.client
.Draw(_draw2Objects())
1972 def OnPlotDraw3(self
, event
):
1973 self
.resetDefaults()
1974 self
.client
.SetFont(wx
.Font(10,wx
.SCRIPT
,wx
.NORMAL
,wx
.NORMAL
))
1975 self
.client
.SetFontSizeAxis(20)
1976 self
.client
.SetFontSizeLegend(12)
1977 self
.client
.SetXSpec('min')
1978 self
.client
.SetYSpec('none')
1979 self
.client
.Draw(_draw3Objects())
1981 def OnPlotDraw4(self
, event
):
1982 self
.resetDefaults()
1983 drawObj
= _draw4Objects()
1984 self
.client
.Draw(drawObj
)
1986 ## start = _time.clock()
1987 ## for x in range(10):
1988 ## self.client.Draw(drawObj)
1989 ## print "10 plots of Draw4 took: %f sec."%(_time.clock() - start)
1992 def OnPlotDraw5(self
, event
):
1993 # Empty plot with just axes
1994 self
.resetDefaults()
1995 drawObj
= _draw5Objects()
1996 # make the axis X= (0,5), Y=(0,10)
1997 # (default with None is X= (-1,1), Y= (-1,1))
1998 self
.client
.Draw(drawObj
, xAxis
= (0,5), yAxis
= (0,10))
2000 def OnPlotDraw6(self
, event
):
2002 self
.resetDefaults()
2003 #self.client.SetEnableLegend(True) #turn on Legend
2004 #self.client.SetEnableGrid(True) #turn on Grid
2005 self
.client
.SetXSpec('none') #turns off x-axis scale
2006 self
.client
.SetYSpec('auto')
2007 self
.client
.Draw(_draw6Objects(), xAxis
= (0,7))
2009 def OnPlotDraw7(self
, event
):
2010 #Bar Graph and custom ticks Example, using PolyBar
2011 self
.resetDefaults()
2012 bars
, tickGenerator
= _draw7Objects()
2013 #note that yTicks is not necessary here, used only
2014 #to show off custom ticks
2015 self
.client
.Draw(bars
, xAxis
=(0,4),yAxis
= (0,5),
2016 xTicks
=tickGenerator
, yTicks
=[0,1,3])
2018 def OnPlotDraw8(self
, event
):
2019 #Bar Graph and custom ticks Example, using PolyBar
2020 self
.resetDefaults()
2021 plot
, tickGenerator
= _draw8Objects()
2022 self
.client
.Draw(plot
, xAxis
=(0,4),yAxis
= (0,5), xTicks
=tickGenerator
)
2025 def OnPlotRedraw(self
,event
):
2026 self
.client
.Redraw()
2028 def OnPlotClear(self
,event
):
2031 def OnPlotScale(self
, event
):
2032 if self
.client
.BeenDrawn():
2033 self
.client
.ChangeAxes((1,3.05),(0,1))
2036 def OnEnableZoom(self
, event
):
2037 self
.client
.SetEnableZoom(event
.IsChecked())
2039 def OnEnableGrid(self
, event
):
2040 self
.client
.SetEnableGrid(event
.IsChecked())
2042 def OnEnableLegend(self
, event
):
2043 self
.client
.SetEnableLegend(event
.IsChecked())
2045 def OnEnablePointLabel(self
, event
):
2046 self
.client
.SetEnablePointLabel(event
.IsChecked())
2048 def OnScrUp(self
, event
):
2049 self
.client
.ScrollUp(1)
2051 def OnScrRt(self
,event
):
2052 self
.client
.ScrollRight(2)
2054 def OnReset(self
,event
):
2057 def OnHelpAbout(self
, event
):
2058 from wx
.lib
.dialogs
import ScrolledMessageDialog
2059 about
= ScrolledMessageDialog(self
, __doc__
, "About...")
2062 def resetDefaults(self
):
2063 """Just to reset the fonts back to the PlotCanvas defaults"""
2064 self
.client
.SetFont(wx
.Font(10,wx
.SWISS
,wx
.NORMAL
,wx
.NORMAL
))
2065 self
.client
.SetFontSizeAxis(10)
2066 self
.client
.SetFontSizeLegend(7)
2067 self
.client
.SetXSpec('auto')
2068 self
.client
.SetYSpec('auto')
2073 class MyApp(wx
.App
):
2075 wx
.InitAllImageHandlers()
2076 frame
= TestFrame(None, -1, "PlotCanvas")
2078 self
.SetTopWindow(frame
)
2085 if __name__
== '__main__':