]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/floatcanvas.py
8276da20f0163832cd258e4ca9e1ca0096c9f1dd
[wxWidgets.git] / wxPython / wxPython / lib / floatcanvas.py
1 #!/usr/bin/env python2.2
2
3 from Numeric import array,Float,cos,pi,sum,minimum,maximum
4
5 from time import clock
6 from wxPython.wx import *
7 import types
8 import os
9
10 ID_ZOOM_IN_BUTTON = wxNewId()
11 ID_ZOOM_OUT_BUTTON = wxNewId()
12 ID_ZOOM_TO_FIT_BUTTON = wxNewId()
13 ID_MOVE_MODE_BUTTON = wxNewId()
14 ID_TEST_BUTTON = wxNewId()
15 ID_ABOUT_MENU = wxNewId()
16 ID_EXIT_MENU = wxNewId()
17 ID_ZOOM_TO_FIT_MENU = wxNewId()
18 ID_DRAWTEST_MENU = wxNewId()
19 ID_DRAWMAP_MENU = wxNewId()
20 ID_CLEAR_MENU = wxNewId()
21
22 ID_TEST = wxNewId()
23
24
25 ### These are some functions for bitmaps of icons.
26 import cPickle, zlib
27
28 def GetHandData():
29 return cPickle.loads(zlib.decompress(
30 'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
31 \x01\xc8S\xb6t\x06A(\x1f\x0b\xa0\xa9\x8c\x9e\x1e6\x19\xa0\xa8\x1e\x88\xd4C\
32 \x97\xd1\x83\xe8\x80 \x9c2zh\xa6\xc1\x11X\n\xab\x8c\x02\x8a\x0cD!\x92\x12\
33 \x98\x8c\x1e\x8a\x8b\xd1d\x14\xf4\x90%\x90LC\xf6\xbf\x1e\xba\xab\x91%\xd0\
34 \xdc\x86C\x06\xd9m\xe8!\xaa\x87S\x86\x1a1\xa7\x07\x00v\x0f[\x17' ))
35
36 def GetHandBitmap():
37 return wxBitmapFromXPMData(GetHandData())
38
39 #----------------------------------------------------------------------
40 def GetPlusData():
41 return cPickle.loads(zlib.decompress(
42 'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
43 \x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=l2`\r\
44 \xe82HF\xe9a\xc8\xe8\xe9A\x9c@\x8a\x0c\x0e\xd3p\xbb\x00\x8f\xab\xe1>\xd5\xd3\
45 \xc3\x15:P)l!\n\x91\xc2\x1a\xd6`)\xec\xb1\x00\x92\xc2\x11?\xb8e\x88\x8fSt\
46 \x19=\x00\x82\x16[\xf7' ))
47
48 def GetPlusBitmap():
49 return wxBitmapFromXPMData(GetPlusData())
50
51 #----------------------------------------------------------------------
52 def GetMinusData():
53 return cPickle.loads(zlib.decompress(
54 'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
55 \x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=\xa2e\
56 \x10\x16@\x99\xc82zz\x10\'\x90"\x83\xc34r\xdc\x86\xf0\xa9\x9e\x1e\xae\xd0\
57 \x81Ja\x0bQ\x88\x14\xd6\xb0\x06Ka\x8f\x05\x90\x14\x8e\xf8\xc1-C|\x9c\xa2\xcb\
58 \xe8\x01\x00\xed\x0f[\x87' ))
59
60 def GetMinusBitmap():
61 return wxBitmapFromXPMData(GetMinusData())
62
63 ## This is a bunch of stuff for implimenting interactive use: catching
64 ## when objects are clicked on by the mouse, etc. I've made a start, so if
65 ## you are interesed in making that work, let me know, I may have gotten
66 ## it going by then
67
68 #### I probably want a full set of events someday:
69 ## #### mouse over, right click, left click mouse up etc, etc.
70 ## ##FLOATCANVAS_EVENT_LEFT_DOWN = wxNewEventType()
71 ## ##FLOATCANVAS_EVENT_LEFT_UP = wxNewEventType()
72 ## ##FLOATCANVAS_EVENT_RIGHT_DOWN = wxNewEventType()
73 ## ##FLOATCANVAS_EVENT_RIGHT_UP = wxNewEventType()
74 ## ##FLOATCANVAS_EVENT_MOUSE_OVER = wxNewEventType()
75
76 ##WXFLOATCANVASEVENT = wxNewEventType()
77
78 ##def EVT_WXFLOATCANVASEVENT( window, function ):
79
80 ## """Your documentation here"""
81
82 ## window.Connect( -1, -1, WXFLOATCANVASEVENT, function )
83
84 ##class wxFloatCanvasObjectEvent(wxPyCommandEvent):
85 ## def __init__(self, WindowID,Object):
86 ## wxPyCommandEvent.__init__(self, WXFLOATCANVASEVENT, WindowID)
87 ## self.Object = Object
88
89 ## def Clone( self ):
90 ## self.__class__( self.GetId() )
91
92 ##class ColorGenerator:
93
94 ## """ An instance of this class generates a unique color each time
95 ## GetNextColor() is called. Someday I will use a proper Python
96 ## generator for this class.
97
98 ## The point of this generator is for the hit-test bitmap, each object
99 ## needs to be a unique color. Also, each system can be running a
100 ## different number of colors, and it doesn't appear to be possible to
101 ## have a wxMemDC with a different colordepth as the screen so this
102 ## generates colors far enough apart that they can be distinguished on
103 ## a 16bit screen. Anything less than 16bits won't work.
104 ## """
105
106 ## def __init__(self,depth = 16):
107 ## self.r = 0
108 ## self.g = 0
109 ## self.b = 0
110 ## if depth == 16:
111 ## self.step = 8
112 ## elif depth >= 24:
113 ## self.step = 1
114 ## else:
115 ## raise "ColorGenerator does not work with depth = %s"%depth
116
117 ## def GetNextColor(self):
118 ## step = self.step
119 ## ##r,g,b = self.r,self.g,self.b
120 ## self.r += step
121 ## if self.r > 255:
122 ## self.r = step
123 ## self.g += step
124 ## if self.g > 255:
125 ## self.g = step
126 ## self.b += step
127 ## if self.b > 255:
128 ## ## fixme: this should be a derived exception
129 ## raise "Too many objects for HitTest"
130 ## return (self.r,self.g,self.b)
131
132
133 class draw_object:
134 """
135 This is the base class for all the objects that can be drawn.
136
137 each object has the following properties; (incomplete)
138
139 BoundingBox : is of the form: array((min_x,min_y),(max_x,max_y))
140 Pen
141 Brush
142
143 """
144
145 def __init__(self,Foreground = 0):
146 self.Foreground = Foreground
147
148 self._Canvas = None
149
150 # I pre-define all these as class variables to provide an easier
151 # interface, and perhaps speed things up by caching all the Pens
152 # and Brushes, although that may not help, as I think wx now
153 # does that on it's own. Send me a note if you know!
154
155 BrushList = {
156 ( None,"Transparent") : wxTRANSPARENT_BRUSH,
157 ("Blue","Solid") : wxBLUE_BRUSH,
158 ("Green","Solid") : wxGREEN_BRUSH,
159 ("White","Solid") : wxWHITE_BRUSH,
160 ("Black","Solid") : wxBLACK_BRUSH,
161 ("Grey","Solid") : wxGREY_BRUSH,
162 ("MediumGrey","Solid") : wxMEDIUM_GREY_BRUSH,
163 ("LightGrey","Solid") : wxLIGHT_GREY_BRUSH,
164 ("Cyan","Solid") : wxCYAN_BRUSH,
165 ("Red","Solid") : wxRED_BRUSH
166 }
167 PenList = {
168 (None,"Transparent",1) : wxTRANSPARENT_PEN,
169 ("Green","Solid",1) : wxGREEN_PEN,
170 ("White","Solid",1) : wxWHITE_PEN,
171 ("Black","Solid",1) : wxBLACK_PEN,
172 ("Grey","Solid",1) : wxGREY_PEN,
173 ("MediumGrey","Solid",1) : wxMEDIUM_GREY_PEN,
174 ("LightGrey","Solid",1) : wxLIGHT_GREY_PEN,
175 ("Cyan","Solid",1) : wxCYAN_PEN,
176 ("Red","Solid",1) : wxRED_PEN
177 }
178
179 FillStyleList = {
180 "Transparent" : wxTRANSPARENT,
181 "Solid" : wxSOLID,
182 "BiDiagonalHatch": wxBDIAGONAL_HATCH,
183 "CrossDiagHatch" : wxCROSSDIAG_HATCH,
184 "FDiagonal_Hatch": wxFDIAGONAL_HATCH,
185 "CrossHatch" : wxCROSS_HATCH,
186 "HorizontalHatch": wxHORIZONTAL_HATCH,
187 "VerticalHatch" : wxVERTICAL_HATCH
188 }
189
190 LineStyleList = {
191 "Solid" : wxSOLID,
192 "Transparent": wxTRANSPARENT,
193 "Dot" : wxDOT,
194 "LongDash" : wxLONG_DASH,
195 "ShortDash" : wxSHORT_DASH,
196 "DotDash" : wxDOT_DASH,
197 }
198
199 def SetBrush(self,FillColor,FillStyle):
200 if FillColor is None or FillStyle is None:
201 self.Brush = wxTRANSPARENT_BRUSH
202 self.FillStyle = "Transparent"
203 else:
204 if not self.BrushList.has_key((FillColor,FillStyle)):
205 self.BrushList[(FillColor,FillStyle)] = wxBrush(FillColor,self.FillStyleList[FillStyle])
206 self.Brush = self.BrushList[(FillColor,FillStyle)]
207
208 def SetPen(self,LineColor,LineStyle,LineWidth):
209 if LineColor is None or LineStyle is None:
210 self.Pen = wxTRANSPARENT_PEN
211 self.LineStyle = 'Transparent'
212 if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
213 self.PenList[(LineColor,LineStyle,LineWidth)] = wxPen(LineColor,LineWidth,self.LineStyleList[LineStyle])
214 self.Pen = self.PenList[(LineColor,LineStyle,LineWidth)]
215
216 def SetPens(self,LineColors,LineStyles,LineWidths):
217 """
218 This method used when an object could have a list of pens, rather than just one
219 It is used for LineSet, and perhaps others in the future.
220
221 fixme: this is really kludgy, there has got to be a better way!
222
223 """
224
225 length = 1
226 if type(LineColors) == types.ListType:
227 length = len(LineColors)
228 else:
229 LineColors = [LineColors]
230
231 if type(LineStyles) == types.ListType:
232 length = len(LineStyles)
233 else:
234 LineStyles = [LineStyles]
235
236 if type(LineWidths) == types.ListType:
237 length = len(LineWidths)
238 else:
239 LineWidths = [LineWidths]
240
241 if length > 1:
242 if len(LineColors) == 1:
243 LineColors = LineColors*length
244 if len(LineStyles) == 1:
245 LineStyles = LineStyles*length
246 if len(LineWidths) == 1:
247 LineWidths = LineWidths*length
248
249 self.Pens = []
250 for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths):
251 if LineColor is None or LineStyle is None:
252 self.Pens.append(wxTRANSPARENT_PEN)
253 # what's this for?> self.LineStyle = 'Transparent'
254 if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
255 Pen = wxPen(LineColor,LineWidth,self.LineStyleList[LineStyle])
256 self.Pens.append(Pen)
257 else:
258 self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)])
259 if length == 1:
260 self.Pens = self.Pens[0]
261
262 def PutInBackground(self):
263 if self._Canvas and self.Foreground:
264 self._Canvas._TopDrawList.remove(self)
265 self._Canvas._DrawList.append(self)
266 self._Canvas._BackgroundDirty = 1
267 self.Foreground = 0
268
269 def PutInForeground(self):
270 if self._Canvas and (not self.Foreground):
271 self._Canvas._TopDrawList.append(self)
272 self._Canvas._DrawList.remove(self)
273 self._Canvas._BackgroundDirty = 1
274 self.Foreground = 1
275
276
277 class Polygon(draw_object):
278
279 """
280
281 The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of
282 point coordinates. so that Points[N][0] is the x-coordinate of
283 point N and Points[N][1] is the y-coordinate or Points[N,0] is the
284 x-coordinate of point N and Points[N,1] is the y-coordinate for
285 arrays.
286
287 """
288 def __init__(self,Points,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
289 draw_object.__init__(self,Foreground)
290 self.Points = array(Points,Float)
291 self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
292
293 self.LineColor = LineColor
294 self.LineStyle = LineStyle
295 self.LineWidth = LineWidth
296 self.FillColor = FillColor
297 self.FillStyle = FillStyle
298
299 self.SetPen(LineColor,LineStyle,LineWidth)
300 self.SetBrush(FillColor,FillStyle)
301
302
303 def _Draw(self,dc,WorldToPixel,ScaleFunction):
304 Points = WorldToPixel(self.Points)
305 dc.SetPen(self.Pen)
306 dc.SetBrush(self.Brush)
307 #dc.DrawPolygon(map(lambda x: (x[0],x[1]), Points.tolist()))
308 dc.DrawPolygon(Points)
309
310 class PolygonSet(draw_object):
311 """
312 The PolygonSet class takes a Geometry.Polygon object.
313 so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
314
315 it creates a set of line segments, from (x1,y1) to (x2,y2)
316
317 """
318
319 def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,Foreground = 0):
320 draw_object.__init__(self, Foreground)
321
322 ##fixme: there should be some error checking for everything being the right length.
323
324
325 self.Points = array(Points,Float)
326 self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
327
328 self.LineColors = LineColors
329 self.LineStyles = LineStyles
330 self.LineWidths = LineWidths
331 self.FillColors = FillColors
332 self.FillStyles = FillStyles
333
334 self.SetPens(LineColors,LineStyles,LineWidths)
335
336 def _Draw(self,dc,WorldToPixel,ScaleFunction):
337 Points = WorldToPixel(self.Points)
338 Points.shape = (-1,4)
339 dc.DrawLineList(Points,self.Pens)
340
341
342 class Line(draw_object):
343 """
344 The Line class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
345 so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
346 or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
347
348 It will draw a straight line if there are two points, and a polyline if there are more than two.
349
350 """
351 def __init__(self,Points,LineColor,LineStyle,LineWidth,Foreground = 0):
352 draw_object.__init__(self, Foreground)
353
354 self.Points = array(Points,Float)
355 self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
356
357 self.LineColor = LineColor
358 self.LineStyle = LineStyle
359 self.LineWidth = LineWidth
360
361 self.SetPen(LineColor,LineStyle,LineWidth)
362
363 def _Draw(self,dc,WorldToPixel,ScaleFunction):
364 Points = WorldToPixel(self.Points)
365 dc.SetPen(self.Pen)
366 #dc.DrawLines(map(lambda x: (x[0],x[1]), Points.tolist()))
367 dc.DrawLines(Points)
368
369
370 class LineSet(draw_object):
371 """
372 The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
373 so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
374
375 it creates a set of line segments, from (x1,y1) to (x2,y2)
376
377 """
378
379 def __init__(self,Points,LineColors,LineStyles,LineWidths,Foreground = 0):
380 draw_object.__init__(self, Foreground)
381
382 NumLines = len(Points) / 2
383 ##fixme: there should be some error checking for everything being the right length.
384
385
386 self.Points = array(Points,Float)
387 self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
388
389 self.LineColors = LineColors
390 self.LineStyles = LineStyles
391 self.LineWidths = LineWidths
392
393 self.SetPens(LineColors,LineStyles,LineWidths)
394
395 def _Draw(self,dc,WorldToPixel,ScaleFunction):
396 Points = WorldToPixel(self.Points)
397 Points.shape = (-1,4)
398 dc.DrawLineList(Points,self.Pens)
399
400
401 class PointSet(draw_object):
402 """
403 The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
404 so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
405 or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
406
407 Each point will be drawn the same color and Diameter. The Diameter is in screen points,
408 not world coordinates.
409
410 """
411 def __init__(self,Points,Color,Diameter,Foreground = 0):
412 draw_object.__init__(self,Foreground)
413
414 self.Points = array(Points,Float)
415 self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
416 self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
417
418 self.Color = Color
419 self.Diameter = Diameter
420
421 self.SetPen(Color,"Solid",1)
422 self.SetBrush(Color,"Solid")
423
424 def SetPoints(self,Points):
425 self.Points = Points
426 self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
427 if self._Canvas:
428 # It looks like this shouldn't be private
429 self._Canvas.BoundingBoxDirty = 1
430
431 def _Draw(self,dc,WorldToPixel,ScaleFunction):
432 dc.SetPen(self.Pen)
433 Points = WorldToPixel(self.Points)
434 if self.Diameter <= 1:
435 dc.DrawPointList(Points)
436 elif self.Diameter <= 2:
437 # A Little optimization for a diameter2 - point
438 dc.DrawPointList(Points)
439 dc.DrawPointList(Points + (1,0))
440 dc.DrawPointList(Points + (0,1))
441 dc.DrawPointList(Points + (1,1))
442 else:
443 dc.SetBrush(self.Brush)
444 radius = int(round(self.Diameter/2))
445 for (x,y) in Points:
446 dc.DrawEllipse((x - radius), (y - radius), self.Diameter, self.Diameter)
447
448
449
450 class Dot(draw_object):
451 """
452 The Dot class takes an x.y coordinate pair, and the Diameter of the circle.
453 The Diameter is in pixels, so it won't change with zoom.
454
455 Also Fill and line data
456
457 """
458 def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
459 draw_object.__init__(self,Foreground)
460
461 self.X = x
462 self.Y = y
463 self.Diameter = Diameter
464 # NOTE: the bounding box does not include the diameter of the dot, as that is in pixel coords.
465 # If this is a problem, perhaps you should use a circle, instead!
466 self.BoundingBox = array(((x,y),(x,y)),Float)
467
468 self.LineColor = LineColor
469 self.LineStyle = LineStyle
470 self.LineWidth = LineWidth
471 self.FillColor = FillColor
472 self.FillStyle = FillStyle
473
474 self.SetPen(LineColor,LineStyle,LineWidth)
475 self.SetBrush(FillColor,FillStyle)
476
477 def _Draw(self,dc,WorldToPixel,ScaleFunction):
478 dc.SetPen(self.Pen)
479 dc.SetBrush(self.Brush)
480 radius = int(round(self.Diameter/2))
481 (X,Y) = WorldToPixel((self.X,self.Y))
482 dc.DrawEllipse((X - radius), (Y - radius), self.Diameter, self.Diameter)
483
484
485 class Rectangle(draw_object):
486 def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
487 draw_object.__init__(self,Foreground)
488
489 self.X = x
490 self.Y = y
491 self.Width = width
492 self.Height = height
493 self.BoundingBox = array(((x,y),(x+width,y+height)),Float)
494 self.LineColor = LineColor
495 self.LineStyle = LineStyle
496 self.LineWidth = LineWidth
497 self.FillColor = FillColor
498 self.FillStyle = FillStyle
499
500 self.SetPen(LineColor,LineStyle,LineWidth)
501 self.SetBrush(FillColor,FillStyle)
502
503 def _Draw(self,dc,WorldToPixel,ScaleFunction):
504 (X,Y) = WorldToPixel((self.X,self.Y))
505 (Width,Height) = ScaleFunction((self.Width,self.Height))
506
507 dc.SetPen(self.Pen)
508 dc.SetBrush(self.Brush)
509 dc.DrawRectangle(X,Y,Width,Height)
510
511 class Ellipse(draw_object):
512 def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
513 draw_object.__init__(self,Foreground)
514
515 self.X = x
516 self.Y = y
517 self.Width = width
518 self.Height = height
519 self.BoundingBox = array(((x,y),(x+width,y+height)),Float)
520 self.LineColor = LineColor
521 self.LineStyle = LineStyle
522 self.LineWidth = LineWidth
523 self.FillColor = FillColor
524 self.FillStyle = FillStyle
525
526 self.SetPen(LineColor,LineStyle,LineWidth)
527 self.SetBrush(FillColor,FillStyle)
528
529 def _Draw(self,dc,WorldToPixel,ScaleFunction):
530 (X,Y) = WorldToPixel((self.X,self.Y))
531 (Width,Height) = ScaleFunction((self.Width,self.Height))
532
533 dc.SetPen(self.Pen)
534 dc.SetBrush(self.Brush)
535 dc.DrawEllipse(X,Y,Width,Height)
536
537 class Circle(draw_object):
538 def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
539 draw_object.__init__(self,Foreground)
540
541 self.X = x
542 self.Y = y
543 self.Diameter = Diameter
544 self.BoundingBox = array(((x-Diameter/2,y-Diameter/2),(x+Diameter/2,y+Diameter/2)),Float)
545 self.LineColor = LineColor
546 self.LineStyle = LineStyle
547 self.LineWidth = LineWidth
548 self.FillColor = FillColor
549 self.FillStyle = FillStyle
550
551 self.SetPen(LineColor,LineStyle,LineWidth)
552 self.SetBrush(FillColor,FillStyle)
553
554 def _Draw(self,dc,WorldToPixel,ScaleFunction):
555 (X,Y) = WorldToPixel((self.X,self.Y))
556 (Diameter,dummy) = ScaleFunction((self.Diameter,self.Diameter))
557
558 dc.SetPen(self.Pen)
559 dc.SetBrush(self.Brush)
560 dc.DrawEllipse(X-Diameter/2,Y-Diameter/2,Diameter,Diameter)
561
562 class Text(draw_object):
563 """
564
565 This class creates a text object, placed at the coordinates,
566 x,y. the "Position" argument is a two charactor string, indicating
567 where in relation to the coordinates the string should be oriented.
568
569 The first letter is: t, c, or b, for top, center and bottom
570 The second letter is: l, c, or r, for left, center and right
571
572 I've provided arguments for Family, Style, and Weight of font, but
573 have not yet implimented them, so all text is: wxSWISS, wxNORMAL, wxNORMAL.
574 I'd love it if someone would impliment that!
575
576 The size is fixed, and does not scale with the drawing.
577
578 """
579
580 def __init__(self,String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground = 0):
581 draw_object.__init__(self,Foreground)
582
583 self.String = String
584 self.Size = Size
585
586 self.ForeGround = ForeGround
587 if BackGround is None:
588 self.BackGround = None
589 else:
590 self.BackGround = BackGround
591 self.Family = Family
592 self.Style = Style
593 self.Weight = Weight
594 self.Underline = Underline
595 self.Position = Position
596 # fixme: this should use the passed in parameters!
597 self.Font = wxFont(Size, wxMODERN, wxNORMAL, wxNORMAL, Underline, )
598
599 self.BoundingBox = array(((x,y),(x,y)),Float)
600
601 self.X = x
602 self.Y = y
603 self.x_shift = None
604 self.y_shift = None
605
606 def _Draw(self,dc,WorldToPixel,ScaleFunction):
607 (X,Y) = WorldToPixel((self.X,self.Y))
608 dc.SetFont(self.Font)
609 dc.SetTextForeground(self.ForeGround)
610 if self.BackGround:
611 dc.SetBackgroundMode(wxSOLID)
612 dc.SetTextBackground(self.BackGround)
613 else:
614 dc.SetBackgroundMode(wxTRANSPARENT)
615
616 # compute the shift, and adjust the coordinates, if neccesary
617 # This had to be put in here, becsuse there is no wxDC during __init__
618 if self.x_shift is None or self.y_shift is None:
619 if self.Position == 'tl':
620 x_shift,y_shift = 0,0
621 else:
622 (w,h) = dc.GetTextExtent(self.String)
623 if self.Position[0] == 't':
624 y_shift = 0
625 elif self.Position[0] == 'c':
626 y_shift = h/2
627 elif self.Position[0] == 'b':
628 y_shift = h
629 else:
630 ##fixme: this should be a real derived exception
631 raise "Invalid value for Text Object Position Attribute"
632 if self.Position[1] == 'l':
633 x_shift = 0
634 elif self.Position[1] == 'c':
635 x_shift = w/2
636 elif self.Position[1] == 'r':
637 x_shift = w
638 else:
639 ##fixme: this should be a real derived exception
640 raise "Invalid value for Text Object Position Attribute"
641 self.x_shift = x_shift
642 self.y_shift = y_shift
643 dc.DrawText(self.String, X-self.x_shift, Y-self.y_shift)
644
645
646 #---------------------------------------------------------------------------
647
648 class FloatCanvas(wxPanel):
649 """
650 FloatCanvas.py
651
652 This is a high level window for drawing maps and anything else in an
653 arbitrary coordinate system.
654
655 The goal is to provide a convenient way to draw stuff on the screen
656 without having to deal with handling OnPaint events, converting to pixel
657 coordinates, knowing about wxWindows brushes, pens, and colors, etc. It
658 also provides virtually unlimited zooming and scrolling
659
660 I am using it for two things:
661 1) general purpose drawing in floating point coordinates
662 2) displaying map data in Lat-long coordinates
663
664 If the projection is set to None, it will draw in general purpose
665 floating point coordinates. If the projection is set to 'FlatEarth', it
666 will draw a FlatEarth projection, centered on the part of the map that
667 you are viewing. You can also pass in your own projection function.
668
669 It is double buffered, so re-draws after the window is uncovered by something
670 else are very quick.
671
672 It relies on NumPy, which is needed for speed
673
674 Bugs and Limitations:
675 Lots: patches, fixes welcome
676
677 For Map drawing: It ignores the fact that the world is, in fact, a
678 sphere, so it will do strange things if you are looking at stuff near
679 the poles or the date line. so far I don't have a need to do that, so I
680 havn't bothered to add any checks for that yet.
681
682 Zooming:
683 I have set no zoom limits. What this means is that if you zoom in really
684 far, you can get integer overflows, and get wierd results. It
685 doesn't seem to actually cause any problems other than wierd output, at
686 least when I have run it.
687
688 Speed:
689 I have done a couple of things to improve speed in this app. The one
690 thing I have done is used NumPy Arrays to store the coordinates of the
691 points of the objects. This allowed me to use array oriented functions
692 when doing transformations, and should provide some speed improvement
693 for objects with a lot of points (big polygons, polylines, pointsets).
694
695 The real slowdown comes when you have to draw a lot of objects, because
696 you have to call the wxDC.DrawSomething call each time. This is plenty
697 fast for tens of objects, OK for hundreds of objects, but pretty darn
698 slow for thousands of objects.
699
700 The solution is to be able to pass some sort of object set to the DC
701 directly. I've used DC.DrawPointList(Points), and it helped a lot with
702 drawing lots of points. I havn't got a LineSet type object, so I havn't
703 used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList()
704 methods implimented, and then I'd also have a full set of Object sets
705 that could take advantage of them. I hope to get to it some day.
706
707 Copyright: Christopher Barker
708
709 License: Same as wxPython
710
711 Please let me know if you're using this!!!
712
713 Contact me at:
714
715 Chris.Barker@noaa.gov
716
717 """
718
719 def __init__(self, parent, id = -1,
720 size = wxDefaultSize,
721 ProjectionFun = None,
722 BackgroundColor = "WHITE",
723 Debug = 0,
724 EnclosingFrame = None,
725 UseToolbar = 1,
726 UseBackground = 0,
727 UseHitTest = 0):
728
729 wxPanel.__init__( self, parent, id, wxDefaultPosition, size)
730
731 if ProjectionFun == 'FlatEarth':
732 self.ProjectionFun = self.FlatEarthProjection
733 elif type(ProjectionFun) == types.FunctionType:
734 self.ProjectionFun = ProjectionFun
735 elif ProjectionFun is None:
736 self.ProjectionFun = lambda x=None: array( (1,1), Float)
737 else:
738 raise('Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector')
739
740 self.UseBackground = UseBackground
741 self.UseHitTest = UseHitTest
742
743 self.NumBetweenBlits = 40
744
745 ## you can have a toolbar with buttons for zoom-in, zoom-out and
746 ## move. If you don't use the toolbar, you should provide your
747 ## own way of navigating the canvas
748 if UseToolbar:
749 ## Create the vertical sizer for the toolbar and Panel
750 box = wxBoxSizer(wxVERTICAL)
751 box.Add(self.BuildToolbar(), 0, wxALL | wxALIGN_LEFT | wxGROW, 4)
752
753 self.DrawPanel = wxWindow(self,-1,wxDefaultPosition,wxDefaultSize,wxSUNKEN_BORDER)
754 box.Add(self.DrawPanel,1,wxGROW)
755
756 box.Fit(self)
757 self.SetAutoLayout(True)
758 self.SetSizer(box)
759 else:
760 self.DrawPanel = self
761
762 self.DrawPanel.BackgroundBrush = wxBrush(BackgroundColor,wxSOLID)
763
764 self.Debug = Debug
765
766 self.EnclosingFrame = EnclosingFrame
767
768 EVT_PAINT(self.DrawPanel, self.OnPaint)
769 EVT_SIZE(self.DrawPanel, self.OnSize)
770
771 EVT_LEFT_DOWN(self.DrawPanel, self.LeftButtonEvent)
772 EVT_LEFT_UP(self.DrawPanel, self.LeftButtonEvent)
773 EVT_RIGHT_DOWN(self.DrawPanel, self.RightButtonEvent)
774 EVT_MOTION(self.DrawPanel, self.LeftButtonEvent)
775
776
777 self._DrawList = []
778 if self.UseBackground:
779 self._TopDrawList = []
780 self.BoundingBox = None
781 self.BoundingBoxDirty = 0
782 self.ViewPortCenter= array( (0,0), Float)
783
784 self.MapProjectionVector = array( (1,1), Float) # No Projection to start!
785 self.TransformVector = array( (1,-1), Float) # default Transformation
786
787 self.Scale = 1
788
789 # called just to make sure everything is initialized
790 self.OnSize(None)
791
792 self.GUIMode = None
793 self.StartRBBox = None
794 self.StartMove = None
795
796 def BuildToolbar(self):
797 tb = wxToolBar(self,-1)
798 self.ToolBar = tb
799
800 tb.AddTool(ID_ZOOM_IN_BUTTON, GetPlusBitmap(), isToggle=true,shortHelpString = "Zoom In")
801 EVT_TOOL(self, ID_ZOOM_IN_BUTTON, self.SetMode)
802
803 tb.AddTool(ID_ZOOM_OUT_BUTTON, GetMinusBitmap(), isToggle=true,shortHelpString = "Zoom Out")
804 EVT_TOOL(self, ID_ZOOM_OUT_BUTTON, self.SetMode)
805
806 tb.AddTool(ID_MOVE_MODE_BUTTON, GetHandBitmap(), isToggle=true,shortHelpString = "Move")
807 EVT_TOOL(self, ID_MOVE_MODE_BUTTON, self.SetMode)
808
809 tb.AddSeparator()
810
811 tb.AddControl(wxButton(tb, ID_ZOOM_TO_FIT_BUTTON, "Zoom To Fit",wxDefaultPosition, wxDefaultSize))
812 EVT_BUTTON(self, ID_ZOOM_TO_FIT_BUTTON, self.ZoomToFit)
813
814 tb.Realize()
815 return tb
816
817 def SetMode(self,event):
818 for id in [ID_ZOOM_IN_BUTTON,ID_ZOOM_OUT_BUTTON,ID_MOVE_MODE_BUTTON]:
819 self.ToolBar.ToggleTool(id,0)
820 self.ToolBar.ToggleTool(event.GetId(),1)
821 if event.GetId() == ID_ZOOM_IN_BUTTON:
822 self.SetGUIMode("ZoomIn")
823 elif event.GetId() == ID_ZOOM_OUT_BUTTON:
824 self.SetGUIMode("ZoomOut")
825 elif event.GetId() == ID_MOVE_MODE_BUTTON:
826 self.SetGUIMode("Move")
827
828
829 def SetGUIMode(self,Mode):
830 if Mode in ["ZoomIn","ZoomOut","Move",None]:
831 self.GUIMode = Mode
832 else:
833 raise "Not a valid Mode"
834
835 def FlatEarthProjection(self,CenterPoint):
836 return array((cos(pi*CenterPoint[1]/180),1),Float)
837
838 def LeftButtonEvent(self,event):
839 if self.EnclosingFrame:
840 if event.Moving:
841 position = self.PixelToWorld((event.GetX(),event.GetY()))
842 self.EnclosingFrame.SetStatusText("%8.3f, %8.3f"%tuple(position))
843 if self.GUIMode:
844 if self.GUIMode == "ZoomIn":
845 if event.LeftDown():
846 self.StartRBBox = (event.GetX(),event.GetY())
847 self.PrevRBBox = None
848 elif event.Dragging() and event.LeftIsDown() and self.StartRBBox:
849 x0,y0 = self.StartRBBox
850 x1,y1 = event.GetX(),event.GetY()
851 w, h = abs(x1-x0),abs(y1-y0)
852 w = max(w,int(h*self.AspectRatio))
853 h = int(w/self.AspectRatio)
854 x_c, y_c = (x0+x1)/2 , (y0+y1)/2
855 dc = wxClientDC(self.DrawPanel)
856 dc.BeginDrawing()
857 dc.SetPen(wxPen('WHITE', 2,wxSHORT_DASH))
858 dc.SetBrush(wxTRANSPARENT_BRUSH)
859 dc.SetLogicalFunction(wxXOR)
860 if self.PrevRBBox:
861 dc.DrawRectangle(*self.PrevRBBox)
862 dc.DrawRectangle(x_c-w/2,y_c-h/2,w,h)
863 self.PrevRBBox = (x_c-w/2,y_c-h/2,w,h)
864 dc.EndDrawing()
865
866 elif event.LeftUp() and self.StartRBBox :
867 EndRBBox = (event.GetX(),event.GetY())
868 StartRBBox = self.StartRBBox
869 # if mouse has moved less that ten pixels, don't use the box.
870 if abs(StartRBBox[0] - EndRBBox[0]) > 10 and abs(StartRBBox[1] - EndRBBox[1]) > 10:
871 EndRBBox = self.PixelToWorld(EndRBBox)
872 StartRBBox = self.PixelToWorld(StartRBBox)
873 BB = array(((min(EndRBBox[0],StartRBBox[0]), min(EndRBBox[1],StartRBBox[1])),
874 (max(EndRBBox[0],StartRBBox[0]), max(EndRBBox[1],StartRBBox[1]))),Float)
875 self.ZoomToBB(BB)
876 else:
877 Center = self.PixelToWorld(StartRBBox)
878 self.Zoom(1.5,Center)
879 self.StartRBBox = None
880 if self.GUIMode == "ZoomOut":
881 if event.LeftDown():
882 Center = self.PixelToWorld((event.GetX(),event.GetY()))
883 self.Zoom(1/1.5,Center)
884 elif self.GUIMode == "Move":
885 if event.LeftDown():
886 self.StartMove = array((event.GetX(),event.GetY()))
887 self.PrevMoveBox = None
888 elif event.Dragging() and event.LeftIsDown() and self.StartMove:
889 x_1,y_1 = event.GetX(),event.GetY()
890 w, h = self.PanelSize
891 x_tl, y_tl = x_1 - self.StartMove[0], y_1 - self.StartMove[1]
892 dc = wxClientDC(self.DrawPanel)
893 dc.BeginDrawing()
894 dc.SetPen(wxPen('WHITE', 1,))
895 dc.SetBrush(wxTRANSPARENT_BRUSH)
896 dc.SetLogicalFunction(wxXOR)
897 if self.PrevMoveBox:
898 dc.DrawRectangle(*self.PrevMoveBox)
899 dc.DrawRectangle(x_tl,y_tl,w,h)
900 self.PrevMoveBox = (x_tl,y_tl,w,h)
901 dc.EndDrawing()
902
903 elif event.LeftUp() and self.StartMove:
904 StartMove = self.StartMove
905 EndMove = array((event.GetX(),event.GetY()))
906 if sum((StartMove-EndMove)**2) > 16:
907 self.Move(StartMove-EndMove,'Pixel')
908 self.StartMove = None
909
910 def RightButtonEvent(self,event):
911 if self.GUIMode:
912 if self.GUIMode == "ZoomIn":
913 Center = self.PixelToWorld((event.GetX(),event.GetY()))
914 self.Zoom(1/1.5,Center)
915 elif self.GUIMode == "ZoomOut":
916 Center = self.PixelToWorld((event.GetX(),event.GetY()))
917 self.Zoom(1.5,Center)
918 else:
919 event.Skip()
920
921 def MakeNewBuffers(self):
922 # Make new offscreen bitmap:
923 self._Buffer = wxEmptyBitmap(int(self.PanelSize[0]), int(self.PanelSize[1]))
924 if self.UseBackground:
925 self._BackBuffer = wxEmptyBitmap((self.PanelSize[0]), (self.PanelSize[1]))
926 self._BackgroundDirty = 1
927 else:
928 pass
929
930 def OnSize(self,event):
931 self.PanelSize = array(self.DrawPanel.GetClientSizeTuple(),Float)
932 try:
933 self.AspectRatio = self.PanelSize[0]/self.PanelSize[1]
934 except ZeroDivisionError:
935 self.AspectRatio = 1.0
936 self.MakeNewBuffers()
937 self.Draw()
938
939 def OnPaint(self, event):
940 #dc = wxBufferedPaintDC(self.DrawPanel, self._Buffer)
941 dc = wxPaintDC(self.DrawPanel)
942 dc.DrawBitmap(self._Buffer,0,0)
943
944 def Draw(self):
945 """
946 The Draw method gets pretty complicated because of all the buffers
947
948 There is a main buffer set up to double buffer the screen, so
949 you can get quick re-draws when the window gets uncovered.
950
951 If self.UseBackground is set, and an object is set up with the
952 "ForeGround" flag, then it gets drawn to the screen after blitting
953 the background. This is done so that you can have a complicated
954 background, but have something changing on the foreground,
955 without having to wait for the background to get re-drawn. This
956 can be used to support simple animation, for instance.
957
958 """
959 if self.Debug: start = clock()
960 ScreenDC = wxClientDC(self.DrawPanel)
961 if self.UseBackground:
962 dc = wxMemoryDC()
963 dc.SelectObject(self._BackBuffer)
964 dc.SetBackground(self.DrawPanel.BackgroundBrush)
965 if self._DrawList:
966 if self._BackgroundDirty:
967 dc.BeginDrawing()
968 dc.Clear()
969 i = 0
970 for Object in self._DrawList:
971 if self.BBCheck(Object.BoundingBox,ViewPortBB):
972 #print "object is in Bounding Box"
973 i+=1
974 Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
975 if i % self.NumBetweenBlits == 0:
976 ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
977 dc.EndDrawing()
978 else:
979 dc.Clear()
980 self._BackgroundDirty = 0
981 dc.SelectObject(self._Buffer)
982 dc.BeginDrawing()
983 ##Draw Background on Main Buffer:
984 dc.DrawBitmap(self._BackBuffer,0,0)
985 #Draw the OnTop stuff
986 i = 0
987 for Object in self._TopDrawList:
988 i+=1
989 Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
990 if i % self.NumBetweenBlits == 0:
991 ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
992 dc.EndDrawing()
993 else: # not using a Background DC
994 dc = wxMemoryDC()
995 dc.SelectObject(self._Buffer)
996 dc.SetBackground(self.DrawPanel.BackgroundBrush)
997 if self._DrawList:
998 dc.BeginDrawing()
999 dc.Clear()
1000 i = 0
1001 ViewPortWorld = array((self.PixelToWorld((0,0)), self.PixelToWorld(self.DrawPanel.GetSizeTuple() ) ),Float )
1002 ViewPortBB = array( (minimum.reduce(ViewPortWorld), maximum.reduce(ViewPortWorld)) )
1003 for Object in self._DrawList:
1004 if self.BBCheck(Object.BoundingBox,ViewPortBB):
1005 #print "object is in Bounding Box"
1006 i+=1
1007 Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
1008 if i % self.NumBetweenBlits == 0:
1009 ScreenDC.Blit(0, 0, int(self.PanelSize[0]), int(self.PanelSize[1]), dc, 0, 0)
1010 print "there were %i objects drawn"%i
1011 dc.EndDrawing()
1012 else:
1013 dc.Clear()
1014 # now refresh the screen
1015 ScreenDC.DrawBitmap(self._Buffer,0,0)
1016 if self.Debug: print "Drawing took %f seconds of CPU time"%(clock()-start)
1017
1018 def BBCheck(self, BB1, BB2):
1019 """
1020
1021 BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
1022
1023 """
1024 if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
1025 (BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
1026 return True
1027 else:
1028 return False
1029
1030 def Move(self,shift,CoordType):
1031 """
1032 move the image in the window.
1033
1034 shift is an (x,y) tuple, specifying the amount to shift in each direction
1035
1036 It can be in any of three coordinates: Panel, Pixel, World,
1037 specified by the CoordType parameter
1038
1039 Panel coordinates means you want to shift the image by some
1040 fraction of the size of the displaed image
1041
1042 Pixel coordinates means you want to shift the image by some number of pixels
1043
1044 World coordinates meand you want to shift the image by an amount
1045 in Floating point world coordinates
1046
1047 """
1048
1049 shift = array(shift,Float)
1050 if CoordType == 'Panel':# convert from panel coordinates
1051 shift = shift * array((-1,1),Float) *self.PanelSize/self.TransformVector
1052 elif CoordType == 'Pixel': # convert from pixel coordinates
1053 shift = shift/self.TransformVector
1054 elif CoordType == 'World': # No conversion
1055 pass
1056 else:
1057 raise 'CoordType must be either "Panel", "Pixel", or "World"'
1058
1059 self.ViewPortCenter = self.ViewPortCenter + shift
1060 self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
1061 self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
1062 self._BackgroundDirty = 1
1063 self.Draw()
1064
1065 def Zoom(self,factor,center = None):
1066
1067 """
1068 Zoom(factor, center) changes the amount of zoom of the image by factor.
1069 If factor is greater than one, the image gets larger.
1070 If factor is less than one, the image gets smaller.
1071
1072 Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
1073 If center is not given, the center will stay the same.
1074
1075 """
1076 self.Scale = self.Scale*factor
1077 if center:
1078 self.ViewPortCenter = array(center,Float)
1079 self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
1080 self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
1081 self._BackgroundDirty = 1
1082 self.Draw()
1083
1084 def ZoomToFit(self,event):
1085 self.ZoomToBB()
1086
1087 def ZoomToBB(self,NewBB = None,DrawFlag = 1):
1088
1089 """
1090
1091 Zooms the image to the bounding box given, or to the bounding
1092 box of all the objects on the canvas, if none is given.
1093
1094 """
1095
1096 if NewBB:
1097 BoundingBox = NewBB
1098 else:
1099 if self.BoundingBoxDirty:
1100 self._ResetBoundingBox()
1101 BoundingBox = self.BoundingBox
1102 if BoundingBox:
1103 self.ViewPortCenter = array(((BoundingBox[0,0]+BoundingBox[1,0])/2,
1104 (BoundingBox[0,1]+BoundingBox[1,1])/2 ),Float)
1105 self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
1106 # Compute the new Scale
1107 BoundingBox = BoundingBox * self.MapProjectionVector
1108 try:
1109 self.Scale = min((self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0])),
1110 (self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1])))*0.95
1111 except ZeroDivisionError: # this will happen if the BB has zero width or height
1112 try: #width
1113 self.Scale = (self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0]))*0.95
1114 except ZeroDivisionError:
1115 try: # height
1116 self.Scale = (self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1]))*0.95
1117 except ZeroDivisionError: #zero size! (must be a single point)
1118 self.Scale = 1
1119
1120 self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
1121 if DrawFlag:
1122 self._BackgroundDirty = 1
1123 self.Draw()
1124
1125 def RemoveObjects(self,Objects):
1126 for Object in Objects:
1127 self.RemoveObject(Object,ResetBB = 0)
1128 self.BoundingBoxDirty = 1
1129
1130 def RemoveObject(self,Object,ResetBB = 1):
1131 if Object.Foreground:
1132 self._TopDrawList.remove(Object)
1133 else:
1134 self._DrawList.remove(Object)
1135 self._BackgroundDirty = 1
1136
1137 if ResetBB:
1138 self.BoundingBoxDirty = 1
1139
1140 def Clear(self, ResetBB = True):
1141 self._DrawList = []
1142 if self.UseBackground:
1143 self._TopDrawList = []
1144 self._BackgroundDirty = 1
1145 if ResetBB:
1146 self._ResetBoundingBox()
1147
1148 def _AddBoundingBox(self,NewBB):
1149 if self.BoundingBox is None:
1150 self.BoundingBox = NewBB
1151 self.ZoomToBB(NewBB,DrawFlag = 0)
1152 else:
1153 self.BoundingBox = array(((min(self.BoundingBox[0,0],NewBB[0,0]),
1154 min(self.BoundingBox[0,1],NewBB[0,1])),
1155 (max(self.BoundingBox[1,0],NewBB[1,0]),
1156 max(self.BoundingBox[1,1],NewBB[1,1]))),Float)
1157 def _ResetBoundingBox(self):
1158 # NOTE: could you remove an item without recomputing the entire bounding box?
1159 self.BoundingBox = None
1160 if self._DrawList:
1161 self.BoundingBox = self._DrawList[0].BoundingBox
1162 for Object in self._DrawList[1:]:
1163 self._AddBoundingBox(Object.BoundingBox)
1164 if self.UseBackground:
1165 for Object in self._TopDrawList:
1166 self._AddBoundingBox(Object.BoundingBox)
1167 if self.BoundingBox is None:
1168 self.ViewPortCenter= array( (0,0), Float)
1169 self.TransformVector = array( (1,-1), Float)
1170 self.MapProjectionVector = array( (1,1), Float)
1171 self.Scale = 1
1172 self.BoundingBoxDirty = 0
1173
1174 def PixelToWorld(self,Points):
1175 """
1176 Converts coordinates from Pixel coordinates to world coordinates.
1177
1178 Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
1179
1180 """
1181 return (((array(Points,Float) - (self.PanelSize/2))/self.TransformVector) + self.ViewPortCenter)
1182
1183 def WorldToPixel(self,Coordinates):
1184 """
1185 This function will get passed to the drawing functions of the objects,
1186 to transform from world to pixel coordinates.
1187 Coordinates should be a NX2 array of (x,y) coordinates, or
1188 a 2-tuple, or sequence of 2-tuples.
1189 """
1190 return (((array(Coordinates,Float) - self.ViewPortCenter)*self.TransformVector)+(self.PanelSize/2)).astype('i')
1191
1192 def ScaleFunction(self,Lengths):
1193 """
1194 This function will get passed to the drawing functions of the objects,
1195 to Change a length from world to pixel coordinates.
1196
1197 Lengths should be a NX2 array of (x,y) coordinates, or
1198 a 2-tuple, or sequence of 2-tuples.
1199 """
1200 return (array(Lengths,Float)*self.TransformVector).astype('i')
1201
1202
1203 ## This is a set of methods that add objects to the Canvas. It kind
1204 ## of seems like a lot of duplication, but I wanted to be able to
1205 ## instantiate the draw objects separatley form adding them, but
1206 ## also to be able to do add one oin one step. I'm open to better
1207 ## ideas...
1208 def AddRectangle(self,x,y,width,height,
1209 LineColor = "Black",
1210 LineStyle = "Solid",
1211 LineWidth = 1,
1212 FillColor = None,
1213 FillStyle = "Solid",
1214 Foreground = 0):
1215 Object = Rectangle(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
1216 self.AddObject(Object)
1217 return Object
1218
1219 def AddEllipse(self,x,y,width,height,
1220 LineColor = "Black",
1221 LineStyle = "Solid",
1222 LineWidth = 1,
1223 FillColor = None,
1224 FillStyle = "Solid",
1225 Foreground = 0):
1226 Object = Ellipse(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
1227 self.AddObject(Object)
1228 return Object
1229
1230 def AddCircle(self,x,y,Diameter,
1231 LineColor = "Black",
1232 LineStyle = "Solid",
1233 LineWidth = 1,
1234 FillColor = None,
1235 FillStyle = "Solid",
1236 Foreground = 0):
1237 Object = Circle(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
1238 self.AddObject(Object)
1239 return Object
1240
1241 def AddDot(self,x,y,Diameter,
1242 LineColor = "Black",
1243 LineStyle = "Solid",
1244 LineWidth = 1,
1245 FillColor = None,
1246 FillStyle = "Solid",
1247 Foreground = 0):
1248 Object = Dot(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
1249 self.AddObject(Object)
1250 return Object
1251
1252 def AddPolygon(self,Points,
1253 LineColor = "Black",
1254 LineStyle = "Solid",
1255 LineWidth = 1,
1256 FillColor = None,
1257 FillStyle = "Solid",
1258 Foreground = 0):
1259
1260 Object = Polygon(Points,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
1261 self.AddObject(Object)
1262 return Object
1263
1264 def AddLine(self,Points,
1265 LineColor = "Black",
1266 LineStyle = "Solid",
1267 LineWidth = 1,
1268 Foreground = 0):
1269
1270 Object = Line(Points,LineColor,LineStyle,LineWidth,Foreground)
1271 self.AddObject(Object)
1272 return Object
1273
1274 def AddLineSet(self,Points,
1275 LineColors = "Black",
1276 LineStyles = "Solid",
1277 LineWidths = 1,
1278 Foreground = 0):
1279
1280 Object = LineSet(Points,LineColors,LineStyles,LineWidths,Foreground)
1281 self.AddObject(Object)
1282 return Object
1283
1284 def AddPointSet(self,Points,
1285 Color = "Black",
1286 Diameter = 1,
1287 Foreground = 0):
1288
1289 Object = PointSet(Points,Color,Diameter,Foreground)
1290 self.AddObject(Object)
1291 return Object
1292
1293 def AddText(self,String,x,y,
1294 Size = 20,
1295 ForeGround = 'Black',
1296 BackGround = None,
1297 Family = 'Swiss',
1298 Style = 'Normal',
1299 Weight = 'Normal',
1300 Underline = 0,
1301 Position = 'tl',
1302 Foreground = 0):
1303 Object = Text(String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground)
1304 self.AddObject(Object)
1305 return Object
1306
1307 def AddObject(self,obj):
1308 # put in a reference to the Canvas, so remove and other stuff can work
1309 obj._Canvas = self
1310 if obj.Foreground and self.UseBackground:
1311 self._TopDrawList.append(obj)
1312 else:
1313 self._DrawList.append(obj)
1314 self._backgrounddirty = 1
1315 self._AddBoundingBox(obj.BoundingBox)
1316 return None
1317
1318
1319
1320
1321
1322