]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/floatcanvas/FloatCanvas.py
2 from Numeric
import array
, asarray
, Float
, cos
, pi
, sum , minimum
, maximum
, Int32
, zeros
, ones
, concatenate
, sqrt
, argmin
, power
, absolute
4 from numarray
import array
, asarray
, Float
, cos
, pi
, sum , minimum
, maximum
, Int32
, zeros
, concatenate
6 from time
import clock
, sleep
13 ## A global variable to hold the Pixels per inch that wxWindows thinks is in use
14 ## This is used for scaling fonts.
15 ## This can't be computed on module __init__, because a wx.App might not have iniitalized yet.
18 ## a custom Exceptions:
20 class FloatCanvasException ( Exception ):
23 ## Create all the mouse events
24 #EVT_FC_ENTER_WINDOW = wx.NewEventType()
25 #EVT_FC_LEAVE_WINDOW = wx.NewEventType()
26 EVT_FC_LEFT_DOWN
= wx
. NewEventType ()
27 EVT_FC_LEFT_UP
= wx
. NewEventType ()
28 EVT_FC_LEFT_DCLICK
= wx
. NewEventType ()
29 EVT_FC_MIDDLE_DOWN
= wx
. NewEventType ()
30 EVT_FC_MIDDLE_UP
= wx
. NewEventType ()
31 EVT_FC_MIDDLE_DCLICK
= wx
. NewEventType ()
32 EVT_FC_RIGHT_DOWN
= wx
. NewEventType ()
33 EVT_FC_RIGHT_UP
= wx
. NewEventType ()
34 EVT_FC_RIGHT_DCLICK
= wx
. NewEventType ()
35 EVT_FC_MOTION
= wx
. NewEventType ()
36 EVT_FC_MOUSEWHEEL
= wx
. NewEventType ()
37 ## these two are for the hit-test stuff, I never make them real Events
38 EVT_FC_ENTER_OBJECT
= wx
. NewEventType ()
39 EVT_FC_LEAVE_OBJECT
= wx
. NewEventType ()
41 ##Create all mouse event binding functions
42 #def EVT_ENTER_WINDOW( window, function ):
43 # window.Connect( -1, -1, EVT_FC_ENTER_WINDOW, function )
44 #def EVT_LEAVE_WINDOW( window, function ):
45 # window.Connect( -1, -1,EVT_FC_LEAVE_WINDOW , function )
46 def EVT_LEFT_DOWN ( window
, function
):
47 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_DOWN
, function
)
48 def EVT_LEFT_UP ( window
, function
):
49 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_UP
, function
)
50 def EVT_LEFT_DCLICK ( window
, function
):
51 window
. Connect ( - 1 , - 1 , EVT_FC_LEFT_DCLICK
, function
)
52 def EVT_MIDDLE_DOWN ( window
, function
):
53 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_DOWN
, function
)
54 def EVT_MIDDLE_UP ( window
, function
):
55 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_UP
, function
)
56 def EVT_MIDDLE_DCLICK ( window
, function
):
57 window
. Connect ( - 1 , - 1 , EVT_FC_MIDDLE_DCLICK
, function
)
58 def EVT_RIGHT_DOWN ( window
, function
):
59 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_DOWN
, function
)
60 def EVT_RIGHT_UP ( window
, function
):
61 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_UP
, function
)
62 def EVT_RIGHT_DCLICK ( window
, function
):
63 window
. Connect ( - 1 , - 1 , EVT_FC_RIGHT_DCLICK
, function
)
64 def EVT_MOTION ( window
, function
):
65 window
. Connect ( - 1 , - 1 , EVT_FC_MOTION
, function
)
66 def EVT_MOUSEWHEEL ( window
, function
):
67 window
. Connect ( - 1 , - 1 , EVT_FC_MOUSEWHEEL
, function
)
69 class _MouseEvent ( wx
. PyCommandEvent
):
73 This event class takes a regular wxWindows mouse event as a parameter,
74 and wraps it so that there is access to all the original methods. This
75 is similar to subclassing, but you can't subclass a wxWindows event
77 The goal is to be able to it just like a regular mouse event.
81 GetCoords() , which returns and (x,y) tuple in world coordinates.
83 Another difference is that it is a CommandEvent, which propagates up
84 the window hierarchy until it is handled.
88 def __init__ ( self
, EventType
, NativeEvent
, WinID
, Coords
= None ):
89 wx
. PyCommandEvent
.__ init
__ ( self
)
91 self
. SetEventType ( EventType
)
92 self
._ NativeEvent
= NativeEvent
95 def SetCoords ( self
, Coords
):
101 def __getattr__ ( self
, name
):
102 #return eval(self.NativeEvent.__getattr__(name) )
103 return getattr ( self
._ NativeEvent
, name
)
105 def _cycleidxs ( indexcount
, maxvalue
, step
):
108 Utility function used by _colorGenerator
114 for idx
in xrange ( 0 , maxvalue
, step
):
115 for tail
in _cycleidxs ( indexcount
- 1 , maxvalue
, step
):
118 def _colorGenerator ():
122 Generates a seris of unique colors used to do hit-tests with the HIt
127 if sys
. platform
== 'darwin' :
130 b
= wx
. EmptyBitmap ( 1 , 1 )
137 raise "ColorGenerator does not work with depth = %s " % depth
138 return _cycleidxs ( indexcount
= 3 , maxvalue
= 256 , step
= step
)
141 #### I don't know if the Set objects are useful, beyond the pointset
142 #### object The problem is that when zoomed in, the BB is checked to see
143 #### whether to draw the object. A Set object can defeat this. ONe day
144 #### I plan to write some custon C++ code to draw sets of objects
146 ##class ObjectSetMixin:
148 ## A mix-in class for draw objects that are sets of objects
150 ## It contains methods for setting lists of pens and brushes
153 ## def SetPens(self,LineColors,LineStyles,LineWidths):
155 ## This method used when an object could have a list of pens, rather than just one
156 ## It is used for LineSet, and perhaps others in the future.
158 ## fixme: this should be in a mixin
160 ## fixme: this is really kludgy, there has got to be a better way!
165 ## if type(LineColors) == types.ListType:
166 ## length = len(LineColors)
168 ## LineColors = [LineColors]
170 ## if type(LineStyles) == types.ListType:
171 ## length = len(LineStyles)
173 ## LineStyles = [LineStyles]
175 ## if type(LineWidths) == types.ListType:
176 ## length = len(LineWidths)
178 ## LineWidths = [LineWidths]
181 ## if len(LineColors) == 1:
182 ## LineColors = LineColors*length
183 ## if len(LineStyles) == 1:
184 ## LineStyles = LineStyles*length
185 ## if len(LineWidths) == 1:
186 ## LineWidths = LineWidths*length
189 ## for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths):
190 ## if LineColor is None or LineStyle is None:
191 ## self.Pens.append(wx.TRANSPARENT_PEN)
192 ## # what's this for?> self.LineStyle = 'Transparent'
193 ## if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
194 ## Pen = wx.Pen(LineColor,LineWidth,self.LineStyleList[LineStyle])
195 ## self.Pens.append(Pen)
197 ## self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)])
199 ## self.Pens = self.Pens[0]
203 This is the base class for all the objects that can be drawn.
205 One must subclass from this (and an assortment of Mixins) to create
210 def __init__ ( self
, InForeground
= False ):
211 self
. InForeground
= InForeground
216 self
. CallBackFuncs
= {}
218 ## these are the defaults
222 self
. MinHitLineWidth
= 3
223 self
. HitLineWidth
= 3 ## this gets re-set by the subclasses if necessary
228 self
. FillStyle
= "Solid"
230 # I pre-define all these as class variables to provide an easier
231 # interface, and perhaps speed things up by caching all the Pens
232 # and Brushes, although that may not help, as I think wx now
233 # does that on it's own. Send me a note if you know!
236 ( None , "Transparent" ) : wx
. TRANSPARENT_BRUSH
,
237 ( "Blue" , "Solid" ) : wx
. BLUE_BRUSH
,
238 ( "Green" , "Solid" ) : wx
. GREEN_BRUSH
,
239 ( "White" , "Solid" ) : wx
. WHITE_BRUSH
,
240 ( "Black" , "Solid" ) : wx
. BLACK_BRUSH
,
241 ( "Grey" , "Solid" ) : wx
. GREY_BRUSH
,
242 ( "MediumGrey" , "Solid" ) : wx
. MEDIUM_GREY_BRUSH
,
243 ( "LightGrey" , "Solid" ) : wx
. LIGHT_GREY_BRUSH
,
244 ( "Cyan" , "Solid" ) : wx
. CYAN_BRUSH
,
245 ( "Red" , "Solid" ) : wx
. RED_BRUSH
248 ( None , "Transparent" , 1 ) : wx
. TRANSPARENT_PEN
,
249 ( "Green" , "Solid" , 1 ) : wx
. GREEN_PEN
,
250 ( "White" , "Solid" , 1 ) : wx
. WHITE_PEN
,
251 ( "Black" , "Solid" , 1 ) : wx
. BLACK_PEN
,
252 ( "Grey" , "Solid" , 1 ) : wx
. GREY_PEN
,
253 ( "MediumGrey" , "Solid" , 1 ) : wx
. MEDIUM_GREY_PEN
,
254 ( "LightGrey" , "Solid" , 1 ) : wx
. LIGHT_GREY_PEN
,
255 ( "Cyan" , "Solid" , 1 ) : wx
. CYAN_PEN
,
256 ( "Red" , "Solid" , 1 ) : wx
. RED_PEN
260 "Transparent" : wx
. TRANSPARENT
,
262 "BiDiagonalHatch" : wx
. BDIAGONAL_HATCH
,
263 "CrossDiagHatch" : wx
. CROSSDIAG_HATCH
,
264 "FDiagonal_Hatch" : wx
. FDIAGONAL_HATCH
,
265 "CrossHatch" : wx
. CROSS_HATCH
,
266 "HorizontalHatch" : wx
. HORIZONTAL_HATCH
,
267 "VerticalHatch" : wx
. VERTICAL_HATCH
272 "Transparent" : wx
. TRANSPARENT
,
274 "LongDash" : wx
. LONG_DASH
,
275 "ShortDash" : wx
. SHORT_DASH
,
276 "DotDash" : wx
. DOT_DASH
,
279 def Bind ( self
, Event
, CallBackFun
):
280 self
. CallBackFuncs
[ Event
] = CallBackFun
282 self
._ Canvas
. UseHitTest
= True
283 if not self
._ Canvas
._ HTdc
:
284 self
._ Canvas
. MakeNewHTdc ()
285 if not self
. HitColor
:
286 if not self
._ Canvas
. HitColorGenerator
:
287 self
._ Canvas
. HitColorGenerator
= _colorGenerator ()
288 self
._ Canvas
. HitColorGenerator
. next () # first call to prevent the background color from being used.
289 self
. HitColor
= self
._ Canvas
. HitColorGenerator
. next ()
290 self
. SetHitPen ( self
. HitColor
, self
. HitLineWidth
)
291 self
. SetHitBrush ( self
. HitColor
)
292 # put the object in the hit dict, indexed by it's color
293 if not self
._ Canvas
. HitDict
:
294 self
._ Canvas
. MakeHitDict ()
295 self
._ Canvas
. HitDict
[ Event
][ self
. HitColor
] = ( self
) # put the object in the hit dict, indexed by it's color
299 ## fixme: this only removes one from each list, there could be more.
300 if self
._ Canvas
. HitDict
:
301 for List
in self
._ Canvas
. HitDict
. itervalues ():
309 def SetBrush ( self
, FillColor
, FillStyle
):
310 if FillColor
is None or FillStyle
is None :
311 self
. Brush
= wx
. TRANSPARENT_BRUSH
312 self
. FillStyle
= "Transparent"
314 self
. Brush
= self
. BrushList
. setdefault ( ( FillColor
, FillStyle
), wx
. Brush ( FillColor
, self
. FillStyleList
[ FillStyle
] ) )
316 def SetPen ( self
, LineColor
, LineStyle
, LineWidth
):
317 if ( LineColor
is None ) or ( LineStyle
is None ):
318 self
. Pen
= wx
. TRANSPARENT_PEN
319 self
. LineStyle
= 'Transparent'
321 self
. Pen
= self
. PenList
. setdefault ( ( LineColor
, LineStyle
, LineWidth
), wx
. Pen ( LineColor
, LineWidth
, self
. LineStyleList
[ LineStyle
]) )
323 def SetHitBrush ( self
, HitColor
):
325 self
. HitBrush
= wx
. TRANSPARENT_BRUSH
327 self
. HitBrush
= self
. BrushList
. setdefault ( ( HitColor
, "solid" ), wx
. Brush ( HitColor
, self
. FillStyleList
[ "Solid" ] ) )
329 def SetHitPen ( self
, HitColor
, LineWidth
):
331 self
. HitPen
= wx
. TRANSPARENT_PEN
333 self
. HitPen
= self
. PenList
. setdefault ( ( HitColor
, "solid" , self
. HitLineWidth
), wx
. Pen ( HitColor
, self
. HitLineWidth
, self
. LineStyleList
[ "Solid" ]) )
335 def PutInBackground ( self
):
336 if self
._ Canvas
and self
. InForeground
:
337 self
._ Canvas
._ ForeDrawList
. remove ( self
)
338 self
._ Canvas
._ DrawList
. append ( self
)
339 self
._ Canvas
._ BackgroundDirty
= True
340 self
. InForeground
= False
342 def PutInForeground ( self
):
343 if self
._ Canvas
and ( not self
. InForeground
):
344 self
._ Canvas
._ ForeDrawList
. append ( self
)
345 self
._ Canvas
._ DrawList
. remove ( self
)
346 self
._ Canvas
._ BackgroundDirty
= True
347 self
. InForeground
= True
349 class ColorOnlyMixin
:
352 Mixin class for objects that have just one color, rather than a fill
357 def SetColor ( self
, Color
):
358 self
. SetPen ( Color
, "Solid" , 1 )
359 self
. SetBrush ( Color
, "Solid" )
361 SetFillColor
= SetColor
# Just to provide a consistant interface
366 Mixin class for objects that have just one color, rather than a fill
371 def SetLineColor ( self
, LineColor
):
372 self
. LineColor
= LineColor
373 self
. SetPen ( LineColor
, self
. LineStyle
, self
. LineWidth
)
375 def SetLineStyle ( self
, LineStyle
):
376 self
. LineStyle
= LineStyle
377 self
. SetPen ( self
. LineColor
, LineStyle
, self
. LineWidth
)
379 def SetLineWidth ( self
, LineWidth
):
380 self
. LineWidth
= LineWidth
381 self
. SetPen ( self
. LineColor
, self
. LineStyle
, LineWidth
)
383 class LineAndFillMixin ( LineOnlyMixin
):
386 Mixin class for objects that have both a line and a fill color and
390 def SetFillColor ( self
, FillColor
):
391 self
. FillColor
= FillColor
392 self
. SetBrush ( FillColor
, self
. FillStyle
)
394 def SetFillStyle ( self
, FillStyle
):
395 self
. FillStyle
= FillStyle
396 self
. SetBrush ( self
. FillColor
, FillStyle
)
401 This is a mixin class that provides some methods suitable for use
402 with objects that have a single (x,y) coordinate pair.
406 def Move ( self
, Delta
):
409 Move(Delta): moves the object by delta, where delta is a
410 (dx,dy) pair. Ideally a Numpy array of shape (2,)
414 Delta
= asarray ( Delta
, Float
)
416 self
. BoundingBox
= self
. BoundingBox
+ Delta
418 self
._ Canvas
. BoundingBoxDirty
= True
420 def SetXY ( self
, x
, y
):
421 self
. XY
= array ( ( x
, y
), Float
)
422 self
. CalcBoundingBox ()
424 def SetPoint ( self
, xy
):
425 self
. XY
= array ( xy
, Float
)
427 self
. CalcBoundingBox ()
429 class PointsObjectMixin
:
432 This is a mixin class that provides some methods suitable for use
433 with objects that have a set of (x,y) coordinate pairs.
438 ## This is code for the PointsObjectMixin object, it needs to be adapted and tested.
439 ## Is the neccesary at all: you can always do:
440 ## Object.SetPoints( Object.Points + delta, copy = False)
441 ## def Move(self, Delta ):
444 ## Move(Delta): moves the object by delta, where delta is an (dx,
445 ## dy) pair. Ideally a Numpy array of shape (2,)
449 ## Delta = array(Delta, Float)
451 ## self.BoundingBox = self.BoundingBox + Delta##array((self.XY, (self.XY + self.WH)), Float)
453 ## self._Canvas.BoundingBoxDirty = True
455 def CalcBoundingBox ( self
):
456 self
. BoundingBox
= array ((( min ( self
. Points
[:, 0 ]),
457 min ( self
. Points
[:, 1 ]) ),
458 ( max ( self
. Points
[:, 0 ]),
459 max ( self
. Points
[:, 1 ]) ) ), Float
)
461 self
._ Canvas
. BoundingBoxDirty
= True
463 def SetPoints ( self
, Points
, copy
= True ):
465 Sets the coordinates of the points of the object to Points (NX2 array).
467 By default, a copy is made, if copy is set to False, a reference
468 is used, iff Points is a NumPy array of Floats. This allows you
469 to change some or all of the points without making any copies.
473 Points = Object.Points
474 Points += (5,10) # shifts the points 5 in the x dir, and 10 in the y dir.
475 Object.SetPoints(Points, False) # Sets the points to the same array as it was
479 self
. Points
= array ( Points
, Float
)
480 self
. Points
. shape
= (- 1 , 2 ) # Make sure it is a NX2 array, even if there is only one point
482 self
. Points
= asarray ( Points
, Float
)
483 self
. CalcBoundingBox ()
486 class Polygon ( DrawObject
, PointsObjectMixin
, LineAndFillMixin
):
490 The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of
491 point coordinates. so that Points[N][0] is the x-coordinate of
492 point N and Points[N][1] is the y-coordinate or Points[N,0] is the
493 x-coordinate of point N and Points[N,1] is the y-coordinate for
496 The other parameters specify various properties of the Polygon, and
497 should be self explanatory.
507 InForeground
= False ):
508 DrawObject
.__ init
__ ( self
, InForeground
)
509 self
. Points
= array ( Points
, Float
) # this DOES need to make a copy
510 self
. CalcBoundingBox ()
512 self
. LineColor
= LineColor
513 self
. LineStyle
= LineStyle
514 self
. LineWidth
= LineWidth
515 self
. FillColor
= FillColor
516 self
. FillStyle
= FillStyle
518 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
520 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
521 self
. SetBrush ( FillColor
, FillStyle
)
523 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
= None , HTdc
= None ):
524 Points
= WorldToPixel ( self
. Points
)
526 dc
. SetBrush ( self
. Brush
)
527 dc
. DrawPolygon ( Points
)
528 if HTdc
and self
. HitAble
:
529 HTdc
. SetPen ( self
. HitPen
)
530 HTdc
. SetBrush ( self
. HitBrush
)
531 HTdc
. DrawPolygon ( Points
)
533 ##class PolygonSet(DrawObject):
535 ## The PolygonSet class takes a Geometry.Polygon object.
536 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
538 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
542 ## def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,InForeground = False):
543 ## DrawObject.__init__(self, InForeground)
545 ## ##fixme: there should be some error checking for everything being the right length.
548 ## self.Points = array(Points,Float)
549 ## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
551 ## self.LineColors = LineColors
552 ## self.LineStyles = LineStyles
553 ## self.LineWidths = LineWidths
554 ## self.FillColors = FillColors
555 ## self.FillStyles = FillStyles
557 ## self.SetPens(LineColors,LineStyles,LineWidths)
559 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
560 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
561 ## Points = WorldToPixel(self.Points)
562 ## Points.shape = (-1,4)
563 ## dc.DrawLineList(Points,self.Pens)
566 class Line ( DrawObject
, PointsObjectMixin
, LineOnlyMixin
):
569 The Line class takes a list of 2-tuples, or a NX2 NumPy Float array
570 of point coordinates.
572 It will draw a straight line if there are two points, and a polyline
573 if there are more than two.
576 def __init__ ( self
, Points
,
580 InForeground
= False ):
581 DrawObject
.__ init
__ ( self
, InForeground
)
584 self
. Points
= array ( Points
, Float
)
585 self
. CalcBoundingBox ()
587 self
. LineColor
= LineColor
588 self
. LineStyle
= LineStyle
589 self
. LineWidth
= LineWidth
591 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
593 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
596 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
597 Points
= WorldToPixel ( self
. Points
)
600 if HTdc
and self
. HitAble
:
601 HTdc
. SetPen ( self
. HitPen
)
602 HTdc
. DrawLines ( Points
)
604 ##class LineSet(DrawObject, ObjectSetMixin):
606 ## The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
607 ## so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
609 ## it creates a set of line segments, from (x1,y1) to (x2,y2)
613 ## def __init__(self,Points,LineColors,LineStyles,LineWidths,InForeground = False):
614 ## DrawObject.__init__(self, InForeground)
616 ## NumLines = len(Points) / 2
617 ## ##fixme: there should be some error checking for everything being the right length.
620 ## self.Points = array(Points,Float)
621 ## self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
623 ## self.LineColors = LineColors
624 ## self.LineStyles = LineStyles
625 ## self.LineWidths = LineWidths
627 ## self.SetPens(LineColors,LineStyles,LineWidths)
629 ## #def _Draw(self,dc,WorldToPixel,ScaleWorldToPixel):
630 ## def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None):
631 ## Points = WorldToPixel(self.Points)
632 ## Points.shape = (-1,4)
633 ## dc.DrawLineList(Points,self.Pens)
635 class PointSet ( DrawObject
, PointsObjectMixin
, ColorOnlyMixin
):
638 The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of
641 If Points is a sequence of tuples: Points[N][0] is the x-coordinate of
642 point N and Points[N][1] is the y-coordinate.
644 If Points is a NumPy array: Points[N,0] is the x-coordinate of point
645 N and Points[N,1] is the y-coordinate for arrays.
647 Each point will be drawn the same color and Diameter. The Diameter
648 is in screen pixels, not world coordinates.
650 The hit-test code does not distingish between the points, you will
651 only know that one of the points got hit, not which one. You can use
652 PointSet.FindClosestPoint(WorldPoint) to find out which one
654 In the case of points, the HitLineWidth is used as diameter.
657 def __init__ ( self
, Points
, Color
= "Black" , Diameter
= 1 , InForeground
= False ):
658 DrawObject
.__ init
__ ( self
, InForeground
)
660 self
. Points
= array ( Points
, Float
)
661 self
. Points
. shape
= (- 1 , 2 ) # Make sure it is a NX2 array, even if there is only one point
662 self
. CalcBoundingBox ()
663 self
. Diameter
= Diameter
665 self
. HitLineWidth
= self
. MinHitLineWidth
668 def SetDiameter ( self
, Diameter
):
669 self
. Diameter
= Diameter
672 def FindClosestPoint ( self
, XY
):
675 Returns the index of the closest point to the point, XY, given
676 in World coordinates. It's essentially random which you get if
677 there are more than one that are the same.
679 This can be used to figure out which point got hit in a mouse
680 binding callback, for instance. It's a lot faster that using a
681 lot of separate points.
684 ## kind of ugly to minimize data copying
686 d
= sum ( power ( d
, 2 , d
), 1 )
687 d
= absolute ( d
, d
) # don't need the real distance, just which is smallest
688 #dist = sqrt( sum( (self.Points - XY)**2), 1) )
691 def DrawD2 ( self
, dc
, Points
):
692 # A Little optimization for a diameter2 - point
693 dc
. DrawPointList ( Points
)
694 dc
. DrawPointList ( Points
+ ( 1 , 0 ))
695 dc
. DrawPointList ( Points
+ ( 0 , 1 ))
696 dc
. DrawPointList ( Points
+ ( 1 , 1 ))
698 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
700 Points
= WorldToPixel ( self
. Points
)
701 if self
. Diameter
<= 1 :
702 dc
. DrawPointList ( Points
)
703 elif self
. Diameter
<= 2 :
704 self
. DrawD2 ( dc
, Points
)
706 dc
. SetBrush ( self
. Brush
)
707 radius
= int ( round ( self
. Diameter
/ 2 ))
708 if len ( Points
) > 100 :
710 xywh
= concatenate (( xy
- radius
, ones ( xy
. shape
) * self
. Diameter
), 1 )
711 dc
. DrawEllipseList ( xywh
)
714 dc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
715 if HTdc
and self
. HitAble
:
716 HTdc
. SetPen ( self
. HitPen
)
717 HTdc
. SetBrush ( self
. HitBrush
)
718 if self
. Diameter
<= 1 :
719 HTdc
. DrawPointList ( Points
)
720 elif self
. Diameter
<= 2 :
721 self
. DrawD2 ( HTdc
, Points
)
723 if len ( Points
) > 100 :
725 xywh
= concatenate (( xy
- radius
, ones ( xy
. shape
) * self
. Diameter
), 1 )
726 HTdc
. DrawEllipseList ( xywh
)
729 HTdc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
731 class Point ( DrawObject
, XYObjectMixin
, ColorOnlyMixin
):
734 The Point class takes a 2-tuple, or a (2,) NumPy array of point
737 The Diameter is in screen points, not world coordinates, So the
738 Bounding box is just the point, and doesn't include the Diameter.
740 The HitLineWidth is used as diameter for the
744 def __init__ ( self
, XY
, Color
= "Black" , Diameter
= 1 , InForeground
= False ):
745 DrawObject
.__ init
__ ( self
, InForeground
)
747 self
. XY
= array ( XY
, Float
)
748 self
. XY
. shape
= ( 2 ,) # Make sure it is a 1X2 array, even if there is only one point
749 self
. CalcBoundingBox ()
751 self
. Diameter
= Diameter
753 self
. HitLineWidth
= self
. MinHitLineWidth
755 def SetDiameter ( self
, Diameter
):
756 self
. Diameter
= Diameter
758 def CalcBoundingBox ( self
):
759 self
. BoundingBox
= array ( ( self
. XY
, self
. XY
), Float
)
761 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
763 xy
= WorldToPixel ( self
. XY
)
764 if self
. Diameter
<= 1 :
765 dc
. DrawPoint ( xy
[ 0 ], xy
[ 1 ])
767 dc
. SetBrush ( self
. Brush
)
768 radius
= int ( round ( self
. Diameter
/ 2 ))
769 dc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
770 if HTdc
and self
. HitAble
:
771 HTdc
. SetPen ( self
. HitPen
)
772 if self
. Diameter
<= 1 :
773 HTdc
. DrawPoint ( xy
[ 0 ], xy
[ 1 ])
775 HTdc
. SetBrush ( self
. HitBrush
)
776 HTdc
. DrawCircle ( xy
[ 0 ], xy
[ 1 ], radius
)
778 class RectEllipse ( DrawObject
, XYObjectMixin
, LineAndFillMixin
):
779 def __init__ ( self
, x
, y
, width
, height
,
785 InForeground
= False ):
787 DrawObject
.__ init
__ ( self
, InForeground
)
789 self
. XY
= array ( ( x
, y
), Float
)
790 self
. WH
= array ( ( width
, height
), Float
)
791 self
. BoundingBox
= array ((( x
, y
), ( self
. XY
+ self
. WH
)), Float
)
792 self
. LineColor
= LineColor
793 self
. LineStyle
= LineStyle
794 self
. LineWidth
= LineWidth
795 self
. FillColor
= FillColor
796 self
. FillStyle
= FillStyle
798 self
. HitLineWidth
= max ( LineWidth
, self
. MinHitLineWidth
)
800 self
. SetPen ( LineColor
, LineStyle
, LineWidth
)
801 self
. SetBrush ( FillColor
, FillStyle
)
803 def SetShape ( self
, x
, y
, width
, height
):
804 self
. XY
= array ( ( x
, y
), Float
)
805 self
. WH
= array ( ( width
, height
), Float
)
806 self
. CalcBoundingBox ()
809 def SetUpDraw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
):
811 dc
. SetBrush ( self
. Brush
)
812 if HTdc
and self
. HitAble
:
813 HTdc
. SetPen ( self
. HitPen
)
814 HTdc
. SetBrush ( self
. HitBrush
)
815 return ( WorldToPixel ( self
. XY
),
816 ScaleWorldToPixel ( self
. WH
) )
818 def CalcBoundingBox ( self
):
819 self
. BoundingBox
= array (( self
. XY
, ( self
. XY
+ self
. WH
) ), Float
)
820 self
._ Canvas
. BoundingBoxDirty
= True
823 class Rectangle ( RectEllipse
):
824 # def __init__(*args, **kwargs):
825 # RectEllipse.__init__(*args, **kwargs)
828 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
829 ( XY
, WH
) = self
. SetUpDraw ( dc
,
833 dc
. DrawRectanglePointSize ( XY
, WH
)
834 if HTdc
and self
. HitAble
:
835 HTdc
. DrawRectanglePointSize ( XY
, WH
)
837 class Ellipse ( RectEllipse
):
838 # def __init__(*args, **kwargs):
839 # RectEllipse.__init__(*args, **kwargs)
841 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
842 ( XY
, WH
) = self
. SetUpDraw ( dc
,
846 dc
. DrawEllipsePointSize ( XY
, WH
)
847 if HTdc
and self
. HitAble
:
848 HTdc
. DrawEllipsePointSize ( XY
, WH
)
850 class Circle ( Ellipse
):
851 def __init__ ( self
, x
, y
, Diameter
, ** kwargs
):
852 self
. Center
= array (( x
, y
), Float
)
853 RectEllipse
.__ init
__ ( self
,
860 def SetDiameter ( self
, Diameter
):
861 x
, y
= self
. Center
- ( Diameter
/ 2 .)
867 class TextObjectMixin ( XYObjectMixin
):
870 A mix in class that holds attributes and methods that are needed by
875 ## I'm caching fonts, because on GTK, getting a new font can take a
876 ## while. However, it gets cleared after every full draw as hanging
877 ## on to a bunch of large fonts takes a massive amount of memory.
881 def SetFont ( self
, Size
, Family
, Style
, Weight
, Underline
, FaceName
):
882 self
. Font
= self
. FontList
. setdefault ( ( Size
,
896 def SetColor ( self
, Color
):
899 def SetBackgroundColor ( self
, BackgroundColor
):
900 self
. BackgroundColor
= BackgroundColor
902 ## store the function that shift the coords for drawing text. The
903 ## "c" parameter is the correction for world coordinates, rather
904 ## than pixel coords as the y axis is reversed
905 ShiftFunDict
= { 'tl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
) ,
906 'tc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
) ,
907 'tr' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
) ,
908 'cl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
- h
/ 2 + world
* h
) ,
909 'cc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
- h
/ 2 + world
* h
) ,
910 'cr' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
- h
/ 2 + world
* h
) ,
911 'bl' : lambda x
, y
, w
, h
, world
= 0 : ( x
, y
- h
+ 2 * world
* h
) ,
912 'bc' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
/ 2 , y
- h
+ 2 * world
* h
) ,
913 'br' : lambda x
, y
, w
, h
, world
= 0 : ( x
- w
, y
- h
+ 2 * world
* h
)}
915 class Text ( DrawObject
, TextObjectMixin
):
917 This class creates a text object, placed at the coordinates,
918 x,y. the "Position" argument is a two charactor string, indicating
919 where in relation to the coordinates the string should be oriented.
921 The first letter is: t, c, or b, for top, center and bottom The
922 second letter is: l, c, or r, for left, center and right The
923 position refers to the position relative to the text itself. It
924 defaults to "tl" (top left).
926 Size is the size of the font in pixels, or in points for printing
927 (if it ever gets implimented). Those will be the same, If you assume
931 Font family, a generic way of referring to fonts without
932 specifying actual facename. One of:
933 wx.DEFAULT: Chooses a default font.
934 wx.DECORATIVE: A decorative font.
935 wx.ROMAN: A formal, serif font.
936 wx.SCRIPT: A handwriting font.
937 wx.SWISS: A sans-serif font.
938 wx.MODERN: A fixed pitch font.
939 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
941 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
943 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
945 The value can be True or False. At present this may have an an
946 effect on Windows only.
948 Alternatively, you can set the kw arg: Font, to a wx.Font, and the above will be ignored.
950 The size is fixed, and does not scale with the drawing.
952 The hit-test is done on the entire text extent
956 def __init__ ( self
, String
, x
, y
,
959 BackgroundColor
= None ,
965 InForeground
= False ,
968 DrawObject
.__ init
__ ( self
, InForeground
)
971 # Input size in in Pixels, compute points size from PPI info.
972 # fixme: for printing, we'll have to do something a little different
973 self
. Size
= int ( round ( 72.0 * Size
/ ScreenPPI
))
976 self
. BackgroundColor
= BackgroundColor
981 FaceName
= Font
. GetFaceName ()
982 Family
= Font
. GetFamily ()
983 Size
= Font
. GetPointSize ()
984 Style
= Font
. GetStyle ()
985 Underlined
= Font
. GetUnderlined ()
986 Weight
= Font
. GetWeight ()
987 self
. SetFont ( Size
, Family
, Style
, Weight
, Underline
, FaceName
)
989 self
. BoundingBox
= array ((( x
, y
),( x
, y
)), Float
)
993 ( self
. TextWidth
, self
. TextHeight
) = ( None , None )
994 self
. ShiftFun
= self
. ShiftFunDict
[ Position
]
996 def CalcBoundingBox ( self
):
997 self
. BoundingBox
= array (( self
. XY
, self
. XY
), Float
)
1000 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
1001 XY
= WorldToPixel ( self
. XY
)
1002 dc
. SetFont ( self
. Font
)
1003 dc
. SetTextForeground ( self
. Color
)
1004 if self
. BackgroundColor
:
1005 dc
. SetBackgroundMode ( wx
. SOLID
)
1006 dc
. SetTextBackground ( self
. BackgroundColor
)
1008 dc
. SetBackgroundMode ( wx
. TRANSPARENT
)
1009 if self
. TextWidth
is None or self
. TextHeight
is None :
1010 ( self
. TextWidth
, self
. TextHeight
) = dc
. GetTextExtent ( self
. String
)
1011 XY
= self
. ShiftFun ( XY
[ 0 ], XY
[ 1 ], self
. TextWidth
, self
. TextHeight
)
1012 dc
. DrawTextPoint ( self
. String
, XY
)
1013 if HTdc
and self
. HitAble
:
1014 HTdc
. SetPen ( self
. HitPen
)
1015 HTdc
. SetBrush ( self
. HitBrush
)
1016 HTdc
. DrawRectanglePointSize ( XY
, ( self
. TextWidth
, self
. TextHeight
) )
1018 class ScaledText ( DrawObject
, TextObjectMixin
):
1020 This class creates a text object that is scaled when zoomed. It is
1021 placed at the coordinates, x,y. the "Position" argument is a two
1022 charactor string, indicating where in relation to the coordinates
1023 the string should be oriented.
1025 The first letter is: t, c, or b, for top, center and bottom The
1026 second letter is: l, c, or r, for left, center and right The
1027 position refers to the position relative to the text itself. It
1028 defaults to "tl" (top left).
1030 Size is the size of the font in world coordinates.
1033 Font family, a generic way of referring to fonts without
1034 specifying actual facename. One of:
1035 wx.DEFAULT: Chooses a default font.
1036 wx.DECORATI: A decorative font.
1037 wx.ROMAN: A formal, serif font.
1038 wx.SCRIPT: A handwriting font.
1039 wx.SWISS: A sans-serif font.
1040 wx.MODERN: A fixed pitch font.
1041 NOTE: these are only as good as the wxWindows defaults, which aren't so good.
1043 One of wx.NORMAL, wx.SLANT and wx.ITALIC.
1045 One of wx.NORMAL, wx.LIGHT and wx.BOLD.
1047 The value can be True or False. At present this may have an an
1048 effect on Windows only.
1050 Alternatively, you can set the kw arg: Font, to a wx.Font, and the
1051 above will be ignored. The size of the font you specify will be
1052 ignored, but the rest of it's attributes will be preserved.
1054 The size will scale as the drawing is zoomed.
1058 As fonts are scaled, the do end up a little different, so you don't
1059 get exactly the same picture as you scale up and doen, but it's
1062 On wxGTK1 on my Linux system, at least, using a font of over about
1063 3000 pts. brings the system to a halt. It's the Font Server using
1064 huge amounts of memory. My work around is to max the font size to
1065 3000 points, so it won't scale past there. GTK2 uses smarter font
1066 drawing, so that may not be an issue in future versions, so feel
1067 free to test. Another smarter way to do it would be to set a global
1068 zoom limit at that point.
1070 The hit-test is done on the entire text extent. This could be made
1071 optional, but I havn't gotten around to it.
1075 def __init__ ( self
, String
, x
, y
, Size
,
1077 BackgroundColor
= None ,
1084 InForeground
= False ):
1086 DrawObject
.__ init
__ ( self
, InForeground
)
1088 self
. String
= String
1089 self
. XY
= array ( ( x
, y
), Float
)
1092 self
. BackgroundColor
= BackgroundColor
1093 self
. Family
= Family
1095 self
. Weight
= Weight
1096 self
. Underline
= Underline
1100 self
. FaceName
= Font
. GetFaceName ()
1101 self
. Family
= Font
. GetFamily ()
1102 self
. Style
= Font
. GetStyle ()
1103 self
. Underlined
= Font
. GetUnderlined ()
1104 self
. Weight
= Font
. GetWeight ()
1106 # Experimental max font size value on wxGTK2: this works OK on
1107 # my system. If it's a lot larger, there is a crash, with the
1110 # The application 'FloatCanvasDemo.py' lost its
1111 # connection to the display :0.0; most likely the X server was
1112 # shut down or you killed/destroyed the application.
1114 # Windows and OS-X seem to be better behaved in this regard.
1115 # They may not draw it, but they don't crash either!
1116 self
. MaxFontSize
= 1000
1118 self
. ShiftFun
= self
. ShiftFunDict
[ Position
]
1120 self
. CalcBoundingBox ()
1123 def CalcBoundingBox ( self
):
1124 ## this isn't exact, as fonts don't scale exactly.
1126 bitmap
= wx
. EmptyBitmap ( 1 , 1 )
1127 dc
. SelectObject ( bitmap
) #wxMac needs a Bitmap selected for GetTextExtent to work.
1128 DrawingSize
= 40 # pts This effectively determines the resolution that the BB is computed to.
1129 ScaleFactor
= float ( self
. Size
) / DrawingSize
1130 dc
. SetFont ( self
. SetFont ( DrawingSize
, self
. Family
, self
. Style
, self
. Weight
, self
. Underline
, self
. FaceName
) )
1131 ( w
, h
) = dc
. GetTextExtent ( self
. String
)
1134 x
, y
= self
. ShiftFun ( self
. XY
[ 0 ], self
. XY
[ 1 ], w
, h
, world
= 1 )
1135 self
. BoundingBox
= array ((( x
, y
- h
),( x
+ w
, y
)), Float
)
1137 def _Draw ( self
, dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
= None ):
1138 ( X
, Y
) = WorldToPixel ( ( self
. XY
) )
1140 # compute the font size:
1141 Size
= abs ( ScaleWorldToPixel ( ( self
. Size
, self
. Size
) )[ 1 ] ) # only need a y coordinate length
1142 ## Check to see if the font size is large enough to blow up the X font server
1143 ## If so, limit it. Would it be better just to not draw it?
1144 ## note that this limit is dependent on how much memory you have, etc.
1145 Size
= min ( Size
, self
. MaxFontSize
)
1146 dc
. SetFont ( self
. SetFont ( Size
, self
. Family
, self
. Style
, self
. Weight
, self
. Underline
, self
. FaceName
))
1147 dc
. SetTextForeground ( self
. Color
)
1148 if self
. BackgroundColor
:
1149 dc
. SetBackgroundMode ( wx
. SOLID
)
1150 dc
. SetTextBackground ( self
. BackgroundColor
)
1152 dc
. SetBackgroundMode ( wx
. TRANSPARENT
)
1153 ( w
, h
) = dc
. GetTextExtent ( self
. String
)
1154 # compute the shift, and adjust the coordinates, if neccesary
1155 # This had to be put in here, because it changes with Zoom, as
1156 # fonts don't scale exactly.
1157 xy
= self
. ShiftFun ( X
, Y
, w
, h
)
1159 dc
. DrawTextPoint ( self
. String
, xy
)
1160 if HTdc
and self
. HitAble
:
1161 HTdc
. SetPen ( self
. HitPen
)
1162 HTdc
. SetBrush ( self
. HitBrush
)
1163 HTdc
. DrawRectanglePointSize ( xy
, ( w
, h
) )
1166 #---------------------------------------------------------------------------
1167 class FloatCanvas ( wx
. Panel
):
1171 This is a high level window for drawing maps and anything else in an
1172 arbitrary coordinate system.
1174 The goal is to provide a convenient way to draw stuff on the screen
1175 without having to deal with handling OnPaint events, converting to pixel
1176 coordinates, knowing about wxWindows brushes, pens, and colors, etc. It
1177 also provides virtually unlimited zooming and scrolling
1179 I am using it for two things:
1180 1) general purpose drawing in floating point coordinates
1181 2) displaying map data in Lat-long coordinates
1183 If the projection is set to None, it will draw in general purpose
1184 floating point coordinates. If the projection is set to 'FlatEarth', it
1185 will draw a FlatEarth projection, centered on the part of the map that
1186 you are viewing. You can also pass in your own projection function.
1188 It is double buffered, so re-draws after the window is uncovered by something
1189 else are very quick.
1191 It relies on NumPy, which is needed for speed (maybe, I havn't profiled it)
1193 Bugs and Limitations:
1194 Lots: patches, fixes welcome
1196 For Map drawing: It ignores the fact that the world is, in fact, a
1197 sphere, so it will do strange things if you are looking at stuff near
1198 the poles or the date line. so far I don't have a need to do that, so I
1199 havn't bothered to add any checks for that yet.
1202 I have set no zoom limits. What this means is that if you zoom in really
1203 far, you can get integer overflows, and get wierd results. It
1204 doesn't seem to actually cause any problems other than wierd output, at
1205 least when I have run it.
1208 I have done a couple of things to improve speed in this app. The one
1209 thing I have done is used NumPy Arrays to store the coordinates of the
1210 points of the objects. This allowed me to use array oriented functions
1211 when doing transformations, and should provide some speed improvement
1212 for objects with a lot of points (big polygons, polylines, pointsets).
1214 The real slowdown comes when you have to draw a lot of objects, because
1215 you have to call the wx.DC.DrawSomething call each time. This is plenty
1216 fast for tens of objects, OK for hundreds of objects, but pretty darn
1217 slow for thousands of objects.
1219 The solution is to be able to pass some sort of object set to the DC
1220 directly. I've used DC.DrawPointList(Points), and it helped a lot with
1221 drawing lots of points. I havn't got a LineSet type object, so I havn't
1222 used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList()
1223 methods implimented, and then I'd also have a full set of Object sets
1224 that could take advantage of them. I hope to get to it some day.
1228 At this point, there are a full set of custom mouse events. They are
1229 just like the rebulsr mouse events, but include an extra attribute:
1230 Event.GetCoords(), that returns the (x,y) position in world
1231 coordinates, as a length-2 NumPy vector of Floats.
1233 Copyright: Christopher Barker
1235 License: Same as the version of wxPython you are using it with
1237 Please let me know if you're using this!!!
1241 Chris.Barker@noaa.gov
1245 def __init__ ( self
, parent
, id = - 1 ,
1246 size
= wx
. DefaultSize
,
1247 ProjectionFun
= None ,
1248 BackgroundColor
= "WHITE" ,
1251 wx
. Panel
.__ init
__ ( self
, parent
, id , wx
. DefaultPosition
, size
)
1253 global ScreenPPI
## A global variable to hold the Pixels per inch that wxWindows thinks is in use.
1255 ScreenPPI
= dc
. GetPPI ()[ 0 ] # Assume square pixels
1258 self
. HitColorGenerator
= None
1259 self
. UseHitTest
= None
1261 self
. NumBetweenBlits
= 500
1263 self
. BackgroundBrush
= wx
. Brush ( BackgroundColor
, wx
. SOLID
)
1267 wx
. EVT_PAINT ( self
, self
. OnPaint
)
1268 wx
. EVT_SIZE ( self
, self
. OnSize
)
1270 wx
. EVT_LEFT_DOWN ( self
, self
. LeftDownEvent
)
1271 wx
. EVT_LEFT_UP ( self
, self
. LeftUpEvent
)
1272 wx
. EVT_LEFT_DCLICK ( self
, self
. LeftDoubleClickEvent
)
1273 wx
. EVT_MIDDLE_DOWN ( self
, self
. MiddleDownEvent
)
1274 wx
. EVT_MIDDLE_UP ( self
, self
. MiddleUpEvent
)
1275 wx
. EVT_MIDDLE_DCLICK ( self
, self
. MiddleDoubleClickEvent
)
1276 wx
. EVT_RIGHT_DOWN ( self
, self
. RightDownEvent
)
1277 wx
. EVT_RIGHT_UP ( self
, self
. RightUpEvent
)
1278 wx
. EVT_RIGHT_DCLICK ( self
, self
. RightDoubleCLickEvent
)
1279 wx
. EVT_MOTION ( self
, self
. MotionEvent
)
1280 wx
. EVT_MOUSEWHEEL ( self
, self
. WheelEvent
)
1282 ## CHB: I'm leaving these out for now.
1283 #wx.EVT_ENTER_WINDOW(self, self. )
1284 #wx.EVT_LEAVE_WINDOW(self, self. )
1286 ## create the Hit Test Dicts:
1291 self
._ ForeDrawList
= []
1292 self
._ ForegroundBuffer
= None
1293 self
. BoundingBox
= None
1294 self
. BoundingBoxDirty
= False
1295 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1297 self
. SetProjectionFun ( ProjectionFun
)
1299 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
) # No Projection to start!
1300 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
) # default Transformation
1305 self
. StartRBBox
= None
1306 self
. PrevRBBox
= None
1307 self
. StartMove
= None
1308 self
. PrevMoveXY
= None
1309 self
. ObjectUnderMouse
= None
1311 # called just to make sure everything is initialized
1316 def SetProjectionFun ( self
, ProjectionFun
):
1317 if ProjectionFun
== 'FlatEarth' :
1318 self
. ProjectionFun
= self
. FlatEarthProjection
1319 elif type ( ProjectionFun
) == types
. FunctionType
:
1320 self
. ProjectionFun
= ProjectionFun
1321 elif ProjectionFun
is None :
1322 self
. ProjectionFun
= lambda x
= None : array ( ( 1 , 1 ), Float
)
1324 raise FloatCanvasException ( 'Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector' )
1326 def FlatEarthProjection ( self
, CenterPoint
):
1327 return array (( cos ( pi
* CenterPoint
[ 1 ]/ 180 ), 1 ), Float
)
1329 def SetMode ( self
, Mode
):
1330 if Mode
in [ "ZoomIn" , "ZoomOut" , "Move" , "Mouse" , None ]:
1333 raise FloatCanvasException ( '" %s " is Not a valid Mode' % Mode
)
1335 def MakeHitDict ( self
):
1336 ##fixme: Should this just be None if nothing has been bound?
1337 self
. HitDict
= {EVT_FC_LEFT_DOWN: {}
,
1339 EVT_FC_LEFT_DCLICK
: {},
1340 EVT_FC_MIDDLE_DOWN
: {},
1341 EVT_FC_MIDDLE_UP
: {},
1342 EVT_FC_MIDDLE_DCLICK
: {},
1343 EVT_FC_RIGHT_DOWN
: {},
1344 EVT_FC_RIGHT_UP
: {},
1345 EVT_FC_RIGHT_DCLICK
: {},
1346 EVT_FC_ENTER_OBJECT
: {},
1347 EVT_FC_LEAVE_OBJECT
: {},
1350 def _RaiseMouseEvent ( self
, Event
, EventType
):
1352 This is called in various other places to raise a Mouse Event
1354 #print "in Raise Mouse Event", Event
1355 pt
= self
. PixelToWorld ( Event
. GetPosition () )
1356 evt
= _MouseEvent ( EventType
, Event
, self
. GetId (), pt
)
1357 self
. GetEventHandler (). ProcessEvent ( evt
)
1359 def HitTest ( self
, event
, HitEvent
):
1361 # check if there are any objects in the dict for this event
1362 if self
. HitDict
[ HitEvent
]:
1363 xy
= event
. GetPosition ()
1364 if self
._ ForegroundHTdc
:
1365 hitcolor
= self
._ ForegroundHTdc
. GetPixelPoint ( xy
)
1367 hitcolor
= self
._ HTdc
. GetPixelPoint ( xy
)
1368 color
= ( hitcolor
. Red (), hitcolor
. Green (), hitcolor
. Blue () )
1369 if color
in self
. HitDict
[ HitEvent
]:
1370 Object
= self
. HitDict
[ HitEvent
][ color
]
1371 ## Add the hit coords to the Object
1372 Object
. HitCoords
= self
. PixelToWorld ( xy
)
1373 Object
. CallBackFuncs
[ HitEvent
]( Object
)
1377 def MouseOverTest ( self
, event
):
1378 ##fixme: Can this be cleaned up?
1380 xy
= event
. GetPosition ()
1381 if self
._ ForegroundHTdc
:
1382 hitcolor
= self
._ ForegroundHTdc
. GetPixelPoint ( xy
)
1384 hitcolor
= self
._ HTdc
. GetPixelPoint ( xy
)
1385 color
= ( hitcolor
. Red (), hitcolor
. Green (), hitcolor
. Blue () )
1386 OldObject
= self
. ObjectUnderMouse
1387 ObjectCallbackCalled
= False
1388 if color
in self
. HitDict
[ EVT_FC_ENTER_OBJECT
]:
1389 Object
= self
. HitDict
[ EVT_FC_ENTER_OBJECT
][ color
]
1390 if ( OldObject
is None ):
1392 Object
. CallBackFuncs
[ EVT_FC_ENTER_OBJECT
]( Object
)
1393 ObjectCallbackCalled
= True
1395 pass # this means the enter event isn't bound for that object
1396 elif OldObject
== Object
: # the mouse is still on the same object
1398 ## Is the mouse on a differnt object as it was...
1399 elif not ( Object
== OldObject
):
1400 # call the leave object callback
1402 OldObject
. CallBackFuncs
[ EVT_FC_LEAVE_OBJECT
]( OldObject
)
1403 ObjectCallbackCalled
= True
1405 pass # this means the leave event isn't bound for that object
1407 Object
. CallBackFuncs
[ EVT_FC_ENTER_OBJECT
]( Object
)
1408 ObjectCallbackCalled
= True
1410 pass # this means the enter event isn't bound for that object
1411 ## set the new object under mouse
1412 self
. ObjectUnderMouse
= Object
1413 elif color
in self
. HitDict
[ EVT_FC_LEAVE_OBJECT
]:
1414 Object
= self
. HitDict
[ EVT_FC_LEAVE_OBJECT
][ color
]
1415 self
. ObjectUnderMouse
= Object
1417 # no objects under mouse bound to mouse-over events
1418 self
. ObjectUnderMouse
= None
1421 OldObject
. CallBackFuncs
[ EVT_FC_LEAVE_OBJECT
]( OldObject
)
1422 ObjectCallbackCalled
= True
1424 pass # this means the leave event isn't bound for that object
1425 return ObjectCallbackCalled
1428 ## fixme: There is a lot of repeated code here
1429 ## Is there a better way?
1430 def LeftDoubleClickEvent ( self
, event
):
1431 if self
. GUIMode
== "Mouse" :
1432 EventType
= EVT_FC_LEFT_DCLICK
1433 if not self
. HitTest ( event
, EventType
):
1434 self
._ RaiseMouseEvent
( event
, EventType
)
1437 def MiddleDownEvent ( self
, event
):
1438 if self
. GUIMode
== "Mouse" :
1439 EventType
= EVT_FC_MIDDLE_DOWN
1440 if not self
. HitTest ( event
, EventType
):
1441 self
._ RaiseMouseEvent
( event
, EventType
)
1443 def MiddleUpEvent ( self
, event
):
1444 if self
. GUIMode
== "Mouse" :
1445 EventType
= EVT_FC_MIDDLE_UP
1446 if not self
. HitTest ( event
, EventType
):
1447 self
._ RaiseMouseEvent
( event
, EventType
)
1449 def MiddleDoubleClickEvent ( self
, event
):
1450 if self
. GUIMode
== "Mouse" :
1451 EventType
= EVT_FC_MIDDLE_DCLICK
1452 if not self
. HitTest ( event
, EventType
):
1453 self
._ RaiseMouseEvent
( event
, EventType
)
1455 def RightUpEvent ( self
, event
):
1456 if self
. GUIMode
== "Mouse" :
1457 EventType
= EVT_FC_RIGHT_UP
1458 if not self
. HitTest ( event
, EventType
):
1459 self
._ RaiseMouseEvent
( event
, EventType
)
1461 def RightDoubleCLickEvent ( self
, event
):
1462 if self
. GUIMode
== "Mouse" :
1463 EventType
= EVT_FC_RIGHT_DCLICK
1464 if not self
. HitTest ( event
, EventType
):
1465 self
._ RaiseMouseEvent
( event
, EventType
)
1467 def WheelEvent ( self
, event
):
1468 if self
. GUIMode
== "Mouse" :
1469 self
._ RaiseMouseEvent
( event
, EVT_FC_MOUSEWHEEL
)
1472 def LeftDownEvent ( self
, event
):
1474 if self
. GUIMode
== "ZoomIn" :
1475 self
. StartRBBox
= array ( event
. GetPosition () )
1476 self
. PrevRBBox
= None
1478 elif self
. GUIMode
== "ZoomOut" :
1479 Center
= self
. PixelToWorld ( event
. GetPosition () )
1480 self
. Zoom ( 1 / 1.5 , Center
)
1481 elif self
. GUIMode
== "Move" :
1482 self
. StartMove
= array ( event
. GetPosition () )
1483 self
. PrevMoveXY
= ( 0 , 0 )
1484 elif self
. GUIMode
== "Mouse" :
1486 if not self
. HitTest ( event
, EVT_FC_LEFT_DOWN
):
1487 self
._ RaiseMouseEvent
( event
, EVT_FC_LEFT_DOWN
)
1491 def LeftUpEvent ( self
, event
):
1492 if self
. HasCapture ():
1495 if self
. GUIMode
== "ZoomIn" :
1496 if event
. LeftUp () and not self
. StartRBBox
is None :
1497 self
. PrevRBBox
= None
1498 EndRBBox
= event
. GetPosition ()
1499 StartRBBox
= self
. StartRBBox
1500 # if mouse has moved less that ten pixels, don't use the box.
1501 if ( abs ( StartRBBox
[ 0 ] - EndRBBox
[ 0 ]) > 10
1502 and abs ( StartRBBox
[ 1 ] - EndRBBox
[ 1 ]) > 10 ):
1503 EndRBBox
= self
. PixelToWorld ( EndRBBox
)
1504 StartRBBox
= self
. PixelToWorld ( StartRBBox
)
1505 BB
= array ((( min ( EndRBBox
[ 0 ], StartRBBox
[ 0 ]),
1506 min ( EndRBBox
[ 1 ], StartRBBox
[ 1 ])),
1507 ( max ( EndRBBox
[ 0 ], StartRBBox
[ 0 ]),
1508 max ( EndRBBox
[ 1 ], StartRBBox
[ 1 ]))), Float
)
1511 Center
= self
. PixelToWorld ( StartRBBox
)
1512 self
. Zoom ( 1.5 , Center
)
1513 self
. StartRBBox
= None
1514 elif self
. GUIMode
== "Move" :
1515 if not self
. StartMove
is None :
1516 StartMove
= self
. StartMove
1517 EndMove
= array (( event
. GetX (), event
. GetY ()))
1518 if sum (( StartMove
- EndMove
)** 2 ) > 16 :
1519 self
. Move ( StartMove
- EndMove
, 'Pixel' )
1520 self
. StartMove
= None
1521 elif self
. GUIMode
== "Mouse" :
1522 EventType
= EVT_FC_LEFT_UP
1523 if not self
. HitTest ( event
, EventType
):
1524 self
._ RaiseMouseEvent
( event
, EventType
)
1528 def MotionEvent ( self
, event
):
1530 if self
. GUIMode
== "ZoomIn" :
1531 if event
. Dragging () and event
. LeftIsDown () and not ( self
. StartRBBox
is None ):
1532 xy0
= self
. StartRBBox
1533 xy1
= array ( event
. GetPosition () )
1535 wh
[ 0 ] = max ( wh
[ 0 ], int ( wh
[ 1 ]* self
. AspectRatio
))
1536 wh
[ 1 ] = int ( wh
[ 0 ] / self
. AspectRatio
)
1537 xy_c
= ( xy0
+ xy1
) / 2
1538 dc
= wx
. ClientDC ( self
)
1540 dc
. SetPen ( wx
. Pen ( 'WHITE' , 2 , wx
. SHORT_DASH
))
1541 dc
. SetBrush ( wx
. TRANSPARENT_BRUSH
)
1542 dc
. SetLogicalFunction ( wx
. XOR
)
1544 dc
. DrawRectanglePointSize (* self
. PrevRBBox
)
1545 self
. PrevRBBox
= ( xy_c
- wh
/ 2 , wh
)
1546 dc
. DrawRectanglePointSize ( * self
. PrevRBBox
)
1548 elif self
. GUIMode
== "Move" :
1549 if event
. Dragging () and event
. LeftIsDown () and not self
. StartMove
is None :
1550 xy1
= array ( event
. GetPosition () )
1552 xy_tl
= xy1
- self
. StartMove
1553 dc
= wx
. ClientDC ( self
)
1555 x1
, y1
= self
. PrevMoveXY
1557 w
, h
= self
. PanelSize
1558 if x2
> x1
and y2
> y1
:
1565 elif x2
> x1
and y2
<= y1
:
1574 elif x2
<= x1
and y2
> y1
:
1583 elif x2
<= x1
and y2
<= y1
:
1593 dc
. SetPen ( wx
. TRANSPARENT_PEN
)
1594 dc
. SetBrush ( self
. BackgroundBrush
)
1595 dc
. DrawRectangle ( xa
, ya
, wa
, ha
)
1596 dc
. DrawRectangle ( xb
, yb
, wb
, hb
)
1597 self
. PrevMoveXY
= xy_tl
1598 if self
._ ForegroundBuffer
:
1599 dc
. DrawBitmapPoint ( self
._ ForegroundBuffer
, xy_tl
)
1601 dc
. DrawBitmapPoint ( self
._ Buffer
, xy_tl
)
1603 elif self
. GUIMode
== "Mouse" :
1604 ## Only do something if there are mouse over events bound
1605 if self
. HitDict
and ( self
. HitDict
[ EVT_FC_ENTER_OBJECT
] or self
. HitDict
[ EVT_FC_LEAVE_OBJECT
] ):
1606 if not self
. MouseOverTest ( event
):
1607 self
._ RaiseMouseEvent
( event
, EVT_FC_MOTION
)
1610 self
._ RaiseMouseEvent
( event
, EVT_FC_MOTION
)
1614 def RightDownEvent ( self
, event
):
1616 if self
. GUIMode
== "ZoomIn" :
1617 Center
= self
. PixelToWorld (( event
. GetX (), event
. GetY ()))
1618 self
. Zoom ( 1 / 1.5 , Center
)
1619 elif self
. GUIMode
== "ZoomOut" :
1620 Center
= self
. PixelToWorld (( event
. GetX (), event
. GetY ()))
1621 self
. Zoom ( 1.5 , Center
)
1622 elif self
. GUIMode
== "Mouse" :
1623 EventType
= EVT_FC_RIGHT_DOWN
1624 if not self
. HitTest ( event
, EventType
):
1625 self
._ RaiseMouseEvent
( event
, EventType
)
1629 def MakeNewBuffers ( self
):
1630 self
._ BackgroundDirty
= True
1631 # Make new offscreen bitmap:
1632 self
._ Buffer
= wx
. EmptyBitmap (* self
. PanelSize
)
1634 #dc.SelectObject(self._Buffer)
1636 if self
._ ForeDrawList
:
1637 self
._ ForegroundBuffer
= wx
. EmptyBitmap (* self
. PanelSize
)
1639 self
._ ForegroundBuffer
= None
1644 self
._ ForegroundHTdc
= None
1646 def MakeNewHTdc ( self
):
1647 ## Note: While it's considered a "bad idea" to keep a
1648 ## MemoryDC around I'm doing it here because a wx.Bitmap
1649 ## doesn't have a GetPixel method so a DC is needed to do
1650 ## the hit-test. It didn't seem like a good idea to re-create
1651 ## a wx.MemoryDC on every single mouse event, so I keep it
1653 self
._ HTdc
= wx
. MemoryDC ()
1654 self
._ HTBitmap
= wx
. EmptyBitmap (* self
. PanelSize
)
1655 self
._ HTdc
. SelectObject ( self
._ HTBitmap
)
1656 self
._ HTdc
. SetBackground ( wx
. BLACK_BRUSH
)
1657 if self
._ ForeDrawList
:
1658 self
._ ForegroundHTdc
= wx
. MemoryDC ()
1659 self
._ ForegroundHTBitmap
= wx
. EmptyBitmap (* self
. PanelSize
)
1660 self
._ ForegroundHTdc
. SelectObject ( self
._ ForegroundHTBitmap
)
1661 self
._ ForegroundHTdc
. SetBackground ( wx
. BLACK_BRUSH
)
1663 self
._ ForegroundHTdc
= None
1665 def OnSize ( self
, event
):
1666 self
. PanelSize
= array ( self
. GetClientSizeTuple (), Int32
)
1667 self
. HalfPanelSize
= self
. PanelSize
/ 2 # lrk: added for speed in WorldToPixel
1668 if self
. PanelSize
[ 0 ] == 0 or self
. PanelSize
[ 1 ] == 0 :
1669 self
. AspectRatio
= 1.0
1671 self
. AspectRatio
= float ( self
. PanelSize
[ 0 ]) / self
. PanelSize
[ 1 ]
1672 self
. MakeNewBuffers ()
1675 def OnPaint ( self
, event
):
1676 dc
= wx
. PaintDC ( self
)
1677 if self
._ ForegroundBuffer
:
1678 dc
. DrawBitmap ( self
._ ForegroundBuffer
, 0 , 0 )
1680 dc
. DrawBitmap ( self
._ Buffer
, 0 , 0 )
1682 def Draw ( self
, Force
= False ):
1684 There is a main buffer set up to double buffer the screen, so
1685 you can get quick re-draws when the window gets uncovered.
1687 If there are any objects in self._ForeDrawList, then the
1688 background gets drawn to a new buffer, and the foreground
1689 objects get drawn on top of it. The final result if blitted to
1690 the screen, and stored for future Paint events. This is done so
1691 that you can have a complicated background, but have something
1692 changing on the foreground, without having to wait for the
1693 background to get re-drawn. This can be used to support simple
1694 animation, for instance.
1698 if self
. PanelSize
< ( 1 , 1 ): # it's possible for this to get called before being properly initialized.
1700 if self
. Debug
: start
= clock ()
1701 ScreenDC
= wx
. ClientDC ( self
)
1702 ViewPortWorld
= ( self
. PixelToWorld (( 0 , 0 )),
1703 self
. PixelToWorld ( self
. PanelSize
) )
1704 ViewPortBB
= array ( ( minimum
. reduce ( ViewPortWorld
),
1705 maximum
. reduce ( ViewPortWorld
) ) )
1707 dc
. SelectObject ( self
._ Buffer
)
1708 if self
._ BackgroundDirty
or Force
:
1709 #print "Background is Dirty"
1710 dc
. SetBackground ( self
. BackgroundBrush
)
1714 self
._ DrawObjects
( dc
, self
._ DrawList
, ScreenDC
, ViewPortBB
, self
._ HTdc
)
1715 self
._ BackgroundDirty
= False
1717 if self
._ ForeDrawList
:
1718 ## If an object was just added to the Foreground, there might not yet be a buffer
1719 if self
._ ForegroundBuffer
is None :
1720 self
._ ForegroundBuffer
= wx
. EmptyBitmap ( self
. PanelSize
[ 0 ],
1723 dc
= wx
. MemoryDC () ## I got some strange errors (linewidths wrong) if I didn't make a new DC here
1724 dc
. SelectObject ( self
._ ForegroundBuffer
)
1725 dc
. DrawBitmap ( self
._ Buffer
, 0 , 0 )
1726 if self
._ ForegroundHTdc
is None :
1727 self
._ ForegroundHTdc
= wx
. MemoryDC ()
1728 self
._ ForegroundHTdc
. SelectObject ( wx
. EmptyBitmap (
1730 self
. PanelSize
[ 1 ]) )
1732 ## blit the background HT buffer to the foreground HT buffer
1733 self
._ ForegroundHTdc
. Blit ( 0 , 0 ,
1734 self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ],
1736 self
._ DrawObjects
( dc
,
1740 self
._ ForegroundHTdc
)
1741 ScreenDC
. Blit ( 0 , 0 , self
. PanelSize
[ 0 ], self
. PanelSize
[ 1 ], dc
, 0 , 0 )
1742 # If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
1743 # This seeems out of place, but it works.
1745 ScreenDC
. SetPen ( wx
. Pen ( 'WHITE' , 2 , wx
. SHORT_DASH
))
1746 ScreenDC
. SetBrush ( wx
. TRANSPARENT_BRUSH
)
1747 ScreenDC
. SetLogicalFunction ( wx
. XOR
)
1748 ScreenDC
. DrawRectanglePointSize (* self
. PrevRBBox
)
1749 if self
. Debug
: print "Drawing took %f seconds of CPU time" %( clock ()- start
)
1751 ## Clear the font cache
1752 ## IF you don't do this, the X font server starts to take up Massive amounts of memory
1753 ## This is mostly a problem with very large fonts, that you get with scaled text when zoomed in.
1754 DrawObject
. FontList
= {}
1756 def _ShouldRedraw ( DrawList
, ViewPortBB
): # lrk: adapted code from BBCheck
1757 # lrk: Returns the objects that should be redrawn
1761 for Object
in DrawList
:
1762 BB1
= Object
. BoundingBox
1763 if ( BB1
[ 1 , 0 ] > BB2
[ 0 , 0 ] and BB1
[ 0 , 0 ] < BB2
[ 1 , 0 ] and
1764 BB1
[ 1 , 1 ] > BB2
[ 0 , 1 ] and BB1
[ 0 , 1 ] < BB2
[ 1 , 1 ]):
1765 redrawlist
. append ( Object
)
1767 _ShouldRedraw
= staticmethod ( _ShouldRedraw
)
1770 ## def BBCheck(self, BB1, BB2):
1773 ## BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
1776 ## if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
1777 ## (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
1782 def Move ( self
, shift
, CoordType
):
1784 move the image in the window.
1786 shift is an (x,y) tuple, specifying the amount to shift in each direction
1788 It can be in any of three coordinates: Panel, Pixel, World,
1789 specified by the CoordType parameter
1791 Panel coordinates means you want to shift the image by some
1792 fraction of the size of the displaed image
1794 Pixel coordinates means you want to shift the image by some number of pixels
1796 World coordinates mean you want to shift the image by an amount
1797 in Floating point world coordinates
1801 shift
= array ( shift
, Float
)
1802 if CoordType
== 'Panel' : # convert from panel coordinates
1803 shift
= shift
* array ((- 1 , 1 ), Float
) * self
. PanelSize
/ self
. TransformVector
1804 elif CoordType
== 'Pixel' : # convert from pixel coordinates
1805 shift
= shift
/ self
. TransformVector
1806 elif CoordType
== 'World' : # No conversion
1809 raise FloatCanvasException ( 'CoordType must be either "Panel", "Pixel", or "World"' )
1811 self
. ViewPortCenter
= self
. ViewPortCenter
+ shift
1812 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1813 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1814 self
._ BackgroundDirty
= True
1817 def Zoom ( self
, factor
, center
= None ):
1820 Zoom(factor, center) changes the amount of zoom of the image by factor.
1821 If factor is greater than one, the image gets larger.
1822 If factor is less than one, the image gets smaller.
1824 Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
1825 If center is not given, the center will stay the same.
1828 self
. Scale
= self
. Scale
* factor
1829 if not center
is None :
1830 self
. ViewPortCenter
= array ( center
, Float
)
1831 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1832 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
) * self
. MapProjectionVector
1833 self
._ BackgroundDirty
= True
1836 def ZoomToBB ( self
, NewBB
= None , DrawFlag
= True ):
1840 Zooms the image to the bounding box given, or to the bounding
1841 box of all the objects on the canvas, if none is given.
1845 if not NewBB
is None :
1848 if self
. BoundingBoxDirty
:
1849 self
._ ResetBoundingBox
()
1850 BoundingBox
= self
. BoundingBox
1851 if not BoundingBox
is None :
1852 self
. ViewPortCenter
= array ((( BoundingBox
[ 0 , 0 ]+ BoundingBox
[ 1 , 0 ])/ 2 ,
1853 ( BoundingBox
[ 0 , 1 ]+ BoundingBox
[ 1 , 1 ])/ 2 ), Float
)
1854 self
. MapProjectionVector
= self
. ProjectionFun ( self
. ViewPortCenter
)
1855 # Compute the new Scale
1856 BoundingBox
= BoundingBox
* self
. MapProjectionVector
1858 self
. Scale
= min ( abs ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ])),
1859 abs ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ])) )* 0.95
1860 except ZeroDivisionError : # this will happen if the BB has zero width or height
1862 self
. Scale
= ( self
. PanelSize
[ 0 ] / ( BoundingBox
[ 1 , 0 ]- BoundingBox
[ 0 , 0 ]))* 0.95
1863 except ZeroDivisionError :
1865 self
. Scale
= ( self
. PanelSize
[ 1 ] / ( BoundingBox
[ 1 , 1 ]- BoundingBox
[ 0 , 1 ]))* 0.95
1866 except ZeroDivisionError : #zero size! (must be a single point)
1869 self
. TransformVector
= array (( self
. Scale
,- self
. Scale
), Float
)* self
. MapProjectionVector
1871 self
._ BackgroundDirty
= True
1874 # Reset the shifting and scaling to defaults when there is no BB
1875 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1876 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
) # No Projection to start!
1877 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
) # default Transformation
1880 def RemoveObjects ( self
, Objects
):
1881 for Object
in Objects
:
1882 self
. RemoveObject ( Object
, ResetBB
= False )
1883 self
. BoundingBoxDirty
= True
1885 def RemoveObject ( self
, Object
, ResetBB
= True ):
1886 ##fixme: Using the list.remove method is kind of slow
1887 if Object
. InForeground
:
1888 self
._ ForeDrawList
. remove ( Object
)
1890 self
._ DrawList
. remove ( Object
)
1891 self
._ BackgroundDirty
= True
1893 self
. BoundingBoxDirty
= True
1895 def ClearAll ( self
, ResetBB
= True ):
1897 self
._ ForeDrawList
= []
1898 self
._ BackgroundDirty
= True
1899 self
. HitColorGenerator
= None
1900 self
. UseHitTest
= False
1902 self
._ ResetBoundingBox
()
1903 self
. MakeNewBuffers ()
1907 ## def _AddBoundingBox(self,NewBB):
1908 ## if self.BoundingBox is None:
1909 ## self.BoundingBox = NewBB
1910 ## self.ZoomToBB(NewBB,DrawFlag = False)
1912 ## self.BoundingBox = array( ( (min(self.BoundingBox[0,0],NewBB[0,0]),
1913 ## min(self.BoundingBox[0,1],NewBB[0,1])),
1914 ## (max(self.BoundingBox[1,0],NewBB[1,0]),
1915 ## max(self.BoundingBox[1,1],NewBB[1,1]))),
1918 def _getboundingbox ( bboxarray
): # lrk: added this
1920 upperleft
= minimum
. reduce ( bboxarray
[:, 0 ])
1921 lowerright
= maximum
. reduce ( bboxarray
[:, 1 ])
1922 return array (( upperleft
, lowerright
), Float
)
1924 _getboundingbox
= staticmethod ( _getboundingbox
)
1926 def _ResetBoundingBox ( self
):
1927 if self
._ DrawList
or self
._ ForeDrawList
:
1928 bboxarray
= zeros (( len ( self
._ DrawList
)+ len ( self
._ ForeDrawList
), 2 , 2 ), Float
)
1929 i
= - 1 # just in case _DrawList is empty
1930 for ( i
, BB
) in enumerate ( self
._ DrawList
):
1931 bboxarray
[ i
] = BB
. BoundingBox
1932 for ( j
, BB
) in enumerate ( self
._ ForeDrawList
):
1933 bboxarray
[ i
+ j
+ 1 ] = BB
. BoundingBox
1934 self
. BoundingBox
= self
._ getboundingbox
( bboxarray
)
1936 self
. BoundingBox
= None
1937 self
. ViewPortCenter
= array ( ( 0 , 0 ), Float
)
1938 self
. TransformVector
= array ( ( 1 ,- 1 ), Float
)
1939 self
. MapProjectionVector
= array ( ( 1 , 1 ), Float
)
1941 self
. BoundingBoxDirty
= False
1943 def PixelToWorld ( self
, Points
):
1945 Converts coordinates from Pixel coordinates to world coordinates.
1947 Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
1950 return ((( asarray ( Points
, Float
) - ( self
. PanelSize
/ 2 ))/ self
. TransformVector
) + self
. ViewPortCenter
)
1952 def WorldToPixel ( self
, Coordinates
):
1954 This function will get passed to the drawing functions of the objects,
1955 to transform from world to pixel coordinates.
1956 Coordinates should be a NX2 array of (x,y) coordinates, or
1957 a 2-tuple, or sequence of 2-tuples.
1959 #Note: this can be called by users code for various reasons, so asarray is needed.
1960 return ((( asarray ( Coordinates
, Float
) -
1961 self
. ViewPortCenter
)* self
. TransformVector
)+
1962 ( self
. HalfPanelSize
)). astype ( 'i' )
1964 def ScaleWorldToPixel ( self
, Lengths
):
1966 This function will get passed to the drawing functions of the objects,
1967 to Change a length from world to pixel coordinates.
1969 Lengths should be a NX2 array of (x,y) coordinates, or
1970 a 2-tuple, or sequence of 2-tuples.
1972 return ( ( asarray ( Lengths
, Float
)* self
. TransformVector
) ). astype ( 'i' )
1974 def ScalePixelToWorld ( self
, Lengths
):
1976 This function computes a pair of x.y lengths,
1977 to change then from pixel to world coordinates.
1979 Lengths should be a NX2 array of (x,y) coordinates, or
1980 a 2-tuple, or sequence of 2-tuples.
1983 return ( asarray ( Lengths
, Float
) / self
. TransformVector
)
1985 def AddObject ( self
, obj
):
1986 # put in a reference to the Canvas, so remove and other stuff can work
1988 if obj
. InForeground
:
1989 self
._ ForeDrawList
. append ( obj
)
1990 self
. UseForeground
= True
1992 self
._ DrawList
. append ( obj
)
1993 self
._ BackgroundDirty
= True
1994 self
. BoundingBoxDirty
= True
1997 def _DrawObjects ( self
, dc
, DrawList
, ScreenDC
, ViewPortBB
, HTdc
= None ):
1999 This is a convenience function;
2000 This function takes the list of objects and draws them to specified
2003 dc
. SetBackground ( self
. BackgroundBrush
)
2006 PanelSize0
, PanelSize1
= self
. PanelSize
# for speed
2007 WorldToPixel
= self
. WorldToPixel
# for speed
2008 ScaleWorldToPixel
= self
. ScaleWorldToPixel
# for speed
2009 Blit
= ScreenDC
. Blit
# for speed
2010 NumBetweenBlits
= self
. NumBetweenBlits
# for speed
2011 for i
, Object
in enumerate ( self
._ ShouldRedraw
( DrawList
, ViewPortBB
)):
2012 Object
._ Draw
( dc
, WorldToPixel
, ScaleWorldToPixel
, HTdc
)
2013 if i
% NumBetweenBlits
== 0 :
2014 Blit ( 0 , 0 , PanelSize0
, PanelSize1
, dc
, 0 , 0 )
2017 ## ## This is a way to automatically add a AddObject method for each
2018 ## ## object type This code has been replaced by Leo's code above, so
2019 ## ## that it happens at module init, rather than as needed. The
2020 ## ## primary advantage of this is that dir(FloatCanvas) will have
2021 ## ## them, and docstrings are preserved. Probably more useful
2022 ## ## exceptions if there is a problem, as well.
2023 ## def __getattr__(self, name):
2024 ## if name[:3] == "Add":
2025 ## func=globals()[name[3:]]
2026 ## def AddFun(*args, **kwargs):
2027 ## Object = func(*args, **kwargs)
2028 ## self.AddObject(Object)
2030 ## ## add it to FloatCanvas' dict for future calls.
2031 ## self.__dict__[name] = AddFun
2034 ## raise AttributeError("FloatCanvas has no attribute '%s'"%name)
2036 def _makeFloatCanvasAddMethods (): ## lrk's code for doing this in module __init__
2037 classnames
= [ "Circle" , "Ellipse" , "Rectangle" , "ScaledText" , "Polygon" ,
2038 "Line" , "Text" , "PointSet" , "Point" ]
2039 for classname
in classnames
:
2040 klass
= globals ()[ classname
]
2041 def getaddshapemethod ( klass
= klass
):
2042 def addshape ( self
, * args
, ** kwargs
):
2043 Object
= klass (* args
, ** kwargs
)
2044 self
. AddObject ( Object
)
2047 addshapemethod
= getaddshapemethod ()
2048 methodname
= "Add" + classname
2049 setattr ( FloatCanvas
, methodname
, addshapemethod
)
2050 docstring
= "Creates %s and adds its reference to the canvas. \n " % classname
2051 docstring
+= "Argument protocol same as %s class" % classname
2053 docstring
+= ", whose docstring is: \n %s " % klass
.__ doc
__
2054 FloatCanvas
.__ dict
__ [ methodname
] .__ doc
__ = docstring
2056 _makeFloatCanvasAddMethods ()