Robin,
I booted up Windows for the first time in a while yesterday, and tried
out the latest wxPython version.
It turns out that the floatcanvas in the demo is broken. It's all bugs
that I had fixed, but must have not gotten into the version you had. I
have no idea when you might do a next release, but I thought you should
have a functioning version. I've enclosed the main module:
floatcanvas.py
and the demo:
FloatCanvas.py
these versions work on my Win2k box fine. Sorry not to send a patch, I
don't have diff on Windows, and there were enough changes that I stopped
trying to figure out what changed.
Sorry I didn't get to this earlier.
Hmm. I'll make sure to update my ftp site as well.
-Chris
-- --
Christopher Barker, Ph.D.
Oceanographer
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception
Chris.Barker@noaa.gov
------------------------------------------------------------------------
from wxPython.wx import *
## Stuff to integrate FloatCanvas into wxPython Demo
try:
import Numeric
haveNumeric = True
except ImportError:
haveNumeric = False
if not haveNumeric:
errorText = """\
The FloatCanvas requires the Numeric module:
You can get it at:
http://sourceforge.net/projects/numpy
""" def runTest(frame, nb, log):
dlg = wxMessageDialog(frame, errorText, 'Sorry', wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
overview = ""
else:
def runTest(frame, nb, log):
"""
This method is used by the wxPython Demo Framework for integrating
this demo with the rest.
"""
win = DrawFrame(NULL, -1, "FloatCanvas Drawing Window",wxDefaultPosition,wxSize(500,500))
frame.otherWin = win
win.Show(True)
from wxPython.lib import floatcanvas
import wxPython.lib.colourdb
ID_ABOUT_MENU = wxNewId() ID_EXIT_MENU = wxNewId() ID_ZOOM_TO_FIT_MENU = wxNewId()
ID_DRAWTEST_MENU = wxNewId()
ID_LINETEST_MENU = wxNewId()
ID_DRAWMAP_MENU = wxNewId()
ID_DRAWMAP2_MENU = wxNewId()
ID_CLEAR_MENU = wxNewId()
wxPython.lib.colourdb.updateColourDB()
colors = wxPython.lib.colourdb.getColourList()
LineStyles = floatcanvas.draw_object.LineStyleList.keys()
class DrawFrame(wxFrame):
"""
A frame used for the FloatCanvas Demo
"""
def __init__(self,parent, id,title,position,size):
wxFrame.__init__(self,parent, id,title,position, size)
## Set up the MenuBar
MenuBar = wxMenuBar()
file_menu = wxMenu()
file_menu.Append(ID_EXIT_MENU, "&Close","Close this frame")
EVT_MENU(self, ID_EXIT_MENU, self.OnQuit)
MenuBar.Append(file_menu, "&File")
draw_menu = wxMenu()
draw_menu.Append(ID_DRAWTEST_MENU, "&Draw Test","Run a test of drawing random components")
EVT_MENU(self, ID_DRAWTEST_MENU,self.DrawTest)
draw_menu.Append(ID_LINETEST_MENU, "&Line Test","Run a test of drawing random lines")
EVT_MENU(self, ID_LINETEST_MENU,self.LineTest)
draw_menu.Append(ID_DRAWMAP_MENU, "Draw &Map","Run a test of drawing a map")
EVT_MENU(self, ID_DRAWMAP_MENU,self.DrawMap)
draw_menu.Append(ID_CLEAR_MENU, "&Clear","Clear the Canvas")
EVT_MENU(self, ID_CLEAR_MENU,self.Clear)
MenuBar.Append(draw_menu, "&Draw")
view_menu.Append(ID_ZOOM_TO_FIT_MENU, "Zoom to &Fit","Zoom to fit the window")
EVT_MENU(self, ID_ZOOM_TO_FIT_MENU,self.ZoomToFit)
MenuBar.Append(view_menu, "&View")
help_menu = wxMenu()
help_menu.Append(ID_ABOUT_MENU, "&About",
"More information About this program")
EVT_MENU(self, ID_ABOUT_MENU, self.OnAbout)
MenuBar.Append(help_menu, "&Help")
self.SetMenuBar(MenuBar)
self.CreateStatusBar()
self.SetStatusText("")
EVT_CLOSE(self, self.OnCloseWindow)
# Other event handlers:
EVT_RIGHT_DOWN(self, self.RightButtonEvent)
# Add the Canvas
self.Canvas = floatcanvas.FloatCanvas(self,-1,(500,500),
ProjectionFun = 'FlatEarth',
Debug = 0,
EnclosingFrame = self,
BackgroundColor = "DARK SLATE BLUE",
UseBackground = 0,
UseToolbar = 1)
self.Show(True)
self.object_list =
return None
def RightButtonEvent(self,event):
print "Right Button has been clicked in DrawFrame"
print "coords are: %i, %i"%(event.GetX(),event.GetY())
event.Skip()
def OnAbout(self, event):
dlg = wxMessageDialog(self, "This is a small program to demonstrate\n"
"the use of the FloatCanvas\n",
"About Me", wxOK | wxICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def SetMode(self,event):
for id in [ID_ZOOM_IN_BUTTON,ID_ZOOM_OUT_BUTTON,ID_MOVE_MODE_BUTTON]:
self.ToolBar.ToggleTool(id,0)
self.ToolBar.ToggleTool(event.GetId(),1)
if event.GetId() == ID_ZOOM_IN_BUTTON:
self.Canvas.SetGUIMode("ZoomIn")
elif event.GetId() == ID_ZOOM_OUT_BUTTON:
self.Canvas.SetGUIMode("ZoomOut")
elif event.GetId() == ID_MOVE_MODE_BUTTON:
self.Canvas.SetGUIMode("Move")
def ZoomToFit(self,event):
self.Canvas.ZoomToBB()
def Clear(self,event = None):
self.Canvas.RemoveObjects(self.object_list)
self.object_list =
self.Canvas.Draw()
def OnQuit(self,event):
self.Close(True)
def OnCloseWindow(self, event):
self.Destroy()
def DrawTest(self,event):
wxGetApp().Yield()
import random
import RandomArray
Range = (-10,10)
Canvas = self.Canvas
object_list = self.object_list
## Random tests of everything:
# Rectangles
for i in range(5):
x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1]))
lw = random.randint(1,5)
cf = random.randint(0,len(colors)-1)
h = random.randint(1,5)
w = random.randint(1,5)
object_list.append(Canvas.AddRectangle(x,y,h,w,LineWidth = lw,FillColor = colors[cf]))
# Ellipses
for i in range(5):
x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1]))
lw = random.randint(1,5)
cf = random.randint(0,len(colors)-1)
h = random.randint(1,5)
w = random.randint(1,5)
object_list.append(Canvas.AddEllipse(x,y,h,w,LineWidth = lw,FillColor = colors[cf]))
# Dots
for i in range(5):
x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1]))
D = random.randint(1,50)
lw = random.randint(1,5)
cf = random.randint(0,len(colors)-1)
cl = random.randint(0,len(colors)-1)
object_list.append(Canvas.AddDot(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf]))
# Circles
for i in range(5):
x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1]))
D = random.randint(1,5)
lw = random.randint(1,5)
cf = random.randint(0,len(colors)-1)
cl = random.randint(0,len(colors)-1)
object_list.append(Canvas.AddCircle(x,y,D,LineWidth = lw,LineColor = colors[cl],FillColor = colors[cf]))
self.object_list.append(self.Canvas.AddText("Circle # %i"%(i),x,y,Size = 12,BackGround = None,Position = "cc"))
# Lines
for i in range(5):
points =
for j in range(random.randint(2,10)):
point = (random.randint(Range[0],Range[1]),random.randint(Range[0],Range[1]))
points.append(point)
lw = random.randint(1,10)
cf = random.randint(0,len(colors)-1)
cl = random.randint(0,len(colors)-1)
self.object_list.append(self.Canvas.AddLine(points, LineWidth = lw, LineColor = colors[cl]))
# Polygons
for i in range(3):
points =
for j in range(random.randint(2,6)):
point = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1]))
points.append(point)
lw = random.randint(1,6)
cf = random.randint(0,len(colors)-1)
cl = random.randint(0,len(colors)-1)
self.object_list.append(self.Canvas.AddPolygon(points,
LineWidth = lw,
LineColor = colors[cl],
FillColor = colors[cf],
FillStyle = 'Solid'))
for i in range(4):
points =
points = RandomArray.uniform(Range[0],Range[1],(100,2))
cf = random.randint(0,len(colors)-1)
D = random.randint(1,4)
self.object_list.append(self.Canvas.AddPointSet(points, Color = colors[cf], Diameter = D))
# Text
String = "Some text"
for i in range(10):
ts = random.randint(10,40)
cf = random.randint(0,len(colors)-1)
x,y = (random.uniform(Range[0],Range[1]),random.uniform(Range[0],Range[1]))
self.object_list.append(self.Canvas.AddText(String,x,y,Size = ts,ForeGround = colors[cf],Position = "cc"))
self.Canvas.ZoomToBB()
def DrawMap(self,event = None):
wxGetApp().Yield()
import os, time
## Test of Actual Map Data
self.Clear()
start = time.clock()
Shorelines = Read_MapGen(os.path.join("data",'world.dat'),stats = 0)
print "It took %f seconds to load %i shorelines"%(time.clock() - start,len(Shorelines) )
start = time.clock()
for segment in Shorelines:
self.object_list.append(self.Canvas.AddLine(segment))
print "It took %f seconds to add %i shorelines"%(time.clock() - start,len(Shorelines) )
start = time.clock()
self.Canvas.ZoomToBB()
print "It took %f seconds to draw %i shorelines"%(time.clock() - start,len(Shorelines) )
## def LineTest(self,event = None):
## wxGetApp().Yield()
## import os, time
## import random
## Range = (-10,10)
## ## Test of drawing lots of lines
## self.Clear()
## start = time.clock()
## linepoints =
## linecolors =
## linewidths =
## linestyles =
## for i in range(500):
## points = (random.randint(Range[0],Range[1]),
## random.randint(Range[0],Range[1]))
## linepoints.append(points)
## points = (random.randint(Range[0],Range[1]),
## random.randint(Range[0],Range[1]))
## linepoints.append(points)
## linewidths.append(random.randint(1,10) )
## linecolors.append(colors[random.randint(0,len(colors)-1) ])
## linestyles.append(LineStyles[random.randint(0, len(LineStyles)-1)])
## self.object_list.append(self.Canvas.AddLineSet(linepoints, LineWidths = linewidths, LineColors = linecolors, LineStyles = linestyles))
## print "It took %f seconds to add %i lines"%(time.clock() - start,len(linepoints) )
## start = time.clock()
## self.Canvas.ZoomToBB()
## print "It took %f seconds to draw %i lines"%(time.clock() - start,len(linepoints) )
def LineTest(self,event = None):
wxGetApp().Yield()
import os, time
import random
Range = (-10,10)
## Test of drawing lots of lines
self.Clear()
start = time.clock()
linepoints =
linecolors =
linewidths =
for i in range(2000):
points = (random.randint(Range[0],Range[1]),
random.randint(Range[0],Range[1]),
random.randint(Range[0],Range[1]))
linepoints.append(points)
linewidths.append(random.randint(1,10) )
linecolors.append(random.randint(0,len(colors)-1) )
for (points,color,width) in zip(linepoints,linecolors,linewidths):
self.object_list.append(self.Canvas.AddLine((points[0:2],points[2:4]), LineWidth = width, LineColor = colors[color]))
print "It took %f seconds to add %i lines"%(time.clock() - start,len(linepoints) )
start = time.clock()
self.Canvas.ZoomToBB()
print "It took %f seconds to draw %i lines"%(time.clock() - start,len(linepoints) )
class DemoApp(wxApp):
"""
How the demo works:
Under the Draw menu, there are three options:
*Draw Test: will put up a picture of a bunch of randomly generated
objects, of each kind supported.
*Draw Map: will draw a map of the world. Be patient, it is a big map,
with a lot of data, and will take a while to load and draw (about 10 sec on my 450Mhz PIII). Redraws take about 2 sec. This demonstrates how the
performance is not very good for large drawings.
*Clear: Clears the Canvas.
Once you have a picture drawn, you can zoom in and out and move about
the picture. There is a tool bar with three tools that can be
selected.
The magnifying glass with the plus is the zoom in tool. Once selected,
if you click the image, it will zoom in, centered on where you
clicked. If you click and drag the mouse, you will get a rubber band
box, and the image will zoom to fit that box when you release it.
The magnifying glass with the minus is the zoom out tool. Once selected,
if you click the image, it will zoom out, centered on where you
clicked. (note that this takes a while when you are looking at the map,
as it has a LOT of lines to be drawn. The image is double buffered, so
you don't see the drawing in progress)
The hand is the move tool. Once selected, if you click and drag on the
image, it will move so that the part you clicked on ends up where you
release the mouse. Nothing is changed while you are dragging. The
drawing is too slow for that.
I'd like the cursor to change as you change tools, but the stock
wxCursors didn't include anything I liked, so I stuck with the
pointer. Please let me know if you have any nice cursor images for me to
use.
-Chris Barker
Chris.Barker@noaa.gov
"""
def OnInit(self):
frame = DrawFrame(NULL, -1, "FloatCanvas Demo App",wxDefaultPosition,wxSize(700,700))
self.SetTopWindow(frame)
return True
def Read_MapGen(filename,stats = 0,AllLines=0):
"""
This function reads a MapGen Format file, and
returns a list of NumPy arrays with the line segments in them.
Each NumPy array in the list is an NX2 array of Python Floats.
The demo should have come with a file, "world.dat" that is the
shorelines of the whole world, in MapGen format.
"""
import string
from Numeric import array
file = open(filename,'rt')
data = file.readlines()
data = map(string.strip,data)
Shorelines =
segment =
for line in data:
if line:
if line == "# -b": #New segment beginning
if segment: Shorelines.append(array(segment))
segment =
else:
segment.append(map(float,string.split(line)))
if segment: Shorelines.append(array(segment))
if stats:
NumSegments = len(Shorelines)
NumPoints = 0
for segment in Shorelines:
NumPoints = NumPoints + len(segment)
AvgPoints = NumPoints / NumSegments
print "Number of Segments: ", NumSegments
print "Average Number of Points per segment: ",AvgPoints
if AllLines:
Lines =
for segment in Shorelines:
Lines.append(segment[0])
for point in segment[1:-1]:
Lines.append(point)
Lines.append(segment[-1])
#print Shorelines
#for point in Lines: print point
return Lines
else:
return Shorelines
## for the wxPython demo:
overview = floatcanvas.FloatCanvas.__doc__
if __name__ == "__main__":
if not haveNumeric:
print errorText
else:
app = DemoApp(0)
app.MainLoop()
------------------------------------------------------------------------
#!/usr/bin/env python2.3
from Numeric import array,Float,cos,pi,sum,minimum,maximum,Int32
from time import clock, sleep
from wxPython.wx import *
import types
import os
ID_ZOOM_IN_BUTTON = wxNewId()
ID_ZOOM_OUT_BUTTON = wxNewId()
ID_ZOOM_TO_FIT_BUTTON = wxNewId()
ID_MOVE_MODE_BUTTON = wxNewId()
ID_TEST_BUTTON = wxNewId()
ID_ABOUT_MENU = wxNewId() ID_EXIT_MENU = wxNewId() ID_ZOOM_TO_FIT_MENU = wxNewId()
ID_DRAWTEST_MENU = wxNewId()
ID_DRAWMAP_MENU = wxNewId()
ID_CLEAR_MENU = wxNewId()
ID_TEST = wxNewId()
### These are some functions for bitmaps of icons.
import cPickle, zlib
def GetHandData():
return cPickle.loads(zlib.decompress(
'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
\x01\xc8S\xb6t\x06A(\x1f\x0b\xa0\xa9\x8c\x9e\x1e6\x19\xa0\xa8\x1e\x88\xd4C\
\x97\xd1\x83\xe8\x80 \x9c2zh\xa6\xc1\x11X\n\xab\x8c\x02\x8a\x0cD!\x92\x12\
\x98\x8c\x1e\x8a\x8b\xd1d\x14\xf4\x90%\x90LC\xf6\xbf\x1e\xba\xab\x91%\xd0\
\xdc\x86C\x06\xd9m\xe8!\xaa\x87S\x86\x1a1\xa7\x07\x00v\x0f[\x17' ))
def GetHandBitmap():
return wxBitmapFromXPMData(GetHandData())
#----------------------------------------------------------------------
def GetPlusData():
return cPickle.loads(zlib.decompress(
'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=l2`\r\
\xe82HF\xe9a\xc8\xe8\xe9A\x9c@\x8a\x0c\x0e\xd3p\xbb\x00\x8f\xab\xe1>\xd5\xd3\
\xc3\x15:P)l!\n\x91\xc2\x1a\xd6`)\xec\xb1\x00\x92\xc2\x11?\xb8e\x88\x8fSt\
\x19=\x00\x82\x16[\xf7' ))
def GetPlusBitmap():
return wxBitmapFromXPMData(GetPlusData())
#----------------------------------------------------------------------
def GetMinusData():
return cPickle.loads(zlib.decompress(
'x\xda\xd3\xc8)0\xe4\nV72T\x00!\x05Cu\xae\xc4`u=\x85d\x05\xa7\x9c\xc4\xe4l0O\
\x01\xc8S\xb6t\x06A(\x1f\x0b RF\x0f\x08\xb0\xc9@D\xe1r\x08\x19\xb8j=\xa2e\
\x10\x16@\x99\xc82zz\x10\'\x90"\x83\xc34r\xdc\x86\xf0\xa9\x9e\x1e\xae\xd0\
\x81Ja\x0bQ\x88\x14\xd6\xb0\x06Ka\x8f\x05\x90\x14\x8e\xf8\xc1-C|\x9c\xa2\xcb\
\xe8\x01\x00\xed\x0f[\x87' ))
def GetMinusBitmap():
return wxBitmapFromXPMData(GetMinusData())
## This is a bunch of stuff for implimenting interactive use: catching
## when objects are clicked on by the mouse, etc. I've made a start, so if
## you are interesed in making that work, let me know, I may have gotten
## it going by then
#### I probably want a full set of events someday:
## #### mouse over, right click, left click mouse up etc, etc.
## ##FLOATCANVAS_EVENT_LEFT_DOWN = wxNewEventType()
## ##FLOATCANVAS_EVENT_LEFT_UP = wxNewEventType()
## ##FLOATCANVAS_EVENT_RIGHT_DOWN = wxNewEventType()
## ##FLOATCANVAS_EVENT_RIGHT_UP = wxNewEventType()
## ##FLOATCANVAS_EVENT_MOUSE_OVER = wxNewEventType()
##WXFLOATCANVASEVENT = wxNewEventType()
##def EVT_WXFLOATCANVASEVENT( window, function ):
## """Your documentation here"""
## window.Connect( -1, -1, WXFLOATCANVASEVENT, function )
##class wxFloatCanvasObjectEvent(wxPyCommandEvent):
## def __init__(self, WindowID,Object):
## wxPyCommandEvent.__init__(self, WXFLOATCANVASEVENT, WindowID)
## self.Object = Object
## def Clone( self ): ## self.__class__( self.GetId() )
##class ColorGenerator:
## """ An instance of this class generates a unique color each time
## GetNextColor() is called. Someday I will use a proper Python
## generator for this class.
## The point of this generator is for the hit-test bitmap, each object
## needs to be a unique color. Also, each system can be running a
## different number of colors, and it doesn't appear to be possible to
## have a wxMemDC with a different colordepth as the screen so this
## generates colors far enough apart that they can be distinguished on
## a 16bit screen. Anything less than 16bits won't work.
## """
## def __init__(self,depth = 16):
## self.r = 0
## self.g = 0
## self.b = 0
## if depth == 16:
## self.step = 8
## elif depth >= 24:
## self.step = 1
## else:
## raise "ColorGenerator does not work with depth = %s"%depth
## def GetNextColor(self):
## step = self.step
## ##r,g,b = self.r,self.g,self.b
## self.r += step
## if self.r > 255:
## self.r = step
## self.g += step
## if self.g > 255:
## self.g = step
## self.b += step
## if self.b > 255:
## ## fixme: this should be a derived exception
## raise "Too many objects for HitTest"
## return (self.r,self.g,self.b)
class draw_object:
"""
This is the base class for all the objects that can be drawn.
each object has the following properties; (incomplete)
BoundingBox : is of the form: array((min_x,min_y),(max_x,max_y)) Pen
Brush
"""
def __init__(self,Foreground = 0):
self.Foreground = Foreground
self._Canvas = None
# I pre-define all these as class variables to provide an easier
# interface, and perhaps speed things up by caching all the Pens
# and Brushes, although that may not help, as I think wx now
# does that on it's own. Send me a note if you know!
BrushList = {
( None,"Transparent") : wxTRANSPARENT_BRUSH,
("Blue","Solid") : wxBLUE_BRUSH,
("Green","Solid") : wxGREEN_BRUSH,
("White","Solid") : wxWHITE_BRUSH,
("Black","Solid") : wxBLACK_BRUSH,
("Grey","Solid") : wxGREY_BRUSH,
("MediumGrey","Solid") : wxMEDIUM_GREY_BRUSH,
("LightGrey","Solid") : wxLIGHT_GREY_BRUSH,
("Cyan","Solid") : wxCYAN_BRUSH,
("Red","Solid") : wxRED_BRUSH
}
PenList = {
(None,"Transparent",1) : wxTRANSPARENT_PEN,
("Green","Solid",1) : wxGREEN_PEN,
("White","Solid",1) : wxWHITE_PEN,
("Black","Solid",1) : wxBLACK_PEN,
("Grey","Solid",1) : wxGREY_PEN,
("MediumGrey","Solid",1) : wxMEDIUM_GREY_PEN,
("LightGrey","Solid",1) : wxLIGHT_GREY_PEN,
("Cyan","Solid",1) : wxCYAN_PEN,
("Red","Solid",1) : wxRED_PEN
}
FillStyleList = {
"Transparent" : wxTRANSPARENT,
"Solid" : wxSOLID,
"BiDiagonalHatch": wxBDIAGONAL_HATCH,
"CrossDiagHatch" : wxCROSSDIAG_HATCH,
"FDiagonal_Hatch": wxFDIAGONAL_HATCH,
"CrossHatch" : wxCROSS_HATCH,
"HorizontalHatch": wxHORIZONTAL_HATCH,
"VerticalHatch" : wxVERTICAL_HATCH
}
LineStyleList = {
"Solid" : wxSOLID,
"Transparent": wxTRANSPARENT,
"Dot" : wxDOT,
"LongDash" : wxLONG_DASH,
"ShortDash" : wxSHORT_DASH,
"DotDash" : wxDOT_DASH,
}
def SetBrush(self,FillColor,FillStyle):
if FillColor is None or FillStyle is None:
self.Brush = wxTRANSPARENT_BRUSH
self.FillStyle = "Transparent"
else:
if not self.BrushList.has_key((FillColor,FillStyle)):
self.BrushList[(FillColor,FillStyle)] = wxBrush(FillColor,self.FillStyleList[FillStyle])
self.Brush = self.BrushList[(FillColor,FillStyle)]
def SetPen(self,LineColor,LineStyle,LineWidth):
if (LineColor is None) or (LineStyle is None):
self.Pen = wxTRANSPARENT_PEN
self.LineStyle = 'Transparent'
else:
if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
self.PenList[(LineColor,LineStyle,LineWidth)] = wxPen(LineColor,LineWidth,self.LineStyleList[LineStyle])
self.Pen = self.PenList[(LineColor,LineStyle,LineWidth)]
def SetPens(self,LineColors,LineStyles,LineWidths):
"""
This method used when an object could have a list of pens, rather than just one
It is used for LineSet, and perhaps others in the future.
fixme: this is really kludgy, there has got to be a better way!
"""
length = 1
if type(LineColors) == types.ListType:
length = len(LineColors)
else:
LineColors = [LineColors]
if type(LineStyles) == types.ListType:
length = len(LineStyles)
else:
LineStyles = [LineStyles]
if type(LineWidths) == types.ListType:
length = len(LineWidths)
else:
LineWidths = [LineWidths]
if length > 1:
if len(LineColors) == 1:
LineColors = LineColors*length
if len(LineStyles) == 1:
LineStyles = LineStyles*length
if len(LineWidths) == 1:
LineWidths = LineWidths*length
self.Pens =
for (LineColor,LineStyle,LineWidth) in zip(LineColors,LineStyles,LineWidths):
if LineColor is None or LineStyle is None:
self.Pens.append(wxTRANSPARENT_PEN)
# what's this for?> self.LineStyle = 'Transparent'
if not self.PenList.has_key((LineColor,LineStyle,LineWidth)):
Pen = wxPen(LineColor,LineWidth,self.LineStyleList[LineStyle])
self.Pens.append(Pen)
else:
self.Pens.append(self.PenList[(LineColor,LineStyle,LineWidth)])
if length == 1:
self.Pens = self.Pens[0]
def PutInBackground(self):
if self._Canvas and self.Foreground:
self._Canvas._TopDrawList.remove(self)
self._Canvas._DrawList.append(self)
self._Canvas._BackgroundDirty = 1
self.Foreground = 0
def PutInForeground(self):
if self._Canvas and (not self.Foreground):
self._Canvas._TopDrawList.append(self)
self._Canvas._DrawList.remove(self)
self._Canvas._BackgroundDirty = 1
self.Foreground = 1
class Polygon(draw_object):
"""
The Polygon class takes a list of 2-tuples, or a NX2 NumPy array of
point coordinates. so that Points[N][0] is the x-coordinate of
point N and Points[N][1] is the y-coordinate or Points[N,0] is the
x-coordinate of point N and Points[N,1] is the y-coordinate for
arrays.
"""
def __init__(self,Points,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.Points = array(Points,Float)
self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
self.LineColor = LineColor
self.LineStyle = LineStyle
self.LineWidth = LineWidth
self.FillColor = FillColor
self.FillStyle = FillStyle
self.SetPen(LineColor,LineStyle,LineWidth)
self.SetBrush(FillColor,FillStyle)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
Points = WorldToPixel(self.Points)
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
#dc.DrawPolygon(map(lambda x: (x[0],x[1]), Points.tolist()))
dc.DrawPolygon(Points)
class PolygonSet(draw_object):
"""
The PolygonSet class takes a Geometry.Polygon object.
so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
it creates a set of line segments, from (x1,y1) to (x2,y2)
"""
def __init__(self,PolySet,LineColors,LineStyles,LineWidths,FillColors,FillStyles,Foreground = 0):
draw_object.__init__(self, Foreground)
##fixme: there should be some error checking for everything being the right length.
self.Points = array(Points,Float)
self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
self.LineColors = LineColors
self.LineStyles = LineStyles
self.LineWidths = LineWidths
self.FillColors = FillColors
self.FillStyles = FillStyles
self.SetPens(LineColors,LineStyles,LineWidths)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
Points = WorldToPixel(self.Points)
Points.shape = (-1,4)
dc.DrawLineList(Points,self.Pens)
class Line(draw_object):
"""
The Line class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
It will draw a straight line if there are two points, and a polyline if there are more than two.
"""
def __init__(self,Points,LineColor,LineStyle,LineWidth,Foreground = 0):
draw_object.__init__(self, Foreground)
self.Points = array(Points,Float)
self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
self.LineColor = LineColor
self.LineStyle = LineStyle
self.LineWidth = LineWidth
self.SetPen(LineColor,LineStyle,LineWidth)
def SetPoints(self,Points):
self.Points = Points
self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
if self._Canvas:
# It looks like this shouldn't be private
self._Canvas.BoundingBoxDirty = 1
def _Draw(self,dc,WorldToPixel,ScaleFunction):
Points = WorldToPixel(self.Points)
dc.SetPen(self.Pen)
#dc.DrawLines(map(lambda x: (x[0],x[1]), Points.tolist()))
dc.DrawLines(Points)
class LineSet(draw_object):
"""
The LineSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
so that Points[N] = (x1,y1) and Points[N+1] = (x2,y2). N must be an even number!
it creates a set of line segments, from (x1,y1) to (x2,y2)
"""
def __init__(self,Points,LineColors,LineStyles,LineWidths,Foreground = 0):
draw_object.__init__(self, Foreground)
NumLines = len(Points) / 2
##fixme: there should be some error checking for everything being the right length.
self.Points = array(Points,Float)
self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
self.LineColors = LineColors
self.LineStyles = LineStyles
self.LineWidths = LineWidths
self.SetPens(LineColors,LineStyles,LineWidths)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
Points = WorldToPixel(self.Points)
Points.shape = (-1,4)
dc.DrawLineList(Points,self.Pens)
class PointSet(draw_object):
"""
The PointSet class takes a list of 2-tuples, or a NX2 NumPy array of point coordinates.
so that Points[N][0] is the x-coordinate of point N and Points[N][1] is the y-coordinate
or Points[N,0] is the x-coordinate of point N and Points[N,1] is the y-coordinate for arrays.
Each point will be drawn the same color and Diameter. The Diameter is in screen points,
not world coordinates.
"""
def __init__(self,Points,Color,Diameter,Foreground = 0):
draw_object.__init__(self,Foreground)
self.Points = array(Points,Float)
self.Points.shape = (-1,2) # Make sure it is a NX2 array, even if there is only one point
self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
self.Color = Color
self.Diameter = Diameter
self.SetPen(Color,"Solid",1)
self.SetBrush(Color,"Solid")
def SetPoints(self,Points):
self.Points = Points
self.BoundingBox = array(((min(self.Points[:,0]),min(self.Points[:,1])),(max(self.Points[:,0]),max(self.Points[:,1]))),Float)
if self._Canvas:
# It looks like this shouldn't be private
self._Canvas.BoundingBoxDirty = 1
def _Draw(self,dc,WorldToPixel,ScaleFunction):
dc.SetPen(self.Pen)
Points = WorldToPixel(self.Points)
if self.Diameter <= 1:
dc.DrawPointList(Points)
elif self.Diameter <= 2:
# A Little optimization for a diameter2 - point
dc.DrawPointList(Points)
dc.DrawPointList(Points + (1,0))
dc.DrawPointList(Points + (0,1))
dc.DrawPointList(Points + (1,1))
else:
dc.SetBrush(self.Brush)
radius = int(round(self.Diameter/2))
for (x,y) in Points:
dc.DrawEllipse((x - radius), (y - radius), self.Diameter, self.Diameter)
class Dot(draw_object):
"""
The Dot class takes an x.y coordinate pair, and the Diameter of the circle.
The Diameter is in pixels, so it won't change with zoom.
Also Fill and line data
"""
def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.X = x
self.Y = y
self.Diameter = Diameter
# NOTE: the bounding box does not include the diameter of the dot, as that is in pixel coords.
# If this is a problem, perhaps you should use a circle, instead!
self.BoundingBox = array(((x,y),(x,y)),Float)
self.LineColor = LineColor
self.LineStyle = LineStyle
self.LineWidth = LineWidth
self.FillColor = FillColor
self.FillStyle = FillStyle
self.SetPen(LineColor,LineStyle,LineWidth)
self.SetBrush(FillColor,FillStyle)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
radius = int(round(self.Diameter/2))
(X,Y) = WorldToPixel((self.X,self.Y))
dc.DrawEllipse((X - radius), (Y - radius), self.Diameter, self.Diameter)
class Rectangle(draw_object):
def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.X = x
self.Y = y
self.Width = width
self.Height = height
self.BoundingBox = array(((x,y),(x+width,y+height)),Float)
self.LineColor = LineColor
self.LineStyle = LineStyle
self.LineWidth = LineWidth
self.FillColor = FillColor
self.FillStyle = FillStyle
self.SetPen(LineColor,LineStyle,LineWidth)
self.SetBrush(FillColor,FillStyle)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
(Width,Height) = ScaleFunction((self.Width,self.Height))
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
dc.DrawRectangle(X,Y,Width,Height)
class Ellipse(draw_object):
def __init__(self,x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.X = x
self.Y = y
self.Width = width
self.Height = height
self.BoundingBox = array(((x,y),(x+width,y+height)),Float)
self.LineColor = LineColor
self.LineStyle = LineStyle
self.LineWidth = LineWidth
self.FillColor = FillColor
self.FillStyle = FillStyle
self.SetPen(LineColor,LineStyle,LineWidth)
self.SetBrush(FillColor,FillStyle)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
(Width,Height) = ScaleFunction((self.Width,self.Height))
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
dc.DrawEllipse(X,Y,Width,Height)
class Circle(draw_object):
def __init__(self,x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground = 0):
draw_object.__init__(self,Foreground)
self.X = x
self.Y = y
self.Diameter = Diameter
self.BoundingBox = array(((x-Diameter/2,y-Diameter/2),(x+Diameter/2,y+Diameter/2)),Float)
self.LineColor = LineColor
self.LineStyle = LineStyle
self.LineWidth = LineWidth
self.FillColor = FillColor
self.FillStyle = FillStyle
self.SetPen(LineColor,LineStyle,LineWidth)
self.SetBrush(FillColor,FillStyle)
def _Draw(self,dc,WorldToPixel,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
(Diameter,dummy) = ScaleFunction((self.Diameter,self.Diameter))
dc.SetPen(self.Pen)
dc.SetBrush(self.Brush)
dc.DrawEllipse(X-Diameter/2,Y-Diameter/2,Diameter,Diameter)
class Text(draw_object):
"""
This class creates a text object, placed at the coordinates,
x,y. the "Position" argument is a two charactor string, indicating
where in relation to the coordinates the string should be oriented.
The first letter is: t, c, or b, for top, center and bottom
The second letter is: l, c, or r, for left, center and right
I've provided arguments for Family, Style, and Weight of font, but
have not yet implimented them, so all text is: wxSWISS, wxNORMAL, wxNORMAL.
I'd love it if someone would impliment that!
The size is fixed, and does not scale with the drawing.
"""
def __init__(self,String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground = 0):
draw_object.__init__(self,Foreground)
self.String = String
self.Size = Size
self.ForeGround = ForeGround
if BackGround is None:
self.BackGround = None
else:
self.BackGround = BackGround
self.Family = Family
self.Style = Style
self.Weight = Weight
self.Underline = Underline
self.Position = Position
# fixme: this should use the passed in parameters!
self.Font = wxFont(Size, wxMODERN, wxNORMAL, wxNORMAL, Underline, )
self.BoundingBox = array(((x,y),(x,y)),Float)
self.X = x
self.Y = y
self.x_shift = None
self.y_shift = None
def _Draw(self,dc,WorldToPixel,ScaleFunction):
(X,Y) = WorldToPixel((self.X,self.Y))
dc.SetFont(self.Font)
dc.SetTextForeground(self.ForeGround)
if self.BackGround:
dc.SetBackgroundMode(wxSOLID)
dc.SetTextBackground(self.BackGround)
else:
dc.SetBackgroundMode(wxTRANSPARENT)
# compute the shift, and adjust the coordinates, if neccesary
# This had to be put in here, becsuse there is no wxDC during __init__
if self.x_shift is None or self.y_shift is None:
if self.Position == 'tl':
x_shift,y_shift = 0,0
else:
(w,h) = dc.GetTextExtent(self.String)
if self.Position[0] == 't':
y_shift = 0
elif self.Position[0] == 'c':
y_shift = h/2
elif self.Position[0] == 'b':
y_shift = h
else:
##fixme: this should be a real derived exception
raise "Invalid value for Text Object Position Attribute"
if self.Position[1] == 'l':
x_shift = 0
elif self.Position[1] == 'c':
x_shift = w/2
elif self.Position[1] == 'r':
x_shift = w
else:
##fixme: this should be a real derived exception
raise "Invalid value for Text Object Position Attribute"
self.x_shift = x_shift
self.y_shift = y_shift
dc.DrawText(self.String, X-self.x_shift, Y-self.y_shift)
#---------------------------------------------------------------------------
class FloatCanvas(wxPanel):
"""
FloatCanvas.py
This is a high level window for drawing maps and anything else in an
arbitrary coordinate system.
The goal is to provide a convenient way to draw stuff on the screen
without having to deal with handling OnPaint events, converting to pixel
coordinates, knowing about wxWindows brushes, pens, and colors, etc. It
also provides virtually unlimited zooming and scrolling
I am using it for two things:
1) general purpose drawing in floating point coordinates
2) displaying map data in Lat-long coordinates
If the projection is set to None, it will draw in general purpose
floating point coordinates. If the projection is set to 'FlatEarth', it
will draw a FlatEarth projection, centered on the part of the map that
you are viewing. You can also pass in your own projection function.
It is double buffered, so re-draws after the window is uncovered by something
else are very quick.
It relies on NumPy, which is needed for speed
Bugs and Limitations:
Lots: patches, fixes welcome
For Map drawing: It ignores the fact that the world is, in fact, a
sphere, so it will do strange things if you are looking at stuff near
the poles or the date line. so far I don't have a need to do that, so I
havn't bothered to add any checks for that yet.
Zooming:
I have set no zoom limits. What this means is that if you zoom in really far, you can get integer overflows, and get wierd results. It
doesn't seem to actually cause any problems other than wierd output, at
least when I have run it.
Speed:
I have done a couple of things to improve speed in this app. The one
thing I have done is used NumPy Arrays to store the coordinates of the
points of the objects. This allowed me to use array oriented functions
when doing transformations, and should provide some speed improvement
for objects with a lot of points (big polygons, polylines, pointsets).
The real slowdown comes when you have to draw a lot of objects, because
you have to call the wxDC.DrawSomething call each time. This is plenty
fast for tens of objects, OK for hundreds of objects, but pretty darn
slow for thousands of objects.
The solution is to be able to pass some sort of object set to the DC
directly. I've used DC.DrawPointList(Points), and it helped a lot with
drawing lots of points. I havn't got a LineSet type object, so I havn't
used DC.DrawLineList yet. I'd like to get a full set of DrawStuffList()
methods implimented, and then I'd also have a full set of Object sets
that could take advantage of them. I hope to get to it some day.
Copyright: Christopher Barker
License: Same as wxPython
Please let me know if you're using this!!!
Contact me at:
Chris.Barker@noaa.gov
"""
def __init__(self, parent, id = -1,
size = wxDefaultSize,
ProjectionFun = None,
BackgroundColor = "WHITE",
Debug = 0,
EnclosingFrame = None,
UseToolbar = 1,
UseBackground = 0,
UseHitTest = 0):
wxPanel.__init__( self, parent, id, wxDefaultPosition, size)
if ProjectionFun == 'FlatEarth':
self.ProjectionFun = self.FlatEarthProjection elif type(ProjectionFun) == types.FunctionType:
self.ProjectionFun = ProjectionFun elif ProjectionFun is None:
self.ProjectionFun = lambda x=None: array( (1,1), Float)
else:
raise('Projectionfun must be either: "FlatEarth", None, or a function that takes the ViewPortCenter and returns a MapProjectionVector')
self.UseBackground = UseBackground
self.UseHitTest = UseHitTest
self.NumBetweenBlits = 40
## you can have a toolbar with buttons for zoom-in, zoom-out and
## move. If you don't use the toolbar, you should provide your
## own way of navigating the canvas
if UseToolbar:
## Create the vertical sizer for the toolbar and Panel
box = wxBoxSizer(wxVERTICAL)
box.Add(self.BuildToolbar(), 0, wxALL | wxALIGN_LEFT | wxGROW, 4)
self.DrawPanel = wxWindow(self,-1,wxDefaultPosition,wxDefaultSize,wxSUNKEN_BORDER)
box.Add(self.DrawPanel,1,wxGROW)
box.Fit(self)
self.SetAutoLayout(True)
self.SetSizer(box)
else:
self.DrawPanel = self
self.DrawPanel.BackgroundBrush = wxBrush(BackgroundColor,wxSOLID)
self.Debug = Debug
self.EnclosingFrame = EnclosingFrame
EVT_PAINT(self.DrawPanel, self.OnPaint)
EVT_SIZE(self.DrawPanel, self.OnSize)
EVT_LEFT_DOWN(self.DrawPanel, self.LeftButtonEvent)
EVT_LEFT_UP(self.DrawPanel, self.LeftButtonEvent)
EVT_RIGHT_DOWN(self.DrawPanel, self.RightButtonEvent)
EVT_MOTION(self.DrawPanel, self.LeftButtonEvent)
if self.UseBackground:
self._TopDrawList =
self.BoundingBox = None
self.BoundingBoxDirty = 0
self.ViewPortCenter= array( (0,0), Float)
self.MapProjectionVector = array( (1,1), Float) # No Projection to start!
self.TransformVector = array( (1,-1), Float) # default Transformation
self.Scale = 1
self.GUIMode = None
self.StartRBBox = None
self.PrevRBBox = None
self.StartMove = None
self.PrevMoveBox = None
# called just to make sure everything is initialized
self.OnSize(None)
def BuildToolbar(self):
tb = wxToolBar(self,-1)
self.ToolBar = tb
tb.SetToolBitmapSize((23,23))
tb.AddTool(ID_ZOOM_IN_BUTTON, GetPlusBitmap(), isToggle=true,shortHelpString = "Zoom In")
EVT_TOOL(self, ID_ZOOM_IN_BUTTON, self.SetMode)
tb.AddTool(ID_ZOOM_OUT_BUTTON, GetMinusBitmap(), isToggle=true,shortHelpString = "Zoom Out")
EVT_TOOL(self, ID_ZOOM_OUT_BUTTON, self.SetMode)
tb.AddTool(ID_MOVE_MODE_BUTTON, GetHandBitmap(), isToggle=true,shortHelpString = "Move")
EVT_TOOL(self, ID_MOVE_MODE_BUTTON, self.SetMode)
tb.AddSeparator()
tb.AddControl(wxButton(tb, ID_ZOOM_TO_FIT_BUTTON, "Zoom To Fit",wxDefaultPosition, wxDefaultSize))
EVT_BUTTON(self, ID_ZOOM_TO_FIT_BUTTON, self.ZoomToFit)
tb.Realize()
return tb
def SetMode(self,event):
for id in [ID_ZOOM_IN_BUTTON,ID_ZOOM_OUT_BUTTON,ID_MOVE_MODE_BUTTON]:
self.ToolBar.ToggleTool(id,0)
self.ToolBar.ToggleTool(event.GetId(),1)
if event.GetId() == ID_ZOOM_IN_BUTTON:
self.SetGUIMode("ZoomIn")
elif event.GetId() == ID_ZOOM_OUT_BUTTON:
self.SetGUIMode("ZoomOut")
elif event.GetId() == ID_MOVE_MODE_BUTTON:
self.SetGUIMode("Move")
def SetGUIMode(self,Mode):
if Mode in ["ZoomIn","ZoomOut","Move",None]:
self.GUIMode = Mode
else:
raise "Not a valid Mode"
def FlatEarthProjection(self,CenterPoint):
return array((cos(pi*CenterPoint[1]/180),1),Float)
def LeftButtonEvent(self,event):
if self.EnclosingFrame:
if event.Moving:
position = self.PixelToWorld((event.GetX(),event.GetY()))
self.EnclosingFrame.SetStatusText("%8.3f, %8.3f"%tuple(position))
if self.GUIMode:
if self.GUIMode == "ZoomIn":
if event.LeftDown():
self.StartRBBox = (event.GetX(),event.GetY())
self.PrevRBBox = None
elif event.Dragging() and event.LeftIsDown() and self.StartRBBox:
x0,y0 = self.StartRBBox
x1,y1 = event.GetX(),event.GetY()
w, h = abs(x1-x0),abs(y1-y0)
w = max(w,int(h*self.AspectRatio))
h = int(w/self.AspectRatio)
x_c, y_c = (x0+x1)/2 , (y0+y1)/2
dc = wxClientDC(self.DrawPanel)
dc.BeginDrawing()
dc.SetPen(wxPen('WHITE', 2,wxSHORT_DASH))
dc.SetBrush(wxTRANSPARENT_BRUSH)
dc.SetLogicalFunction(wxXOR)
if self.PrevRBBox:
dc.DrawRectangle(*self.PrevRBBox)
dc.DrawRectangle(x_c-w/2,y_c-h/2,w,h)
self.PrevRBBox = (x_c-w/2,y_c-h/2,w,h)
dc.EndDrawing()
elif event.LeftUp() and self.StartRBBox :
self.PrevRBBox = None
EndRBBox = (event.GetX(),event.GetY())
StartRBBox = self.StartRBBox
# if mouse has moved less that ten pixels, don't use the box.
if abs(StartRBBox[0] - EndRBBox[0]) > 10 and abs(StartRBBox[1] - EndRBBox[1]) > 10:
EndRBBox = self.PixelToWorld(EndRBBox)
StartRBBox = self.PixelToWorld(StartRBBox)
BB = array(((min(EndRBBox[0],StartRBBox[0]), min(EndRBBox[1],StartRBBox[1])),
(max(EndRBBox[0],StartRBBox[0]), max(EndRBBox[1],StartRBBox[1]))),Float)
self.ZoomToBB(BB)
else:
Center = self.PixelToWorld(StartRBBox)
self.Zoom(1.5,Center)
self.StartRBBox = None
if self.GUIMode == "ZoomOut":
if event.LeftDown():
Center = self.PixelToWorld((event.GetX(),event.GetY()))
self.Zoom(1/1.5,Center)
elif self.GUIMode == "Move":
if event.LeftDown():
self.StartMove = array((event.GetX(),event.GetY()))
self.PrevMoveBox = None
elif event.Dragging() and event.LeftIsDown() and self.StartMove:
x_1,y_1 = event.GetX(),event.GetY()
w, h = self.PanelSize
x_tl, y_tl = x_1 - self.StartMove[0], y_1 - self.StartMove[1]
dc = wxClientDC(self.DrawPanel)
dc.BeginDrawing()
dc.SetPen(wxPen('WHITE', 1,))
dc.SetBrush(wxTRANSPARENT_BRUSH)
dc.SetLogicalFunction(wxXOR)
if self.PrevMoveBox:
dc.DrawRectangle(*self.PrevMoveBox)
dc.DrawRectangle(x_tl,y_tl,w,h)
self.PrevMoveBox = (x_tl,y_tl,w,h)
dc.EndDrawing()
elif event.LeftUp() and self.StartMove:
self.PrevMoveBox = None
StartMove = self.StartMove
EndMove = array((event.GetX(),event.GetY()))
if sum((StartMove-EndMove)**2) > 16:
self.Move(StartMove-EndMove,'Pixel')
self.StartMove = None
def RightButtonEvent(self,event):
if self.GUIMode:
if self.GUIMode == "ZoomIn":
Center = self.PixelToWorld((event.GetX(),event.GetY()))
self.Zoom(1/1.5,Center)
elif self.GUIMode == "ZoomOut":
Center = self.PixelToWorld((event.GetX(),event.GetY()))
self.Zoom(1.5,Center)
else:
event.Skip()
event.Skip()
def MakeNewBuffers(self):
# Make new offscreen bitmap:
self._Buffer = wxEmptyBitmap(self.PanelSize[0],self.PanelSize[1])
if self.UseBackground:
self._BackBuffer = wxEmptyBitmap(self.PanelSize[0],self.PanelSize[1])
self._BackgroundDirty = 1
else:
pass
def OnSize(self,event):
self.PanelSize = array(self.DrawPanel.GetClientSizeTuple(),Int32)
try:
self.AspectRatio = self.PanelSize[0]/self.PanelSize[1]
except ZeroDivisionError:
self.AspectRatio = 1.0
self.MakeNewBuffers()
self.Draw()
def OnPaint(self, event):
#dc = wxBufferedPaintDC(self.DrawPanel, self._Buffer)
dc = wxPaintDC(self.DrawPanel)
dc.DrawBitmap(self._Buffer,0,0)
def Draw(self):
"""
The Draw method gets pretty complicated because of all the buffers
There is a main buffer set up to double buffer the screen, so
you can get quick re-draws when the window gets uncovered.
If self.UseBackground is set, and an object is set up with the
"ForeGround" flag, then it gets drawn to the screen after blitting
the background. This is done so that you can have a complicated
background, but have something changing on the foreground,
without having to wait for the background to get re-drawn. This
can be used to support simple animation, for instance.
"""
if self.Debug: start = clock()
ScreenDC = wxClientDC(self.DrawPanel)
ViewPortWorld = ( self.PixelToWorld((0,0)), self.PixelToWorld(self.PanelSize) )
ViewPortBB = array( ( minimum.reduce(ViewPortWorld), maximum.reduce(ViewPortWorld) ) )
if self.UseBackground:
dc = wxMemoryDC()
dc.SelectObject(self._BackBuffer)
dc.SetBackground(self.DrawPanel.BackgroundBrush)
if self._DrawList:
if self._BackgroundDirty:
dc.BeginDrawing()
dc.Clear()
i = 0
for Object in self._DrawList:
if self.BBCheck(Object.BoundingBox,ViewPortBB):
#print "object is in Bounding Box"
i+=1
Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
if i % self.NumBetweenBlits == 0:
ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
dc.EndDrawing()
else:
dc.Clear()
self._BackgroundDirty = 0
dc.SelectObject(self._Buffer)
dc.BeginDrawing()
##Draw Background on Main Buffer:
dc.DrawBitmap(self._BackBuffer,0,0)
#Draw the OnTop stuff
i = 0
for Object in self._TopDrawList:
i+=1
Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
if i % self.NumBetweenBlits == 0:
ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
dc.EndDrawing()
else: # not using a Background DC
dc = wxMemoryDC()
dc.SelectObject(self._Buffer)
dc.SetBackground(self.DrawPanel.BackgroundBrush)
if self._DrawList:
dc.BeginDrawing()
dc.Clear()
i = 0
for Object in self._DrawList:
if self.BBCheck(Object.BoundingBox,ViewPortBB):
#print "object is in Bounding Box"
i+=1
Object._Draw(dc,self.WorldToPixel,self.ScaleFunction)
if i % self.NumBetweenBlits == 0:
ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
dc.EndDrawing()
else:
dc.Clear()
# now refresh the screen
#ScreenDC.DrawBitmap(self._Buffer,0,0) #NOTE: uisng DrawBitmap didn't work right on MSW
ScreenDC.Blit(0, 0, self.PanelSize[0],self.PanelSize[1], dc, 0, 0)
# If the canvas is in the middle of a zoom or move, the Rubber Band box needs to be re-drawn
if self.PrevRBBox:
ScreenDC.SetPen(wxPen('WHITE', 2,wxSHORT_DASH))
ScreenDC.SetBrush(wxTRANSPARENT_BRUSH)
ScreenDC.SetLogicalFunction(wxXOR)
ScreenDC.DrawRectangle(*self.PrevRBBox)
elif self.PrevMoveBox:
ScreenDC.SetPen(wxPen('WHITE', 1,))
ScreenDC.SetBrush(wxTRANSPARENT_BRUSH)
ScreenDC.SetLogicalFunction(wxXOR)
ScreenDC.DrawRectangle(*self.PrevMoveBox)
if self.Debug: print "Drawing took %f seconds of CPU time"%(clock()-start)
def BBCheck(self, BB1, BB2):
"""
BBCheck(BB1, BB2) returns True is the Bounding boxes intesect, False otherwise
"""
if ( (BB1[1,0] > BB2[0,0]) and (BB1[0,0] < BB2[1,0]) and
(BB1[1,1] > BB2[0,1]) and (BB1[0,1] < BB2[1,1]) ):
return True
else:
return False
def Move(self,shift,CoordType):
"""
move the image in the window.
shift is an (x,y) tuple, specifying the amount to shift in each direction
It can be in any of three coordinates: Panel, Pixel, World,
specified by the CoordType parameter
Panel coordinates means you want to shift the image by some
fraction of the size of the displaed image
Pixel coordinates means you want to shift the image by some number of pixels
World coordinates meand you want to shift the image by an amount
in Floating point world coordinates
"""
shift = array(shift,Float)
if CoordType == 'Panel':# convert from panel coordinates
shift = shift * array((-1,1),Float) *self.PanelSize/self.TransformVector
elif CoordType == 'Pixel': # convert from pixel coordinates
shift = shift/self.TransformVector
elif CoordType == 'World': # No conversion
pass
else:
raise 'CoordType must be either "Panel", "Pixel", or "World"'
self.ViewPortCenter = self.ViewPortCenter + shift self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
self._BackgroundDirty = 1
self.Draw()
def Zoom(self,factor,center = None):
"""
Zoom(factor, center) changes the amount of zoom of the image by factor.
If factor is greater than one, the image gets larger.
If factor is less than one, the image gets smaller.
Center is a tuple of (x,y) coordinates of the center of the viewport, after zooming.
If center is not given, the center will stay the same.
"""
self.Scale = self.Scale*factor
if center:
self.ViewPortCenter = array(center,Float)
self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
self._BackgroundDirty = 1
self.Draw()
def ZoomToFit(self,event):
self.ZoomToBB()
def ZoomToBB(self,NewBB = None,DrawFlag = 1):
"""
Zooms the image to the bounding box given, or to the bounding
box of all the objects on the canvas, if none is given.
"""
if NewBB:
BoundingBox = NewBB
else:
if self.BoundingBoxDirty:
self._ResetBoundingBox()
BoundingBox = self.BoundingBox
if BoundingBox:
self.ViewPortCenter = array(((BoundingBox[0,0]+BoundingBox[1,0])/2,
(BoundingBox[0,1]+BoundingBox[1,1])/2 ),Float)
self.MapProjectionVector = self.ProjectionFun(self.ViewPortCenter)
# Compute the new Scale
BoundingBox = BoundingBox * self.MapProjectionVector
try:
self.Scale = min((self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0])),
(self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1])))*0.95
except ZeroDivisionError: # this will happen if the BB has zero width or height
try: #width
self.Scale = (self.PanelSize[0] / (BoundingBox[1,0]-BoundingBox[0,0]))*0.95
except ZeroDivisionError:
try: # height
self.Scale = (self.PanelSize[1] / (BoundingBox[1,1]-BoundingBox[0,1]))*0.95
except ZeroDivisionError: #zero size! (must be a single point)
self.Scale = 1
self.TransformVector = array((self.Scale,-self.Scale),Float)* self.MapProjectionVector
if DrawFlag:
self._BackgroundDirty = 1
self.Draw()
def RemoveObjects(self,Objects):
for Object in Objects:
self.RemoveObject(Object,ResetBB = 0)
self.BoundingBoxDirty = 1
def RemoveObject(self,Object,ResetBB = 1):
if Object.Foreground:
self._TopDrawList.remove(Object)
else:
self._DrawList.remove(Object)
self._BackgroundDirty = 1
if ResetBB:
self.BoundingBoxDirty = 1
def Clear(self, ResetBB = True):
self._DrawList =
if self.UseBackground:
self._TopDrawList =
self._BackgroundDirty = 1
if ResetBB:
self._ResetBoundingBox()
def _AddBoundingBox(self,NewBB):
if self.BoundingBox is None:
self.BoundingBox = NewBB
self.ZoomToBB(NewBB,DrawFlag = 0)
else:
self.BoundingBox = array(((min(self.BoundingBox[0,0],NewBB[0,0]),
min(self.BoundingBox[0,1],NewBB[0,1])),
(max(self.BoundingBox[1,0],NewBB[1,0]),
max(self.BoundingBox[1,1],NewBB[1,1]))),Float)
def _ResetBoundingBox(self):
# NOTE: could you remove an item without recomputing the entire bounding box?
self.BoundingBox = None
if self._DrawList:
self.BoundingBox = self._DrawList[0].BoundingBox
for Object in self._DrawList[1:]:
self._AddBoundingBox(Object.BoundingBox)
if self.UseBackground:
for Object in self._TopDrawList:
self._AddBoundingBox(Object.BoundingBox)
if self.BoundingBox is None:
self.ViewPortCenter= array( (0,0), Float)
self.TransformVector = array( (1,-1), Float)
self.MapProjectionVector = array( (1,1), Float)
self.Scale = 1
self.BoundingBoxDirty = 0
def PixelToWorld(self,Points):
"""
Converts coordinates from Pixel coordinates to world coordinates.
Points is a tuple of (x,y) coordinates, or a list of such tuples, or a NX2 Numpy array of x,y coordinates.
"""
return (((array(Points,Float) - (self.PanelSize/2))/self.TransformVector) + self.ViewPortCenter)
def WorldToPixel(self,Coordinates):
"""
This function will get passed to the drawing functions of the objects,
to transform from world to pixel coordinates.
Coordinates should be a NX2 array of (x,y) coordinates, or
a 2-tuple, or sequence of 2-tuples.
"""
return (((array(Coordinates,Float) - self.ViewPortCenter)*self.TransformVector)+(self.PanelSize/2)).astype('i')
def ScaleFunction(self,Lengths):
"""
This function will get passed to the drawing functions of the objects,
to Change a length from world to pixel coordinates.
Lengths should be a NX2 array of (x,y) coordinates, or
a 2-tuple, or sequence of 2-tuples.
"""
return (array(Lengths,Float)*self.TransformVector).astype('i')
## This is a set of methods that add objects to the Canvas. It kind
## of seems like a lot of duplication, but I wanted to be able to
## instantiate the draw objects separatley form adding them, but
## also to be able to do add one oin one step. I'm open to better
## ideas...
def AddRectangle(self,x,y,width,height,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Rectangle(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddEllipse(self,x,y,width,height,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Ellipse(x,y,width,height,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddCircle(self,x,y,Diameter,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Circle(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddDot(self,x,y,Diameter,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Dot(x,y,Diameter,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddPolygon(self,Points,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
FillColor = None,
FillStyle = "Solid",
Foreground = 0):
Object = Polygon(Points,LineColor,LineStyle,LineWidth,FillColor,FillStyle,Foreground)
self.AddObject(Object)
return Object
def AddLine(self,Points,
LineColor = "Black",
LineStyle = "Solid",
LineWidth = 1,
Foreground = 0):
Object = Line(Points,LineColor,LineStyle,LineWidth,Foreground)
self.AddObject(Object)
return Object
def AddLineSet(self,Points,
LineColors = "Black",
LineStyles = "Solid",
LineWidths = 1,
Foreground = 0):
Object = LineSet(Points,LineColors,LineStyles,LineWidths,Foreground)
self.AddObject(Object)
return Object
def AddPointSet(self,Points,
Color = "Black",
Diameter = 1,
Foreground = 0):
Object = PointSet(Points,Color,Diameter,Foreground)
self.AddObject(Object)
return Object
def AddText(self,String,x,y,
Size = 20,
ForeGround = 'Black',
BackGround = None,
Family = 'Swiss',
Style = 'Normal',
Weight = 'Normal',
Underline = 0,
Position = 'tl',
Foreground = 0):
Object = Text(String,x,y,Size,ForeGround,BackGround,Family,Style,Weight,Underline,Position,Foreground)
self.AddObject(Object)
return Object
def AddObject(self,obj):
# put in a reference to the Canvas, so remove and other stuff can work
obj._Canvas = self
if obj.Foreground and self.UseBackground:
self._TopDrawList.append(obj)
else:
self._DrawList.append(obj)
self._backgrounddirty = 1
self._AddBoundingBox(obj.BoundingBox)
return None
------------------------------------------------------------------------
---------------------------------------------------------------------
To unsubscribe, e-mail: wxPython-dev-unsubscribe@lists.wxwindows.org
For additional commands, e-mail: wxPython-dev-help@lists.wxwindows.org