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