]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/floatcanvas/FloatCanvas.py
2 from Numeric
import array
, asarray
, Float
, cos
, sin
, pi
, sum , minimum
, maximum
, Int32
, zeros
, ones
, concatenate
, sqrt
, argmin
, power
, absolute
, matrixmultiply
, transpose
, sometrue
5 from numarray
import array
, asarray
, Float
, cos
, sin
, pi
, sum , minimum
, maximum
, Int32
, zeros
, concatenate
, matrixmultiply
, transpose
, sometrue
7 raise ImportError ( "I could not import either Numeric or numarray" )
9 from time
import clock
, sleep
16 ## A global variable to hold the Pixels per inch that wxWindows thinks is in use
17 ## This is used for scaling fonts.
18 ## This can't be computed on module __init__, because a wx.App might not have iniitalized yet.
21 ## a custom Exceptions:
23 class FloatCanvasError ( Exception ):
26 ## Create all the mouse events
27 # I don't see a need for these two, but maybe some day!
28 #EVT_FC_ENTER_WINDOW = wx.NewEventType()
29 #EVT_FC_LEAVE_WINDOW = wx.NewEventType()
30 EVT_FC_LEFT_DOWN
= wx
. NewEventType ()
31 EVT_FC_LEFT_UP
= wx
. NewEventType ()
32 EVT_FC_LEFT_DCLICK
= wx
. NewEventType ()
33 EVT_FC_MIDDLE_DOWN
= wx
. NewEventType ()
34 EVT_FC_MIDDLE_UP
= wx
. NewEventType ()
35 EVT_FC_MIDDLE_DCLICK
= wx
. NewEventType ()
36 EVT_FC_RIGHT_DOWN
= wx
. NewEventType ()
37 EVT_FC_RIGHT_UP
= wx
. NewEventType ()
38 EVT_FC_RIGHT_DCLICK
= wx
. NewEventType ()
39 EVT_FC_MOTION
= wx
. NewEventType ()
40 EVT_FC_MOUSEWHEEL
= wx
. NewEventType ()
41 ## these two are for the hit-test stuff, I never make them real Events
42 EVT_FC_ENTER_OBJECT
= wx
. NewEventType ()
43 EVT_FC_LEAVE_OBJECT
= wx
. NewEventType ()
45 ##Create all mouse event binding functions
46 #def EVT_ENTER_WINDOW( window, function ):
47 # window.Connect( -1, -1, EVT_FC_ENTER_WINDOW, function )
48 #def EVT_LEAVE_WINDOW( window, function ):
49 # window.Connect( -1, -1,EVT_FC_LEAVE_WINDOW , function )
50 def EVT_LEFT_DOWN ( window
, function
):
51 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_DOWN
, function
)
52 def EVT_LEFT_UP ( window
, function
):
53 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_UP
, function
)
54 def EVT_LEFT_DCLICK ( window
, function
):
55 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_DCLICK
, function
)
56 def EVT_MIDDLE_DOWN ( window
, function
):
57 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_DOWN
, function
)
58 def EVT_MIDDLE_UP ( window
, function
):
59 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_UP
, function
)
60 def EVT_MIDDLE_DCLICK ( window
, function
):
61 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_DCLICK
, function
)
62 def EVT_RIGHT_DOWN ( window
, function
):
63 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_DOWN
, function
)
64 def EVT_RIGHT_UP ( window
, function
):
65 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_UP
, function
)
66 def EVT_RIGHT_DCLICK ( window
, function
):
67 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_DCLICK
, function
)
68 def EVT_MOTION ( window
, function
):
69 window
. Connect ( - 1 , - 1 , EVT_FC_MOTION
, function
)
70 def EVT_MOUSEWHEEL ( window
, function
):
71 window
. Connect ( - 1 , - 1 , EVT_FC_MOUSEWHEEL
, function
)
73 class _MouseEvent ( wx
. PyCommandEvent
):
77 This event class takes a regular wxWindows mouse event as a parameter,
78 and wraps it so that there is access to all the original methods. This
79 is similar to subclassing, but you can't subclass a wxWindows event
81 The goal is to be able to it just like a regular mouse event.
85 GetCoords() , which returns and (x,y) tuple in world coordinates.
87 Another difference is that it is a CommandEvent, which propagates up
88 the window hierarchy until it is handled.
92 def __init__ ( self
, EventType
, NativeEvent
, WinID
, Coords
= None ):
93 wx
. PyCommandEvent
.__ init
__ ( self
)
95 self
. SetEventType ( EventType
)
96 self
._ NativeEvent
= NativeEvent
99 # I don't think this is used.
100 # def SetCoords(self,Coords):
101 # self.Coords = Coords
106 def __getattr__ ( self
, name
):
107 #return eval(self.NativeEvent.__getattr__(name) )
108 return getattr ( self
._ NativeEvent
, name
)
110 def _cycleidxs ( indexcount
, maxvalue
, step
):
113 Utility function used by _colorGenerator
119 for idx
in xrange ( 0 , maxvalue
, step
):
120 for tail
in _cycleidxs ( indexcount
- 1 , maxvalue
, step
):
123 def _colorGenerator ():
127 Generates a seris of unique colors used to do hit-tests with the HIt
132 if sys
. platform
== 'darwin' :
135 b
= wx
. EmptyBitmap ( 1 , 1 )
142 raise "ColorGenerator does not work with depth = %s " % depth
143 return _cycleidxs ( indexcount
= 3 , maxvalue
= 256 , step
= step
)
146 #### I don't know if the Set objects are useful, beyond the pointset
147 #### object The problem is that when zoomed in, the BB is checked to see
148 #### whether to draw the object. A Set object can defeat this. ONe day
149 #### I plan to write some custon C++ code to draw sets of objects
151 ##class ObjectSetMixin:
153 ## A mix-in class for draw objects that are sets of objects
155 ## It contains methods for setting lists of pens and brushes
158 ## def SetPens(self,LineColors,LineStyles,LineWidths):
160 ## This method used when an object could have a list of pens, rather than just one
161 ## It is used for LineSet, and perhaps others in the future.
163 ## fixme: this should be in a mixin
165 ## fixme: this is really kludgy, there has got to be a better way!
170 ## if type(LineColors) == types.ListType:
171 ## length = len(LineColors)
173 ## LineColors = [LineColors]
175 ## if type(LineStyles) == types.ListType:
176 ## length = len(LineStyles)
178 ## LineStyles = [LineStyles]
180 ## if type(LineWidths) == types.ListType:
181 ## length = len(LineWidths)
183 ## LineWidths = [LineWidths]
186 ## if len(LineColors) == 1:
187 ## LineColors = LineColors*length
188 ## if len(LineStyles) == 1:
189 ## LineStyles = LineStyles*length
190 ## if len(LineWidths) == 1:
191 ## LineWidths = LineWidths*length
194 ## for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths):
195 ## if LineColor is None or LineStyle is None:
196 ## self.Pens.append(wx.TRANSPARENT_PEN)
197 ## # what's this for?> self.LineStyle = 'Transparent'
198 ## if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
199 ## Pen = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle])
200 ## self.Pens.append(Pen)
202 ## self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)])
204 ## self.Pens = self.Pens[0]
208 This is the base class for all the objects that can be drawn.
210 One must subclass from this (and an assortment of Mixins) to create
215 def __init__ ( self
, InForeground
= False ):
216 self
. InForeground
= InForeground
221 self
. CallBackFuncs
= {}
223 ## these are the defaults
227 self
. MinHitLineWidth
= 3
228 self
. HitLineWidth
= 3 ## this gets re-set by the subclasses if necessary
233 self
. FillStyle
= "Solid"
235 # I pre-define all these as class variables to provide an easier
236 # interface, and perhaps speed things up by caching all the Pens
237 # and Brushes, although that may not help, as I think wx now
238 # does that on it's own. Send me a note if you know!
241 ( None , "Transparent" ) : wx
. TRANSPARENT_BRUSH
,
242 ( "Blue" , "Solid" ) : wx
. BLUE_BRUSH
,
243 ( "Green" , "Solid" ) : wx
. GREEN_BRUSH
,
244 ( "White" , "Solid" ) : wx
. WHITE_BRUSH
,
245 ( "Black" , "Solid" ) : wx
. BLACK_BRUSH
,
246 ( "Grey" , "Solid" ) : wx
. GREY_BRUSH
,
247 ( "MediumGrey" , "Solid" ) : wx
. MEDIUM_GREY_BRUSH
,
248 ( "LightGrey" , "Solid" ) : wx
. LIGHT_GREY_BRUSH
,
249 ( "Cyan" , "Solid" ) : wx
. CYAN_BRUSH
,
250 ( "Red" , "Solid" ) : wx
. RED_BRUSH
253 ( None , "Transparent" , 1 ) : wx
. TRANSPARENT_PEN
,
254 ( "Green" , "Solid" , 1 ) : wx
. GREEN_PEN
,
255 ( "White" , "Solid" , 1 ) : wx
. WHITE_PEN
,
256 ( "Black" , "Solid" , 1 ) : wx
. BLACK_PEN
,
257 ( "Grey" , "Solid" , 1 ) : wx
. GREY_PEN
,
258 ( "MediumGrey" , "Solid" , 1 ) : wx
. MEDIUM_GREY_PEN
,
259 ( "LightGrey" , "Solid" , 1 ) : wx
. LIGHT_GREY_PEN
,
260 ( "Cyan" , "Solid" , 1 ) : wx
. CYAN_PEN
,
261 ( "Red" , "Solid" , 1 ) : wx
. RED_PEN
265 "Transparent" : wx
. TRANSPARENT
,
267 "BiDiagonalHatch" : wx
. BDIAGONAL_HATCH
,
268 "CrossDiagHatch" : wx
. CROSSDIAG_HATCH
,
269 "FDiagonal_Hatch" : wx
. FDIAGONAL_HATCH
,
270 "CrossHatch" : wx
. CROSS_HATCH
,
271 "HorizontalHatch" : wx
. HORIZONTAL_HATCH
,
272 "VerticalHatch" : wx
. VERTICAL_HATCH
277 "Transparent" : wx
. TRANSPARENT
,
279 "LongDash" : wx
. LONG_DASH
,
280 "ShortDash" : wx
. SHORT_DASH
,
281 "DotDash" : wx
. DOT_DASH
,
284 def Bind ( self
, Event
, CallBackFun
):
285 self
. CallBackFuncs
[ Event
] = CallBackFun
287 self
._ Canvas
. UseHitTest
= True
288 if not self
._ Canvas
._ HTdc
:
289 self
._ Canvas
. MakeNewHTdc ()
290 if not self
. HitColor
:
291 if not self
._ Canvas
. HitColorGenerator
:
292 self
._ Canvas
. HitColorGenerator
= _colorGenerator ()
293 self
._ Canvas
. HitColorGenerator
. next () # first call to prevent the background color from being used.
294 self
. HitColor
= self
._ Canvas
. HitColorGenerator
. next ()
295 self
. SetHitPen ( self
. HitColor
, self
. HitLineWidth
)
296 self
. SetHitBrush ( self
. HitColor
)
297 # put the object in the hit dict, indexed by it's color
298 if not self
._ Canvas
. HitDict
:
299 self
._ Canvas
. MakeHitDict ()
300 self
._ Canvas
. HitDict
[ Event
][ self
. HitColor
] = ( self
) # put the object in the hit dict, indexed by it's color
303 ## fixme: this only removes one from each list, there could be more.
304 if self
._ Canvas
. HitDict
:
305 for List
in self
._ Canvas
. HitDict
. itervalues ():
313 def SetBrush ( self
, FillColor
, FillStyle
):
314 if FillColor
is None or FillStyle
is None :
315 self
. Brush
= wx
. TRANSPARENT_BRUSH
316 self
. FillStyle
= "Transparent"
318 self
. Brush
= self
. BrushList
. setdefault ( ( FillColor
, FillStyle
), wx
. Brush ( FillColor
, self
. FillStyleList
[ FillStyle
] ) )
320 def SetPen ( self
, LineColor
, LineStyle
, LineWidth
):
321 if ( LineColor
is None ) or ( LineStyle
is None ):
322 self
. Pen
= wx
. TRANSPARENT_PEN
323 self
. LineStyle
= 'Transparent'
325 self
. Pen
= self
. PenList
. setdefault ( ( LineColor
, LineStyle
, LineWidth
), wx
. Pen ( LineColor
, LineWidth
, self
. LineStyleList
[ LineStyle
]) )
327 def SetHitBrush ( self
, HitColor
):
329 self
. HitBrush
= wx
. TRANSPARENT_BRUSH
331 self
. HitBrush
= self
. BrushList
. setdefault ( ( HitColor
, "solid" ), wx
. Brush ( HitColor
, self
. FillStyleList
[ "Solid" ] ) )
333 def SetHitPen ( self
, HitColor
, LineWidth
):
335 self
. HitPen
= wx
. TRANSPARENT_PEN
337 self
. HitPen
= self
. PenList
. setdefault ( ( HitColor
, "solid" , self
. HitLineWidth
), wx
. Pen ( HitColor
, self
. HitLineWidth
, self
. LineStyleList
[ "Solid" ]) )
339 def PutInBackground ( self
):
340 if self
._ Canvas
and self
. InForeground
:
341 self
._ Canvas
._ ForeDrawList
. remove ( self
)
342 self
._ Canvas
._ DrawList
. append ( self
)
343 self
._ Canvas
._ BackgroundDirty
= True
344 self
. InForeground
= False
346 def PutInForeground ( self
):
347 if self
._ Canvas
and ( not self
. InForeground
):
348 self
._ Canvas
._ ForeDrawList
. append ( self
)
349 self
._ Canvas
._ DrawList
. remove ( self
)
350 self
._ Canvas
._ BackgroundDirty
= True
351 self
. InForeground
= True
353 class ColorOnlyMixin
:
356 Mixin class for objects that have just one color, rather than a fill
361 def SetColor ( self
, Color
):
362 self
. SetPen ( Color
, "Solid" , 1 )
363 self
. SetBrush ( Color
, "Solid" )
365 SetFillColor
= SetColor
# Just to provide a consistant interface
370 Mixin class for objects that have just one color, rather than a fill
375 def SetLineColor ( self
, LineColor
):
376 self
. LineColor
= LineColor
377 self
. SetPen ( LineColor
, self
. LineStyle
, self
. LineWidth
)
379 def SetLineStyle ( self
, LineStyle
):
380 self
. LineStyle
= LineStyle
381 self
. SetPen ( self
. LineColor
, LineStyle
, self
. LineWidth
)
383 def SetLineWidth ( self
, LineWidth
):
384 self
. LineWidth
= LineWidth
385 self
. SetPen ( self
. LineColor
, self
. LineStyle
, LineWidth
)
387 class LineAndFillMixin ( LineOnlyMixin
):
390 Mixin class for objects that have both a line and a fill color and
394 def SetFillColor ( self
, FillColor
):
395 self
. FillColor
= FillColor
396 self
. SetBrush ( FillColor
, self
. FillStyle
)
398 def SetFillStyle ( self
, FillStyle
):
399 self
. FillStyle
= FillStyle
400 self
. SetBrush ( self
. FillColor
, FillStyle
)
405 This is a mixin class that provides some methods suitable for use
406 with objects that have a single (x,y) coordinate pair.
410 def Move ( self
, Delta
):
413 Move(Delta): moves the object by delta, where delta is a
414 (dx,dy) pair. Ideally a Numpy array of shape (2,)
418 Delta
= asarray ( Delta
, Float
)
420 self
. BoundingBox
= self
. BoundingBox
+ Delta
422 self
._ Canvas
. BoundingBoxDirty
= True
424 def SetXY ( self
, x
, y
):
425 self
. XY
= array ( ( x
, y
), Float
)
426 self
. CalcBoundingBox ()
428 def CalcBoundingBox ( self
):
429 ## This may get overwritten in some subclasses
430 self
. BoundingBox
= array ( ( self
. XY
, self
. XY
), Float
)
432 def SetPoint ( self
, xy
):
433 self
. XY
= array ( xy
, Float
)
435 self
. CalcBoundingBox ()
437 class PointsObjectMixin
:
440 This is a mixin class that provides some methods suitable for use
441 with objects that have a set of (x,y) coordinate pairs.
446 ## This is code for the PointsObjectMixin object, it needs to be adapted and tested.
447 ## Is the neccesary at all: you can always do:
448 ## Object.SetPoints( Object.Points + delta, copy = False)
449 ## def Move(self, Delta ):
452 ## Move(Delta): moves the object by delta, where delta is an (dx,
453 ## dy) pair. Ideally a Numpy array of shape (2,)
457 ## Delta = array(Delta, Float)
459 ## self.BoundingBox = self.BoundingBox + Delta##array((self.XY, (self.XY + self.WH)), Float)
461 ## self._Canvas.BoundingBoxDirty = True
463 def CalcBoundingBox ( self
):
464 self
. BoundingBox
= array ((( min ( self
. Points
[:, 0 ]),
465 min ( self
. Points
[:, 1 ]) ),
466 ( max ( self
. Points
[:, 0 ]),
467 max ( self
. Points
[:, 1 ]) ) ), Float
)
469 self
._ Canvas
. BoundingBoxDirty
= True
471 def SetPoints ( self
, Points
, copy
= True ):
473 Sets the coordinates of the points of the object to Points (NX2 array).
475 By default, a copy is made, if copy is set to False, a reference
476 is used, iff Points is a NumPy array of Floats. This allows you
477 to change some or all of the points without making any copies.
481 Points = Object.Points
482 Points += (5,10) # shifts the points 5 in the x dir, and 10 in the y dir.
483 Object.SetPoints(Points, False) # Sets the points to the same array as it was
487 self
. Points
= array ( Points
, Float
)
488 self
. Points
. shape
= (- 1 , 2 ) # Make sure it is a NX2 array, even if there is only one point
490 self
. Points
= asarray ( Points
, Float
)
491 self
. CalcBoundingBox ()
494 class Polygon ( DrawObject
, PointsObjectMixin
, LineAndFillMixin
):
498 The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of
499 point coordinates. so that Points[N][0] is the x-coordinate of
500 point N and Points[N][1] is the y-coordinate or Points[N,0] is the
501 x-coordinate of point N and Points[N,1] is the y-coordinate for
504 The other parameters specify various properties of the Polygon, and
505 should be self explanatory.
515 InForeground
= False ):
516 DrawObject
.__ init
__ ( self
, InForeground
)
517 self
. Points
= array ( Points
, Float
) # this DOES need to make a copy
518 self
. CalcBoundingBox ()
520 self
. LineColor
= LineColor
521 self
. LineStyle
= LineStyle
522 self
. LineWidth
= LineWidth
523 self
. FillColor
= FillColor
524 self
. FillStyle
= FillStyle
526 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
528 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
529 self
. SetBrush ( FillColor
, FillStyle
)
531 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
= None , HTdc
= None ):
532 Points
= WorldToPixel ( self
. Points
)
534 dc
. SetBrush ( self
. Brush
)
535 dc
. DrawPolygon ( Points
)
536 if HTdc
and self
. HitAble
:
537 HTdc
. SetPen ( self
. HitPen
)
538 HTdc
. SetBrush ( self
. HitBrush
)
539 HTdc
. DrawPolygon ( Points
)
541 ##class PolygonSet(DrawObject):
543 ## The PolygonSet class takes a Geometry.Polygon object.
544 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
546 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
550 ## def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,InForeground = False):
551 ## DrawObject.__init__(self, InForeground)
553 ## ##fixme: there should be some error checking for everything being the right length.
556 ## self.Points = array(Points,Float)
557 ## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
559 ## self.LineColors = LineColors
560 ## self.LineStyles = LineStyles
561 ## self.LineWidths = LineWidths
562 ## self.FillColors = FillColors
563 ## self.FillStyles = FillStyles
565 ## self.SetPens(LineColors,LineStyles,LineWidths)
567 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
568 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
569 ## Points = WorldToPixel(self.Points)
570 ## Points.shape = (-1,4)
571 ## dc.DrawLineList(Points,self.Pens)
574 class Line ( DrawObject
, PointsObjectMixin
, LineOnlyMixin
):
577 The Line class takes a list of 2-tuples, or a NX2 NumPy Float array
578 of point coordinates.
580 It will draw a straight line if there are two points, and a polyline
581 if there are more than two.
584 def __init__ ( self
, Points
,
588 InForeground
= False ):
589 DrawObject
.__ init
__ ( self
, InForeground
)
592 self
. Points
= array ( Points
, Float
)
593 self
. CalcBoundingBox ()
595 self
. LineColor
= LineColor
596 self
. LineStyle
= LineStyle
597 self
. LineWidth
= LineWidth
599 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
601 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
604 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
605 Points
= WorldToPixel ( self
. Points
)
608 if HTdc
and self
. HitAble
:
609 HTdc
. SetPen ( self
. HitPen
)
610 HTdc
. DrawLines ( Points
)
612 class Arrow ( DrawObject
, XYObjectMixin
, LineOnlyMixin
):
615 Arrow(XY, # coords of origin of arrow (x,y)
616 Length, # length of arrow in pixels
617 theta, # angle of arrow in degrees: zero is straight up
618 # angle is to the right
624 InForeground = False):
626 It will draw an arrow , starting at the point, (X,Y) pointing in
637 LineWidth
= 2 , # pixels
638 ArrowHeadSize
= 8 , # pixels
639 ArrowHeadAngle
= 30 , # degrees
640 InForeground
= False ):
642 DrawObject
.__ init
__ ( self
, InForeground
)
644 self
. XY
= array ( XY
, Float
)
645 self
. XY
. shape
= ( 2 ,) # Make sure it is a 1X2 array, even if there is only one point
647 self
. Direction
= float ( Direction
)
648 self
. ArrowHeadSize
= ArrowHeadSize
649 self
. ArrowHeadAngle
= float ( ArrowHeadAngle
)
651 self
. CalcArrowPoints ()
652 self
. CalcBoundingBox ()
654 self
. LineColor
= LineColor
655 self
. LineStyle
= LineStyle
656 self
. LineWidth
= LineWidth
658 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
660 ##fixme: How should the HitTest be drawn?
661 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
663 def SetDirection ( self
, Direction
):
664 self
. Direction
= float ( Direction
)
665 self
. CalcArrowPoints ()
667 def SetLength ( self
, Length
):
669 self
. CalcArrowPoints ()
671 def SetLengthDirection ( self
, Length
, Direction
):
672 self
. Direction
= float ( Direction
)
674 self
. CalcArrowPoints ()
676 def SetLength ( self
, Length
):
678 self
. CalcArrowPoints ()
680 def CalcArrowPoints ( self
):
682 S
= self
. ArrowHeadSize
683 phi
= self
. ArrowHeadAngle
* pi
/ 360
684 theta
= ( self
. Direction
- 90.0 ) * pi
/ 180
685 ArrowPoints
= array ( ( ( 0 , L
, L
- S
* cos ( phi
), L
, L
- S
* cos ( phi
) ),
686 ( 0 , 0 , S
* sin ( phi
), 0 , - S
* sin ( phi
) ) ),
688 RotationMatrix
= array ( ( ( cos ( theta
), - sin ( theta
) ),
689 ( sin ( theta
), cos ( theta
) ) ),
692 ArrowPoints
= matrixmultiply ( RotationMatrix
, ArrowPoints
)
693 self
. ArrowPoints
= transpose ( ArrowPoints
)
695 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
697 xy
= WorldToPixel ( self
. XY
)
698 ArrowPoints
= xy
+ self
. ArrowPoints
699 dc
. DrawLines ( ArrowPoints
)
700 if HTdc
and self
. HitAble
:
701 HTdc
. SetPen ( self
. HitPen
)
702 HTdc
. DrawLines ( ArrowPoints
)
704 ##class LineSet(DrawObject, ObjectSetMixin):
706 ## The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
707 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
709 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
713 ## def __init__(self,Points,LineColors,LineStyles,LineWidths,InForeground = False):
714 ## DrawObject.__init__(self, InForeground)
716 ## NumLines = len(Points) / 2
717 ## ##fixme: there should be some error checking for everything being the right length.
720 ## self.Points = array(Points,Float)
721 ## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
723 ## self.LineColors = LineColors
724 ## self.LineStyles = LineStyles
725 ## self.LineWidths = LineWidths
727 ## self.SetPens(LineColors,LineStyles,LineWidths)
729 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
730 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
731 ## Points = WorldToPixel(self.Points)
732 ## Points.shape = (-1,4)
733 ## dc.DrawLineList(Points,self.Pens)
735 class PointSet ( DrawObject
, PointsObjectMixin
, ColorOnlyMixin
):
738 The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of
741 If Points is a sequence of tuples: Points[N][0] is the x-coordinate of
742 point N and Points[N][1] is the y-coordinate.
744 If Points is a NumPy array: Points[N,0] is the x-coordinate of point
745 N and Points[N,1] is the y-coordinate for arrays.
747 Each point will be drawn the same color and Diameter. The Diameter
748 is in screen pixels, not world coordinates.
750 The hit-test code does not distingish between the points, you will
751 only know that one of the points got hit, not which one. You can use
752 PointSet.FindClosestPoint(WorldPoint) to find out which one
754 In the case of points, the HitLineWidth is used as diameter.
757 def __init__ ( self
, Points
, Color
= "Black" , Diameter
= 1 , InForeground
= False ):
758 DrawObject
.__ init
__ ( self
, InForeground
)
760 self
. Points
= array ( Points
, Float
)
761 self
. Points
. shape
= (- 1 , 2 ) # Make sure it is a NX2 array, even if there is only one point
762 self
. CalcBoundingBox ()
763 self
. Diameter
= Diameter
765 self
. HitLineWidth
= self
. MinHitLineWidth
768 def SetDiameter ( self
, Diameter
):
769 self
. Diameter
= Diameter
772 def FindClosestPoint ( self
, XY
):
775 Returns the index of the closest point to the point, XY, given
776 in World coordinates. It's essentially random which you get if
777 there are more than one that are the same.
779 This can be used to figure out which point got hit in a mouse
780 binding callback, for instance. It's a lot faster that using a
781 lot of separate points.
784 ## kind of ugly to minimize data copying
786 d
= sum ( power ( d
, 2 , d
), 1 )
787 d
= absolute ( d
, d
) # don't need the real distance, just which is smallest
788 #dist = sqrt( sum( (self.Points - XY)**2), 1) )
791 def DrawD2 ( self
, dc
, Points
):
792 # A Little optimization for a diameter2 - point
793 dc
. DrawPointList ( Points
)
794 dc
. DrawPointList ( Points
+ ( 1 , 0 ))
795 dc
. DrawPointList ( Points
+ ( 0 , 1 ))
796 dc
. DrawPointList ( Points
+ ( 1 , 1 ))
798 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
800 Points
= WorldToPixel ( self
. Points
)
801 if self
. Diameter
<= 1 :
802 dc
. DrawPointList ( Points
)
803 elif self
. Diameter
<= 2 :
804 self
. DrawD2 ( dc
, Points
)
806 dc
. SetBrush ( self
. Brush
)
807 radius
= int ( round ( self
. Diameter
/ 2 ))
808 if len ( Points
) > 100 :
810 xywh
= concatenate (( xy
- radius
, ones ( xy
. shape
) * self
. Diameter
), 1 )
811 dc
. DrawEllipseList ( xywh
)
814 dc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
815 if HTdc
and self
. HitAble
:
816 HTdc
. SetPen ( self
. HitPen
)
817 HTdc
. SetBrush ( self
. HitBrush
)
818 if self
. Diameter
<= 1 :
819 HTdc
. DrawPointList ( Points
)
820 elif self
. Diameter
<= 2 :
821 self
. DrawD2 ( HTdc
, Points
)
823 if len ( Points
) > 100 :
825 xywh
= concatenate (( xy
- radius
, ones ( xy
. shape
) * self
. Diameter
), 1 )
826 HTdc
. DrawEllipseList ( xywh
)
829 HTdc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
831 class Point ( DrawObject
, XYObjectMixin
, ColorOnlyMixin
):
834 The Point class takes a 2-tuple, or a (2,) NumPy array of point
837 The Diameter is in screen points, not world coordinates, So the
838 Bounding box is just the point, and doesn't include the Diameter.
840 The HitLineWidth is used as diameter for the
844 def __init__ ( self
, XY
, Color
= "Black" , Diameter
= 1 , InForeground
= False ):
845 DrawObject
.__ init
__ ( self
, InForeground
)
847 self
. XY
= array ( XY
, Float
)
848 self
. XY
. shape
= ( 2 ,) # Make sure it is a 1X2 array, even if there is only one point
849 self
. CalcBoundingBox ()
851 self
. Diameter
= Diameter
853 self
. HitLineWidth
= self
. MinHitLineWidth
855 def SetDiameter ( self
, Diameter
):
856 self
. Diameter
= Diameter
859 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
861 xy
= WorldToPixel ( self
. XY
)
862 if self
. Diameter
<= 1 :
863 dc
. DrawPoint ( xy
[ 0 ], xy
[ 1 ])
865 dc
. SetBrush ( self
. Brush
)
866 radius
= int ( round ( self
. Diameter
/ 2 ))
867 dc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
868 if HTdc
and self
. HitAble
:
869 HTdc
. SetPen ( self
. HitPen
)
870 if self
. Diameter
<= 1 :
871 HTdc
. DrawPoint ( xy
[ 0 ], xy
[ 1 ])
873 HTdc
. SetBrush ( self
. HitBrush
)
874 HTdc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
876 class RectEllipse ( DrawObject
, XYObjectMixin
, LineAndFillMixin
):
877 def __init__ ( self
, x
, y
, width
, height
,
883 InForeground
= False ):
885 DrawObject
.__ init
__ ( self
, InForeground
)
887 self
. XY
= array ( ( x
, y
), Float
)
888 self
. WH
= array ( ( width
, height
), Float
)
889 self
. BoundingBox
= array ((( x
, y
), ( self
. XY
+ self
. WH
)), Float
)
890 self
. LineColor
= LineColor
891 self
. LineStyle
= LineStyle
892 self
. LineWidth
= LineWidth
893 self
. FillColor
= FillColor
894 self
. FillStyle
= FillStyle
896 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
898 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
899 self
. SetBrush ( FillColor
, FillStyle
)
901 def SetShape ( self
, x
, y
, width
, height
):
902 self
. XY
= array ( ( x
, y
), Float
)
903 self
. WH
= array ( ( width
, height
), Float
)
904 self
. CalcBoundingBox ()
907 def SetUpDraw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
):
909 dc
. SetBrush ( self
. Brush
)
910 if HTdc
and self
. HitAble
:
911 HTdc
. SetPen ( self
. HitPen
)
912 HTdc
. SetBrush ( self
. HitBrush
)
913 return ( WorldToPixel ( self
. XY
),
914 ScaleWorldToPixel ( self
. WH
) )
916 def CalcBoundingBox ( self
):
917 self
. BoundingBox
= array (( self
. XY
, ( self
. XY
+ self
. WH
) ), Float
)
918 self
._ Canvas
. BoundingBoxDirty
= True
921 class Rectangle ( RectEllipse
):
923 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
924 ( XY
, WH
) = self
. SetUpDraw ( dc
,
928 dc
. DrawRectanglePointSize ( XY
, WH
)
929 if HTdc
and self
. HitAble
:
930 HTdc
. DrawRectanglePointSize ( XY
, WH
)
932 class Ellipse ( RectEllipse
):
933 # def __init__(*args, **kwargs):
934 # RectEllipse.__init__(*args, **kwargs)
936 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
937 ( XY
, WH
) = self
. SetUpDraw ( dc
,
941 dc
. DrawEllipsePointSize ( XY
, WH
)
942 if HTdc
and self
. HitAble
:
943 HTdc
. DrawEllipsePointSize ( XY
, WH
)
945 class Circle ( Ellipse
):
946 def __init__ ( self
, x
, y
, Diameter
, ** kwargs
):
947 self
. Center
= array (( x
, y
), Float
)
948 RectEllipse
.__ init
__ ( self
,
955 def SetDiameter ( self
, Diameter
):
956 x
, y
= self
. Center
- ( Diameter
/ 2 .)
962 class TextObjectMixin ( XYObjectMixin
):
965 A mix in class that holds attributes and methods that are needed by
970 ## I'm caching fonts, because on GTK, getting a new font can take a
971 ## while. However, it gets cleared after every full draw as hanging
972 ## on to a bunch of large fonts takes a massive amount of memory.
976 def SetFont ( self
, Size
, Family
, Style
, Weight
, Underline
, FaceName
):
977 self
. Font
= self
. FontList
. setdefault ( ( Size
,
991 def SetColor ( self
, Color
):
994 def SetBackgroundColor ( self
, BackgroundColor
):
995 self
. BackgroundColor
= BackgroundColor
997 ## store the function that shift the coords for drawing text. The
998 ## "c" parameter is the correction for world coordinates, rather
999 ## than pixel coords as the y axis is reversed
1000 ShiftFunDict
= { 'tl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
) ,
1001 'tc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
) ,
1002 'tr' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
) ,
1003 'cl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
- h
/ 2 + world
* h
) ,
1004 'cc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
- h
/ 2 + world
* h
) ,
1005 'cr' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
- h
/ 2 + world
* h
) ,
1006 'bl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
- h
+ 2 * world
* h
) ,
1007 'bc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
- h
+ 2 * world
* h
) ,
1008 'br' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
- h
+ 2 * world
* h
)}
1010 class Text ( DrawObject
, TextObjectMixin
):
1012 This class creates a text object, placed at the coordinates,
1013 x,y. the "Position" argument is a two charactor string, indicating
1014 where in relation to the coordinates the string should be oriented.
1016 The first letter is: t, c, or b, for top, center and bottom The
1017 second letter is: l, c, or r, for left, center and right The
1018 position refers to the position relative to the text itself. It
1019 defaults to "tl" (top left).
1021 Size is the size of the font in pixels, or in points for printing
1022 (if it ever gets implimented). Those will be the same, If you assume
1026 Font family, a generic way of referring to fonts without
1027 specifying actual facename. One of:
1028 wx.DEFAULT: Chooses a default font.
1029 wx.DECORATIVE: A decorative font.
1030 wx.ROMAN: A formal, serif font.
1031 wx.SCRIPT: A handwriting font.
1032 wx.SWISS: A sans-serif font.
1033 wx.MODERN: A fixed pitch font.
1034 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
1036 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
1038 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
1040 The value can be True or False. At present this may have an an
1041 effect on Windows only.
1043 Alternatively, you can set the kw arg: Font, to a wx.Font, and the above will be ignored.
1045 The size is fixed, and does not scale with the drawing.
1047 The hit-test is done on the entire text extent
1051 def __init__ ( self
, String
, x
, y
,
1054 BackgroundColor
= None ,
1060 InForeground
= False ,
1063 DrawObject
.__ init
__ ( self
, InForeground
)
1065 self
. String
= String
1066 # Input size in in Pixels, compute points size from PPI info.
1067 # fixme: for printing, we'll have to do something a little different
1068 self
. Size
= int ( round ( 72.0 * Size
/ ScreenPPI
))
1071 self
. BackgroundColor
= BackgroundColor
1076 FaceName
= Font
. GetFaceName ()
1077 Family
= Font
. GetFamily ()
1078 Size
= Font
. GetPointSize ()
1079 Style
= Font
. GetStyle ()
1080 Underlined
= Font
. GetUnderlined ()
1081 Weight
= Font
. GetWeight ()
1082 self
. SetFont ( Size
, Family
, Style
, Weight
, Underline
, FaceName
)
1084 self
. BoundingBox
= array ((( x
, y
),( x
, y
)), Float
)
1088 ( self
. TextWidth
, self
. TextHeight
) = ( None , None )
1089 self
. ShiftFun
= self
. ShiftFunDict
[ Position
]
1091 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
1092 XY
= WorldToPixel ( self
. XY
)
1093 dc
. SetFont ( self
. Font
)
1094 dc
. SetTextForeground ( self
. Color
)
1095 if self
. BackgroundColor
:
1096 dc
. SetBackgroundMode ( wx
. SOLID
)
1097 dc
. SetTextBackground ( self
. BackgroundColor
)
1099 dc
. SetBackgroundMode ( wx
. TRANSPARENT
)
1100 if self
. TextWidth
is None or self
. TextHeight
is None :
1101 ( self
. TextWidth
, self
. TextHeight
) = dc
. GetTextExtent ( self
. String
)
1102 XY
= self
. ShiftFun ( XY
[ 0 ], XY
[ 1 ], self
. TextWidth
, self
. TextHeight
)
1103 dc
. DrawTextPoint ( self
. String
, XY
)
1104 if HTdc
and self
. HitAble
:
1105 HTdc
. SetPen ( self
. HitPen
)
1106 HTdc
. SetBrush ( self
. HitBrush
)
1107 HTdc
. DrawRectanglePointSize ( XY
, ( self
. TextWidth
, self
. TextHeight
) )
1109 class ScaledText ( DrawObject
, TextObjectMixin
):
1111 This class creates a text object that is scaled when zoomed. It is
1112 placed at the coordinates, x,y. the "Position" argument is a two
1113 charactor string, indicating where in relation to the coordinates
1114 the string should be oriented.
1116 The first letter is: t, c, or b, for top, center and bottom The
1117 second letter is: l, c, or r, for left, center and right The
1118 position refers to the position relative to the text itself. It
1119 defaults to "tl" (top left).
1121 Size is the size of the font in world coordinates.
1124 Font family, a generic way of referring to fonts without
1125 specifying actual facename. One of:
1126 wx.DEFAULT: Chooses a default font.
1127 wx.DECORATI: A decorative font.
1128 wx.ROMAN: A formal, serif font.
1129 wx.SCRIPT: A handwriting font.
1130 wx.SWISS: A sans-serif font.
1131 wx.MODERN: A fixed pitch font.
1132 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
1134 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
1136 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
1138 The value can be True or False. At present this may have an an
1139 effect on Windows only.
1141 Alternatively, you can set the kw arg: Font, to a wx.Font, and the
1142 above will be ignored. The size of the font you specify will be
1143 ignored, but the rest of it's attributes will be preserved.
1145 The size will scale as the drawing is zoomed.
1149 As fonts are scaled, the do end up a little different, so you don't
1150 get exactly the same picture as you scale up and doen, but it's
1153 On wxGTK1 on my Linux system, at least, using a font of over about
1154 3000 pts. brings the system to a halt. It's the Font Server using
1155 huge amounts of memory. My work around is to max the font size to
1156 3000 points, so it won't scale past there. GTK2 uses smarter font
1157 drawing, so that may not be an issue in future versions, so feel
1158 free to test. Another smarter way to do it would be to set a global
1159 zoom limit at that point.
1161 The hit-test is done on the entire text extent. This could be made
1162 optional, but I havn't gotten around to it.
1166 def __init__ ( self
, String
, x
, y
, Size
,
1168 BackgroundColor
= None ,
1175 InForeground
= False ):
1177 DrawObject
.__ init
__ ( self
, InForeground
)
1179 self
. String
= String
1180 self
. XY
= array ( ( x
, y
), Float
)
1183 self
. BackgroundColor
= BackgroundColor
1184 self
. Family
= Family
1186 self
. Weight
= Weight
1187 self
. Underline
= Underline
1191 self
. FaceName
= Font
. GetFaceName ()
1192 self
. Family
= Font
. GetFamily ()
1193 self
. Style
= Font
. GetStyle ()
1194 self
. Underlined
= Font
. GetUnderlined ()
1195 self
. Weight
= Font
. GetWeight ()
1197 # Experimental max font size value on wxGTK2: this works OK on
1198 # my system. If it's a lot larger, there is a crash, with the
1201 # The application 'FloatCanvasDemo.py' lost its
1202 # connection to the display :0.0; most likely the X server was
1203 # shut down or you killed/destroyed the application.
1205 # Windows and OS-X seem to be better behaved in this regard.
1206 # They may not draw it, but they don't crash either!
1207 self
. MaxFontSize
= 1000
1209 self
. ShiftFun
= self
. ShiftFunDict
[ Position
]
1211 self
. CalcBoundingBox ()
1214 def CalcBoundingBox ( self
):
1215 ## this isn't exact, as fonts don't scale exactly.
1217 bitmap
= wx
. EmptyBitmap ( 1 , 1 )
1218 dc
. SelectObject ( bitmap
) #wxMac needs a Bitmap selected for GetTextExtent to work.
1219 DrawingSize
= 40 # pts This effectively determines the resolution that the BB is computed to.
1220 ScaleFactor
= float ( self
. Size
) / DrawingSize
1221 dc
. SetFont ( self
. SetFont ( DrawingSize
, self
. Family
, self
. Style
, self
. Weight
, self
. Underline
, self
. FaceName
) )
1222 ( w
, h
) = dc
. GetTextExtent ( self
. String
)
1225 x
, y
= self
. ShiftFun ( self
. XY
[ 0 ], self
. XY
[ 1 ], w
, h
, world
= 1 )
1226 self
. BoundingBox
= array ((( x
, y
- h
),( x
+ w
, y
)), Float
)
1228 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
1229 ( X
, Y
) = WorldToPixel ( ( self
. XY
) )
1231 # compute the font size:
1232 Size
= abs ( ScaleWorldToPixel ( ( self
. Size
, self
. Size
) )[ 1 ] ) # only need a y coordinate length
1233 ## Check to see if the font size is large enough to blow up the X font server
1234 ## If so, limit it. Would it be better just to not draw it?
1235 ## note that this limit is dependent on how much memory you have, etc.
1236 Size
= min ( Size
, self
. MaxFontSize
)
1237 dc
. SetFont ( self
. SetFont ( Size
, self
. Family
, self
. Style
, self
. Weight
, self
. Underline
, self
. FaceName
))
1238 dc
. SetTextForeground ( self
. Color
)
1239 if self
. BackgroundColor
:
1240 dc
. SetBackgroundMode ( wx
. SOLID
)
1241 dc
. SetTextBackground ( self
. BackgroundColor
)
1243 dc
. SetBackgroundMode ( wx
. TRANSPARENT
)
1244 ( w
, h
) = dc
. GetTextExtent ( self
. String
)
1245 # compute the shift, and adjust the coordinates, if neccesary
1246 # This had to be put in here, because it changes with Zoom, as
1247 # fonts don't scale exactly.
1248 xy
= self
. ShiftFun ( X
, Y
, w
, h
)
1250 dc
. DrawTextPoint ( self
. String
, xy
)
1251 if HTdc
and self
. HitAble
:
1252 HTdc
. SetPen ( self
. HitPen
)
1253 HTdc
. SetBrush ( self
. HitBrush
)
1254 HTdc
. DrawRectanglePointSize ( xy
, ( w
, h
) )
1257 #---------------------------------------------------------------------------
1258 class FloatCanvas ( wx
. Panel
):
1262 This is a high level window for drawing maps and anything else in an
1263 arbitrary coordinate system.
1265 The goal is to provide a convenient way to draw stuff on the screen
1266 without having to deal with handling OnPaint events, converting to pixel
1267 coordinates, knowing about wxWindows brushes, pens, and colors, etc. It
1268 also provides virtually unlimited zooming and scrolling
1270 I am using it for two things:
1271 1) general purpose drawing in floating point coordinates
1272 2) displaying map data in Lat-long coordinates
1274 If the projection is set to None, it will draw in general purpose
1275 floating point coordinates. If the projection is set to 'FlatEarth', it
1276 will draw a FlatEarth projection, centered on the part of the map that
1277 you are viewing. You can also pass in your own projection function.
1279 It is double buffered, so re-draws after the window is uncovered by something
1280 else are very quick.
1282 It relies on NumPy, which is needed for speed (maybe, I havn't profiled it)
1284 Bugs and Limitations:
1285 Lots: patches, fixes welcome
1287 For Map drawing: It ignores the fact that the world is, in fact, a
1288 sphere, so it will do strange things if you are looking at stuff near
1289 the poles or the date line. so far I don't have a need to do that, so I
1290 havn't bothered to add any checks for that yet.
1293 I have set no zoom limits. What this means is that if you zoom in really
1294 far, you can get integer overflows, and get wierd results. It
1295 doesn't seem to actually cause any problems other than wierd output, at
1296 least when I have run it.
1299 I have done a couple of things to improve speed in this app. The one
1300 thing I have done is used NumPy Arrays to store the coordinates of the
1301 points of the objects. This allowed me to use array oriented functions
1302 when doing transformations, and should provide some speed improvement
1303 for objects with a lot of points (big polygons, polylines, pointsets).
1305 The real slowdown comes when you have to draw a lot of objects, because
1306 you have to call the wx.DC.DrawSomething call each time. This is plenty
1307 fast for tens of objects, OK for hundreds of objects, but pretty darn
1308 slow for thousands of objects.
1310 The solution is to be able to pass some sort of object set to the DC
1311 directly. I've used DC.DrawPointList(Points), and it helped a lot with
1312 drawing lots of points. I havn't got a LineSet type object, so I havn't
1313 used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList()
1314 methods implimented, and then I'd also have a full set of Object sets
1315 that could take advantage of them. I hope to get to it some day.
1319 At this point, there are a full set of custom mouse events. They are
1320 just like the rebulsr mouse events, but include an extra attribute:
1321 Event.GetCoords(), that returns the (x,y) position in world
1322 coordinates, as a length-2 NumPy vector of Floats.
1324 Copyright: Christopher Barker
1326 License: Same as the version of wxPython you are using it with
1328 Please let me know if you're using this!!!
1332 Chris.Barker@noaa.gov
1336 def __init__ ( self
, parent
, id = - 1 ,
1337 size
= wx
. DefaultSize
,
1338 ProjectionFun
= None ,
1339 BackgroundColor
= "WHITE" ,
1342 wx
. Panel
.__ init
__ ( self
, parent
, id , wx
. DefaultPosition
, size
)
1344 global ScreenPPI
## A global variable to hold the Pixels per inch that wxWindows thinks is in use.
1346 ScreenPPI
= dc
. GetPPI ()[ 0 ] # Assume square pixels
1349 self
. HitColorGenerator
= None
1350 self
. UseHitTest
= None
1352 self
. NumBetweenBlits
= 500
1354 self
. BackgroundBrush
= wx
. Brush ( BackgroundColor
, wx
. SOLID
)
1358 wx
. EVT_PAINT ( self
, self
. OnPaint
)
1359 wx
. EVT_SIZE ( self
, self
. OnSize
)
1361 wx
. EVT_LEFT_DOWN ( self
, self
. LeftDownEvent
)
1362 wx
. EVT_LEFT_UP ( self
, self
. LeftUpEvent
)
1363 wx
. EVT_LEFT_DCLICK ( self
, self
. LeftDoubleClickEvent
)
1364 wx
. EVT_MIDDLE_DOWN ( self
, self
. MiddleDownEvent
)
1365 wx
. EVT_MIDDLE_UP ( self
, self
. MiddleUpEvent
)
1366 wx
. EVT_MIDDLE_DCLICK ( self
, self
. MiddleDoubleClickEvent
)
1367 wx
. EVT_RIGHT_DOWN ( self
, self
. RightDownEvent
)
1368 wx
. EVT_RIGHT_UP ( self
, self
. RightUpEvent
)
1369 wx
. EVT_RIGHT_DCLICK ( self
, self
. RightDoubleCLickEvent
)
1370 wx
. EVT_MOTION ( self
, self
. MotionEvent
)
1371 wx
. EVT_MOUSEWHEEL ( self
, self
. WheelEvent
)
1373 ## CHB: I'm leaving these out for now.
1374 #wx.EVT_ENTER_WINDOW(self, self. )
1375 #wx.EVT_LEAVE_WINDOW(self, self. )
1377 ## create the Hit Test Dicts:
1382 self
._ ForeDrawList
= []
1383 self
._ ForegroundBuffer
= None
1384 self
. BoundingBox
= None
1385 self
. BoundingBoxDirty
= False
1386 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1388 self
. SetProjectionFun ( ProjectionFun
)
1390 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
) # No Projection to start!
1391 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
) # default Transformation
1396 self
. StartRBBox
= None
1397 self
. PrevRBBox
= None
1398 self
. StartMove
= None
1399 self
. PrevMoveXY
= None
1400 self
. ObjectUnderMouse
= None
1402 # called just to make sure everything is initialized
1407 def SetProjectionFun ( self
, ProjectionFun
):
1408 if ProjectionFun
== 'FlatEarth' :
1409 self
. ProjectionFun
= self
. FlatEarthProjection
1410 elif type ( ProjectionFun
) == types
. FunctionType
:
1411 self
. ProjectionFun
= ProjectionFun
1412 elif ProjectionFun
is None :
1413 self
. ProjectionFun
= lambda x
= None : array ( ( 1 , 1 ), Float
)
1415 raise FloatCanvasError ( 'Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector' )
1417 def FlatEarthProjection ( self
, CenterPoint
):
1418 return array (( cos ( pi
* CenterPoint
[ 1 ]/ 180 ), 1 ), Float
)
1420 def SetMode ( self
, Mode
):
1421 if Mode
in [ "ZoomIn" , "ZoomOut" , "Move" , "Mouse" , None ]:
1424 raise FloatCanvasError ( '" %s " is Not a valid Mode' % Mode
)
1426 def MakeHitDict ( self
):
1427 ##fixme: Should this just be None if nothing has been bound?
1428 self
. HitDict
= {EVT_FC_LEFT_DOWN: {}
,
1430 EVT_FC_LEFT_DCLICK
: {},
1431 EVT_FC_MIDDLE_DOWN
: {},
1432 EVT_FC_MIDDLE_UP
: {},
1433 EVT_FC_MIDDLE_DCLICK
: {},
1434 EVT_FC_RIGHT_DOWN
: {},
1435 EVT_FC_RIGHT_UP
: {},
1436 EVT_FC_RIGHT_DCLICK
: {},
1437 EVT_FC_ENTER_OBJECT
: {},
1438 EVT_FC_LEAVE_OBJECT
: {},
1441 def _RaiseMouseEvent ( self
, Event
, EventType
):
1443 This is called in various other places to raise a Mouse Event
1445 #print "in Raise Mouse Event", Event
1446 pt
= self
. PixelToWorld ( Event
. GetPosition () )
1447 evt
= _MouseEvent ( EventType
, Event
, self
. GetId (), pt
)
1448 self
. GetEventHandler (). ProcessEvent ( evt
)
1450 def HitTest ( self
, event
, HitEvent
):
1452 # check if there are any objects in the dict for this event
1453 if self
. HitDict
[ HitEvent
]:
1454 xy
= event
. GetPosition ()
1455 if self
._ ForegroundHTdc
:
1456 hitcolor
= self
._ ForegroundHTdc
. GetPixelPoint ( xy
)
1458 hitcolor
= self
._ HTdc
. GetPixelPoint ( xy
)
1459 color
= ( hitcolor
. Red (), hitcolor
. Green (), hitcolor
. Blue () )
1460 if color
in self
. HitDict
[ HitEvent
]:
1461 Object
= self
. HitDict
[ HitEvent
][ color
]
1462 ## Add the hit coords to the Object
1463 Object
. HitCoords
= self
. PixelToWorld ( xy
)
1464 Object
. CallBackFuncs
[ HitEvent
]( Object
)
1468 def MouseOverTest ( self
, event
):
1469 ##fixme: Can this be cleaned up?
1471 xy
= event
. GetPosition ()
1472 if self
._ ForegroundHTdc
:
1473 hitcolor
= self
._ ForegroundHTdc
. GetPixelPoint ( xy
)
1475 hitcolor
= self
._ HTdc
. GetPixelPoint ( xy
)
1476 color
= ( hitcolor
. Red (), hitcolor
. Green (), hitcolor
. Blue () )
1477 OldObject
= self
. ObjectUnderMouse
1478 ObjectCallbackCalled
= False
1479 if color
in self
. HitDict
[ EVT_FC_ENTER_OBJECT
]:
1480 Object
= self
. HitDict
[ EVT_FC_ENTER_OBJECT
][ color
]
1481 if ( OldObject
is None ):
1483 Object
. CallBackFuncs
[ EVT_FC_ENTER_OBJECT
]( Object
)
1484 ObjectCallbackCalled
= True
1486 pass # this means the enter event isn't bound for that object
1487 elif OldObject
== Object
: # the mouse is still on the same object
1489 ## Is the mouse on a differnt object as it was...
1490 elif not ( Object
== OldObject
):
1491 # call the leave object callback
1493 OldObject
. CallBackFuncs
[ EVT_FC_LEAVE_OBJECT
]( OldObject
)
1494 ObjectCallbackCalled
= True
1496 pass # this means the leave event isn't bound for that object
1498 Object
. CallBackFuncs
[ EVT_FC_ENTER_OBJECT
]( Object
)
1499 ObjectCallbackCalled
= True
1501 pass # this means the enter event isn't bound for that object
1502 ## set the new object under mouse
1503 self
. ObjectUnderMouse
= Object
1504 elif color
in self
. HitDict
[ EVT_FC_LEAVE_OBJECT
]:
1505 Object
= self
. HitDict
[ EVT_FC_LEAVE_OBJECT
][ color
]
1506 self
. ObjectUnderMouse
= Object
1508 # no objects under mouse bound to mouse-over events
1509 self
. ObjectUnderMouse
= None
1512 OldObject
. CallBackFuncs
[ EVT_FC_LEAVE_OBJECT
]( OldObject
)
1513 ObjectCallbackCalled
= True
1515 pass # this means the leave event isn't bound for that object
1516 return ObjectCallbackCalled
1519 ## fixme: There is a lot of repeated code here
1520 ## Is there a better way?
1521 def LeftDoubleClickEvent ( self
, event
):
1522 if self
. GUIMode
== "Mouse" :
1523 EventType
= EVT_FC_LEFT_DCLICK
1524 if not self
. HitTest ( event
, EventType
):
1525 self
._ RaiseMouseEvent
( event
, EventType
)
1528 def MiddleDownEvent ( self
, event
):
1529 if self
. GUIMode
== "Mouse" :
1530 EventType
= EVT_FC_MIDDLE_DOWN
1531 if not self
. HitTest ( event
, EventType
):
1532 self
._ RaiseMouseEvent
( event
, EventType
)
1534 def MiddleUpEvent ( self
, event
):
1535 if self
. GUIMode
== "Mouse" :
1536 EventType
= EVT_FC_MIDDLE_UP
1537 if not self
. HitTest ( event
, EventType
):
1538 self
._ RaiseMouseEvent
( event
, EventType
)
1540 def MiddleDoubleClickEvent ( self
, event
):
1541 if self
. GUIMode
== "Mouse" :
1542 EventType
= EVT_FC_MIDDLE_DCLICK
1543 if not self
. HitTest ( event
, EventType
):
1544 self
._ RaiseMouseEvent
( event
, EventType
)
1546 def RightUpEvent ( self
, event
):
1547 if self
. GUIMode
== "Mouse" :
1548 EventType
= EVT_FC_RIGHT_UP
1549 if not self
. HitTest ( event
, EventType
):
1550 self
._ RaiseMouseEvent
( event
, EventType
)
1552 def RightDoubleCLickEvent ( self
, event
):
1553 if self
. GUIMode
== "Mouse" :
1554 EventType
= EVT_FC_RIGHT_DCLICK
1555 if not self
. HitTest ( event
, EventType
):
1556 self
._ RaiseMouseEvent
( event
, EventType
)
1558 def WheelEvent ( self
, event
):
1559 ##if self.GUIMode == "Mouse":
1560 ## Why not always raise this?
1561 self
._ RaiseMouseEvent
( event
, EVT_FC_MOUSEWHEEL
)
1564 def LeftDownEvent ( self
, event
):
1566 if self
. GUIMode
== "ZoomIn" :
1567 self
. StartRBBox
= array ( event
. GetPosition () )
1568 self
. PrevRBBox
= None
1570 elif self
. GUIMode
== "ZoomOut" :
1571 Center
= self
. PixelToWorld ( event
. GetPosition () )
1572 self
. Zoom ( 1 / 1.5 , Center
)
1573 elif self
. GUIMode
== "Move" :
1574 self
. StartMove
= array ( event
. GetPosition () )
1575 self
. PrevMoveXY
= ( 0 , 0 )
1576 elif self
. GUIMode
== "Mouse" :
1578 if not self
. HitTest ( event
, EVT_FC_LEFT_DOWN
):
1579 self
._ RaiseMouseEvent
( event
, EVT_FC_LEFT_DOWN
)
1583 def LeftUpEvent ( self
, event
):
1584 if self
. HasCapture ():
1587 if self
. GUIMode
== "ZoomIn" :
1588 if event
. LeftUp () and not self
. StartRBBox
is None :
1589 self
. PrevRBBox
= None
1590 EndRBBox
= event
. GetPosition ()
1591 StartRBBox
= self
. StartRBBox
1592 # if mouse has moved less that ten pixels, don't use the box.
1593 if ( abs ( StartRBBox
[ 0 ] - EndRBBox
[ 0 ]) > 10
1594 and abs ( StartRBBox
[ 1 ] - EndRBBox
[ 1 ]) > 10 ):
1595 EndRBBox
= self
. PixelToWorld ( EndRBBox
)
1596 StartRBBox
= self
. PixelToWorld ( StartRBBox
)
1597 BB
= array ((( min ( EndRBBox
[ 0 ], StartRBBox
[ 0 ]),
1598 min ( EndRBBox
[ 1 ], StartRBBox
[ 1 ])),
1599 ( max ( EndRBBox
[ 0 ], StartRBBox
[ 0 ]),
1600 max ( EndRBBox
[ 1 ], StartRBBox
[ 1 ]))), Float
)
1603 Center
= self
. PixelToWorld ( StartRBBox
)
1604 self
. Zoom ( 1.5 , Center
)
1605 self
. StartRBBox
= None
1606 elif self
. GUIMode
== "Move" :
1607 if not self
. StartMove
is None :
1608 StartMove
= self
. StartMove
1609 EndMove
= array (( event
. GetX (), event
. GetY ()))
1610 if sum (( StartMove
- EndMove
)** 2 ) > 16 :
1611 self
. MoveImage ( StartMove
- EndMove
, 'Pixel' )
1612 self
. StartMove
= None
1613 elif self
. GUIMode
== "Mouse" :
1614 EventType
= EVT_FC_LEFT_UP
1615 if not self
. HitTest ( event
, EventType
):
1616 self
._ RaiseMouseEvent
( event
, EventType
)
1620 def MotionEvent ( self
, event
):
1622 if self
. GUIMode
== "ZoomIn" :
1623 if event
. Dragging () and event
. LeftIsDown () and not ( self
. StartRBBox
is None ):
1624 xy0
= self
. StartRBBox
1625 xy1
= array ( event
. GetPosition () )
1627 wh
[ 0 ] = max ( wh
[ 0 ], int ( wh
[ 1 ]* self
. AspectRatio
))
1628 wh
[ 1 ] = int ( wh
[ 0 ] / self
. AspectRatio
)
1629 xy_c
= ( xy0
+ xy1
) / 2
1630 dc
= wx
. ClientDC ( self
)
1632 dc
. SetPen ( wx
. Pen ( 'WHITE' , 2 , wx
. SHORT_DASH
))
1633 dc
. SetBrush ( wx
. TRANSPARENT_BRUSH
)
1634 dc
. SetLogicalFunction ( wx
. XOR
)
1636 dc
. DrawRectanglePointSize (* self
. PrevRBBox
)
1637 self
. PrevRBBox
= ( xy_c
- wh
/ 2 , wh
)
1638 dc
. DrawRectanglePointSize ( * self
. PrevRBBox
)
1640 elif self
. GUIMode
== "Move" :
1641 if event
. Dragging () and event
. LeftIsDown () and not self
. StartMove
is None :
1642 xy1
= array ( event
. GetPosition () )
1644 xy_tl
= xy1
- self
. StartMove
1645 dc
= wx
. ClientDC ( self
)
1647 x1
, y1
= self
. PrevMoveXY
1649 w
, h
= self
. PanelSize
1650 if x2
> x1
and y2
> y1
:
1657 elif x2
> x1
and y2
<= y1
:
1666 elif x2
<= x1
and y2
> y1
:
1675 elif x2
<= x1
and y2
<= y1
:
1685 dc
. SetPen ( wx
. TRANSPARENT_PEN
)
1686 dc
. SetBrush ( self
. BackgroundBrush
)
1687 dc
. DrawRectangle ( xa
, ya
, wa
, ha
)
1688 dc
. DrawRectangle ( xb
, yb
, wb
, hb
)
1689 self
. PrevMoveXY
= xy_tl
1690 if self
._ ForegroundBuffer
:
1691 dc
. DrawBitmapPoint ( self
._ ForegroundBuffer
, xy_tl
)
1693 dc
. DrawBitmapPoint ( self
._ Buffer
, xy_tl
)
1695 elif self
. GUIMode
== "Mouse" :
1696 ## Only do something if there are mouse over events bound
1697 if self
. HitDict
and ( self
. HitDict
[ EVT_FC_ENTER_OBJECT
] or self
. HitDict
[ EVT_FC_LEAVE_OBJECT
] ):
1698 if not self
. MouseOverTest ( event
):
1699 self
._ RaiseMouseEvent
( event
, EVT_FC_MOTION
)
1702 self
._ RaiseMouseEvent
( event
, EVT_FC_MOTION
)
1706 def RightDownEvent ( self
, event
):
1708 if self
. GUIMode
== "ZoomIn" :
1709 Center
= self
. PixelToWorld (( event
. GetX (), event
. GetY ()))
1710 self
. Zoom ( 1 / 1.5 , Center
)
1711 elif self
. GUIMode
== "ZoomOut" :
1712 Center
= self
. PixelToWorld (( event
. GetX (), event
. GetY ()))
1713 self
. Zoom ( 1.5 , Center
)
1714 elif self
. GUIMode
== "Mouse" :
1715 EventType
= EVT_FC_RIGHT_DOWN
1716 if not self
. HitTest ( event
, EventType
):
1717 self
._ RaiseMouseEvent
( event
, EventType
)
1721 def MakeNewBuffers ( self
):
1722 self
._ BackgroundDirty
= True
1723 # Make new offscreen bitmap:
1724 self
._ Buffer
= wx
. EmptyBitmap (* self
. PanelSize
)
1726 #dc.SelectObject(self._Buffer)
1728 if self
._ ForeDrawList
:
1729 self
._ ForegroundBuffer
= wx
. EmptyBitmap (* self
. PanelSize
)
1731 self
._ ForegroundBuffer
= None
1736 self
._ ForegroundHTdc
= None
1738 def MakeNewHTdc ( self
):
1739 ## Note: While it's considered a "bad idea" to keep a
1740 ## MemoryDC around I'm doing it here because a wx.Bitmap
1741 ## doesn't have a GetPixel method so a DC is needed to do
1742 ## the hit-test. It didn't seem like a good idea to re-create
1743 ## a wx.MemoryDC on every single mouse event, so I keep it
1745 self
._ HTdc
= wx
. MemoryDC ()
1746 self
._ HTBitmap
= wx
. EmptyBitmap (* self
. PanelSize
)
1747 self
._ HTdc
. SelectObject ( self
._ HTBitmap
)
1748 self
._ HTdc
. SetBackground ( wx
. BLACK_BRUSH
)
1749 if self
._ ForeDrawList
:
1750 self
._ ForegroundHTdc
= wx
. MemoryDC ()
1751 self
._ ForegroundHTBitmap
= wx
. EmptyBitmap (* self
. PanelSize
)
1752 self
._ ForegroundHTdc
. SelectObject ( self
._ ForegroundHTBitmap
)
1753 self
._ ForegroundHTdc
. SetBackground ( wx
. BLACK_BRUSH
)
1755 self
._ ForegroundHTdc
= None
1757 def OnSize ( self
, event
):
1758 self
. PanelSize
= array ( self
. GetClientSizeTuple (), Int32
)
1759 self
. HalfPanelSize
= self
. PanelSize
/ 2 # lrk: added for speed in WorldToPixel
1760 if self
. PanelSize
[ 0 ] == 0 or self
. PanelSize
[ 1 ] == 0 :
1761 self
. AspectRatio
= 1.0
1763 self
. AspectRatio
= float ( self
. PanelSize
[ 0 ]) / self
. PanelSize
[ 1 ]
1764 self
. MakeNewBuffers ()
1767 def OnPaint ( self
, event
):
1768 dc
= wx
. PaintDC ( self
)
1769 if self
._ ForegroundBuffer
:
1770 dc
. DrawBitmap ( self
._ ForegroundBuffer
, 0 , 0 )
1772 dc
. DrawBitmap ( self
._ Buffer
, 0 , 0 )
1774 def Draw ( self
, Force
= False ):
1776 There is a main buffer set up to double buffer the screen, so
1777 you can get quick re-draws when the window gets uncovered.
1779 If there are any objects in self._ForeDrawList, then the
1780 background gets drawn to a new buffer, and the foreground
1781 objects get drawn on top of it. The final result if blitted to
1782 the screen, and stored for future Paint events. This is done so
1783 that you can have a complicated background, but have something
1784 changing on the foreground, without having to wait for the
1785 background to get re-drawn. This can be used to support simple
1786 animation, for instance.
1789 # print "in Draw", self.PanelSize
1790 if sometrue ( self
. PanelSize
< 1 ): # it's possible for this to get called before being properly initialized.
1791 # if self.PanelSize < (1,1): # it's possible for this to get called before being properly initialized.
1793 if self
. Debug
: start
= clock ()
1794 ScreenDC
= wx
. ClientDC ( self
)
1795 ViewPortWorld
= ( self
. PixelToWorld (( 0 , 0 )),
1796 self
. PixelToWorld ( self
. PanelSize
) )
1797 ViewPortBB
= array ( ( minimum
. reduce ( ViewPortWorld
),
1798 maximum
. reduce ( ViewPortWorld
) ) )
1800 dc
. SelectObject ( self
._ Buffer
)
1801 if self
._ BackgroundDirty
or Force
:
1802 #print "Background is Dirty"
1803 dc
. SetBackground ( self
. BackgroundBrush
)
1807 self
._ DrawObjects
( dc
, self
._ DrawList
, ScreenDC
, ViewPortBB
, self
._ HTdc
)
1808 self
._ BackgroundDirty
= False
1810 if self
._ ForeDrawList
:
1811 ## If an object was just added to the Foreground, there might not yet be a buffer
1812 if self
._ ForegroundBuffer
is None :
1813 self
._ ForegroundBuffer
= wx
. EmptyBitmap ( self
. PanelSize
[ 0 ],
1816 dc
= wx
. MemoryDC () ## I got some strange errors (linewidths wrong) if I didn't make a new DC here
1817 dc
. SelectObject ( self
._ ForegroundBuffer
)
1818 dc
. DrawBitmap ( self
._ Buffer
, 0 , 0 )
1819 if self
._ ForegroundHTdc
is None :
1820 self
._ ForegroundHTdc
= wx
. MemoryDC ()
1821 self
._ ForegroundHTdc
. SelectObject ( wx
. EmptyBitmap (
1823 self
. PanelSize
[ 1 ]) )
1825 ## blit the background HT buffer to the foreground HT buffer
1826 self
._ ForegroundHTdc
. Blit ( 0 , 0 ,
1827 self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ],
1829 self
._ DrawObjects
( dc
,
1833 self
._ ForegroundHTdc
)
1834 ScreenDC
. Blit ( 0 , 0 , self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ], dc
, 0 , 0 )
1835 # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
1836 # This seeems out of place, but it works.
1838 ScreenDC
. SetPen ( wx
. Pen ( 'WHITE' , 2 , wx
. SHORT_DASH
))
1839 ScreenDC
. SetBrush ( wx
. TRANSPARENT_BRUSH
)
1840 ScreenDC
. SetLogicalFunction ( wx
. XOR
)
1841 ScreenDC
. DrawRectanglePointSize (* self
. PrevRBBox
)
1842 if self
. Debug
: print "Drawing took %f seconds of CPU time" %( clock ()- start
)
1844 ## Clear the font cache
1845 ## IF you don't do this, the X font server starts to take up Massive amounts of memory
1846 ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in.
1847 DrawObject
. FontList
= {}
1849 def _ShouldRedraw ( DrawList
, ViewPortBB
): # lrk: adapted code from BBCheck
1850 # lrk: Returns the objects that should be redrawn
1854 for Object
in DrawList
:
1855 BB1
= Object
. BoundingBox
1856 if ( BB1
[ 1 , 0 ] > BB2
[ 0 , 0 ] and BB1
[ 0 , 0 ] < BB2
[ 1 , 0 ] and
1857 BB1
[ 1 , 1 ] > BB2
[ 0 , 1 ] and BB1
[ 0 , 1 ] < BB2
[ 1 , 1 ]):
1858 redrawlist
. append ( Object
)
1860 _ShouldRedraw
= staticmethod ( _ShouldRedraw
)
1863 ## def BBCheck(self, BB1, BB2):
1866 ## BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
1869 ## if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
1870 ## (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
1875 def MoveImage ( self
, shift
, CoordType
):
1877 move the image in the window.
1879 shift is an (x,y) tuple, specifying the amount to shift in each direction
1881 It can be in any of three coordinates: Panel, Pixel, World,
1882 specified by the CoordType parameter
1884 Panel coordinates means you want to shift the image by some
1885 fraction of the size of the displaed image
1887 Pixel coordinates means you want to shift the image by some number of pixels
1889 World coordinates mean you want to shift the image by an amount
1890 in Floating point world coordinates
1894 shift
= asarray ( shift
, Float
)
1895 #print "shifting by:", shift
1896 if CoordType
== 'Panel' : # convert from panel coordinates
1897 shift
= shift
* array ((- 1 , 1 ), Float
) * self
. PanelSize
/ self
. TransformVector
1898 elif CoordType
== 'Pixel' : # convert from pixel coordinates
1899 shift
= shift
/ self
. TransformVector
1900 elif CoordType
== 'World' : # No conversion
1903 raise FloatCanvasError ( 'CoordType must be either "Panel", "Pixel", or "World"' )
1905 #print "shifting by:", shift
1907 self
. ViewPortCenter
= self
. ViewPortCenter
+ shift
1908 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1909 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1910 self
._ BackgroundDirty
= True
1913 def Zoom ( self
, factor
, center
= None ):
1916 Zoom(factor, center) changes the amount of zoom of the image by factor.
1917 If factor is greater than one, the image gets larger.
1918 If factor is less than one, the image gets smaller.
1920 Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
1921 If center is not given, the center will stay the same.
1924 self
. Scale
= self
. Scale
* factor
1925 if not center
is None :
1926 self
. ViewPortCenter
= array ( center
, Float
)
1927 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1928 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1929 self
._ BackgroundDirty
= True
1932 def ZoomToBB ( self
, NewBB
= None , DrawFlag
= True ):
1936 Zooms the image to the bounding box given, or to the bounding
1937 box of all the objects on the canvas, if none is given.
1941 if not NewBB
is None :
1944 if self
. BoundingBoxDirty
:
1945 self
._ ResetBoundingBox
()
1946 BoundingBox
= self
. BoundingBox
1947 if not BoundingBox
is None :
1948 self
. ViewPortCenter
= array ((( BoundingBox
[ 0 , 0 ]+ BoundingBox
[ 1 , 0 ])/ 2 ,
1949 ( BoundingBox
[ 0 , 1 ]+ BoundingBox
[ 1 , 1 ])/ 2 ), Float
)
1950 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1951 # Compute the new Scale
1952 BoundingBox
= BoundingBox
* self
. MapProjectionVector
1954 self
. Scale
= min ( abs ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ])),
1955 abs ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ])) )* 0.95
1956 except ZeroDivisionError : # this will happen if the BB has zero width or height
1958 self
. Scale
= ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ]))* 0.95
1959 except ZeroDivisionError :
1961 self
. Scale
= ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ]))* 0.95
1962 except ZeroDivisionError : #zero size! (must be a single point)
1965 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
)* self
. MapProjectionVector
1967 self
._ BackgroundDirty
= True
1970 # Reset the shifting and scaling to defaults when there is no BB
1971 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1972 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
) # No Projection to start!
1973 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
) # default Transformation
1976 def RemoveObjects ( self
, Objects
):
1977 for Object
in Objects
:
1978 self
. RemoveObject ( Object
, ResetBB
= False )
1979 self
. BoundingBoxDirty
= True
1981 def RemoveObject ( self
, Object
, ResetBB
= True ):
1982 ##fixme: Using the list.remove method is kind of slow
1983 if Object
. InForeground
:
1984 self
._ ForeDrawList
. remove ( Object
)
1986 self
._ DrawList
. remove ( Object
)
1987 self
._ BackgroundDirty
= True
1989 self
. BoundingBoxDirty
= True
1991 def ClearAll ( self
, ResetBB
= True ):
1993 self
._ ForeDrawList
= []
1994 self
._ BackgroundDirty
= True
1995 self
. HitColorGenerator
= None
1996 self
. UseHitTest
= False
1998 self
._ ResetBoundingBox
()
1999 self
. MakeNewBuffers ()
2003 ## def _AddBoundingBox(self,NewBB):
2004 ## if self.BoundingBox is None:
2005 ## self.BoundingBox = NewBB
2006 ## self.ZoomToBB(NewBB,DrawFlag = False)
2008 ## self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]),
2009 ## min(self.BoundingBox[0,1],NewBB[0,1])),
2010 ## (max(self.BoundingBox[1,0],NewBB[1,0]),
2011 ## max(self.BoundingBox[1,1],NewBB[1,1]))),
2014 def _getboundingbox ( bboxarray
): # lrk: added this
2016 upperleft
= minimum
. reduce ( bboxarray
[:, 0 ])
2017 lowerright
= maximum
. reduce ( bboxarray
[:, 1 ])
2018 return array (( upperleft
, lowerright
), Float
)
2020 _getboundingbox
= staticmethod ( _getboundingbox
)
2022 def _ResetBoundingBox ( self
):
2023 if self
._ DrawList
or self
._ ForeDrawList
:
2024 bboxarray
= zeros (( len ( self
._ DrawList
)+ len ( self
._ ForeDrawList
), 2 , 2 ), Float
)
2025 i
= - 1 # just in case _DrawList is empty
2026 for ( i
, BB
) in enumerate ( self
._ DrawList
):
2027 bboxarray
[ i
] = BB
. BoundingBox
2028 for ( j
, BB
) in enumerate ( self
._ ForeDrawList
):
2029 bboxarray
[ i
+ j
+ 1 ] = BB
. BoundingBox
2030 self
. BoundingBox
= self
._ getboundingbox
( bboxarray
)
2032 self
. BoundingBox
= None
2033 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
2034 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
)
2035 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
)
2037 self
. BoundingBoxDirty
= False
2039 def PixelToWorld ( self
, Points
):
2041 Converts coordinates from Pixel coordinates to world coordinates.
2043 Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
2046 return ((( asarray ( Points
, Float
) - ( self
. PanelSize
/ 2 ))/ self
. TransformVector
) + self
. ViewPortCenter
)
2048 def WorldToPixel ( self
, Coordinates
):
2050 This function will get passed to the drawing functions of the objects,
2051 to transform from world to pixel coordinates.
2052 Coordinates should be a NX2 array of (x,y) coordinates, or
2053 a 2-tuple, or sequence of 2-tuples.
2055 #Note: this can be called by users code for various reasons, so asarray is needed.
2056 return ((( asarray ( Coordinates
, Float
) -
2057 self
. ViewPortCenter
)* self
. TransformVector
)+
2058 ( self
. HalfPanelSize
)). astype ( 'i' )
2060 def ScaleWorldToPixel ( self
, Lengths
):
2062 This function will get passed to the drawing functions of the objects,
2063 to Change a length from world to pixel coordinates.
2065 Lengths should be a NX2 array of (x,y) coordinates, or
2066 a 2-tuple, or sequence of 2-tuples.
2068 return ( ( asarray ( Lengths
, Float
)* self
. TransformVector
) ). astype ( 'i' )
2070 def ScalePixelToWorld ( self
, Lengths
):
2072 This function computes a pair of x.y lengths,
2073 to change then from pixel to world coordinates.
2075 Lengths should be a NX2 array of (x,y) coordinates, or
2076 a 2-tuple, or sequence of 2-tuples.
2079 return ( asarray ( Lengths
, Float
) / self
. TransformVector
)
2081 def AddObject ( self
, obj
):
2082 # put in a reference to the Canvas, so remove and other stuff can work
2084 if obj
. InForeground
:
2085 self
._ ForeDrawList
. append ( obj
)
2086 self
. UseForeground
= True
2088 self
._ DrawList
. append ( obj
)
2089 self
._ BackgroundDirty
= True
2090 self
. BoundingBoxDirty
= True
2093 def _DrawObjects ( self
, dc
, DrawList
, ScreenDC
, ViewPortBB
, HTdc
= None ):
2095 This is a convenience function;
2096 This function takes the list of objects and draws them to specified
2099 dc
. SetBackground ( self
. BackgroundBrush
)
2102 PanelSize0
, PanelSize1
= self
. PanelSize
# for speed
2103 WorldToPixel
= self
. WorldToPixel
# for speed
2104 ScaleWorldToPixel
= self
. ScaleWorldToPixel
# for speed
2105 Blit
= ScreenDC
. Blit
# for speed
2106 NumBetweenBlits
= self
. NumBetweenBlits
# for speed
2107 for i
, Object
in enumerate ( self
._ ShouldRedraw
( DrawList
, ViewPortBB
)):
2108 Object
._ Draw
( dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
)
2109 if i
+ 1 % NumBetweenBlits
== 0 :
2110 Blit ( 0 , 0 , PanelSize0
, PanelSize1
, dc
, 0 , 0 )
2113 ## ## This is a way to automatically add a AddObject method for each
2114 ## ## object type This code has been replaced by Leo's code above, so
2115 ## ## that it happens at module init, rather than as needed. The
2116 ## ## primary advantage of this is that dir(FloatCanvas) will have
2117 ## ## them, and docstrings are preserved. Probably more useful
2118 ## ## exceptions if there is a problem, as well.
2119 ## def __getattr__(self, name):
2120 ## if name[:3] == "Add":
2121 ## func=globals()[name[3:]]
2122 ## def AddFun(*args, **kwargs):
2123 ## Object = func(*args, **kwargs)
2124 ## self.AddObject(Object)
2126 ## ## add it to FloatCanvas' dict for future calls.
2127 ## self.__dict__[name] = AddFun
2130 ## raise AttributeError("FloatCanvas has no attribute '%s'"%name)
2132 def _makeFloatCanvasAddMethods (): ## lrk's code for doing this in module __init__
2133 classnames
= [ "Circle" , "Ellipse" , "Rectangle" , "ScaledText" , "Polygon" ,
2134 "Line" , "Text" , "PointSet" , "Point" , "Arrow" ]
2135 for classname
in classnames
:
2136 klass
= globals ()[ classname
]
2137 def getaddshapemethod ( klass
= klass
):
2138 def addshape ( self
, * args
, ** kwargs
):
2139 Object
= klass (* args
, ** kwargs
)
2140 self
. AddObject ( Object
)
2143 addshapemethod
= getaddshapemethod ()
2144 methodname
= "Add" + classname
2145 setattr ( FloatCanvas
, methodname
, addshapemethod
)
2146 docstring
= "Creates %s and adds its reference to the canvas. \n " % classname
2147 docstring
+= "Argument protocol same as %s class" % classname
2149 docstring
+= ", whose docstring is: \n %s " % klass
.__ doc
__
2150 FloatCanvas
.__ dict
__ [ methodname
] .__ doc
__ = docstring
2152 _makeFloatCanvasAddMethods ()